xanoutas/solidity-security-findings huggingface dataset:
Borrower can prevent yield position repayment and closure by the bot
Audit: 2024-07-benddao-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-benddao/blob/main/src/yield/YieldStakingBase.sol#L418


# Vulnerability details

## Impact
User may revoke the underlying asset approval from the yield contract and prevent the `botAdmin` from repaying and closing his position. As a result, the debt will not be repaid, leaving the protocol at a loss.

## Proof of Concept
Let's take a closer look at the `_repay` function:
```solidity
  function _repay(uint32 poolId, address nft, uint256 tokenId) internal virtual {
    RepayLocalVars memory vars;
    ---SNIP---
    // withdraw yield from protocol and repay if possible

    vars.claimedYield = protocolClaimWithdraw(sd);

    vars.nftDebt = _getNftDebtInUnderlyingAsset(sd);
    vars.nftDebtWithFine = vars.nftDebt + sd.unstakeFine;

    // compute repay value
    if (vars.claimedYield >= vars.nftDebtWithFine) {
      vars.remainAmount = vars.claimedYield - vars.nftDebtWithFine;
    } else {
>>    vars.extraAmount = vars.nftDebtWithFine - vars.claimedYield;
    }

    // transfer eth from sender
    if (vars.extraAmount > 0) {
>>    underlyingAsset.safeTransferFrom(vars.nftOwner, address(this), vars.extraAmount);
    }

    if (vars.remainAmount > 0) {
      underlyingAsset.safeTransfer(vars.nftOwner, vars.remainAmount);
    }

    // repay lending pool
    poolYield.yieldRepayERC20(poolId, address(underlyingAsset), vars.nftDebt);

    poolYield.yieldSetERC721TokenData(poolId, nft, tokenId, false, address(underlyingAsset));

    // update shares
    accountYieldInWithdraws[address(vars.yieldAccout)] -= sd.withdrawAmount;
    totalDebtShare -= sd.debtShare;

    delete stakeDatas[nft][tokenId];

    emit Repay(msg.sender, nft, tokenId, vars.nftDebt);
  }
```
If the yield collected by the position is not enough to cover the entire debt and fines, the protocol will attempt to collect `extraAmount` from the position owner, but he may revoke ERC20 approval and cancel the `safeTransferFrom` function call. As a result, `nftDebt` will not be repaid and will continue to collect interest, increasing the protocol's losses.

## Tools Used
Manual review

## Recommended Mitigation Steps
It is recommended to separate the logic of debt repayment and NFT unlocking. The `_repay` function will reduce the debt of the position using the collected yield and unlock the NFT only if `extraAmount` is zero. In case `extraAmount` is greater than zero, leave the token locked and allow the NFT owner to unlock it only after he has repaid the remaining debt. 


## Assessed type

Other
Changing auction duration will have effect on ongoing auctions
Audit: 2024-07-benddao-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-benddao/blob/main/src/libraries/logic/ConfigureLogic.sol#L504
https://github.com/code-423n4/2024-07-benddao/blob/117ef61967d4b318fc65170061c9577e674fffa1/src/libraries/logic/IsolateLogic.sol#L320-L321
https://github.com/code-423n4/2024-07-benddao/blob/117ef61967d4b318fc65170061c9577e674fffa1/src/libraries/logic/IsolateLogic.sol#L423-L424


# Vulnerability details

## Impact
Auctions created before the auction duration was changed will have their duration shortened/extended, which may catch their participants off guard.

## Proof of Concept
When an isolated position reaches it's liquidation threshold, users can put it up for auction to pay off the debt and take the collateral NFT. The auction has a fixed duration during which borrower can redeem the debt and if it doesn't happen, the last bidder will pay off the debt and receive the NFT at the end.
The pool admin can customize the `auctionDuration` variable with the `setAssetAuctionParams` function. This can be a problem because the protocol checks whether the auction has ended as follows:
```solidity
      vars.auctionEndTimestamp = loanData.bidStartTimestamp + nftAssetData.auctionDuration;
      require(block.timestamp > vars.auctionEndTimestamp, Errors.ISOLATE_BID_AUCTION_DURATION_NOT_END);

      vars.auctionEndTimestamp = loanData.bidStartTimestamp + nftAssetData.auctionDuration;
      require(block.timestamp <= vars.auctionEndTimestamp, Errors.ISOLATE_BID_AUCTION_DURATION_HAS_END);
```
Thus, the new duration will be applied to auctions that were started before the duration change. For example, if the duration was shortened, some auctions will end prematurely, which may be unexpected for its participants.

## Tools Used
Manual review

## Recommended Mitigation Steps
Consider calculating and storing the auction end time instead of the start time:
```diff
-       loanData.bidStartTimestamp = uint40(block.timestamp);
+       loanData.auctionEndTimestamp = uint40(block.timestamp) + nftAssetData.auctionDuration;
```


## Assessed type

Timing
Updating fee factor may create issues for the protocol
Audit: 2024-07-benddao-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-benddao/blob/main/src/libraries/logic/ConfigureLogic.sol#L509-L521
https://github.com/code-423n4/2024-07-benddao/blob/117ef61967d4b318fc65170061c9577e674fffa1/src/libraries/logic/InterestLogic.sol#L248
https://github.com/code-423n4/2024-07-benddao/blob/117ef61967d4b318fc65170061c9577e674fffa1/src/libraries/logic/InterestLogic.sol#L201-L202


# Vulnerability details

## Impact
When the protocol fee is modified in the `Configurator` module:
- the new fee percentage will be applied to the interest collected before the fee change
- the protocol will use the supply rate that was calculated using the old fee factor.

## Proof of Concept
The `feeFactor` parameter is used to calculate amount of interest that goes to the treasury:
```solidity
  function _accrueFeeToTreasury(
    DataTypes.AssetData storage assetData,
    DataTypes.GroupData storage groupData,
    uint256 prevGroupBorrowIndex
  ) internal {
    AccrueToTreasuryLocalVars memory vars;

    if (assetData.feeFactor == 0) {
      return;
    }

    vars.totalScaledBorrow = groupData.totalScaledCrossBorrow + groupData.totalScaledIsolateBorrow;

    //calculate the total debt at moment of the last interaction
    vars.prevTotalBorrow = vars.totalScaledBorrow.rayMul(prevGroupBorrowIndex);

    //calculate the new total debt after accumulation of the interest on the index
    vars.currTotalBorrow = vars.totalScaledBorrow.rayMul(groupData.borrowIndex);

    //debt accrued is the sum of the current debt minus the sum of the debt at the last update
    vars.totalDebtAccrued = vars.currTotalBorrow - vars.prevTotalBorrow;

>>  vars.amountToMint = vars.totalDebtAccrued.percentMul(assetData.feeFactor);

    if (vars.amountToMint != 0) {
      assetData.accruedFee += vars.amountToMint.rayDiv(assetData.supplyIndex).toUint128();
    }
```
And to adjust the asset's supply rate taking into account fee collected by the treasury:
```solidity
  function updateInterestRates(
    DataTypes.PoolData storage poolData,
    DataTypes.AssetData storage assetData,
    uint256 liquidityAdded,
    uint256 liquidityTaken
  ) internal {
    ---SNIP---
    // calculate the asset supply rate
    vars.nextAssetSupplyRate = vars.nextAssetBorrowRate.rayMul(vars.assetUtilizationRate);
    vars.nextAssetSupplyRate = vars.nextAssetSupplyRate.percentMul(
      PercentageMath.PERCENTAGE_FACTOR - assetData.feeFactor
    );
>>  assetData.supplyRate = vars.nextAssetSupplyRate.toUint128();
``` 
A discrepancy may occur when the pool admin changes the `feeFactor`. The new `feeFactor` will be applied to interest collected before the `feeFactor` was updated:
```solidity
function updateInterestIndexs(
    DataTypes.PoolData storage /*poolData*/,
    DataTypes.AssetData storage assetData
  ) internal {
    ---SNIP---
    for (vars.i = 0; vars.i < vars.assetGroupIds.length; vars.i++) {
      vars.loopGroupId = uint8(vars.assetGroupIds[vars.i]);
      DataTypes.GroupData storage loopGroupData
Protocol should update interest rate after changing rate model in the configurator module
Audit: 2024-07-benddao-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-benddao/blob/main/src/libraries/logic/ConfigureLogic.sol#L526
https://github.com/code-423n4/2024-07-benddao/blob/main/src/libraries/logic/ConfigureLogic.sol#L626


# Vulnerability details

## Impact
After updating the interest model, the protocol does not update the interest rate, resulting in interest being calculated at the old model rates for some time.

## Proof of Concept
The pool admin can change rate model in the `Configurator.sol` module:
```solidity
  function executeSetAssetLendingRate(
    address msgSender,
    uint32 poolId,
    address asset,
    uint8 groupId,
    address rateModel_
  ) internal {
    DataTypes.PoolStorage storage ps = StorageSlot.getPoolStorage();

    require(groupId >= Constants.GROUP_ID_LEND_MIN, Errors.INVALID_GROUP_ID);
    require(groupId <= Constants.GROUP_ID_LEND_MAX, Errors.INVALID_GROUP_ID);
    require(rateModel_ != address(0), Errors.INVALID_ADDRESS);

    DataTypes.PoolData storage poolData = ps.poolLookup[poolId];
    _validateCallerAndPool(msgSender, ps, poolData);

    DataTypes.AssetData storage assetData = poolData.assetLookup[asset];
    require(assetData.underlyingAsset != address(0), Errors.ASSET_NOT_EXISTS);
    require(assetData.assetType == Constants.ASSET_TYPE_ERC20, Errors.ASSET_TYPE_NOT_ERC20);

    require(assetData.groupList.contains(groupId), Errors.GROUP_NOT_EXISTS);

    DataTypes.GroupData storage groupData = assetData.groupLookup[groupId];
>>  groupData.rateModel = rateModel_;

    emit Events.SetAssetLendingRate(poolId, asset, groupId, rateModel_);
  }
```
However, interest rate is not updated, meaning interest will be calculated with the cached old model rates until `InterestLogic.updateInterestRates` is finally called.

## Tools Used
Manual review

## Recommended Mitigation Steps
```diff
    ---SNIP---

    DataTypes.GroupData storage groupData = assetData.groupLookup[groupId];
    groupData.rateModel = rateModel_;
+   InterestLogic.updateInterestRates(poolData, assetData, 0, 0)

    emit Events.SetAssetLendingRate(poolId, asset, groupId, rateModel_);
  }
```


## Assessed type

Other
Incorrect unwrapNativeTokenInWallet receiver address
Audit: 2024-07-benddao-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-benddao/blob/main/src/modules/BVault.sol#L74
https://github.com/code-423n4/2024-07-benddao/blob/main/src/modules/CrossLending.sol#L50
https://github.com/code-423n4/2024-07-benddao/blob/main/src/modules/IsolateLending.sol#L51


# Vulnerability details

## Impact
If the user specifies native tokens as output assets, the protocol will mistakenly try to unwrap wrapped tokens from the `msg.sender` instead of the `receiver` address that was specified in the function call.

## Proof of Concept
This issue occurs in the following modules:
- BVault
- CrossLending
- IsolateLending

Users are allowed to specify an output asset when borrowing/withdrawing funds from the protocol. If the output asset is a native token, a wrapped version will be sent to the receiver via the `erc20TransferOutLiquidity` function, and then the receiver of these tokens must send them to the protocol, which will unwrap and send them back:
```solidity
  function unwrapNativeTokenInWallet(address wrappedNativeToken, address user, uint256 amount) internal {
    require(amount > 0, Errors.INVALID_AMOUNT);

    bool success = IWETH(wrappedNativeToken).transferFrom(user, address(this), amount);
    require(success, Errors.TOKEN_TRANSFER_FAILED);

    IWETH(wrappedNativeToken).withdraw(amount);

    safeTransferNativeToken(user, amount);
  }
```
The problem here is that the wrapped tokens will be sent to the `params.receiver` address, but the `unwrapNativeTokenInWallet` function will try to get them from `msg.sender`, who may not have them in the wallet. As a result withdraw/borrow functionality may fail if `msg.sender` != `receiver`:
```solidity
  function withdrawERC20(
    uint32 poolId,
    address asset,
    uint256 amount,
    address onBehalf,
    address receiver
  ) public whenNotPaused nonReentrant {
    address msgSender = unpackTrailingParamMsgSender();
    DataTypes.PoolStorage storage ps = StorageSlot.getPoolStorage();
    bool isNative;
    if (asset == Constants.NATIVE_TOKEN_ADDRESS) {
      isNative = true;
      asset = ps.wrappedNativeToken;
    }

    SupplyLogic.executeWithdrawERC20(
      InputTypes.ExecuteWithdrawERC20Params({
        msgSender: msgSender,
        poolId: poolId,
        asset: asset,
        amount: amount,
        onBehalf: onBehalf,
>>      receiver: receiver
      })
    );

    if (isNative) {
>>    VaultLogic.unwrapNativeTokenInWallet(asset, msgSender, amount);
    }
  }
```
Same in `crossBorrowERC20` and `isolateBorrow` functions.
## Tools Used
Manual review
## Recommended Mitigation Steps
```diff
    if (isNative) {
-     VaultLogic.unwrapNativeTokenInWallet(asset, msgSender, amount);
+     VaultLogic.unwrapNativeTokenInWallet(asset, receiver, amount);
    }
```






## Assessed type

Token-Transfer
PriceOracle may use stale prices
Audit: 2024-07-benddao-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-benddao/blob/main/src/PriceOracle.sol#L122-L125


# Vulnerability details

## Impact
Lack of the Chainlink aggregator answer staleness validation may result in incorrect assets pricing and losses for the protocol. 

## Proof of Concept
The BendDao protocol uses the `PriceOracle.sol` contract to fetch assets prices. It consists of two oracles, one for ERC20 assets and one for NFTs. Chainlink price feeds are used to receive ERC20 assets prices in the base currency:
```solidity
  function getAssetPriceFromChainlink(address asset) public view returns (uint256) {
    AggregatorV2V3Interface sourceAgg = assetChainlinkAggregators[asset];
    require(address(sourceAgg) != address(0), Errors.ASSET_AGGREGATOR_NOT_EXIST);

    (uint80 roundId, int256 answer, , uint256 updatedAt, uint80 answeredInRound) = sourceAgg.latestRoundData();
    require(answer > 0, Errors.ASSET_PRICE_IS_ZERO);
>>  require(updatedAt != 0, Errors.ORACLE_PRICE_IS_STALE);
    require(answeredInRound >= roundId, Errors.ORACLE_PRICE_IS_STALE);

    return uint256(answer);
  }
```
However, checking that `updated != 0` is not enough, since the `updatedAt` could be greater than zero and still be out of date.

## Tools Used
Manual review

## Recommended Mitigation Steps
Consider checking that `updatedAt` is in the specified time range:
```solidity
require(updatedAt + timeLimit > block.timestamp, Errors.ORACLE_PRICE_IS_STALE);
```


## Assessed type

Oracle
Swaps affect LP token mint/burn during liquidity addition/removal
Audit: 2023-08-shell-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-08-shell/blob/c61cf0e01bada04c3d6055acb81f61955ed600aa/src/proteus/EvolvingProteus.sol#L463-L490


# Vulnerability details

## Impact

The LP token removal/addition forces a recalculation of the bonding curve, and the utility of the curve. The utility curve in proteus looks like the graph below, where the point A represents a certain composition of the pool.

![ab<1 +ve u](https://gist.github.com/carrotsmuggler/4b6b9aa7fc88cc7a678f547259f05ced/raw/a43fda149b880a8c6a89a6735b0a2c28c741060a/abneg_posu.png)

If we try to remove add/remove liquidity form the pool, the new utility value needs to be calculated, and the bonding curve basically shifts to accomodate the new state of the pool. Here the new state is shown by point B and the curve in blue.

![Liquidity addition](https://gist.github.com/carrotsmuggler/4b6b9aa7fc88cc7a678f547259f05ced/raw/b4fd03ac45e048c2448fda0badf340ea9609708a/liq_Add.png)

The issues is that if the pool is at a different price C, and the new state goes to D due to the same amount of liquidity removal, the new point D is not on the bblue bonding curve. This shows that the final bonding curve due to liquidity addition/removal depends on the price in the AMM.

Thus users can manipulate the price in the AMM, and this affects how many tokens other users will get when they add/remove liquidity.

## Proof of Concept

In this POC, a user adds liquidity of 1 ETH. The same action is repeated, but at a different price after a large swap.

It is shown that after the swap, the user gets less LP tokens for the same amount of ETH added. Thus attackers can frontrun large LP additions/removals to cause losses in this manner, essentially a form of sandwich attack. In protocols like Uni V3 this isnt possible since the users get to choose the exact price ticks they want to supply to.

```solidity
function testAttack() public {
    uint256 x0 = 100e18;
    uint256 y0 = 100e18;
    uint256 s0 = 100e18;
    uint256 depositedAmount = 1e18;

    SpecifiedToken depositedToken = SpecifiedToken.X;
    uint256 minted1 = DUT.depositGivenInputAmount(
        x0,
        y0,
        s0,
        depositedAmount,
        depositedToken
    );

    emit log_named_uint("minted1", minted1);

    //swap
    uint256 swapvalX = 100e18;
    uint256 swapvalY = DUT.swapGivenInputAmount(
        x0,
        y0,
        swapvalX,
        SpecifiedToken.X
    );

    // New values
    uint256 x1 = x0 + swapvalX;
    uint256 y1 = y0 - swapvalY;

    uint256 minted2 = DUT.depositGivenInputAmount(
        x1,
        y1,
        s0,
        depositedAmount,
        depositedToken
    );

    emit log_named_uint("minted2", minted2);
}
```

The output shows:

```bash
Running 1 test for src/test/EvolvingProteusProperties.t.sol:EvolvingProteusProperties
[PASS] testAttack() (gas: 140259)
Logs:
  minted1: 6801162225772413
  minted2: 6718681842515858
```

This shows that the user got lesser amount of tokens in the second minting
AMM's invariant of maximun/minimum slopes is broken
Audit: 2023-08-shell-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-08-shell/blob/main/src/proteus/EvolvingProteus.sol#L353


# Vulnerability details

## Impact
AMM's invariants are broken which might result in stale/unprofitable swaps

## Proof of Concept
the function `depositGivenInputAmount` is used to preview amount of LP tokens using the function `_reserveTokenSpecified`
```solidity
function _reserveTokenSpecified(
        SpecifiedToken specifiedToken,
        int256 specifiedAmount,
        bool feeDirection,
        int256 si,
        int256 xi,
        int256 yi
    ) internal view returns (int256 computedAmount) {
        int256 xf;
        int256 yf;
        int256 ui;
        int256 uf;
        {
            // calculating the final price points considering the fee
            if (specifiedToken == SpecifiedToken.X) {
                xf = xi + _applyFeeByRounding(specifiedAmount, feeDirection);
                yf = yi;
            } else {
                yf = yi + _applyFeeByRounding(specifiedAmount, feeDirection);
                xf = xi;
            }
        }

        ui = _getUtility(xi, yi);
        uf = _getUtility(xf, yf);

        uint256 result = Math.mulDiv(uint256(uf), uint256(si), uint256(ui));
        require(result < INT_MAX);
        int256 sf = int256(result);

        // apply fee to the computed amount
        computedAmount = _applyFeeByRounding(sf - si, feeDirection);
    }
``` 
The function above calculates final_utility(Uf) by updating the balances Xf and Yf according to the specified amount which is reserve token here.
A malicious user can deposit a specified amount so high such that updated Yf/Xf ratio is grater than maximum slope allowed `MAX_M` (0x5f5e1000000000000000000) breaking the invariant as there is no sanity check on balances implemented here.
since the utility has changed by a great factor a swap from specified token X to Y results in a loss to the users

## Tools Used
manual review
## Recommended Mitigation Steps
consider adding `_checkBalances` function in `_reserveTokenSpecified`
```solidity
+     _checkBalances(int256 Xf, int256 Yf)
```
such that it dosen't allow users to deposit amounts that change the balances ratio by a huge factor grater than `MAX_M`





## Assessed type

Invalid Validation
Missing balance checks in `_reserveTokenSpecified()`
Audit: 2023-08-shell-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-08-shell/blob/c61cf0e01bada04c3d6055acb81f61955ed600aa/src/proteus/EvolvingProteus.sol#L563-L595
https://github.com/code-423n4/2023-08-shell/blob/c61cf0e01bada04c3d6055acb81f61955ed600aa/src/proteus/EvolvingProteus.sol#L701-L724
https://github.com/code-423n4/2023-08-shell/blob/c61cf0e01bada04c3d6055acb81f61955ed600aa/src/proteus/EvolvingProteus.sol#L716


# Vulnerability details

## Impact
There is a missing balance check in `_reserveTokenSpecified()`, which allows reserve tokens to be withdrawn or deposited into illegal balances, which may cause subsequent transactions to revert. It also allows the pool to be killed by withdrawing all balances which prevents any further deposit.

## Proof of Concept
The allowed region of possible reserve token balances is constrained by [`_checkBalances()`](https://github.com/code-423n4/2023-08-shell/blob/c61cf0e01bada04c3d6055acb81f61955ed600aa/src/proteus/EvolvingProteus.sol#L809C7-L814), which requires that each balance be at least `MIN_BALANCE` and that the ratio of balances be in the interval [`MIN_M`, `MAX_M`).

The functions available for swapping are [`swapGivenInputAmount()`](https://github.com/code-423n4/2023-08-shell/blob/c61cf0e01bada04c3d6055acb81f61955ed600aa/src/proteus/EvolvingProteus.sol#L272-L304) and [`swapGivenOutputAmount()`](https://github.com/code-423n4/2023-08-shell/blob/c61cf0e01bada04c3d6055acb81f61955ed600aa/src/proteus/EvolvingProteus.sol#L312-L344), both of which make use of [`_swap()`](https://github.com/code-423n4/2023-08-shell/blob/c61cf0e01bada04c3d6055acb81f61955ed600aa/src/proteus/EvolvingProteus.sol#L506-L554) for calculating the unknown value (output and input amounts) which in turn checks the resulting balance after the swap with `_checkBalances()`. Thus it is impossible to swap out of the allowed region.

The functions [`depositGivenOutputAmount()`](https://github.com/code-423n4/2023-08-shell/blob/c61cf0e01bada04c3d6055acb81f61955ed600aa/src/proteus/EvolvingProteus.sol#L389-L416) and [`withdrawGivenInputAmount()`](https://github.com/code-423n4/2023-08-shell/blob/c61cf0e01bada04c3d6055acb81f61955ed600aa/src/proteus/EvolvingProteus.sol#L463-L490) both make use of [`_lpTokenSpecified()`](https://github.com/code-423n4/2023-08-shell/blob/c61cf0e01bada04c3d6055acb81f61955ed600aa/src/proteus/EvolvingProteus.sol#L607-L647) for the calculation which likewise checks the resulting balance after the deposit or withdrawal with `_checkBalances()`. Thus it is impossible to deposit or withdraw, using these functions, out of the allowed region.

However, the remaining functions [`depositGivenInputAmount()`](https://github.com/code-423n4/2023-08-shell/blob/c61cf0e01bada04c3d6055acb81f61955ed600aa/src/proteus/EvolvingProteus.sol#L353-L380) and [`withdrawGivenOutputAmount()`](https://github.com/code-423n4/2023-08-shell/blob/c61cf0e01bada04c3d6055acb81f61955ed600aa/src/proteus/EvolvingProteus.sol#L426-L453) instead make use of [`_reserveTokenS