xanoutas/solidity-security-findings huggingface dataset:
V3Vault::_deposit incorrectly validates the global lending limit and allows borrowing of assets above the limit
Audit: 2024-04-revert-mitigation-findings
Severity: MEDIUM

# Lines of code

https://github.com/revert-finance/lend/blob/audit/src/V3Vault.sol#L961


# Vulnerability details

## Vulnerability details

`V3Vault` applies a global limit on the total amount of assets that can be deposited for borrowing. This limit is enforced through the `globalLendLimit` state variable, set by the contract owner.

This is how the limit is applied in the `_deposit()` function:

https://github.com/revert-finance/lend/blob/audit/src/V3Vault.sol#L961-L962
```solidity
  function _deposit(address receiver, uint256 amount, bool isShare, bytes memory permitData)
        internal
        returns (uint256 assets, uint256 shares)
    {
        ....
        
        // check for global limit
        if (totalSupply() + shares > globalLendLimit) {
            revert GlobalLendLimit();
        }
        
        // check for daily limit
        if (assets > dailyLendIncreaseLimitLeft) {
            revert DailyLendIncreaseLimit();
        }

        ....
    }
```

If you look closely you can see that `globalLendLimit` is compared against the total shares. This is wrong since `globalLendLimit` is measured in terms of assets, not shares. This can be further validated by looking at the `maxDeposit()` & `maxMint()` functions:

https://github.com/revert-finance/lend/blob/audit/src/V3Vault.sol#L322
```solidity
function maxMint(address) external view override returns (uint256) {
        ....
        uint256 value = _convertToAssets(totalSupply(), lendExchangeRateX96, Math.Rounding.Up);
        if (value >= globalLendLimit) { // <---- value is in assets
            return 0;
        ....
    }
```

https://github.com/revert-finance/lend/blob/audit/src/V3Vault.sol#L306
```solidity
 function maxDeposit(address) external view override returns (uint256) {
        ....
        uint256 value = _convertToAssets(totalSupply(), lendExchangeRateX96, Math.Rounding.Up);
        if (value >= globalLendLimit) { // <---- value is in assets
            return 0;
        ....
    }
```

In time as loan fees accrue the shares to assets ratio will increase, which means that fewer shares would equal greater amount of assets. As a result the global limit will be evaluated against a smaller amount (shares) than it actually should (assets)

## Impact

Comparing global debt limit against shares leads to more assets being deposited for borrowing than it should be allowed, breaking important protocol invariant.

## Recommended Mitigation

Compare the global lend limit in `_deposit` against the assets, not the shares:

```solidity
if (totalAssets() + assets > globalLendLimit) {
     revert GlobalLendLimit();
 }
```


## Assessed type

Invalid Validation
V3Vault::maxWithdrawal incorrectly converts balance to assets
Audit: 2024-04-revert-mitigation-findings
Severity: MEDIUM

# Lines of code

https://github.com/revert-finance/lend/blob/audit/src/V3Vault.sol#L345


# Vulnerability details

## Vulnerability details

The `maxWithdrawal()` function of `V3Vault` calculates the maximum amount of underlying tokens an account can withdraw based on the shares it owns.

The initial problem with `maxWithdrawal()` and `V3Vault` overall was that they were not implemented according to the specs of ERC-4626 standard [as outlined in the original issue](https://github.com/code-423n4/2024-03-revert-lend-findings/issues/249). In the case of `maxWithdrawal()` it did not consider the following [part of the spec](https://eips.ethereum.org/EIPS/eip-4626):

> MUST factor in both global and user-specific limits, like if withdrawals are entirely disabled (even temporarily) it MUST return 0.

In order to remediate the issue and make the `V3Vault` ERC-4626 compliant, protocol devs [prepared the following PR](https://github.com/revert-finance/lend/pull/15/files), where `maxWithdrawal()` was refactored so that it includes the actual daily limit that is applied when withdrawing assets:

https://github.com/revert-finance/lend/blob/audit/src/V3Vault.sol#L335-L347
```solidity
 function maxWithdraw(address owner) external view override returns (uint256) {
-        (, uint256 lendExchangeRateX96) = _calculateGlobalInterest();
-        return _convertToAssets(balanceOf(owner), lendExchangeRateX96, Math.Rounding.Down);

+        (uint256 debtExchangeRateX96, uint256 lendExchangeRateX96) = _calculateGlobalInterest();

+        uint256 ownerShareBalance = balanceOf(owner);
+        uint256 ownerAssetBalance = _convertToAssets(ownerShareBalance, lendExchangeRateX96, Math.Rounding.Down);

+        (uint256 balance, ) = _getBalanceAndReserves(debtExchangeRateX96, lendExchangeRateX96);
+        if (balance > ownerAssetBalance) {
+            return ownerAssetBalance;
+        } else {
+            return _convertToAssets(balance, lendExchangeRateX96, Math.Rounding.Down);
+        }
    }
```

The problem with the new code is this part:

```solidity
   // @audit balance is already converted to assets
   (uint256 balance, ) = _getBalanceAndReserves(debtExchangeRateX96, lendExchangeRateX96);

    // @audit - converts to assets a second time
    } else {
        return _convertToAssets(balance, lendExchangeRateX96, Math.Rounding.Down);
    }
```

If we take a look at `_getBalanceAndReserves()` we can see that the returned `balance` is already converted to `assets`:

https://github.com/revert-finance/lend/blob/audit/src/V3Vault.sol#L1107-L1116
```solidity
function _getBalanceAndReserves(uint256 debtExchangeRateX96, uint256 lendExchangeRateX96)
        internal
        view
        returns (uint256 balance, uint256 reserves)
    {
 --->       balance = totalAssets();
        uint256 debt = _convertToAssets(debtSharesTotal, debtExchangeRateX96, Math.Rounding.Up);
        uint256 lent = _convertToAssets(totalSupply(), lendExchangeRateX96, Math.Rounding.Up);
        res
ADD-02 Unmitigated
Audit: 2024-04-revert-mitigation-findings
Severity: MEDIUM

# Lines of code

https://github.com/revert-finance/lend/blob/dcfa79924c0e0ba009b21697e5d42d938ad9e5e3/src/V3Oracle.sol#L360


# Vulnerability details

## C4 Issue
ADD-02: [Missing L2 sequencer checks for Chainlink oracle ](https://github.com/code-423n4/2024-03-revert-lend-findings/issues/12)

## Original Issue Details

`V3Oracle.sol` did not implement sequencer uptime checks for the Chainlink oracle on L2

## Mitigation
[PR-27](https://github.com/revert-finance/lend/pull/27/files) does not implement properly the sequencer uptime check

## New Vulnerability Details
The sequencer uptime check is not implemented properly and as a result fetching prices on L2 will always revert.

[ChainLink docs](https://docs.chain.link/data-feeds/l2-sequencer-feeds) as well as [the comments in the code](https://github.com/revert-finance/lend/blob/audit/src/V3Oracle.sol#L358-L359) describe the statuses the uptime feed returns:
- if `answer` is `0` it means the sequencer is `UP`
- if `answer` is `1` it means the sequencer is `DOWN`

However the check is implemented in reverse:

https://github.com/revert-finance/lend/blob/audit/src/V3Oracle.sol#L360-L361

```solidity
      // Answer == 0: Sequencer is up
      // Answer == 1: Sequencer is down
      if (sequencerAnswer == 0) {
         revert SequencerDown();
      }
```
This means that the function will revert while the sequencer is `UP` (is `0`), while it should be other way around.

## Recommended Mitigation

Update the check to revert when the sequrncer is `DOWN` (is `1`)

```solidity
       // Answer == 0: Sequencer is up
       // Answer == 1: Sequencer is down
       if (sequencerAnswer == 1) {
           revert SequencerDown();
       }
```

## Conclusion
Not Mitigated


## Assessed type

Invalid Validation
Some functions don't check if liquidity > 0 before calling decreaseLiquidity
Audit: 2024-04-revert-mitigation-findings
Severity: MEDIUM

# Lines of code

https://github.com/revert-finance/lend/blob/audit/src/V3Vault.sol#L654-L658


# Vulnerability details

## Impact
- Users cannot just collect UniswapV3 fees alone.
- Users cannot call `leverageDown` with fee alone.

## Proof of concept
One of the most important features of Revert Lend is that it allows user to take loans using UniswapV3 positions as collateral while at the same time able to manage their positions; this includes collecting fees, decrease liquidity, increase liquidity,... as documented [here](https://docs.revert.finance/revert/technical-docs/auto-compounder/manage-positions)

However, the current implementation will not allow user to just collect fees. `V3Vault` contains a function called `decreaseLiquidityAndCollect`:
```solidity
function decreaseLiquidityAndCollect(DecreaseLiquidityAndCollectParams calldata params)
        external
        override
        returns (uint256 amount0, uint256 amount1)
    {
     ...
     (amount0, amount1) = nonfungiblePositionManager.decreaseLiquidity(
            INonfungiblePositionManager.DecreaseLiquidityParams(
                params.tokenId, params.liquidity, params.amount0Min, params.amount1Min, params.deadline
            )
        );
     ...
    }
```
However as you can see in the above code, the function will call `decreaseLiquidity` without checking if `liquidity` to be removed >0; if `liquidity = 0`, then `decreaseLiquidity` will revert. Below is the UniswapV3 NonfungibleTokenManager code for this situation https://github.com/Uniswap/v3-periphery/blob/main/contracts/NonfungiblePositionManager.sol#L265:

```solidity
 function decreaseLiquidity(DecreaseLiquidityParams calldata params)
        external
        payable
        override
        isAuthorizedForToken(params.tokenId)
        checkDeadline(params.deadline)
        returns (uint256 amount0, uint256 amount1)
    {
        require(params.liquidity > 0);
}
```

Using `V3Utils` transformation will not allow users to just collect fees either. The function `V3Utils.execute` does check if `liquidity >0` and collect fees:
```solidity
function execute(uint256 tokenId, Instructions memory instructions) public returns (uint256 newTokenId) {
        _validateCaller(nonfungiblePositionManager, tokenId);

        (,, address token0, address token1,,,, uint128 liquidity,,,,) = nonfungiblePositionManager.positions(tokenId);

        uint256 amount0;
        uint256 amount1;
        if (instructions.liquidity != 0) {
            (amount0, amount1) = _decreaseLiquidity(
                tokenId,
                instructions.liquidity,
                instructions.deadline,
                instructions.amountRemoveMin0,
                instructions.amountRemoveMin1
            );
        }
        (amount0, amount1) = _collectFees(
            tokenId,
            IERC20(token0),
            IERC20(token1),
            instructions.feeAmount0 == type(uint128).max
                ? type(uint128).max
                : (amount0 + instr
M-14 Unmitigated
Audit: 2024-04-revert-mitigation-findings
Severity: MEDIUM

# Lines of code

https://github.com/revert-finance/lend/blob/dfced79e81feba1146bb3936c60931e025f6131e/src/V3Vault.sol#L343


# Vulnerability details

## C4 Issue
M-14: [V3Vault is not ERC-4626 compliant](https://github.com/code-423n4/2024-03-revert-lend-findings/issues/249)

## Issue Details

The original issue was that `V3Vault.sol` did not implement the [ERC-4626 standard properly](https://eips.ethereum.org/EIPS/eip-4626). More specifically the following functions needed to be refactored in order to comply with the specs defined for ERC-4626 vaults:
- `V3Vault::maxDeposit` - did not check if the amount exceeds the `daily lend limit`
- `V3Vault::maxMint` - did not check if the amount exceeds the `daily lend limit`
- `V3Vault::maxWithdraw` - did not check if the amount exceeds the `current available balance` in the vault
- `V3Vault::maxRedeem` - did not check if the amount exceeds the `current available balance` in the vault

The refactoring was needed because the specs explicitly state that the above functions must include any `global and user-specific limits`

## Mitigation
[PR-15](https://github.com/revert-finance/lend/pull/15/files) attempts to update the `V3Vault.sol` functions so that it becomes compatible with the ERC-4626 standard. However it fails to fully mitigate the problem, because of incorrect conversion conducted in one of the refactored functions

## Conclusion
Not Fully Mitigated

## Vulnerability details

The problem lies in the refactoring made inside the `maxWithdraw` function:
https://github.com/revert-finance/lend/blob/dcfa79924c0e0ba009b21697e5d42d938ad9e5e3/src/V3Vault.sol#L345

```solidity

 function maxWithdraw(address owner) external view override returns (uint256) {
        (uint256 debtExchangeRateX96, uint256 lendExchangeRateX96) = _calculateGlobalInterest();

        uint256 ownerShareBalance = balanceOf(owner);
        uint256 ownerAssetBalance = _convertToAssets(ownerShareBalance, lendExchangeRateX96, Math.Rounding.Down);
        
        (uint256 balance, ) = _getBalanceAndReserves(debtExchangeRateX96, lendExchangeRateX96);
        if (balance > ownerAssetBalance) {
            return ownerAssetBalance;
        } else {
            return _convertToAssets(balance, lendExchangeRateX96, Math.Rounding.Down); // <--- already in assets
        }
    }

```

If you look closely you will see that inside the `else clause`  balance is converted to assets one more time. This is incorrect since `balance` already represents the `totalAssets` in the vault and thus it should not be converted. 

This can be confirmed by looking at the `_getBalanceAndReserves()` function:
https://github.com/revert-finance/lend/blob/dcfa79924c0e0ba009b21697e5d42d938ad9e5e3/src/V3Vault.sol#L1112

```solidity
function _getBalanceAndReserves(uint256 debtExchangeRateX96, uint256 lendExchangeRateX96)
        internal
        view
        returns (uint256 balance, uint256 reserves)
    {
        balance = totalAssets(); // <--- represents assets NOT sh
M-14 Unmitigated
Audit: 2024-04-revert-mitigation-findings
Severity: MEDIUM

# Lines of code

https://github.com/revert-finance/lend/blob/audit/src/V3Vault.sol#L345


# Vulnerability details

## C4 issue
M-14: [V3Vault is not ERC-4626 compliant](https://github.com/code-423n4/2024-03-revert-lend-findings/issues/249)

## Comment
The original contract `V3Vault` is not ERC4626 compliant because functions `maxRedeem`, `maxMint`, `maxWithdraw`, `maxDeposit` don't return the correct value. 

## Proof of concept
The fix for this issue is [PR #15](https://github.com/revert-finance/lend/pull/15)
The mitigation updated the above 4 functions to reflect the correct values; however, there is 1 mistake at function `maxWithdraw`:
```solidity
function maxWithdraw(address owner) external view override returns (uint256) {
        (uint256 debtExchangeRateX96, uint256 lendExchangeRateX96) = _calculateGlobalInterest();

        uint256 ownerShareBalance = balanceOf(owner);
        uint256 ownerAssetBalance = _convertToAssets(ownerShareBalance, lendExchangeRateX96, Math.Rounding.Down);

        (uint256 balance,) = _getBalanceAndReserves(debtExchangeRateX96, lendExchangeRateX96);
        if (balance > ownerAssetBalance) {
            return ownerAssetBalance;
        } else {
            return _convertToAssets(balance, lendExchangeRateX96, Math.Rounding.Down);
        }
    }
```
In case `balance <= ownerAssetBalance`, while the functions should return `balance`, the `balance` get converted to assets and returned (the balance is of type asset itself, not share).

## Tool used
Manual review
## Recommended Mitigation Steps
Return `balance` instead.






## Assessed type

Math
M-12 Unmitigated
Audit: 2024-04-revert-mitigation-findings
Severity: MEDIUM

# Lines of code

https://github.com/revert-finance/lend/blob/audit/src/V3Vault.sol#L961-L963


# Vulnerability details

## C4 issue
M-12: [Wrong global lending limit check in _deposit function](https://github.com/code-423n4/2024-03-revert-lend-findings/issues/324)

## Comment
The original code wrongly uses `totalSupply()` to check against `globalLendLimit`, this needs to be converted to assets first:
```solidity
function _deposit(
    address receiver,
    uint256 amount,
    bool isShare,
    bytes memory permitData
) internal returns (uint256 assets, uint256 shares) {
    ...

    _mint(receiver, shares);

    //@audit must convert totalSupply() to assets before comparing with globalLendLimit
    if (totalSupply() > globalLendLimit) {
        revert GlobalLendLimit();
    }

    if (assets > dailyLendIncreaseLimitLeft) {
        revert DailyLendIncreaseLimit();
    } else {
        dailyLendIncreaseLimitLeft -= assets;
    }
    ...
}
```

## Proof of Concept
[PR #16](https://github.com/revert-finance/lend/pull/16) converts the asset
before comparison:
```solidity
        uint256 totalSupplyValue = _convertToAssets(totalSupply(), newLendExchangeRateX96, Math.Rounding.Up);
        if (totalSupplyValue > globalLendLimit) {
            revert GlobalLendLimit();
        }
```
However, this commit [#161 Gas Optimizations](https://github.com/revert-finance/lend/commit/f7aafe4866dd487634dab48357a83fd79bb798f1) reverts this change back to the original code, the comparison part still compare `totalSupply() + shares` against `globalLendLimit`:
```solidity
if (totalSupply() + shares > globalLendLimit) {
            revert GlobalLendLimit();
        }
        if (assets > dailyLendIncreaseLimitLeft) {
            revert DailyLendIncreaseLimit();
        }
```

## Recommended mitigation 
Bring back the change to convert totalSupply to asset before comparison:
```solidity
uint256 totalSupplyValue = _convertToAssets(totalSupply(), newLendExchangeRateX96, Math.Rounding.Up);
        if (totalSupplyValue > globalLendLimit) {
            revert GlobalLendLimit();
        }
```











## Assessed type

Other
M-11 Unmitigated
Audit: 2024-04-revert-mitigation-findings
Severity: MEDIUM

# Lines of code

https://github.com/revert-finance/lend/blob/audit/src/V3Vault.sol#L566


# Vulnerability details

## C4 issue
M-11: [Lack of safety buffer in _checkLoanIsHealthy could subject users who take out the max loan into a forced liquidation](https://github.com/code-423n4/2024-03-revert-lend-findings/issues/363)

## Comment
The original function `_checkLoanIsHealthy` does not include a buffer:
```solidity
function _checkLoanIsHealthy(uint256 tokenId, uint256 debt)
        internal
        view
        returns (bool isHealthy, uint256 fullValue, uint256 collateralValue, uint256 feeValue)
    {
        (fullValue, feeValue,,) = oracle.getValue(tokenId, address(asset));
        uint256 collateralFactorX32 = _calculateTokenCollateralFactorX32(tokenId);
        collateralValue = fullValue.mulDiv(collateralFactorX32, Q32);
        isHealthy = collateralValue >= debt;
    }
```
If a user borrows max value (debt = collateralValue), then their loan is still considered healthy and `borrow` is successful. This makes their loans very sensitive to market changes since just a small decrease in `collateralValue` could makes their loans considered not healthy and be liquidatable.

## Impact
User loans is not protected with buffer zone in case they transform their loans.

## Proof of concept
The code mitigates the issue by [PR #17](https://github.com/revert-finance/lend/pull/17)
The mitigation added a new variable `withBuffer` to function `_checkLoanIsHealthy`:
```solidity
function _checkLoanIsHealthy(uint256 tokenId, uint256 debt, bool withBuffer)
        internal
        view
        returns (bool isHealthy, uint256 fullValue, uint256 collateralValue, uint256 feeValue)
    {
        (fullValue, feeValue,,) = oracle.getValue(tokenId, address(asset));
        uint256 collateralFactorX32 = _calculateTokenCollateralFactorX32(tokenId);
        collateralValue = fullValue.mulDiv(collateralFactorX32, Q32);
        isHealthy = (withBuffer ? collateralValue * BORROW_SAFETY_BUFFER_X32 / Q32 : collateralValue) >= debt;
    }
```
This flag `withBuffer` value depends on the function it's called. For `borrow` and `decreaseLiquidityAndCollect`, `withBuffer` is true; for `liquidate` it's false. This makes sense because if you calls `borrow` or `decreaseLiquidityAndCollect`, this makes their debt increase or their collateral value decrease and the buffer is required to protect them; while in `liquidate` buffer is not used meaning it's only liquidatable if full `collateralValue < debt`.



However, function `transform` currently has `withBuffer = false`:
```solidity
function transform(uint256 tokenId, address transformer, bytes calldata data)
        external
        override
        returns (uint256 newTokenId)
    {
        ...

        (bool success,) = transformer.call(data);
        if (!success) {
            revert TransformFailed();
        }

        ...
        _requireLoanIsHealthy(tokenId, debt, false);

        
    }
```
The makes user loans not protected
Wrong logic in L2 sequencer check
Audit: 2024-04-revert-mitigation-findings
Severity: MEDIUM

# Lines of code

https://github.com/revert-finance/lend/blob/audit/src/V3Oracle.sol#L360-L362


# Vulnerability details

## C4 issue
ADD-02: [Missing L2 sequencer checks for Chainlink oracle](https://github.com/code-423n4/2024-03-revert-lend-findings/issues/12)

## Impact
- If `sequencerUptimeFeed` is set, then the function will revert most of the time and affect a lot of other functions in Revert Lend.

## Proof of concept
The original issue is fixed by [PR #27](https://github.com/revert-finance/lend/pull/27)
The mitigation code adds sequencer check as follows:
```solidity
// sequencer check on chains where needed
        if (sequencerUptimeFeed != address(0)) {
            (, int256 sequencerAnswer, uint256 startedAt,,) =
                AggregatorV3Interface(sequencerUptimeFeed).latestRoundData();

            // Answer == 0: Sequencer is up
            // Answer == 1: Sequencer is down
            if (sequencerAnswer == 0) {
                revert SequencerDown();
            }

            // Make sure the grace period has passed after the
            // sequencer is back up.
            uint256 timeSinceUp = block.timestamp - startedAt;
            if (timeSinceUp <= SEQUENCER_GRACE_PERIOD_TIME) {
                revert SequencerGracePeriodNotOver();
            }
        }
```
However, as you can see in the comment `sequencerAnswer == 0` indicates that the sequencer is up, yet in that case the code reverts with `SequencerDown` error (wrong logic). This logic is also stated in the [docs](https://docs.chain.link/data-feeds/l2-sequencer-feeds):
`The message calls the updateStatus function in the ArbitrumSequencerUptimeFeed contract and updates the latest sequencer status to 0 if the sequencer is up and 1 if it is down`.

Since most of the time the the sequencer is up (`sequencerAnswer == 0`), the function will revert most of the time and affect many other functions/contracts.


## Recommended Mitigation Steps
Change `if (sequencerAnswer == 0)` to `if (sequencerAnswer == 1)`.














## Assessed type

Invalid Validation
Some  tokens can become locked after delegation is rejected
Audit: 2024-02-uniswap-foundation-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-uniswap-foundation/blob/491c7f63e5799d95a181be4a978b2f074dc219a5/src/DelegationSurrogate.sol#L25


# Vulnerability details

## Impact
When some amount of tokens are requested to be delegated to a validator, the validator can reject the request. The previous status of these tokens should be intact and not changed (locked or unlocked).


## Tools Used
manual

## Recommended Mitigation Steps
Don’t change the status of the rejected tokens.




## Assessed type

call/delegatecall