msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Rounding error when call function `dodoMultiswap()` can lead to revert of transaction or fund of user
Severity: medium

Description:
The calculation of the proportion when do the split swap in function `_multiSwap` doesn't care about the rounding error\\nThe amount of `midToken` will be transfered to the each adapter can be calculated by formula `curAmount = curTotalAmount * weight / totalWeight`\\n```\\nif (assetFrom[i - 1] == address(this)) {\\n    uint256 curAmount = curTotalAmount * curPoolInfo.weight / curTotalWeight;\\n\\n\\n    if (curPoolInfo.poolEdition == 1) {\\n        //For using transferFrom pool (like dodoV1, Curve), pool call transferFrom function to get tokens from adapter\\n        IERC20(midToken[i]).transfer(curPoolInfo.adapter, curAmount);\\n    } else {\\n        //For using transfer pool (like dodoV2), pool determine swapAmount through balanceOf(Token) - reserve\\n        IERC20(midToken[i]).transfer(curPoolInfo.pool, curAmount);\\n    }\\n}\\n```\\n\\nIt will lead to some scenarios when `curTotalAmount * curPoolInfo.weight` is not divisible by `curTotalWeight`, there will be some token left after the swap.\\nFor some tx, if user set a `minReturnAmount` strictly, it may incur the reversion. For some token with small decimal and high value, it can make a big loss for the sender.

Impact:
Revert the transaction because not enough amount of `toToken`\\nSender can lose a small amount of tokens

Recommended mitigation:
Add a accumulation variable to maintain the total amount is transfered after each split swap. In the last split swap, instead of calculating the `curAmount` by formula above, just take the remaining amount to swap.
Vulnerability: Hardcoded divider address in RollerUtils is incorrect and will brick autoroller
Severity: medium

Description:
RollerUtils uses a hard-coded constant for the Divider. This address is incorrect and will cause a revert when trying to call AutoRoller#cooldown. If the adapter is combineRestricted then LPs could potentially be unable to withdraw or eject.\\n```\\naddress internal constant DIVIDER = 0x09B10E45A912BcD4E80a8A3119f0cfCcad1e1f12;\\n```\\n\\nRollerUtils uses a hardcoded constant DIVIDER to store the Divider address. There are two issues with this. The most pertinent issue is that the current address used is not the correct mainnet address. The second is that if the divider is upgraded, changing the address of the RollerUtils may be forgotten.\\n```\\n    (, uint48 prevIssuance, , , , , uint256 iscale, uint256 mscale, ) = DividerLike(DIVIDER).series(adapter, prevMaturity);\\n```\\n\\nWith an incorrect address the divider#series call will revert causing RollerUtils#getNewTargetedRate to revert, which is called in AutoRoller#cooldown. The result is that the AutoRoller cycle can never be completed. LP will be forced to either withdraw or eject to remove their liquidity. Withdraw only works to a certain point because the AutoRoller tries to keep the target ratio. After which the eject would be the only way for LPs to withdraw. During eject the AutoRoller attempts to combine the PT and YT. If the adapter is also combineRestricted then there is no longer any way for the LPs to withdraw, causing loss of their funds.

Impact:
Incorrect hard-coded divider address will brick autorollers for all adapters and will cause loss of funds for combineRestricted adapters

Recommended mitigation:
RollerUtils DIVIDER should be set by constructor. Additionally RollerUtils should be deployed by the factory constructor to make sure they always have the same immutable divider reference.
Vulnerability: Public vault : Initial depositor can manipulate the price per share value and future depositors are forced to deposit huge value in vault.
Severity: high

Description:
Most of the share based vault implementation will face this issue. The vault is based on the ERC4626 where the shares are calculated based on the deposit value. By depositing large amount as initial deposit, initial depositor can influence the future depositors value.\\nBy depositing large amount as initial deposit, first depositor can take advantage over other depositors.\\nI am sharing reference for this type of issue that already reported and acknowledged. This explain how the share price could be manipulated to large value.\\nERC4626 implementation function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) { assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.\\n```\\n    // Need to transfer before minting or ERC777s could reenter.\\n    asset.safeTransferFrom(msg.sender, address(this), assets);\\n\\n    _mint(receiver, shares);\\n\\n    emit Deposit(msg.sender, receiver, assets, shares);\\n\\n    afterDeposit(assets, shares);\\n}\\n\\n  function previewMint(uint256 shares) public view virtual returns (uint256) {\\n    uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.\\n\\n    return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);\\n}\\n```\\n

Impact:
Future depositors are forced for huge value of asset to deposit. It is not practically possible for all the users. This could directly affect on the attrition of users towards this system.

Recommended mitigation:
Consider requiring a minimal amount of share tokens to be minted for the first minter, and send a portion of the initial mints as a reserve to the DAO/ burn so that the price per share can be more resistant to manipulation.
Vulnerability: Funding Rate calculation is not correct
Severity: medium

Description:
According to the docs, the Funding Rate is intended to correspond to the gap between long and short positions that the Float Pool is required to make up. However, as its implemented, the `totalFunding` is calculated only on the size of the overbalanced position, leading to some unexpected situations.\\nAccording to the comments, `totalFunding` is meant to be calculated as follows:\\ntotalFunding is calculated on the notional of between long and short liquidity and 2x long and short liquidity.\\nThis makes sense. The purpose of the funding rate is to compensate the Float Pool for the liquidity provided to balance the market.\\nHowever, the implementation of this function does not accomplish this. Instead, `totalFunding` is based only on the size of the overbalancedValue:\\n```\\nuint256 totalFunding = (2 * overbalancedValue * fundingRateMultiplier * oracleManager.EPOCH_LENGTH()) / (365.25 days * 10000);\\n```\\n\\nThis can be summarized as `2 * overbalancedValue * funding rate percentage * epochs / yr`.\\nThis formula can cause problems, because the size of the overbalanced value doesn't necessarily correspond to the balancing required for the Float Pool.\\nFor these examples, let's set:\\n`fundingRateMultiplier = 100` (1%)\\n`EPOCH_LENGTH() = 3.6525 days` (1% of a year)\\nSITUATION A:\\nOverbalanced: LONG\\nLong Effective Liquidity: 1_000_000 ether\\nShort Effective Liquidity: 999_999 ether\\n`totalFunding = 2 * 1_000_000 ether * 1% * 1% = 200 ether`\\nAmount of balancing supplied by Float = 1mm - 999,999 = 1 ether\\nSITUATION B:\\nOverbalanced: LONG\\nLong Effective Liquidity: 1_000 ether\\nShort Effective Liquidity: 100 ether\\n`totalFunding = 2 * 1_000 ether * 1% * 1% = 0.2 ether`\\nAmount of balancing supplied by Float = 1000 - 100 = 900 ether\\nWe can see that in Situation B, Float supplied 900X more liquidity to the system, and earned 1000X less fees.

Impact:
Funding Rates will not accomplish the stated objective, and will serve to incentivize pools that rely heavily on Float for balancing, while disincentivizing large, balanced markets.

Recommended mitigation:
Adjust the `totalFunding` formula to represent the stated outcome. A simple example of how that might be accomplished is below, but I'm sure there are better implementations:\\n```\\nuint256 totalFunding = ((overbalancedValue - underbalancedValue) * fundingRateMultiplier * oracle.EPOCH_LENGTH()) / (365.25 days * 10_000);\\n```\\n
Vulnerability: Lender#lend for Sense has mismatched decimals
Severity: high

Description:
The decimals of the Sense principal token don't match the decimals of the ERC5095 vault it mints shares to. This can be abused on the USDC market to mint a large number of shares to steal yield from all other users.\\n```\\n    uint256 received;\\n    {\\n        // Get the starting balance of the principal token\\n        uint256 starting = token.balanceOf(address(this));\\n\\n        // Swap those tokens for the principal tokens\\n        ISensePeriphery(x).swapUnderlyingForPTs(adapter, s, lent, r);\\n\\n        // Calculate number of principal tokens received in the swap\\n        received = token.balanceOf(address(this)) - starting;\\n\\n        // Verify that we received the principal tokens\\n        if (received < r) {\\n            revert Exception(11, 0, 0, address(0), address(0));\\n        }\\n    }\\n\\n    // Mint the Illuminate tokens based on the returned amount\\n    IERC5095(principalToken(u, m)).authMint(msg.sender, received);\\n```\\n\\nSense principal tokens for DIA and USDC are 8 decimals to match the decimals of the underlying cTokens, cUSDC and cDAI. The decimals of the ERC5095 vault matches the underlying of the vault. This creates a disparity in decimals that aren't adjusted for in Lender#lend for Sense, which assumes that the vault and Sense principal tokens match in decimals. In the example of USDC the ERC5095 will be 6 decimals but the sense token will be 8 decimals. Each 1e6 USDC token will result in ~1e8 Sense tokens being received. Since the contract mints based on the difference in the number of sense tokens before and after the call, it will mint ~100x the number of vault shares than it should. Since the final yield is distributed pro-rata to the number of shares, the user who minted with sense will be entitled to much more yield than they should be and everyone else will get substantially less.

Impact:
User can mint large number of shares to steal funds from other users

Recommended mitigation:
Issue Lender#lend for Sense has mismatched decimals\\nQuery the decimals of the Sense principal and use that to adjust the decimals to match the decimals of the vault.
Vulnerability: Lend or mint after maturity
Severity: high

Description:
The protocol does not forbid lending or minting after the maturity leaving the possibility to profit from early users.\\nLet's take the mint function as an example:\\n```\\n    function mint(\\n        uint8 p,\\n        address u,\\n        uint256 m,\\n        uint256 a\\n    ) external unpaused(u, m, p) returns (bool) {\\n        // Fetch the desired principal token\\n        address principal = IMarketPlace(marketPlace).token(u, m, p);\\n\\n        // Transfer the users principal tokens to the lender contract\\n        Safe.transferFrom(IERC20(principal), msg.sender, address(this), a);\\n\\n        // Mint the tokens received from the user\\n        IERC5095(principalToken(u, m)).authMint(msg.sender, a);\\n\\n        emit Mint(p, u, m, a);\\n\\n        return true;\\n    }\\n```\\n\\nIt is a simple function that accepts the principal token and mints the corresponding ERC5095 tokens in return. There are no restrictions on timing, the user can mint even after the maturity. Malicious actors can take this as an advantage to pump their bags on behalf of legitimate early users.\\nScenario:\\nLegitimate users lend and mint their ERC5095 tokens before maturity.\\nWhen the maturity kicks in, lender tokens are redeemed and holdings are updated.\\nLegitimate users try to redeem their ERC5095 for the underlying tokens. The formula is `(amount * holdings[u][m]) / token.totalSupply();`\\nA malicious actor sandwiches legitimate users, and mints the ERC5095 thus increasing the totalSupply and reducing other user shares. Then redeem principals again and burn their own shares for increased rewards.\\nExample with concrete values:\\nuserA deposits `100` tokens, user B deposits `200` tokens. The total supply minted is `300` ERC5095 tokens.\\nAfter the maturity the redemption happens and now let's say `holdings[u][m]` is `330` (+30).\\nuserA tries to redeem the underlying. The expected amount is: `100` * `330` / `300` = 110. However, this action is frontrunned by userC (malicious) who mints yet another `500` tokens post-maturity. The total supply becomes `800`. The real value userA now receives is: 110 * `330` / `800` = 45.375.\\nAfter that the malicious actor userC invokes the redemption again, and the `holdings[u][m]` is now `330` - 45.375 + `550` = 834.625.\\nuserC redeems the underlying: `500` * 834.625 / 700 ~= 596.16 (expected was 550).\\nNow all the remaining users will also slightly benefit, e.g. in this case userB redeems what's left: `200` * 238.46 / `200` = 238.46 (expected was 220).

Impact:
The amount legitimate users receive will be devaluated, while malicious actor can increase their ROI without meaningfully contributing to the protocol and locking their tokens.

Recommended mitigation:
Issue Lend or mint after maturity\\nLend/mint should be forbidden post-maturity.
Vulnerability: Incorrect parameters
Severity: medium

Description:
Some functions and integrations receive the wrong parameters.\\nHere, this does not work:\\n```\\n      } else if (p == uint8(Principals.Notional)) {\\n            // Principal token must be approved for Notional's lend\\n            ILender(lender).approve(address(0), address(0), address(0), a);\\n```\\n\\nbecause it basically translates to:\\n```\\n  } else if (p == uint8(Principals.Notional)) {\\n   if (a != address(0)) {\\n        Safe.approve(IERC20(address(0)), a, type(uint256).max);\\n  }\\n```\\n\\nIt tries to approve a non-existing token. It should approve the underlying token and Notional's token contract.\\nAnother issue is with Tempus here:\\n```\\n        // Swap on the Tempus Router using the provided market and params\\n        ITempus(controller).depositAndFix(x, lent, true, r, d);\\n\\n        // Calculate the amount of Tempus principal tokens received after the deposit\\n        uint256 received = IERC20(principal).balanceOf(address(this)) - start;\\n\\n        // Verify that a minimum number of principal tokens were received\\n        if (received < r) {\\n            revert Exception(11, received, r, address(0), address(0));\\n        }\\n```\\n\\nIt passes `r` as a slippage parameter and later checks that received >= `r`. However, in Tempus this parameter is not exactly the minimum amount to receive, it is the ratio which is calculated as follows:\\n```\\n  /// @param minTYSRate Minimum exchange rate of TYS (denominated in TPS) to receive in exchange for TPS\\n    function depositAndFix(\\n        ITempusAMM tempusAMM,\\n        uint256 tokenAmount,\\n        bool isBackingToken,\\n        uint256 minTYSRate,\\n        uint256 deadline\\n    ) external payable nonReentrant {\\n// rest of code\\n  uint256 minReturn = swapAmount.mulfV(minTYSRate, targetPool.backingTokenONE());\\n```\\n

Impact:
Inaccurate parameter values may lead to protocol misfunction down the road, e.g. insufficient approval or unpredicted slippage.

Recommended mitigation:
Review all the integrations and function invocations, and make sure the appropriate parameters are passed.
Vulnerability: Sense PT redemptions do not allow for known loss scenarios
Severity: medium

Description:
Sense PT redemptions do not allow for known loss scenarios, which will lead to principal losses\\nThe Sense PT redemption code in the `Redeemer` expects any losses during redemption to be due to a malicious adapter, and requires that there be no losses. However, there are legitimate reasons for there to be losses which aren't accounted for, which will cause the PTs to be unredeemable. The Lido FAQ page lists two such reasons:\\n```\\n- Slashing risk\\n\\nETH 2.0 validators risk staking penalties, with up to 100% of staked funds at risk if validators fail. To minimise this risk, Lido stakes across multiple professional and reputable node operators with heterogeneous setups, with additional mitigation in the form of insurance that is paid from Lido fees.\\n\\n- stETH price risk\\n\\nUsers risk an exchange price of stETH which is lower than inherent value due to withdrawal restrictions on Lido, making arbitrage and risk-free market-making impossible. \\n\\nThe Lido DAO is driven to mitigate above risks and eliminate them entirely to the extent possible. Despite this, they may still exist and, as such, it is our duty to communicate them.\\n```\\n\\nIf Lido is slashed, or there are withdrawal restrictions, the Sense series sponsor will be forced to settle the series, regardless of the exchange rate (or miss out on their rewards). The Sense `Divider` contract anticipates and properly handles these losses, but the Illuminate code does not.\\nLido is just one example of a Sense token that exists in the Illuminate code base - there may be others added in the future which also require there to be allowances for losses.

Impact:
Permanent freezing of funds\\nThere may be a malicious series sponsor that purposely triggers a loss, either by DOSing Lido validators, or by withdrawing enough to trigger withdrawal restrictions. In such a case, the exchange rate stored by Sense during the settlement will lead to losses, and users that hold Illumimate PTs (not just the users that minted Illuminate PTs with Sense PTs), will lose their principal, because Illuminate PT redemptions are an a share-of-underlying basis, not on the basis of the originally-provided token.\\nWhile the Illuminate project does have an emergency `withdraw()` function that would allow an admin to rescue the funds and manually distribute them, this would not be trustless and defeats the purpose of having a smart contract.

Recommended mitigation:
Allow losses during redemption if Sense's `Periphery.verified()` returns `true`
Vulnerability: Notional PT redemptions do not use flash-resistant prices
Severity: medium

Description:
Notional PT redemptions do not use the correct function for determining balances, which will lead to principal losses\\nEIP-4626 states the following about maxRedeem():\\n```\\nMUST return the maximum amount of shares that could be transferred from `owner` through `redeem` and not cause a revert, which MUST NOT be higher than the actual maximum that would be accepted (it should underestimate if necessary).\\n\\nMUST factor in both global and user-specific limits, like if redemption is entirely disabled (even temporarily) it MUST return 0.\\n```\\n\\nThe above means that the implementer is free to return less than the actual balance, and is in fact required to return zero if the token's backing store is paused, and Notional's can be paused. While neither of these conditions currently apply to the existing wfCashERC4626 implementation, there is nothing stopping Notional from implementing the MUST-return-zero-if-paused fix tomorrow, or from changing their implementation to one that requires `maxRedeem()` to return something other than the current balance.

Impact:
Permanent freezing of funds\\nIf `maxRedeem()` were to return zero, or some other non-exact value, fewer Notional PTs would be redeemed than are available, and users that redeem()ed their shares, would receive fewer underlying (principal if they minted Illuminate PTs with Notional PTs, e.g. to be an LP in the pool) than they are owed. The Notional PTs that weren't redeemed would still be available for a subsequent call, but if a user already redeemed their Illuminate PTs, their loss will already be locked in, since their Illuminate PTs will have been burned. This would affect ALL Illuminate PT holders of a specific market, not just the ones that provided the Notional PTs, because Illuminate PT redemptions are an a share-of-underlying basis, not on the basis of the originally-provided token. Markets that are already live with Notional set cannot be protected via a redemption pause by the Illuminate admin, because redemption of Lender's external PTs for underlying does not use the `unpaused` modifier, and does have any access control.

Recommended mitigation:
Use `balanceOf()` rather than `maxRedeem()` in the call to `INotional.redeem()`, and make sure that Illuminate PTs can't be burned if `Lender` still has Notional PTs that it needs to redeem (based on its own accounting of what is remaining, not based on balance checks, so that it can't be griefed with dust).
Vulnerability: Marketplace.setPrincipal do not approve needed allowance for Element vault and APWine router
Severity: medium

Description:
`Marketplace.setPrincipal` do not approve needed allowance for `Element vault` and `APWine router`\\n`Marketplace.setPrincipal` is used to provide principal token for the base token and maturity when it was not set yet. To set PT you also provide protocol that this token belongs to.\\nIn case of `APWine` protocol there is special block of code to handle all needed allowance. But it is not enough.\\n```\\n        } else if (p == uint8(Principals.Apwine)) {\\n            address futureVault = IAPWineToken(a).futureVault();\\n            address interestBearingToken = IAPWineFutureVault(futureVault)\\n                .getIBTAddress();\\n            IRedeemer(redeemer).approve(interestBearingToken);\\n        } else if (p == uint8(Principals.Notional)) {\\n```\\n\\nBut in `setPrincipal` we don't have such params and allowance is not set. So `Lender` will not be able to work with that tokens correctly.

Impact:
Lender will not provide needed allowance and protocol integration will fail.

Recommended mitigation:
Add 2 more params as in `createMarket` and call `ILender(lender).approve(u, e, a, address(0));`
Vulnerability: ERC5095.mint function calculates slippage incorrectly
Severity: medium

Description:
ERC5095.mint function calculates slippage incorrectly. This leads to lost of funds for user.\\n`ERC5095.mint` function should take amount of shares that user wants to receive and then buy this amount. It uses hardcoded 1% slippage when trades base tokens for principal. But it takes 1% of calculated assets amount, not shares.\\n```\\n    function mint(address r, uint256 s) external override returns (uint256) {\\n        if (block.timestamp > maturity) {\\n            revert Exception(\\n                21,\\n                block.timestamp,\\n                maturity,\\n                address(0),\\n                address(0)\\n            );\\n        }\\n        uint128 assets = Cast.u128(previewMint(s));\\n        Safe.transferFrom(\\n            IERC20(underlying),\\n            msg.sender,\\n            address(this),\\n            assets\\n        );\\n        // consider the hardcoded slippage limit, 4626 compliance requires no minimum param.\\n        uint128 returned = IMarketPlace(marketplace).sellUnderlying(\\n            underlying,\\n            maturity,\\n            assets,\\n            assets - (assets / 100)\\n        );\\n        _transfer(address(this), r, returned);\\n        return returned;\\n    }\\n```\\n\\nThis is how slippage is provided\\n```\\nuint128 returned = IMarketPlace(marketplace).sellUnderlying(\\n            underlying,\\n            maturity,\\n            assets,\\n            assets - (assets / 100)\\n        );\\n```\\n\\nBut the problem is that assets it is amount of base tokens that user should pay for the shares he want to receive. Slippage should be calculated using shares amount user expect to get.\\nExample. User calls mint and provides amount 1000. That means that he wants to get 1000 principal tokens. While converting to assets, assets = 990. That means that user should pay 990 base tokens to get 1000 principal tokens. Then the `sellUnderlying` is send and slippage provided is `990*0.99=980.1`. So when something happens with price it's possible that user will receive 980.1 principal tokens instead of 1000 which is 2% lost.\\nTo fix this you should provide `s - (s / 100)` as slippage.

Impact:
Lost of users funds.

Recommended mitigation:
Use this.\\n```\\nuint128 returned = IMarketPlace(marketplace).sellUnderlying(\\n            underlying,\\n            maturity,\\n            assets,\\n            s- (s / 100)\\n        );\\n```\\n