xanoutas/solidity-security-findings huggingface dataset:
User can front-run liquidation by repaying the minimum amount of tokens and gain time to not be liquidated
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#L696-L698


# Vulnerability details

## Impact
A user undergoing the liquidation process can front-run a call to the `V3Vault.sol::liquidate` by manipulating `debtShares` variable. This is possible due to the equality requirement of user's loan `debtShares` and `LiquidateParams.debtShares` at [lines 696-698](https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/V3Vault.sol#L696-L698):

```
        if (debtShares != params.debtShares) {
            revert DebtChanged();
        }
```

To achieve that, a user can repay their loan with a minimal amount of tokens by calling `V3Vault.sol::repay()` function. This will cause `debtShares` update as can be seen in [lines 990-991](https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/V3Vault.sol#L990-L991).

```
        uint256 loanDebtShares = loan.debtShares - shares;
        loan.debtShares = loanDebtShares;
```

In this manner, a user can evade liquidation an unlimited number of times as long as it remains economically viable for them, potentially leading to bad debt for the protocol.

## Proof of Concept
1. Alice deposits and borrows X amount.
2. The collateral price moves down, exposing Alice's loan to the possible liquidation.
3. Bob the Liquidator executes liquidation of Alice's loan specifying `debtShares ` of the loan as it is last seen on V3Vault contract.
4. Alice executes the `repay` transaction with several wei of the token and a higher gas price. This leads `debtShares ` of the loan to change (move down).
5. Bob's transaction reverts, leaving Alice's loan unhealthy and not liquidated.

## Tools Used
Manual review.

## Recommended Mitigation Steps
Remove `debtShares` equality check and rely solely on the loan health status.











## Assessed type

Other
No check for active Sequencer in V3Oracle.sol
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#L329-L343


# Vulnerability details

## Impact
Chainlink recommends that all Optimistic L2 oracles consult the Sequencer Uptime Feed to ensure that the sequencer is live before trusting the data returned by the oracle. While there are sufficient validations for the price feed answer, V3Oracle.sol is missing a check for the L2 sequencer availability.

If the Arbitrum Sequencer temporarily goes down as it did in [December](https://github.com/ArbitrumFoundation/docs/blob/50ee88b406e6e5f3866b32d147d05a6adb0ab50e/postmortems/15_Dec_2023.md), data returned from the Chainlink oracle will look fresh even though it is not. Users would be able to interact with the protocol directly through the L1 optimistic rollup contract which can be leveraged by malicious actors to gain unfair advantage. You can review the [Chainlink docs](https://docs.chain.link/data-feeds/l2-sequencer-feeds#example-code) on L2 Sequencer Uptime Feeds for more details on this.

## Tools Used
Manual Review 

## Recommended Mitigation Steps
In `V3Oracle::_getChainlinkPriceX96` add a check for sequencer availability similar to the Chainlink [example code](https://docs.chain.link/data-feeds/l2-sequencer-feeds#example-code)


## Assessed type

Other
`priceX96` and `verifyPriceX96` variables are used wrong `V3Oracle::_getReferenceTokenPriceX96`
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#L272-L326


# Vulnerability details

## Impact
`priceX96` and `verifyPriceX96` variables are used wrong `V3Oracle::_getReferenceTokenPriceX96`

The issue occurs when both `usesChainlink` and `usesTWAP` are true. In this case, the `priceX96` variable is set to the value of `chainlinkPriceX96`, and `verifyPriceX96` is set to the value of `twapPriceX96` or vice versa, depending on the oracle mode. However, when the function checks the difference between `priceX96` and `verifyPriceX96`, it does not take into account which variable was set to the chainlink price and which was set to the TWAP price.

This can result in a significant price difference between the two oracle sources, which can cause the function to revert or return incorrect price data. 

For example, if the chainlink price is much higher than the TWAP price, and `priceX96` is set to the chainlink price and `verifyPriceX96` is set to the TWAP price, the function will revert because the difference between the two prices is too high. However, if the opposite is true and `priceX96` is set to the TWAP price and `verifyPriceX96` is set to the chainlink price, the function will not revert and will return incorrect price data.



## Proof of Concept
```solidity
    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 (feedConfig.mode == Mode.NOT_SET) {
            revert NotConfigured();
        }

        uint256 verifyPriceX96;

        bool usesChainlink = (
            feedConfig.mode == Mode.CHAINLINK_TWAP_VERIFY || feedConfig.mode == Mode.TWAP_CHAINLINK_VERIFY
                || feedConfig.mode == Mode.CHAINLINK
        );
        bool usesTWAP = (
            feedConfig.mode == Mode.CHAINLINK_TWAP_VERIFY || feedConfig.mode == Mode.TWAP_CHAINLINK_VERIFY
                || feedConfig.mode == Mode.TWAP
        );

        if (usesChainlink) {
            uint256 chainlinkPriceX96 = _getChainlinkPriceX96(token);
            chainlinkReferencePriceX96 = cachedChainlinkReferencePriceX96 == 0
                ? _getChainlinkPriceX96(referenceToken)
                : cachedChainlinkReferencePriceX96;

            chainlinkPriceX96 = (10 ** referenceTokenDecimals) * chainlinkPriceX96 * Q96 / chainlinkReferencePriceX96
                / (10 ** feedConfig.tokenDecimals);

            if (feedConfig.mode == Mode.TWAP_CHAINLINK_VERIFY) {
                verifyPriceX96 = chainlinkPriceX96;
            } else {
                priceX96 = chainlinkPriceX96;
            }
        }

        if (usesTWAP) {
            uint256 twapPriceX96 = _getTWAPPriceX96(feedConfig);
            if (feedConfig.
using 18 decimals ERC20 tokens as reference token will cause overflow in price calculation
Audit: 2024-03-revert-lend-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

when using 18 decimals token as (TWAP) reference token, token price above 19 of chainlink reference token will overflow and revert in `getValue` ,and cause many vault function can't use.

## Impact

It will cause function `getValue` of oracle revert，and cause many function using `_requireLoanIsHealthy` or `_checkLoanIsHealthy` to `getValue` can't use.

It include function `transform`,`borrow` ,`decreaseLiquidityAndCollect`and `liquidate`.User can't transform loan,borrow asset decrease liiquidity and liquadate portion.

## Proof of Concept

For convenience, we use DAI as  (TWAP) reference token（18 decimals) and 0 address (USD) as chainlink reference token，

In _getReferenceTokenPriceX96,it calculate price in Q96:

```solidity
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 (feedConfig.mode == Mode.NOT_SET) {
            revert NotConfigured();
        }

        uint256 verifyPriceX96;

        bool usesChainlink = (
            feedConfig.mode == Mode.CHAINLINK_TWAP_VERIFY || feedConfig.mode == Mode.TWAP_CHAINLINK_VERIFY
                || feedConfig.mode == Mode.CHAINLINK
        );
        bool usesTWAP = (
            feedConfig.mode == Mode.CHAINLINK_TWAP_VERIFY || feedConfig.mode == Mode.TWAP_CHAINLINK_VERIFY
                || feedConfig.mode == Mode.TWAP
        );

        if (usesChainlink) {
            uint256 chainlinkPriceX96 = _getChainlinkPriceX96(token);
            chainlinkReferencePriceX96 = cachedChainlinkReferencePriceX96 == 0
                ? _getChainlinkPriceX96(referenceToken)
                : cachedChainlinkReferencePriceX96;
            chainlinkPriceX96 = (10 ** referenceTokenDecimals) * chainlinkPriceX96 * Q96 / chainlinkReferencePriceX96
                / (10 ** feedConfig.tokenDecimals);

            if (feedConfig.mode == Mode.TWAP_CHAINLINK_VERIFY) {
                verifyPriceX96 = chainlinkPriceX96;
            } else {
                priceX96 = chainlinkPriceX96;
            }
        }

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

        if (feedConfig.mode == Mode.CHAINLINK_TWAP_VERIFY || feedConfig.mode == Mode.TWAP_CHAINLINK_VERIFY) {
            _requireMaxDifference(priceX96, verifyPriceX96, feedConfig.maxDifference);
        }
    }
```

there is the revert code:

```solidity
`V3Vault::_calculateGlobalInterest` Rounding error
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#L1167-L1195


# Vulnerability details

## Impact
1. In the `_calculateGlobalInterest()` function, the `supplyRateX96` is calculated as follows:

```solidity
supplyRateX96 = supplyRateX96.mulDiv(Q32 - reserveFactorX32, Q32);
```
The `reserveFactorX32` is a value between 0 and 100, representing the percentage of interest that is kept in the protocol for reserves. However, the calculation subtracts `reserveFactorX32` from `Q32` (which is equal to 2\*\*32), resulting in an incorrect calculation of the `supplyRateX96`. 

## Proof of Concept

```solidity
    function _calculateGlobalInterest()
        internal
        view
        returns (uint256 newDebtExchangeRateX96, uint256 newLendExchangeRateX96)
    {
        uint256 oldDebtExchangeRateX96 = lastDebtExchangeRateX96;
        uint256 oldLendExchangeRateX96 = lastLendExchangeRateX96;

        (, uint256 available,) = _getAvailableBalance(oldDebtExchangeRateX96, oldLendExchangeRateX96);

        uint256 debt = _convertToAssets(debtSharesTotal, oldDebtExchangeRateX96, Math.Rounding.Up);

        (uint256 borrowRateX96, uint256 supplyRateX96) = interestRateModel.getRatesPerSecondX96(available, debt);

        supplyRateX96 = supplyRateX96.mulDiv(Q32 - reserveFactorX32, Q32);

        // always growing or equal
        uint256 lastRateUpdate = lastExchangeRateUpdate;

        if (lastRateUpdate > 0) {
            newDebtExchangeRateX96 = oldDebtExchangeRateX96
                + oldDebtExchangeRateX96 * (block.timestamp - lastRateUpdate) * borrowRateX96 / Q96;
            newLendExchangeRateX96 = oldLendExchangeRateX96
                + oldLendExchangeRateX96 * (block.timestamp - lastRateUpdate) * supplyRateX96 / Q96;
        } else {
            newDebtExchangeRateX96 = oldDebtExchangeRateX96;
            newLendExchangeRateX96 = oldLendExchangeRateX96;
        }
    }
```

## Tools Used

Manual Review

## Recommended Mitigation Steps

The correct calculation should be:

```solidity
supplyRateX96 = supplyRateX96.mulDiv(Q32 - reserveFactorX32, Q32 * 100);
```

This ensures that the `supplyRateX96` is correctly calculated based on the reserve factor percentage.


## Assessed type

Decimal
Collateral factor calculates collateral factor unfairly when position has one-sided liquidity
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#L1143-L1149


# Vulnerability details

## Impact
 
Revert assumes that a Uniswap V3 NFT position will always have liquidity in both token0 and token1. However, a NFT position can have liquidity in just one token. When calculating the collateral factor for a NFT position, Revert chooses the smaller of the two token collateral factors, regardless if the position actually owns the token. This can result in lenders receiving an unfair advantage when it comes to the collateral factor for NFT positions that are 100% one-sided on token0 and Revert considers token1 to have a smaller collateral factor than token0.


## Proof of Concept

As you can see below, _calculateTokenCollateralFactorX32 calculates the collateral factor based on which token has a smaller collateral factor:

```solidity
function _calculateTokenCollateralFactorX32(uint256 tokenId) internal view returns (uint32) {
    (,, address token0, address token1,,,,,,,,) = nonfungiblePositionManager.positions(tokenId);
    uint32 factor0X32 = tokenConfigs[token0].collateralFactorX32;
    uint32 factor1X32 = tokenConfigs[token1].collateralFactorX32;
    return factor0X32 > factor1X32 ? factor1X32 : factor0X32;
}
```

Unfortunately there are no checks to see if the NFT position has liquidity on both tokens. A user can create a NFT position where liquidity is applied to just one of the tokens, then deposit the NFT position into the vault, and finally borrow assets against that NFT position. However, the borrower will have an unfair collateral factor applied to their loan if the zero-balance NFT position token has a lower collateral factor. 

We can see how the collateral factor is unfairly applied in the following forge test:

```solidity
 
function testOneSidedNFTCollateralFactorBug() external {
    vault.setTokenConfig(address(WETH), uint32(Q32 * 8 / 10), type(uint32).max); // 80% collateral factor / max 100% collateral value

    // lend 10 USDC
    _deposit(10000000, WHALE_ACCOUNT);

    // Create one-sided NFT position
    INonfungiblePositionManager.MintParams memory mintParams = INonfungiblePositionManager.MintParams({
        token0: address(DAI),
        token1: address(WETH),
        tickLower: int24(200),
        tickUpper: int24(1000),
        fee: uint24(10000),
        recipient: TEST_NFT_ACCOUNT,
        amount0Desired: 10e18,
        amount1Desired: 0,
        amount0Min: 0,
        amount1Min: 0,
        deadline: block.timestamp + 15 minutes
    });

    vm.startPrank(TEST_NFT_ACCOUNT);
    deal(address(DAI), TEST_NFT_ACCOUNT, 30e18);
    deal(address(WETH), TEST_NFT_ACCOUNT, 30e18);

    // AUDIT: 0xa80964C5bBd1A0E95777094420555fead1A26c1e matches the DAI/WETH uniswap v3 pool
    DAI.approve(0xa80964C5bBd1A0E95777094420555fead1A26c1e, 20e18);
    WETH.approve(0xa80964C5bBd1A0E95777094420555fead1A26c1e, 20e18);

    (uint mintedTokenId ,,uint daiInNFTPosition, uint wethInNFTPosition
An attacker can easily bypass the collateral value limit factor checks
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#L1227-L1241


# Vulnerability details

The collateral value limit factor checks in `_updateAndCheckCollateral()` are used to check if the current value of used collateral is more than the allowed limit. The problem here is that these checks are not done when withdrawing lent tokens so an attacker can easily bypass these checks. 

Example:

1. The collateral value limit factor is set to 90% for both collateral tokens.
2. The lent amount is 100 USDC and alice has borrowed 90 USDC
3. An attacker wants to borrow the remaining 10 USDC but he cant because of the checks
4. He executes an attack in 1 transaction - he supplies 10 USDC, borrows 10 USDC and then withdraws the 10 USDC that he lent
5. The amount borrowed is 100 USDC even though the collateral factor is 90%


The attacker can do this to bypass the checks, this can also block calls from the AutoRange automator because whenever a new position is sent  `_updateAndCheckCollateral()` is called which will revert because the attacker surpassed the limits. 

## Impact
The attacker can easily surpass the limits, this can also make calls from AutoRange revert because `_updateAndCheckCollateral()` is called when the position is replaced and it will revert because the limits were surpassed. So the automator will fail to change the range of the positions.  

## Proof of Concept

Add this to `V3Vault.t.sol`, as you can see the limits were surpassed.

```solidity
function testCollateralValueLimit() external {
        _setupBasicLoan(false);

        //set collateral value limit to 90%
        vault.setTokenConfig(address(DAI), uint32(Q32 * 9 / 10), uint32(Q32 * 9 / 10));

        vm.prank(TEST_NFT_ACCOUNT);
        vault.borrow(TEST_NFT, 8e6);

        //10 USDC were lent and 2 USDC were borrowed
        //Attacker can only borrow 1 USDC because of the collateral value limit

        //What he does is supplies 2 USDC, borrows 2 USDC and then withdraws what he lent
        _deposit(2e6, TEST_NFT_ACCOUNT_2);
        _createAndBorrow(TEST_NFT_2, TEST_NFT_ACCOUNT_2, 2e6);

        vm.prank(TEST_NFT_ACCOUNT_2);
        vault.withdraw(2e6, TEST_NFT_ACCOUNT_2, TEST_NFT_ACCOUNT_2);

        (,,uint192 totalDebtShares) = vault.tokenConfigs(address(DAI));
        console.log("The total amount of shares lent:", vault.totalSupply());
        console.log("The total amount of shares borrowed:", totalDebtShares);
    }

```

## Tools Used

Foundry

## Recommended Mitigation Steps

Not sure how this should be fixed as the collateral tokens are configured separately and the collateral factors can differ, however maybe preventing depositing and withdrawing in the same tx/small fee can help this. 


## Assessed type

Invalid Validation
Full repayments can be prevented by frontrunning the payment itself.
Audit: 2024-03-revert-lend-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

# The full repayment can be prevented by anyone.

## Description

The `repay` function in the `V3Vault.sol` contract has no minimum amount to be repayed, and no requirement for the borrower to be the one to repay the loan. This, combined with the `_repay` logic, that reverts if the function tries to pay more shares than in debt, makes it possible for anyone to prevent the full payment. 

The idea behind it would be for a malicious user to repay 1 share of the loan debt right before the full repayment would occur, preventing the payment itself. 

## Impact
This issue can lead to borrowers being unable to cover a loan, and getting liquidated without being able to prevent it. And as such, would detract borrowers from using the protocol.

## Proof Of Concept

Alice already has a position, based on WBTC/WETH. She notices both WETH and WBTC lose value quickly, and wishes to repay her loan to withdraw her liquidity. 

```
Alice calls repay with amount equal to the entire amount she borrowed.
```
Bob, who sees what Alice is trying to do, and knows that by stalling a few blocks he can liquidate her, frontruns Alice transaction.

```
Bob calls repay with amount equal to the price of one share.
```
Expected transaction by alice:
```
Alice calls repay with amount equal to the entire amount she borrowed.
```

Real result of Alice`s transaction:

```
Repay reverts due to Alice trying to repay more shares than the loan debt.
```

Bob does this until Alice position is ready to be liquidated, then calls `liquidate`. Bob ends up receiving the penalty that is awarded for calling the liquidation. 

## Recommended Mitigation Steps

Do not revert if the repay function receives more shares to pay than the debt itself. Just pull the assets necessary to fully pay the debt and ignore the surplus. 




## Assessed type

Context
Griefing attack: attack can prevent full repay by repay dust amount for position
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#L973-#L975


# Vulnerability details

## Vulnerability detail
In the `_repay()` function, there is a checking condition that make sure that repay amount should not excess full amount that need to be repay:

    function _repay(uint256 tokenId, uint256 amount, bool isShare, bytes memory permitData) internal {
        (uint256 newDebtExchangeRateX96, uint256 newLendExchangeRateX96) = _updateGlobalInterest();

        Loan storage loan = loans[tokenId];

        uint256 currentShares = loan.debtShares;

        uint256 shares;
        uint256 assets;

        if (isShare) {
            shares = amount;
            assets = _convertToAssets(amount, newDebtExchangeRateX96, Math.Rounding.Up);
        } else {
            assets = amount;
            shares = _convertToShares(amount, newDebtExchangeRateX96, Math.Rounding.Down);
        }

        // fails if too much repayed
        if (shares > currentShares) {  // <---
            revert RepayExceedsDebt();
        }
        .  .  .  .  .  .
But the problem is anyone can repay for any position, because there is no checking condition to restrict that only owner's position is able to repay. It only need to make sure repay amount is at least `minLoanSize`:

        if (currentShares == shares) {
            _cleanupLoan(tokenId, newDebtExchangeRateX96, newLendExchangeRateX96, owner);
        } else {
            // if resulting loan is too small - revert
            if (_convertToAssets(loanDebtShares, newDebtExchangeRateX96, Math.Rounding.Up) < minLoanSize) {  //<--
                revert MinLoanSize();
            }
        }

Which is set to 0 when deployment, although it can be changed later:

    // minimal size of loan (to protect from non-liquidatable positions because of gas-cost)
    uint256 public minLoanSize = 0;

So the attack is attacker will front-running anyone who willing to full repay for a position and repay dust amount for that position, which lead to revert when trying to repay full that position

## Impact
User are not able to repay full for position.

## Tools Used
Manual review

## Recommended Mitigation Steps
Only allow owner of position to repay for that position:

    function _repay(uint256 tokenId, uint256 amount, bool isShare, bytes memory permitData) internal {
    +   address owner = tokenOwner[tokenId];
    +   require(msg.sender == owner);


## Assessed type

Other
low TWAP interval / slot0 results in easily manipulated oracles 
Audit: 2024-03-revert-lend-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

When implemented correctly, the twap oracle makes it very expensive to manipulate prices, because it will aggregate prices over time. In the current implementation  twap periods are lose and may be set 0. This will expose the oracle to easy flash loan manipulations and should never happen.

## Impact

Price can be manipulated, exploiting the pool and other liquidity providers

## Proof of Concept

[Here](https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Oracle.sol#L366-L368),

```
        if (twapSeconds == 0) {
            (sqrtPriceX96,,,,,,) = pool.slot0();
        } else {
```
In this bit of code the protocol uses slot0 to access the price data which is very easy to manipulate.
An attacker could manipulate the price with a flashloan sell assets at an inflated price and destroy the protocol.

## Tools Used

Manual review

## Recommended Mitigation Steps

Do not use slot0 for price fetching, and use a meaningful `twapSeconds`, such as 1800, 30 minutes, which is a standard. I saw in the tests that 60 secs were being used which is better than slot0 but still easely maniable(cheap compared to 30 minutes). 


## Assessed type

Oracle