`swapUnderlyingForYt` revert due to rounding issues
The core function (swapUnderlyingForYt) of the Router will revert due to rounding issues. Users who intend to swap underlying assets to YT tokens via the Router will be unable to do so.\\nThe `swapUnderlyingForYt` allows users to swap underlying assets to a specific number of YT tokens they desire.\\n```\\nFile: NapierRouter.sol\\n    function swapUnderlyingForYt(\\n        address pool,\\n        uint256 index,\\n        uint256 ytOutDesired,\\n        uint256 underlyingInMax,\\n        address recipient,\\n        uint256 deadline\\n    ) external payable override nonReentrant checkDeadline(deadline) returns (uint256) {\\n..SNIP..\\n            // Variable Definitions:\\n            // - `uDeposit`: The amount of underlying asset that needs to be deposited to issue PT and YT.\\n            // - `ytOutDesired`: The desired amount of PT and YT to be issued.\\n            // - `cscale`: Current scale of the Tranche.\\n            // - `maxscale`: Maximum scale of the Tranche (denoted as 'S' in the formula).\\n            // - `issuanceFee`: Issuance fee in basis points. (10000 =100%).\\n\\n            // Formula for `Tranche.issue`:\\n            // ```\\n            // shares = uDeposit / s\\n            // fee = shares * issuanceFeeBps / 10000\\n            // pyIssue = (shares - fee) * S\\n            // ```\\n\\n            // Solving for `uDeposit`:\\n            // ```\\n            // uDeposit = (pyIssue * s / S) / (1 - issuanceFeeBps / 10000)\\n            // ```\\n            // Hack:\\n            // Buffer is added to the denominator.\\n            // This ensures that at least `ytOutDesired` amount of PT and YT are issued.\\n            // If maximum scale and current scale are significantly different or `ytOutDesired` is small, the function might fail.\\n            // Without this buffer, any rounding errors that reduce the issued PT and YT could lead to an insufficient amount of PT to be repaid to the pool.\\n            uint256 uDepositNoFee = cscale * ytOutDesired / maxscale;\\n            uDeposit = uDepositNoFee * MAX_BPS / (MAX_BPS - (series.issuanceFee + 1)); // 0.01 bps buffer\\n```\\n\\nLine 353-354 above compute the number of underlying deposits needed to send to the Tranche to issue the amount of YT token the users desired. It attempts to add a buffer of 0.01 bps buffer to prevent rounding errors that could lead to insufficient PT being repaid to the pool and result in a revert. During the audit, it was found that this buffer is ineffective in achieving its purpose.\\nThe following example/POC demonstrates a revert could still occur due to insufficient PT being repaid despite having a buffer:\\nLet the state be the following:\\ncscale = 1.2e18\\nmaxScale = 1.25e18\\nytOutDesired = 123\\nissuanceFee = 0% (For simplicity's sake, the fee is set to zero. Having fee or not does not affect the validity of this issue as this is a math problem)\\nThe following computes the number of underlying assets to be transferred to the Tranche to mint/issue PY + YT\\n```\\nuDepositNoFee = cscale * ytOutDesired / maxscale;\\nuDepositNoFee = 1.2e18 * 123 / 1.25e18 = 118.08 = 118 (Round down)\\n\\nuDeposit = uDepositNoFee * MAX_BPS / (MAX_BPS - (series.issuanceFee + 1))\\nuDeposit = 118 * 10000 / (10000 - (0 + 1)) = 118.0118012 = 118 (Round down)\\n```\\n\\nSubsequently, the code will perform a flash-swap via the `swapPtForUnderlying` function. It will borrow 123 PT from the pool, which must be repaid later.\\nIn the swap callback function, the code will transfer 118 underlying assets to the Tranche and execute the `Tranche.issue` function to mint/issue PY + YT.\\nWithin the `Tranche.issue` function, it will trigger the `adapter.prefundedDeposit()` function to mint the estETH/shares. The following is the number of estETH/shares minted:\\n```\\nshares = assets * (total supply/total assets)\\nsahres = 118 * 100e18 / 120e18 = 98.33333333 = 98 shares\\n```\\n\\nNext, Line 219 below of the `Tranche.issue` function will compute the number of PY+YT to be issued/minted\\n```\\nissued = (sharesUsed - fee).mulWadDown(_maxscale);\\nissued = (sharesUsed - 0).mulWadDown(_maxscale);\\nissued = sharesUsed.mulWadDown(_maxscale);\\n\\nissued = sharesUsed * _maxscale / WAD\\nissued = 98 * 1.25e18 / 1e18 = 122.5 = 122 PT (Round down)\\n```\\n\\n```\\nFile: Tranche.sol\\n    function issue(\\n        address to,\\n        uint256 underlyingAmount\\n    ) external nonReentrant whenNotPaused notExpired returns (uint256 issued) {\\n..SNIP..\\n        uint256 sharesUsed = sharesMinted + accruedInTarget;\\n        uint256 fee = sharesUsed.mulDivUp(issuanceFeeBps, MAX_BPS);\\n        issued = (sharesUsed - fee).mulWadDown(_maxscale);\\n```\\n\\nAt the end of the `Tranche.issue` function, 122 PY + YT is issued/minted back to the Router.\\nNote that 123 PT was flash-loaned earlier, and 123 PT needs to be repaid. Otherwise, the code at Line 164 below will revert. The main problem is that only 122 PY was issued/minted (a shortfall of 1 PY). Thus, the swap TX will revert at the end.\\n```\\nFile: NapierRouter.sol\\n    function swapCallback(int256 underlyingDelta, int256 ptDelta, bytes calldata data) external override {\\n..SNIP..\\n            uint256 pyIssued = params.pt.issue({to: address(this), underlyingAmount: params.underlyingDeposit});\\n\\n            // Repay the PT to Napier pool\\n            if (pyIssued < pyDesired) revert Errors.RouterInsufficientPtRepay();\\n```\\n
The buffer does not appear to be the correct approach to manage this rounding error. One could increase the buffer from 0.01% to 1% and solve the issue in the above example, but a different or larger number might cause a rounding error to surface again. Also, a larger buffer means that many unnecessary PTs will be issued.\\nThus, it is recommended that a round-up division be performed when computing the `uDepositNoFee` and `uDeposit` using functions such as `divWadUp` so that the issued/minted PT can cover the debt.
The core function (swapUnderlyingForYt) of the Router will break. Users who intend to swap underlying assets to YT tokens via the Router will not be able to do so.
```\\nFile: NapierRouter.sol\\n    function swapUnderlyingForYt(\\n        address pool,\\n        uint256 index,\\n        uint256 ytOutDesired,\\n        uint256 underlyingInMax,\\n        address recipient,\\n        uint256 deadline\\n    ) external payable override nonReentrant checkDeadline(deadline) returns (uint256) {\\n..SNIP..\\n            // Variable Definitions:\\n            // - `uDeposit`: The amount of underlying asset that needs to be deposited to issue PT and YT.\\n            // - `ytOutDesired`: The desired amount of PT and YT to be issued.\\n            // - `cscale`: Current scale of the Tranche.\\n            // - `maxscale`: Maximum scale of the Tranche (denoted as 'S' in the formula).\\n            // - `issuanceFee`: Issuance fee in basis points. (10000 =100%).\\n\\n            // Formula for `Tranche.issue`:\\n            // ```\\n            // shares = uDeposit / s\\n            // fee = shares * issuanceFeeBps / 10000\\n            // pyIssue = (shares - fee) * S\\n            // ```\\n\\n            // Solving for `uDeposit`:\\n            // ```\\n            // uDeposit = (pyIssue * s / S) / (1 - issuanceFeeBps / 10000)\\n            // ```\\n            // Hack:\\n            // Buffer is added to the denominator.\\n            // This ensures that at least `ytOutDesired` amount of PT and YT are issued.\\n            // If maximum scale and current scale are significantly different or `ytOutDesired` is small, the function might fail.\\n            // Without this buffer, any rounding errors that reduce the issued PT and YT could lead to an insufficient amount of PT to be repaid to the pool.\\n            uint256 uDepositNoFee = cscale * ytOutDesired / maxscale;\\n            uDeposit = uDepositNoFee * MAX_BPS / (MAX_BPS - (series.issuanceFee + 1)); // 0.01 bps buffer\\n```\\n