xanoutas/solidity-security-findings huggingface dataset:
Vault.liquidate() doesn't send liquidation reward to recipient
Audit: 2024-03-revert-lend-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/V3Vault.sol?plain=1#L735
https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/V3Vault.sol?plain=1#L1032-L1073


# Vulnerability details

## Impact
 
When Vault.liquidate() is called, the protocol allows users to provide a LiquidateParams struct as the param. Within the LiquidateParams struct is the recipient attribute which determines who receives the liquidation reward. Unfortunately, the liquidate() function sends the liquidation reward to the msg.sender (the Vault.liquidate() caller). This breaks an API expectation for liquidators who expect the liquidation reward to be delivered to the recipient. If the liquidator is a contract, those rewards may be permanently stuck in the contract and the recipient will never receive the rewards.

## Proof of Concept

Let's look at the Vault.liquidate() function which shows the vulnerability:

```solidity
function liquidate(LiquidateParams calldata params) external override returns (uint256 amount0, uint256 amount1) {
    ...
    // send promised collateral tokens to liquidator
    // AUDIT: note that msg.sender is passed in as the 5th-arg
    (amount0, amount1) =
        _sendPositionValue(params.tokenId, state.liquidationValue, state.fullValue, state.feeValue, msg.sender);
    ...
}

function _sendPositionValue(
    uint256 tokenId,
    uint256 liquidationValue,
    uint256 fullValue,
    uint256 feeValue,
    address recipient // AUDIT: note that this is set as msg.sender and NOT params.recipient in the liquidate()
) internal returns (uint256 amount0, uint256 amount1) {
    uint128 liquidity;
    uint128 fees0;
    uint128 fees1;

    // if full position is liquidated - no analysis needed
    if (liquidationValue == fullValue) {
        (,,,,,,, liquidity,,,,) = nonfungiblePositionManager.positions(tokenId);
        fees0 = type(uint128).max;
        fees1 = type(uint128).max;
    } else {
        (,,, liquidity,,, fees0, fees1) = oracle.getPositionBreakdown(tokenId);

        // only take needed fees
        if (liquidationValue < feeValue) {
            liquidity = 0;
            fees0 = uint128(liquidationValue * fees0 / feeValue);
            fees1 = uint128(liquidationValue * fees1 / feeValue);
        } else {
            // take all fees and needed liquidity
            fees0 = type(uint128).max;
            fees1 = type(uint128).max;
            liquidity = uint128((liquidationValue - feeValue) * liquidity / (fullValue - feeValue));
        }
    }

    if (liquidity > 0) {
        nonfungiblePositionManager.decreaseLiquidity(
            INonfungiblePositionManager.DecreaseLiquidityParams(tokenId, liquidity, 0, 0, block.timestamp)
        );
    }

    (amount0, amount1) = nonfungiblePositionManager.collect(
        // AUDIT: msg.sender ends up getting the reward
        INonfungiblePositionManager.CollectParams(tokenId, recipient, fees0, fees1)
    );
}

```

As you can see above, Vault.liquidate() calls
The daily debt and lend limit are set to 110% instead of 10%
Audit: 2024-03-revert-lend-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Vault.sol#L1251
https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Vault.sol#L1263


# Vulnerability details

As mentioned in the whitepaper(7.3), the increase in the total value of loans and the lent amount within a 24-hour period can only be max 10%. However, the calculation in `_resetDailyLendIncreaseLimit()` and `_resetDailyDebtIncreaseLimit()` is wrong which makes the `dailyLendIncreaseLimitLeft` and `dailyDebtIncreaseLimitLeft` 110% instead of 10%.

```solidity
uint256 lendIncreaseLimit = _convertToAssets(totalSupply(),newLendExchangeRateX96, Math.Rounding.Up)
                * (Q32 + MAX_DAILY_LEND_INCREASE_X32) / Q32;

```
As you can see in both functions the calculation is: `totalLentAmount * (Q32 + Q32 / 10) / Q36` which makes the increase limit left 110% of the total amount lent. 


This will make the max daily debt increase checks useless as anyone can borrow or lend 110% of the total lent amount and not 10%. 



## Impact

The increase limits will become useless and in case of a scenario where a malicious actor discovers a vulnerability he will be able to drain everything because the 10% limit check as described in the whitepaper will not work. 

## Proof of Concept

Add this to `V3Vault.t.sol`

```solidity
function testWrongLimits() external {
        vault.setLimits(1000000, 1000e6, 1000e6, 100e6, 100e6);
        uint256 amount = 100e6;
        
        vm.startPrank(WHALE_ACCOUNT);
        USDC.approve(address(vault), amount);
        vault.deposit(amount, WHALE_ACCOUNT);
        vm.stopPrank();

        //Forcing the limits to be updated
        vault.setLimits(1000000, 1000e6, 1000e6, 100e6, 100e6);

        //110% instead of 10%
        console.log("The USDC balance in the vault:", USDC.balanceOf(address(vault)) / 1e6);
        console.log("The daily lend increase limit is %s USDC", (vault.dailyLendIncreaseLimitLeft() + 1) / 1e6);
        console.log("The daily debt increase limit is %s USDC", (vault.dailyDebtIncreaseLimitLeft() + 1) / 1e6); 
    }

```

## Tools Used

Foundry

## Recommended Mitigation Steps

Do not add Q32 to `MAX_DAILY_LEND_INCREASE_X32` and `MAX_DAILY_DEBT_INCREASE_X32` 


## Assessed type

Math
Rounding error when using all X32 constants
Audit: 2024-03-revert-lend-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/V3Vault.sol#L36-L44
https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/V3Vault.sol#L1100
https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/V3Vault.sol#L1116
https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/V3Vault.sol#L1262-L1263
https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/V3Vault.sol#L1250-L1251


# Vulnerability details

## Impact

The X32 constants used throughout the code base incorrectly do division before multiplication. This causes a small rounding error each time the constants are used. 

If we were to use `MAX_DAILY_LEND_INCREASE_X32` to calculate 10% of 100,000 USDC we would get the following code:
```solidity
uint256 tenPercent = 100000e6 * MAX_DAILY_LEND_INCREASE_X32 / Q32;
                   = 100000e6 * (Q32 / 10) / Q32;
                   = 9_999_999_986
```

Instead, we want the math to be the following:
```solidity
uint256 tenPercent = 100000e6 * Q32 / (10 * Q32);
                   = 10_000_000_000
```

## Proof of Concept

```solidity
function testRoundingErrorWithX32Constants() external {
    uint256 maxDailyLendIncrease = uint256(vault.MAX_DAILY_LEND_INCREASE_X32()); // 10%
    uint256 wrongLendIncrease = 100 * maxDailyLendIncrease / Q32;
    assertEq(wrongLendIncrease, 9);

    uint256 correctLendIncrease = 100 * Q32 / 10 / Q32;
    assertEq(correctLendIncrease, 10);

    uint256 maxCollateralFactor = uint256(vault.MAX_COLLATERAL_FACTOR_X32()); // 90%
    uint256 wrongCollateralAmount = 10_000_000 * (maxCollateralFactor) / Q32;
    assertEq(wrongCollateralAmount, 8_999_999);

    uint256 rightCollateralAmount = 10_000_000 * Q32 * 90 / (Q32 * 100);
    assertEq(rightCollateralAmount, 9_000_000);
}
```

## Tools Used

Manual Review

## Recommended Mitigation Steps

Do not store the constants with division. Instead, do the math inline where necessary.


## Assessed type

Math
Missing slippage in the _sendPositionValue() function, will DoS liquidations
Audit: 2024-03-revert-lend-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Vault.sol#L1072-L1076


# Vulnerability details

When a position gets liquidated, some liquidity from the position may be withdrawn in order to cover the debt. The issue here is that we there is no slippage protection when exchanging liquidity against assets. Consequently the `decreaseLiquidity()` call is exposed MEV attacks. 
Although there is slippage check down the line, that will make the transaction revert, this will lead to complication actually finalizing the liquidation process.


## Impact

Liquidation will revert due to insufficient liquidator rewards returned, liquidate ends up being DoSed

## Proof of Concept

When liquidity is needed in order to cover the liquidator rewards. decreaseLiquidity will decrease the position liquidity in exchange for token0 and token1. 

```
            nonfungiblePositionManager.decreaseLiquidity(
                INonfungiblePositionManager.DecreaseLiquidityParams(tokenId, liquidity, 0, 0, block.timestamp) 
            );
```
the issue here is that no amount of token0 and token1 is expected in return. Which means that this call can result in having no tokens given against the liquidity share provided. 

Down the line of the [liquidation process](https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Vault.sol#L744-L746), we can see that there is indeed a slippage check that is implemented for the liquidator.

```solidity
        if (amount0 < params.amount0Min || amount1 < params.amount1Min) {
            revert SlippageError();
        }
```

However if the `decreaseLiquidity` keeps getting unsatisfying rewards amounts, the liquidation will be DoSed.

## Tools Used

Manual review

## Recommended Mitigation Steps

Provide concrete amount1 and amount0 to return in exchange for the liquidity. 

```
            nonfungiblePositionManager.decreaseLiquidity(
                INonfungiblePositionManager.DecreaseLiquidityParams(tokenId, liquidity, amount0Min, amount1Min, deadline) 
```


## Assessed type

MEV
Risk when settings `twapSeconds` for token's feedConfig: price can be manipulated
Audit: 2024-03-revert-lend-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/V3Oracle.sol#L232
https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/V3Oracle.sol#L362-#L364


# Vulnerability details

## Vulnerability details
In `_getReferencePoolPriceX96()` function, if twaapSeconds = 0, price will be fetched directly by using slot0() function:

    function _getReferencePoolPriceX96(IUniswapV3Pool pool, uint32 twapSeconds) internal view returns (uint256) {
        uint160 sqrtPriceX96;
        // if twap seconds set to 0 just use pool price
        if (twapSeconds == 0) {
            (sqrtPriceX96,,,,,,) = pool.slot0();
This function with `twapSeconds` = 0 is called in the `_checkPoolPrice()`, which then used to check for price manipulate attack:

    function _checkPoolPrice(address token0, address token1, uint24 fee, uint256 derivedPoolPriceX96) internal view {
        IUniswapV3Pool pool = _getPool(token0, token1, fee);
        uint256 priceX96 = _getReferencePoolPriceX96(pool, 0);  //<--
        _requireMaxDifference(priceX96, derivedPoolPriceX96, maxPoolPriceDifference);
    }

It is also called in `_getTWAPPriceX96()` function to fetch price:

    function _getTWAPPriceX96(TokenConfig memory feedConfig) internal view returns (uint256 poolTWAPPriceX96) {
        // get reference pool price
        uint256 priceX96 = _getReferencePoolPriceX96(feedConfig.pool, feedConfig.twapSeconds); //<---
        .  .  .  .  .
It is called at `_getReferenceTokenPriceX96()`, with config is set by owner:

    function _getReferenceTokenPriceX96(address token, uint256 cachedChainlinkReferencePriceX96) internal view returns (uint256 priceX96, uint256 chainlinkReferencePriceX96)
    {
        if (token == referenceToken) {
            return (Q96, chainlinkReferencePriceX96);
        }

        TokenConfig memory feedConfig = feedConfigs[token]; //<--
   
        .  .  .  .  .  .

        if (usesTWAP) {
            uint256 twapPriceX96 = _getTWAPPriceX96(feedConfig);  //<--
            if (feedConfig.mode == Mode.CHAINLINK_TWAP_VERIFY) {
                verifyPriceX96 = twapPriceX96;
            } else {
                priceX96 = twapPriceX96;
            }
        }

In the `setTokenConfig()` function, there is no restriction in settings `twapSeconds`. So if `twapSeconds` of feedConfig is set to 0, attacker can manipulate price and bypass protection

## Impact
Token price can be manipulated, manipulate protection is not work in that case

## Tools Used
Manual review

## Recommended Mitigation Steps
Add checking for `twapSeconds` in `setTokenConfig()` function, make sure it have at least some minutes to prevent TWAP manipulate attack


## Assessed type

Other
`ResetDailyIncreaseLimit` incorrectly calculates 10% of the existing debt/lend supply
Audit: 2024-03-revert-lend-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/V3Vault.sol#L1246-L1268


# Vulnerability details

## Impact

Per the whitepaper, the max daily debt/lend increase should be capped at 10% of the current lend/debt amount. The `V3Vault._resetDaily(Lend/Debt)IncreaseLimit` functions multiply the current amount by 110% instead of 10%.

```solidity
function _resetDailyLendIncreaseLimit(uint256 newLendExchangeRateX96, bool force) internal {
    // daily lend limit reset handling
    uint256 time = block.timestamp / 1 days;
    if (force || time > dailyLendIncreaseLimitLastReset) {
        // @audit-medium Is multiplying by 110% instead of 10%
        uint256 lendIncreaseLimit = _convertToAssets(totalSupply(), newLendExchangeRateX96, Math.Rounding.Up)
            * (Q32 + MAX_DAILY_LEND_INCREASE_X32) / Q32;
        dailyLendIncreaseLimitLeft =
            dailyLendIncreaseLimitMin > lendIncreaseLimit ? dailyLendIncreaseLimitMin : lendIncreaseLimit;
        dailyLendIncreaseLimitLastReset = time;
    }
}
```

This means that if 100,000 USDC has been lended to the vault, the `dailyLendIncreaseLimitLeft` will be set to 110,000 USDC instead of 10,000 USDC.

## Proof of Concept

```solidity
function testDailyIncreaseLimitBug() external {
    // Adjust limits to show vulnerability
    uint256 amount = 10e6;
    vault.setLimits(0, 1000e6, 1000e6, amount, amount);

    // lend 10 USDC to the vault
    assertEq(vault.dailyLendIncreaseLimitLeft(), amount);
    _deposit(amount, WHALE_ACCOUNT);

    // increase time and deposit 0 to update dailyLendIncreaseLimitLeft
    vm.warp(block.timestamp + 1 days);
    _deposit(0, WHALE_ACCOUNT);

    // This should instead be the max between 10e6 (min increase) and 1e6 (10%)
    assertEq(vault.dailyLendIncreaseLimitLeft(), 10_999_999);
}
```

## Tools Used

Manual Review

## Recommended Mitigation Steps

Do not add Q32 to the `MAX_DAILY_LEND_INCREASE_X32` or `MAX_DAILY_DEBT_INCREASE_X32`

```diff
function _resetDailyLendIncreaseLimit(uint256 newLendExchangeRateX96, bool force) internal {
    // daily lend limit reset handling
    uint256 time = block.timestamp / 1 days;
    if (force || time > dailyLendIncreaseLimitLastReset) {
        uint256 lendIncreaseLimit = _convertToAssets(totalSupply(), newLendExchangeRateX96, Math.Rounding.Up)
-           * (Q32 + MAX_DAILY_LEND_INCREASE_X32) / Q32;
+           * uint256(MAX_DAILY_LEND_INCREASE_X32) / Q32;
        dailyLendIncreaseLimitLeft =
            dailyLendIncreaseLimitMin > lendIncreaseLimit ? dailyLendIncreaseLimitMin : lendIncreaseLimit;
        dailyLendIncreaseLimitLastReset = time;
    }
}

function _resetDailyDebtIncreaseLimit(uint256 newLendExchangeRateX96, bool force) internal {
    // daily debt limit reset handling
    uint256 time = block.timestamp / 1 days;
    if (force || time > dailyDebtIncreaseLimitLastReset) {
        uint256 debtIncreaseLimit = _convertToAssets(totalSupply(), newLendExchangeRateX96, Math.Rounding.Up)
-
Lenders cannot withdraw, if borrowers keep borrowing
Audit: 2024-03-revert-lend-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Vault.sol#L920


# Vulnerability details

## Impact
At some point lender would like to withdraw assets, however there can be a situation when borrowers took all vault assets,
which is very nice for lender's APY, but high APY means nothing if lender can not withdraw.

## Proof of Concept
If there is financial incentive, borrowers will borrow until there is no money.
If lender wants to withdraw, they want assets ASAP. 
Currently there is nothing stopping borrowers to take all the assets, and keep them indefinitely while they have enough collateral.
Depending on configuration Interest Rate can be not very high, current protocol tests show config where
> 25.8% per year for 100% utilization

Some tokens price can grow much faster and make some loans indefinite.

## Tools Used
Manual review

## Recommended Mitigation Steps
Add some variable that tracks amount of assets lenders want to withdraw but vault does not have, 
so when borrowers want to take a loan, they do not take assets pending for withdrawal.
Or/and add some expiration time for loans, so we have some predictability on when assets can be withdrawn.


## Assessed type

Other
User griefing auto-range of other users by giving auto-range transformer other users tokenId
Audit: 2024-03-revert-lend-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/transformers/AutoRange.sol#L97
https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Vault.sol#L515


# Vulnerability details

## Impact 
Users can provide to transformes `tokenId` of other users who approved transformers to modify their positions.
That way a user can make position of other user out of range or in less profitable range.

## Proof of Concept
`ExecuteParams params` is configuration feeded to revert-lend bot, which made off-chain with user input.
Bot will then modify position according to provided params.
```solidity
    src/transformers/AutoRange.sol
    function executeWithVault(ExecuteParams calldata params, address vault) external {
```
`ExecuteParams params` includes ` uint256 tokenId;`, which determines which position will be modified.
If Alice and Bob use this transformer, Alice can on purpose or by mistake provide `tokenId` of Bob's position.
Inside `function transform(uint256 tokenId, address transformer, bytes calldata data)` we only check if
transformer has been approved to modify particular position:
```solidity
    src/V3Vault.sol
    function transform(uint256 tokenId, address transformer, bytes calldata data){
        (...)
        address loanOwner = tokenOwner[tokenId];

        // only the owner of the loan, the vault itself or any approved caller can call this
        if (loanOwner != msg.sender && !transformApprovals[loanOwner][tokenId][msg.sender]) {
```
We do not check if `loanOwner` actually initiated and want such position modification.

Currently only `AutoRange` and `AutoCompound` use transform with user's approval.
`AutoRange` can negatively impact user's position income.

## Tools Used
Manual review

## Recommended Mitigation Steps
Developer may chose to mitigate it in different ways and places,
however on a blockchain in `ExecuteParams params` we could provide not a plain `uint256 tokenId;` but a signed message by `loanOwner`,
so when we recover address from signature, we will be sure that this `ExecuteParams params` where provided by the actual `loanOwner`.



## Assessed type

Access Control
Lenders can frontrun and withdraw assets before liquidation of insolvent loans to avoid loss
Audit: 2024-03-revert-lend-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Vault.sol#L1138
https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Vault.sol#L920


# Vulnerability details

## Impact
Lenders can monitor mempool for transaction that change `lastLendExchangeRateX96` to lesser value, which impacts their profit.
Market volatility or malicious activity can be reasons for loans become insolvent.
When protocol reserves are depleted, loss become divided between all lenders.
However some lenders may track mempool for liquidations that reduce `lastLendExchangeRateX96`, frontrun and withdraw assets before liquidation,
making other lenders having even more loss.

## Proof of Concept
Assume two lenders deposit equal amount of assets
- Some was borrowed
- Market prices drop significanly and fast for one of the tokens, beyond collateral value
- Lenders may not withdraw because exhange rate is still growing with time and they are chilling
- Someone calls `liquidate()` on insolvent loan, transaction is in a mempool, we can read parameters or just simulate transaction to know it will reduce `lastLendExchangeRateX96`
- First lender frontruns and withdraws their assets at current `lastLendExchangeRateX96` before liquidation
- Second lender stays with lowered `lastLendExchangeRateX96` even greatly because `totalLent` is lower now, and his losses are doubled
```solidity
            newLendExchangeRateX96 = (totalLent - missing) * newLendExchangeRateX96 / totalLent;
            lastLendExchangeRateX96 = newLendExchangeRateX96;
```
- First lender may deposit again like nothing never happened

## Tools Used
Manual review

## Recommended Mitigation Steps
Maybe track loans and perform insolvent loan liquidation on withdraw, or restrict withdrawals while protol has insolvent loans. Need to think on it.



## Assessed type

MEV
Malicious user can borrow above allowed limitations
Audit: 2024-03-revert-lend-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Vault.sol#L995


# Vulnerability details

## Impact
One of the main reaseon protocol has daily limits on amount borrowed is to prevent posssible exploit to damage protocol too much.
Current implementation of function `_repay()` allows an attacker to repay other people loans and increase amount of tokens allowed to borrow that day `dailyDebtIncreaseLimitLeft`
```solidity
src/V3Vault.sol
995:        dailyDebtIncreaseLimitLeft += assets;
```
Recently Abracadabra protocol was [hacked](https://rekt.news/abra-rekt/) and one of the steps of the exploit was
> 3 Repay liabilities for all other users by calling to repayForAll().
So it can be financially profitable for a hacker to repay debts.

## Proof of Concept
A test below is an example, that shows that after repaying a loan we can borrow more assets, as by design.
Add this test beside your other tests in `test/integration/V3Vault.t.sol`
```solidity
    function testBorrowV3() external {
        vault.setLimits(0, 100_000000, 100_000000, 100_000000, 12_000000);
        _deposit(100_000000, WHALE_ACCOUNT);

        uint256 borrowAmount1 = 8_000000;
        uint256 borrowAmount2 = 16_000000;
        _createAndBorrow(TEST_NFT, TEST_NFT_ACCOUNT, borrowAmount1);
        skip(1 days);//to reset daily limits

        vm.startPrank(TEST_NFT_ACCOUNT_2);
        NPM.approve(address(vault), TEST_NFT_2);
        vault.create(TEST_NFT_2, TEST_NFT_ACCOUNT_2);
        (uint256 debt,,,,) = vault.loanInfo(TEST_NFT);
        USDC.approve(address(vault), debt);
        vault.repay(TEST_NFT, debt, false);
        vault.borrow(TEST_NFT_2, borrowAmount2);

        (uint256 debt2,,,,) = vault.loanInfo(TEST_NFT_2);
        assert(debt2 >= borrowAmount2);
    }
```

## Tools Used
Manual review

## Recommended Mitigation Steps
Do not change `dailyDebtIncreaseLimitLeft` on repay

```diff
src/V3Vault.sol
-995:        dailyDebtIncreaseLimitLeft += assets;
```




## Assessed type

Other