Position Manager providing the wrong strike when storing user's position data
When users mint position using `PositionManager`, users can provide strike that want to be used for the trade. However, if the provided strike data is not exactly the same with IG's current strike, the minted position's will be permanently stuck inside the PositionManager's contract.\\nWhen `mint` is called inside `PositionManager`, it will calculate the premium, transfer the required base token, and eventually call `dvp.mint`, providing the user's provided information.\\n```\\n    function mint(\\n        IPositionManager.MintParams calldata params\\n    ) external override returns (uint256 tokenId, uint256 premium) {\\n        IDVP dvp = IDVP(params.dvpAddr);\\n\\n        if (params.tokenId != 0) {\\n            tokenId = params.tokenId;\\n            ManagedPosition storage position = _positions[tokenId];\\n\\n            if (ownerOf(tokenId) != msg.sender) {\\n                revert NotOwner();\\n            }\\n            // Check token compatibility:\\n            if (position.dvpAddr != params.dvpAddr || position.strike != params.strike) {\\n                revert InvalidTokenID();\\n            }\\n            Epoch memory epoch = dvp.getEpoch();\\n            if (position.expiry != epoch.current) {\\n                revert PositionExpired();\\n            }\\n        }\\n        if ((params.notionalUp > 0 && params.notionalDown > 0) && (params.notionalUp != params.notionalDown)) {\\n            // If amount is a smile, it must be balanced:\\n            revert AsymmetricAmount();\\n        }\\n\\n        uint256 obtainedPremium;\\n        uint256 fee;\\n        (obtainedPremium, fee) = dvp.premium(params.strike, params.notionalUp, params.notionalDown);\\n\\n        // Transfer premium:\\n        // NOTE: The PositionManager is just a middleman between the user and the DVP\\n        IERC20 baseToken = IERC20(dvp.baseToken());\\n        baseToken.safeTransferFrom(msg.sender, address(this), obtainedPremium);\\n\\n        // Premium already include fee\\n        baseToken.safeApprove(params.dvpAddr, obtainedPremium);\\n\\n==>     premium = dvp.mint(\\n            address(this),\\n            params.strike,\\n            params.notionalUp,\\n            params.notionalDown,\\n            params.expectedPremium,\\n            params.maxSlippage,\\n            params.nftAccessTokenId\\n        );\\n\\n        // // rest of code.\\n    }\\n```\\n\\n```\\n    /// @inheritdoc IDVP\\n    function mint(\\n        address recipient,\\n        uint256 strike,\\n        uint256 amountUp,\\n        uint256 amountDown,\\n        uint256 expectedPremium,\\n        uint256 maxSlippage,\\n        uint256 nftAccessTokenId\\n    ) external override returns (uint256 premium_) {\\n        strike;\\n        _checkNFTAccess(nftAccessTokenId, recipient, amountUp + amountDown);\\n        Amount memory amount_ = Amount({up: amountUp, down: amountDown});\\n\\n==>     premium_ = _mint(recipient, financeParameters.currentStrike, amount_, expectedPremium, maxSlippage);\\n    }\\n```\\n\\n```\\n    function mint(\\n        IPositionManager.MintParams calldata params\\n    ) external override returns (uint256 tokenId, uint256 premium) {\\n        // // rest of code\\n\\n        if (obtainedPremium > premium) {\\n            baseToken.safeTransferFrom(address(this), msg.sender, obtainedPremium - premium);\\n        }\\n\\n        if (params.tokenId == 0) {\\n            // Mint token:\\n            tokenId = _nextId++;\\n            _mint(params.recipient, tokenId);\\n\\n            Epoch memory epoch = dvp.getEpoch();\\n\\n            // Save position:\\n            _positions[tokenId] = ManagedPosition({\\n                dvpAddr: params.dvpAddr,\\n==>             strike: params.strike,\\n                expiry: epoch.current,\\n                premium: premium,\\n                leverage: (params.notionalUp + params.notionalDown) / premium,\\n                notionalUp: params.notionalUp,\\n                notionalDown: params.notionalDown,\\n                cumulatedPayoff: 0\\n            });\\n        } else {\\n            ManagedPosition storage position = _positions[tokenId];\\n            // Increase position:\\n            position.premium += premium;\\n            position.notionalUp += params.notionalUp;\\n            position.notionalDown += params.notionalDown;\\n            /* NOTE:\\n                When, within the same epoch, a user wants to buy, sell partially\\n                and then buy again, the leverage computation can fail due to\\n                decreased notional; in order to avoid this issue, we have to\\n                also adjust (decrease) the premium in the burn flow.\\n             */\\n            position.leverage = (position.notionalUp + position.notionalDown) / position.premium;\\n        }\\n\\n        emit BuyDVP(tokenId, _positions[tokenId].expiry, params.notionalUp + params.notionalDown);\\n        emit Buy(params.dvpAddr, _positions[tokenId].expiry, premium, params.recipient);\\n    }\\n```\\n\\nPoC\\nAdd the following test to `PositionManagerTest` contract :\\n```\\n    function testMintAndBurnFail() public {\\n        (uint256 tokenId, ) = initAndMint();\\n        bytes4 PositionNotFound = bytes4(keccak256("PositionNotFound()"));\\n\\n        vm.prank(alice);\\n        vm.expectRevert(PositionNotFound);\\n        pm.sell(\\n            IPositionManager.SellParams({\\n                tokenId: tokenId,\\n                notionalUp: 10 ether,\\n                notionalDown: 0,\\n                expectedMarketValue: 0,\\n                maxSlippage: 0.1e18\\n            })\\n        );\\n    }\\n```\\n\\nModify `initAndMint` function to the following :\\n```\\n    function initAndMint() private returns (uint256 tokenId, IG ig) {\\n        vm.startPrank(admin);\\n        ig = new IG(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        vault.setAllowedDVP(address(ig));\\n\\n        MarketOracle mo = MarketOracle(ap.marketOracle());\\n\\n        mo.setDelay(ig.baseToken(), ig.sideToken(), ig.getEpoch().frequency, 0, true);\\n\\n        Utils.skipDay(true, vm);\\n        ig.rollEpoch();\\n        vm.stopPrank();\\n\\n        uint256 strike = ig.currentStrike();\\n\\n        (uint256 expectedMarketValue, ) = ig.premium(0, 10 ether, 0);\\n        TokenUtils.provideApprovedTokens(admin, baseToken, DEFAULT_SENDER, address(pm), expectedMarketValue, vm);\\n        // NOTE: somehow, the sender is something else without this prank// rest of code\\n        vm.prank(DEFAULT_SENDER);\\n        (tokenId, ) = pm.mint(\\n            IPositionManager.MintParams({\\n                dvpAddr: address(ig),\\n                notionalUp: 10 ether,\\n                notionalDown: 0,\\n                strike: strike + 1,\\n                recipient: alice,\\n                tokenId: 0,\\n                expectedPremium: expectedMarketValue,\\n                maxSlippage: 0.1e18,\\n                nftAccessTokenId: 0\\n            })\\n        );\\n        assertGe(1, tokenId);\\n        assertGe(1, pm.totalSupply());\\n    }\\n```\\n\\nRun the test :\\n```\\nforge test --match-contract PositionManagerTest --match-test testMintAndBurnFail -vvv\\n```\\n
When storing user position data inside PositionManager, query IG's current price and use it instead.\\n```\\n    function mint(\\n        IPositionManager.MintParams calldata params\\n    ) external override returns (uint256 tokenId, uint256 premium) {\\n        // // rest of code\\n\\n        if (params.tokenId == 0) {\\n            // Mint token:\\n            tokenId = _nextId// Add the line below\\n// Add the line below\\n;\\n            _mint(params.recipient, tokenId);\\n\\n            Epoch memory epoch = dvp.getEpoch();\\n// Add the line below\\n          uint256 currentStrike = dvp.currentStrike();\\n\\n            // Save position:\\n            _positions[tokenId] = ManagedPosition({\\n                dvpAddr: params.dvpAddr,\\n// Remove the line below\\n                strike: params.strike,\\n// Add the line below\\n                strike: currentStrike,\\n                expiry: epoch.current,\\n                premium: premium,\\n                leverage: (params.notionalUp // Add the line below\\n params.notionalDown) / premium,\\n                notionalUp: params.notionalUp,\\n                notionalDown: params.notionalDown,\\n                cumulatedPayoff: 0\\n            });\\n        } else {\\n            ManagedPosition storage position = _positions[tokenId];\\n            // Increase position:\\n            position.premium // Add the line below\\n= premium;\\n            position.notionalUp // Add the line below\\n= params.notionalUp;\\n            position.notionalDown // Add the line below\\n= params.notionalDown;\\n            /* NOTE:\\n                When, within the same epoch, a user wants to buy, sell partially\\n                and then buy again, the leverage computation can fail due to\\n                decreased notional; in order to avoid this issue, we have to\\n                also adjust (decrease) the premium in the burn flow.\\n             */\\n            position.leverage = (position.notionalUp // Add the line below\\n position.notionalDown) / position.premium;\\n        }\\n\\n        emit BuyDVP(tokenId, _positions[tokenId].expiry, params.notionalUp // Add the line below\\n params.notionalDown);\\n        emit Buy(params.dvpAddr, _positions[tokenId].expiry, premium, params.recipient);\\n    }\\n```\\n
If the provided strike data does not match IG's current strike price, the user's minted position using `PositionManager` will be stuck and cannot be burned. This happens because when burn is called and `position.strike` is provided, it will revert as it cannot find the corresponding positions inside IG contract.\\nThis issue directly risking user's funds, consider a scenario where users mint a position near the end of the rolling epoch, providing the old epoch's current price. However, when the user's transaction is executed, the epoch is rolled and new epoch's current price is used, causing the mentioned issue to occur, and users' positions and funds will be stuck.
```\\n    function mint(\\n        IPositionManager.MintParams calldata params\\n    ) external override returns (uint256 tokenId, uint256 premium) {\\n        IDVP dvp = IDVP(params.dvpAddr);\\n\\n        if (params.tokenId != 0) {\\n            tokenId = params.tokenId;\\n            ManagedPosition storage position = _positions[tokenId];\\n\\n            if (ownerOf(tokenId) != msg.sender) {\\n                revert NotOwner();\\n            }\\n            // Check token compatibility:\\n            if (position.dvpAddr != params.dvpAddr || position.strike != params.strike) {\\n                revert InvalidTokenID();\\n            }\\n            Epoch memory epoch = dvp.getEpoch();\\n            if (position.expiry != epoch.current) {\\n                revert PositionExpired();\\n            }\\n        }\\n        if ((params.notionalUp > 0 && params.notionalDown > 0) && (params.notionalUp != params.notionalDown)) {\\n            // If amount is a smile, it must be balanced:\\n            revert AsymmetricAmount();\\n        }\\n\\n        uint256 obtainedPremium;\\n        uint256 fee;\\n        (obtainedPremium, fee) = dvp.premium(params.strike, params.notionalUp, params.notionalDown);\\n\\n        // Transfer premium:\\n        // NOTE: The PositionManager is just a middleman between the user and the DVP\\n        IERC20 baseToken = IERC20(dvp.baseToken());\\n        baseToken.safeTransferFrom(msg.sender, address(this), obtainedPremium);\\n\\n        // Premium already include fee\\n        baseToken.safeApprove(params.dvpAddr, obtainedPremium);\\n\\n==>     premium = dvp.mint(\\n            address(this),\\n            params.strike,\\n            params.notionalUp,\\n            params.notionalDown,\\n            params.expectedPremium,\\n            params.maxSlippage,\\n            params.nftAccessTokenId\\n        );\\n\\n        // // rest of code.\\n    }\\n```\\n