msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: ERC5095.deposit doesn't check if received shares is less then provided amount
Severity: medium

Description:
`ERC5095.deposit` doesn't check if received shares is less then provided amount. In some cases this leads to lost of funds.\\nThe main thing with principal tokens is to buy them when the price is lower (you can buy 101 token while paying only 100 base tokens) as underlying price and then at maturity time to get interest(for example in one month you will get 1 base token in our case).\\n`ERC5095.deposit` function takes amount of base token that user wants to deposit and returns amount of shares that he received. To not have loses, the amount of shares should be at least bigger than amount of base tokens provided by user.\\n```\\n    function deposit(address r, uint256 a) 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 shares = Cast.u128(previewDeposit(a));\\n        Safe.transferFrom(IERC20(underlying), msg.sender, address(this), a);\\n        // consider the hardcoded slippage limit, 4626 compliance requires no minimum param.\\n        uint128 returned = IMarketPlace(marketplace).sellUnderlying(\\n            underlying,\\n            maturity,\\n            Cast.u128(a),\\n            shares - (shares / 100)\\n        );\\n        _transfer(address(this), r, returned);\\n        return returned;\\n    }\\n```\\n\\nWhile calling market place, you can see that slippage of 1 percent is provided.\\n```\\nuint128 returned = IMarketPlace(marketplace).sellUnderlying(\\n            underlying,\\n            maturity,\\n            Cast.u128(a),\\n            shares - (shares / 100)\\n        );\\n```\\n\\nBut this is not enough in some cases.\\nFor example we have `ERC5095` token with short maturity which provides `0.5%` of interests. userA calls `deposit` function with 1000 as base amount. He wants to get back 1005 share tokens. And after maturity time earn 5 tokens on this trade.\\nBut because of slippage set to `1%`, it's possible that the price will change and user will receive 995 share tokens instead of 1005, which means that user has lost 5 base tokens.\\nI propose to add one more mechanism except of slippage. We need to check if returned shares amount is bigger then provided assets amount.

Impact:
Lost of funds.

Recommended mitigation:
Add this check at the end `require(returned > a, "received less than provided")`
Vulnerability: Curve LP Controller withdraw and claim function uses wrong signature
Severity: medium

Description:
The function signature used for `WITHDRAWCLAIM` in both CurveLPStakingController.sol and BalancerLPStakingController.sol are incorrect, leading to the function not succeeding.\\nIn both the CurveLPStakingController.sol and BalancerLPStakingController.sol contracts, the function selector `0x00ebf5dd` is used for `WITHDRAWCLAIM`. This selector corresponds to a function signature of `withdraw(uint256,address,bool)`.\\n```\\nbytes4 constant WITHDRAWCLAIM = 0x00ebf5dd;\\n```\\n\\nHowever, the `withdraw()` function in the Curve contract does not have an address argument. Instead, the function signature reads `withdraw(uint256,bool)`, which corresponds to a function selector of `0x38d07436`.

Impact:
Users who have deposited assets into Curve pools will not be able to claim their rewards when they withdraw their tokens.

Recommended mitigation:
Change the function selector in both contracts to `0x38d07436`.
Vulnerability: Strategist nonce is not checked
Severity: medium

Description:
Strategist nonce is not checked while checking commitment. This makes impossible for strategist to cancel signed commitment.\\n`VaultImplementation.commitToLien` is created to give the ability to borrow from the vault. The conditions of loan are discussed off chain and owner or delegate of the vault then creates and signes deal details. Later borrower can provide it as `IAstariaRouter.Commitment calldata params` param to `VaultImplementation.commitToLien`.\\nAfter the checking of signer of commitment `VaultImplementation._validateCommitment` function calls `AstariaRouter.validateCommitment`.\\n```\\n  function validateCommitment(IAstariaRouter.Commitment calldata commitment)\\n    public\\n    returns (bool valid, IAstariaRouter.LienDetails memory ld)\\n  {\\n    require(\\n      commitment.lienRequest.strategy.deadline >= block.timestamp,\\n      "deadline passed"\\n    );\\n\\n\\n    require(\\n      strategyValidators[commitment.lienRequest.nlrType] != address(0),\\n      "invalid strategy type"\\n    );\\n\\n\\n    bytes32 leaf;\\n    (leaf, ld) = IStrategyValidator(\\n      strategyValidators[commitment.lienRequest.nlrType]\\n    ).validateAndParse(\\n        commitment.lienRequest,\\n        COLLATERAL_TOKEN.ownerOf(\\n          commitment.tokenContract.computeId(commitment.tokenId)\\n        ),\\n        commitment.tokenContract,\\n        commitment.tokenId\\n      );\\n\\n\\n    return (\\n      MerkleProof.verifyCalldata(\\n        commitment.lienRequest.merkle.proof,\\n        commitment.lienRequest.merkle.root,\\n        leaf\\n      ),\\n      ld\\n    );\\n  }\\n```\\n\\nThis function check additional params, one of which is `commitment.lienRequest.strategy.deadline`. But it doesn't check for the nonce of strategist here. But this nonce is used while signing.\\nAlso `AstariaRouter` gives ability to increment nonce for strategist, but it is never called. That means that currently strategist use always same nonce and can't cancel his commitment.

Impact:
Strategist can't cancel his commitment. User can use this commitment to borrow up to 5 times.

Recommended mitigation:
Give ability to strategist to call `increaseNonce` function.
Vulnerability: buyoutLien() will cause the vault to fail to processEpoch()
Severity: high

Description:
LienToken#buyoutLien() did not reduce vault#liensOpenForEpoch when vault#processEpoch()will check vault#liensOpenForEpoch[currentEpoch] == uint256(0) so processEpoch() will fail\\nwhen create LienToken , vault#liensOpenForEpoch[currentEpoch] will ++ when repay or liquidate , vault#liensOpenForEpoch[currentEpoch] will -- and LienToken#buyoutLien() will transfer from vault to to other receiver,so liensOpenForEpoch need reduce\\n```\\nfunction buyoutLien(ILienToken.LienActionBuyout calldata params) external {\\n   // rest of code.\\n    /**** tranfer but not liensOpenForEpoch-- *****/\\n    _transfer(ownerOf(lienId), address(params.receiver), lienId);\\n  }\\n```\\n

Impact:
processEpoch() maybe fail

Recommended mitigation:
Issue buyoutLien() will cause the vault to fail to processEpoch()\\n```\\n  function buyoutLien(ILienToken.LienActionBuyout calldata params) external {\\n// rest of code.\\n\\n+   //do decreaseEpochLienCount()\\n+   address lienOwner = ownerOf(lienId);\\n+    bool isPublicVault = IPublicVault(lienOwner).supportsInterface(\\n+      type(IPublicVault).interfaceId\\n+    );\\n+    if (isPublicVault && !AUCTION_HOUSE.auctionExists(collateralId)) {      \\n+        IPublicVault(lienOwner).decreaseEpochLienCount(\\n+          IPublicVault(lienOwner).getLienEpoch(lienData[lienId].start + lienData[lienId].duration)\\n+        );\\n+    }    \\n\\n    lienData[lienId].last = block.timestamp.safeCastTo32();\\n    lienData[lienId].start = block.timestamp.safeCastTo32();\\n    lienData[lienId].rate = ld.rate.safeCastTo240();\\n    lienData[lienId].duration = ld.duration.safeCastTo32();\\n    _transfer(ownerOf(lienId), address(params.receiver), lienId);\\n  }\\n```\\n
Vulnerability: _deleteLienPosition can be called by anyone to delete any lien they wish
Severity: high

Description:
`_deleteLienPosition` is a public function that doesn't check the caller. This allows anyone to call it an remove whatever lien they wish from whatever collateral they wish\\n```\\nfunction _deleteLienPosition(uint256 collateralId, uint256 position) public {\\n  uint256[] storage stack = liens[collateralId];\\n  require(position < stack.length, "index out of bounds");\\n\\n  emit RemoveLien(\\n    stack[position],\\n    lienData[stack[position]].collateralId,\\n    lienData[stack[position]].position\\n  );\\n  for (uint256 i = position; i < stack.length - 1; i++) {\\n    stack[i] = stack[i + 1];\\n  }\\n  stack.pop();\\n}\\n```\\n\\n`_deleteLienPosition` is a `public` function and doesn't validate that it's being called by any permissioned account. The result is that anyone can call it to delete any lien that they want. It wouldn't remove the lien data but it would remove it from the array associated with `collateralId`, which would allow it to pass the `CollateralToken.sol#releaseCheck` and the underlying to be withdrawn by the user.

Impact:
All liens can be deleted completely rugging lenders

Recommended mitigation:
Change `_deleteLienPosition` to `internal` rather than `public`.
Vulnerability: Public vaults can become insolvent because of missing `yIntercept` update
Severity: high

Description:
The deduction of `yIntercept` during payments is missing in `beforePayment()` which can lead to vault insolvency.\\n`yIntercept` is declared as "sum of all LienToken amounts" and documented elsewhere as "yIntercept (virtual assets) of a PublicVault". It is used to calculate the total assets of a public vault as: slope.mulDivDown(delta_t, 1) + `yIntercept`.\\nIt is expected to be updated on deposits, payments, withdrawals, liquidations. However, the deduction of `yIntercept` during payments is missing in `beforePayment()`. As noted in the function's Natspec:\\n```\\n /**\\n   * @notice Hook to update the slope and yIntercept of the PublicVault on payment.\\n   * The rate for the LienToken is subtracted from the total slope of the PublicVault, and recalculated in afterPayment().\\n   * @param lienId The ID of the lien.\\n   * @param amount The amount paid off to deduct from the yIntercept of the PublicVault.\\n   */\\n```\\n\\nthe amount of payment should be deducted from `yIntercept` but is missing.

Impact:
This missing update will inflate the inferred value of the public vault corresponding to its actual value leading to eventual insolvency because of resulting protocol miscalculations.

Recommended mitigation:
Issue Public vaults can become insolvent because of missing `yIntercept` update\\nUpdate `yIntercept` in `beforePayment()` by the `amount` value.
Vulnerability: Any public vault without a delegate can be drained
Severity: high

Description:
If a public vault is created without a delegate, delegate will have the value of `address(0)`. This is also the value returned by `ecrecover` for invalid signatures (for example, if v is set to a position number that is not 27 or 28), which allows a malicious actor to cause the signature validation to pass for arbitrary parameters, allowing them to drain a vault using a worthless NFT as collateral.\\nWhen a new Public Vault is created, the Router calls the `init()` function on the vault as follows:\\n```\\nVaultImplementation(vaultAddr).init(\\n  VaultImplementation.InitParams(delegate)\\n);\\n```\\n\\nIf a delegate wasn't set, this will pass `address(0)` to the vault. If this value is passed, the vault simply skips the assignment, keeping the delegate variable set to the default 0 value:\\n```\\nif (params.delegate != address(0)) {\\n  delegate = params.delegate;\\n}\\n```\\n\\nOnce the delegate is set to the zero address, any commitment can be validated, even if the signature is incorrect. This is because of a quirk in `ecrecover` which returns `address(0)` for invalid signatures. A signature can be made invalid by providing a positive integer that is not 27 or 28 as the `v` value. The result is that the following function call assigns recovered = address(0):\\n```\\n    address recovered = ecrecover(\\n      keccak256(\\n        encodeStrategyData(\\n          params.lienRequest.strategy,\\n          params.lienRequest.merkle.root\\n        )\\n      ),\\n      params.lienRequest.v,\\n      params.lienRequest.r,\\n      params.lienRequest.s\\n    );\\n```\\n\\nTo confirm the validity of the signature, the function performs two checks:\\n```\\nrequire(\\n  recovered == params.lienRequest.strategy.strategist,\\n  "strategist must match signature"\\n);\\nrequire(\\n  recovered == owner() || recovered == delegate,\\n  "invalid strategist"\\n);\\n```\\n\\nThese can be easily passed by setting the `strategist` in the params to `address(0)`. At this point, all checks will pass and the parameters will be accepted as approved by the vault.\\nWith this power, a borrower can create params that allow them to borrow the vault's full funds in exchange for a worthless NFT, allowing them to drain the vault and steal all the user's funds.

Impact:
All user's funds held in a vault with no delegate set can be stolen.

Recommended mitigation:
Issue Any public vault without a delegate can be drained\\nAdd a require statement that the recovered address cannot be the zero address:\\n```\\nrequire(recovered != address(0));\\n```\\n
Vulnerability: Auctions can end in epoch after intended, underpaying withdrawers
Severity: high

Description:
When liens are liquidated, the router checks if the auction will complete in a future epoch and, if it does, sets up a liquidation accountant and other logistics to account for it. However, the check for auction completion does not take into account extended auctions, which can therefore end in an unexpected epoch and cause accounting issues, losing user funds.\\nThe liquidate() function performs the following check to determine if it should set up the liquidation to be paid out in a future epoch:\\n```\\nif (PublicVault(owner).timeToEpochEnd() <= COLLATERAL_TOKEN.auctionWindow())\\n```\\n\\nThis function assumes that the auction will only end in a future epoch if the `auctionWindow` (typically set to 2 days) pushes us into the next epoch.\\nHowever, auctions can last up to an additional 1 day if bids are made within the final 15 minutes. In these cases, auctions are extended repeatedly, up to a maximum of 1 day.\\n```\\nif (firstBidTime + duration - block.timestamp < timeBuffer) {\\n  uint64 newDuration = uint256(\\n    duration + (block.timestamp + timeBuffer - firstBidTime)\\n  ).safeCastTo64();\\n  if (newDuration <= auctions[tokenId].maxDuration) {\\n    auctions[tokenId].duration = newDuration;\\n  } else {\\n    auctions[tokenId].duration =\\n      auctions[tokenId].maxDuration -\\n      firstBidTime;\\n  }\\n  extended = true;\\n}\\n```\\n\\nThe result is that there are auctions for which accounting is set up for them to end in the current epoch, but will actual end in the next epoch.

Impact:
Users who withdrew their funds in the current epoch, who are entitled to a share of the auction's proceeds, will not be paid out fairly.

Recommended mitigation:
Change the check to take the possibility of extension into account:\\n```\\nif (PublicVault(owner).timeToEpochEnd() <= COLLATERAL_TOKEN.auctionWindow() + 1 days)\\n```\\n
Vulnerability: Strategists are paid 10x the vault fee because of a math error
Severity: high

Description:
Strategists set their vault fee in BPS (x / 10,000), but are paid out as x / 1,000. The result is that strategists will always earn 10x whatever vault fee they set.\\nWhenever any payment is made towards a public vault, `beforePayment()` is called, which calls `_handleStrategistInterestReward()`.\\nThe function is intended to take the amount being paid, adjust by the vault fee to get the fee amount, and convert that amount of value into shares, which are added to `strategistUnclaimedShares`.\\n```\\nfunction _handleStrategistInterestReward(uint256 lienId, uint256 amount)\\n    internal\\n    virtual\\n    override\\n  {\\n    if (VAULT_FEE() != uint256(0)) {\\n      uint256 interestOwing = LIEN_TOKEN().getInterest(lienId);\\n      uint256 x = (amount > interestOwing) ? interestOwing : amount;\\n      uint256 fee = x.mulDivDown(VAULT_FEE(), 1000);\\n      strategistUnclaimedShares += convertToShares(fee);\\n    }\\n  }\\n```\\n\\nSince the vault fee is stored in basis points, to get the vault fee, we should take the amount, multiply it by `VAULT_FEE()` and divide by 10,000. However, we accidentally divide by 1,000, which results in a 10x larger reward for the strategist than intended.\\nAs an example, if the vault fee is intended to be 10%, we would set `VAULT_FEE = 1000`. In that case, for any amount paid off, we would calculate `fee = amount * 1000 / 1000` and the full amount would be considered a fee for the strategist.

Impact:
Strategists will be paid 10x the agreed upon rate for their role, with the cost being borne by users.

Recommended mitigation:
Change the `1000` in the `_handleStrategistInterestReward()` function to `10_000`.
Vulnerability: Claiming liquidationAccountant will reduce vault y-intercept by more than the correct amount
Severity: high

Description:
When `claim()` is called on the Liquidation Accountant, it decreases the y-intercept based on the balance of the contract after funds have been distributed, rather than before. The result is that the y-intercept will be decreased more than it should be, siphoning funds from all users.\\nWhen `LiquidationAccountant.sol:claim()` is called, it uses its `withdrawRatio` to send some portion of its earnings to the `WITHDRAW_PROXY` and the rest to the vault.\\nAfter performing these transfers, it updates the vault's y-intercept, decreasing it by the gap between the expected return from the auction, and the reality of how much was sent back to the vault:\\n```\\nPublicVault(VAULT()).decreaseYIntercept(\\n  (expected - ERC20(underlying()).balanceOf(address(this))).mulDivDown(\\n    1e18 - withdrawRatio,\\n    1e18\\n  )\\n);\\n```\\n\\nThis rebalancing uses the balance of the `liquidationAccountant` to perform its calculation, but it is done after the balance has already been distributed, so it will always be 0.\\nLooking at an example:\\n`expected = 1 ether` (meaning the y-intercept is currently based on this value)\\n`withdrawRatio = 0` (meaning all funds will go back to the vault)\\nThe auction sells for exactly 1 ether\\n1 ether is therefore sent directly to the vault\\nIn this case, the y-intercept should not be updated, as the outcome was equal to the `expected` outcome\\nHowever, because the calculation above happens after the funds are distributed, the decrease equals `(expected - 0) * 1e18 / 1e18`, which equals `expected`\\nThat decrease should not happen, and causing problems for the protocol's accounting. For example, when `withdraw()` is called, it uses the y-intercept in its calculation of the `totalAssets()` held by the vault, creating artificially low asset values for a given number of shares.

Impact:
Every time the liquidation accountant is used, the vault's math will be thrown off and user shares will be falsely diluted.

Recommended mitigation:
The amount of assets sent to the vault has already been calculated, as we've already sent it. Therefore, rather than the full existing formula, we can simply call:\\n```\\nPublicVault(VAULT()).decreaseYIntercept(expected - balance)\\n```\\n\\nAlternatively, we can move the current code above the block of code that transfers funds out (L73).
Vulnerability: liquidationAccountant can be claimed at any time
Severity: high

Description:
New liquidations are sent to the `liquidationAccountant` with a `finalAuctionTimestamp` value, but the actual value that is passed in is simply the duration of an auction. The `claim()` function uses this value in a require check, so this error will allow it to be called before the auction is complete.\\nWhen a lien is liquidated, `AstariaRouter.sol:liquidate()` is called. If the lien is set to end in a future epoch, we call `handleNewLiquidation()` on the `liquidationAccountant`.\\nOne of the values passed in this call is the `finalAuctionTimestamp`, which updates the `finalAuctionEnd` variable in the `liquidationAccountant`. This value is then used to protect the `claim()` function from being called too early.\\nHowever, when the router calls `handleLiquidationAccountant()`, it passes the duration of an auction rather than the final timestamp:\\n```\\nLiquidationAccountant(accountant).handleNewLiquidation(\\n  lien.amount,\\n  COLLATERAL_TOKEN.auctionWindow() + 1 days\\n);\\n```\\n\\nAs a result, `finalAuctionEnd` will be set to 259200 (3 days).\\nWhen `claim()` is called, it requires the final auction to have ended for the function to be called:\\n```\\nrequire(\\n  block.timestamp > finalAuctionEnd || finalAuctionEnd == uint256(0),\\n  "final auction has not ended"\\n);\\n```\\n\\nBecause of the error above, `block.timestamp` will always be greater than `finalAuctionEnd`, so this will always be permitted.

Impact:
Anyone can call `claim()` before an auction has ended. This can cause many problems, but the clearest is that it can ruin the protocol's accounting by decreasing the Y intercept of the vault.\\nFor example, if `claim()` is called before the auction, the returned value will be 0, so the Y intercept will be decreased as if there was an auction that returned no funds.

Recommended mitigation:
Adjust the call from the router to use the ending timestamp as the argument, rather than the duration:\\n```\\nLiquidationAccountant(accountant).handleNewLiquidation(\\n  lien.amount,\\n  block.timestamp + COLLATERAL_TOKEN.auctionWindow() + 1 days\\n);\\n```\\n
Vulnerability: Incorrect fees will be charged
Severity: high

Description:
If user has provided transferAmount which is greater than all lien.amount combined then initiatorPayment will be incorrect since it is charged on full amount when only partial was used as shown in poc\\nObserve the _handleIncomingPayment function\\nLets say transferAmount was 1000\\ninitiatorPayment is calculated on this full transferAmount\\n```\\nuint256 initiatorPayment = transferAmount.mulDivDown(\\n      auction.initiatorFee,\\n      100\\n    ); \\n```\\n\\nNow all lien are iterated and lien.amount is kept on deducting from transferAmount until all lien are navigated\\n```\\nif (transferAmount >= lien.amount) {\\n          payment = lien.amount;\\n          transferAmount -= payment;\\n        } else {\\n          payment = transferAmount;\\n          transferAmount = 0;\\n        }\\n\\n        if (payment > 0) {\\n          LIEN_TOKEN.makePayment(tokenId, payment, lien.position, payer);\\n        }\\n      }\\n```\\n\\nLets say after loop completes the transferAmount is still left as 100\\nThis means only 400 transferAmount was used but fees was deducted on full amount 500

Impact:
Excess initiator fees will be deducted which was not required

Recommended mitigation:
Calculate the exact amount of transfer amount required for the transaction and calculate the initiator fee based on this amount