xanoutas/solidity-security-findings huggingface dataset:
Borrower can evade liquidation indefintiely by front running liquidation attempts
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#L954-L1014
https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Vault.sol#L685-L757


# Vulnerability details

## Summary
User can repay tiny portions of a liquidatable loan to cause liquidate functions to revert

## Vulnerability Details
The check indicated below in `liquidate()` opens up a vulnerability whereby user can simply front-run a transaction to liquidate their loan by reducing their debtShares by any amount.

```solidity
    function liquidate(LiquidateParams calldata params) external override returns (uint256 amount0, uint256 amount1) {
        // liquidation is not allowed during transformer mode
        if (transformedTokenId > 0) {
            revert TransformNotAllowed();
        }

        LiquidateState memory state;

        (state.newDebtExchangeRateX96, state.newLendExchangeRateX96) = _updateGlobalInterest();

        uint256 debtShares = loans[params.tokenId].debtShares;
>>>     if (debtShares != params.debtShares) {
            revert DebtChanged();
        }
```

This is possible because there is no health check in `_repay()` when user decreases their position, so a borrower can repay a tiny portion of an unhealthy loan without bringing it back into a healthy status; see [here]("https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Vault.sol#L954-L1014").

## POC
Add the test `testFrontRunLiquidation` below to `V3Vault.t.sol` and run:
<details>
<summary>testFrontRunLiquidation()</summary>

```solidity
    function testFrontRunLiquidation () external {
        uint256 debt;
        uint256 fullValue;
        uint256 collateralValue;
        uint256 liquidationCost;
        uint256 liquidationValue;

        _setupBasicLoan(true);

        // debt is equal to collateralValue
        ( debt, fullValue, collateralValue, liquidationCost, liquidationValue) = vault.loanInfo(TEST_NFT); 
        assertEq(debt, collateralValue);

        // wait 7 day - interest growing
        vm.warp(block.timestamp + 7 days);

        // debt is greater than collateralValue
        ( debt, , collateralValue, liquidationCost, ) = vault.loanInfo(TEST_NFT);
        assertGt(debt, collateralValue);
        assertEq(liquidationCost, 8869647);

        vm.prank(WHALE_ACCOUNT);
        USDC.approve(address(vault), liquidationCost);

        // Whale calls to get debtShares to be liquidated
        (uint256 debtShares) = vault.loans(TEST_NFT);

        // Seeing liquidate transaction in mempool; user repays 2 wei
        vm.startPrank(TEST_NFT_ACCOUNT);
        USDC.approve(address(vault), liquidationCost);        
        vault.repay(TEST_NFT, 2, false);
        vm.stopPrank();

        // When whales transaction goes through it fails on DebtChanged() error
        vm.startPrank(WHALE_ACCOUNT);
        USDC.approve(address(vault),
Lack of deadline in `NonfungiblePositionManager` interactions means transactions can be maliciously executed
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#L1032-L1073
https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/transformers/AutoCompound.sol#L101-L181


# Vulnerability details

## Summary
Lack of `deadline` means transaction can be maliciously executed at any time.

## Vulnerability Details
Use of `block.timestamp` as a deadline when interacting with UniswapV3's `NonfungiblePositionManager` nullifies the `NonfungiblePositionManager's` `checkDeadline` modifier; which is a protection against processing expired transactions; see `checkDeadline` [here](https://github.com/Uniswap/v3-periphery/blob/main/contracts/base/PeripheryValidation.sol).

`block.timestamp` gets set only when the transaction is included in a block and so, no matter what block the transaction is added to; `checkDeadline` will always be comparing like for like and the condition will always pass.

This leaves transactions vulnerable to being maliciously executed by a miner at a later point when conditions are favourable to them. Value could be extracted from the UniswapV3 pool via a sandwich attack for example.
Compounding the lack of deadline is the lack of `minimumOut` Amounts; which are set to 0, giving the malicious actor a lot of leeway.
The issue is present when decreasing liquidity in `V3Vault::_sendPositionValue()`, when increasing liquidity in `AutoCompound::execute()`

```solidity
    function _sendPositionValue(
        uint256 tokenId,
        uint256 liquidationValue,
        uint256 fullValue,
        uint256 feeValue,
        address recipient
    ) internal returns (uint256 amount0, uint256 amount1) {
        uint128 liquidity;
        uint128 fees0;
        uint128 fees1;

        // SOME CODE

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

        // SOME CODE
    }
```

```solidity
    function execute(ExecuteParams calldata params) external nonReentrant {

    // SOME CODE

            if (state.maxAddAmount0 > 0 || state.maxAddAmount1 > 0) {
                _checkApprovals(state.token0, state.token1);

                (, state.compounded0, state.compounded1) = nonfungiblePositionManager.increaseLiquidity(
                    INonfungiblePositionManager.IncreaseLiquidityParams(
                        params.tokenId, state.maxAddAmount0, state.maxAddAmount1, 0, 0, block.timestamp
                    )
                );

                // fees are always calculated based on added amount (to incentivize optimal swap)
                state.amount0Fees = state.compounded0 * rewardX64 / Q64;
                state.amount1Fees = state.compounded1 * rewardX64 / Q64;
            }

    // SOME CODE
    }
```

## Impact
If miner's don't process the transaction quickly,
Protocol can be repeatedly gas griefed in `AutoRange` external call
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#L111-L272


# Vulnerability details

## Summary
Revert controlled `AutoRange` bot can be gas griefed and `execute()` reverted by malicious `onERC721Received` implementation

## Vulnerability Details
The initiator of a transaction pays the transaction gas; in the case of `AutoRange::execute()` and `AutoRange::executeWithVault()`, this will be a Revert controlled bot which is set up as an `operator`.
Newly minted NFTs are sent to users via `NPM::safeTransferFrom()` which uses the `onERC721Received` callback.
An attacker can implement a malicious implementation of this callback; waste all the transaction gas and revreting the function to grief the protocol.
It is expected that the gas spent by bots initiating transactions will be covered by protocol fees; however no protocol fees will be generated from the attacker's position as `AutoRange::execute()` will not complete; so the protocol will experience a loss.

Furthermore, once attacker has set the token's config from `positionConfigs`, the protocol has no way to stop the griefing occuring each time the bot detects that the tokenId meets the conditions for a Range Change.
Token Config is only removed from `positionConfigs` at the end of `execute()` which the gas grief will prevent from being reached making it a recurring attack.
The only recourse to the protocol is shutting down the contract completely by removing the bot address as an `operator` and DOSing the contract.
 
All this makes the likelihood of this attack quite high as it is a very inexpensive attack; user does not even need an open position and loan in the vault.
A determined attacker

## POC
Attacker would need to create a malicious contract to which they send their NPM NFT.
Via this contract they can then add token config for this NFT to the AutoRange contract via `AutoRange::configToken()`.
The malicious contract would need to have a malicious implementation such as the one below which uses as much gas as possible before reverting.

```solidity
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external override returns (bytes4) {

    uint256 initialGas = gasleft();
    uint256 counter = 0;

    // Loop until only small amount gas is left for the revert
    uint256 remainingGasThreshold = 5000;

    while(gasleft() > remainingGasThreshold) {

        counter += 1;
    }

    // Explicitly revert transcation
    revert("Consumed the allotted gas");
        
    }
```

## Impact
Protocol fees can be completely drained; particularly if a determined attacker sets token configs for multiple NFTs in `AutoRange` all linked to the same malicious contract.
Lack of fees can DOS multiple functions like the bot initiated `AutoRange` functions and affect the protocol's profitability by draining fees.

## Tools Used
Man
Insufficient Slippage Handling in decreaseLiquidityAndCollect Function
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#L609


# Vulnerability details

## Impact
Loss of assets

## Proof of Concept
The `decreaseLiquidityAndCollect` function allows the owner of a loan position to decrease the liquidity of their Uniswap V3 position and collect the resulting assets.


```solidity
(amount0, amount1) = nonfungiblePositionManager.decreaseLiquidity(
    INonfungiblePositionManager.DecreaseLiquidityParams(
        params.tokenId, params.liquidity, params.amount0Min, params.amount1Min, params.deadline
    )
);

INonfungiblePositionManager.CollectParams memory collectParams = INonfungiblePositionManager.CollectParams(
    params.tokenId,
    params.recipient,
    params.feeAmount0 == type(uint128).max ? type(uint128).max : SafeCast.toUint128(amount0 + params.feeAmount0),
    params.feeAmount1 == type(uint128).max ? type(uint128).max : SafeCast.toUint128(amount1 + params.feeAmount1)
);

(amount0, amount1) = nonfungiblePositionManager.collect(collectParams);
```

The `decreaseLiquidity` call takes `params.amount0Min` and `params.amount1Min` as arguments, which represent the minimum acceptable amounts of the two tokens to be received when decreasing the liquidity. However, there is no explicit check to ensure that the received amounts (`amount0` and `amount1`) are greater than or equal to the specified minimums.


## Tools Used
Manual

## Recommended Mitigation Steps
Add this check to ensure that the received amounts meet the minimum requirements specified by the user, preventing excessive slippage during the liquidity decrease operation.

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


## Assessed type

Invalid Validation
Vault does not conform to ERC4626
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#L301-L309
https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Vault.sol#L312-L320


# Vulnerability details

## Summary
`maxMint()` and `maxDeposit()` return values do not take into account all lending limits. `maxWithdraw()` and `maxRedeem()` do not take into account the availability of assets.

## Vulnerability Details
The audit README.md states the Vault should comply with `ERC/EIP4626`; however this is not the case.

As stated in the ERC4626 specification:
For `maxDeposit` _MUST factor in both global and user-specific limits, like if deposits are entirely disabled (even temporarily) it MUST return 0._
For `maxMint` _MUST factor in both global and user-specific limits, like if mints are entirely disabled (even temporarily) it MUST return 0._

Both functions take into account the `globalLendLimit` but neither takes into account the daily limit imposed by `dailyLendIncreaseLimitLeft` which can entirely disable lending temporarily which breaks the ERC4626 specification.

As stated in the ERC4626 specification:
For `maxWithdraw()` _MUST factor in both global and user-specific limits, like if mints are entirely disabled (even temporarily) it MUST return 0._
For `maxRedeem()` _MUST factor in both global and user-specific limits, like if redemption is entirely disabled (even temporarily) it MUST return 0._

Neither function takes into account the availabe assets which is a key check in `_withdraw()` and can temporarily disable withdrawls and redemptions.

```solidity
    function _withdraw(address receiver, address owner, uint256 amount, bool isShare)
        internal
        returns (uint256 assets, uint256 shares)
    {
        // SOME CODE

        (, uint256 available,) = _getAvailableBalance(newDebtExchangeRateX96, newLendExchangeRateX96);
        if (available < assets) {
            revert InsufficientLiquidity();
        }

        // SOME CODE
    }
```

## Impact
Vault does not conform to ERC4626 which may break external integrations.

## Tools Used
Manual Review

## Recommendations
Update `maxDeposit()`, `maxMint()`, `maxWithdraw()` and `maxRedeem()` as:

```solidity
function maxDeposit(address) external view override returns (uint256) {
    (, uint256 lendExchangeRateX96) = _calculateGlobalInterest();
    uint256 value = _convertToAssets(totalSupply(), lendExchangeRateX96, Math.Rounding.Up);

    // Check against global limit
    if (value >= globalLendLimit) {
        return 0;
    }
    uint256 maxBasedOnGlobalLimit = globalLendLimit - value;

    // Take daily limit into account
    uint256 maxBasedOnDailyLimit = dailyLendIncreaseLimitLeft;

    // Take minimum of the two limits
    return Math.min(maxBasedOnGlobalLimit, maxBasedOnDailyLimit);
}
```

```solidity
function maxMint(address) external view override returns (uint256) {
    (, uint256 lendExchangeRat
No `minLoanSize` means liquidators will have no incentive to liquidate small positions
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#L550-L602


# Vulnerability details

## Summary
No `minLoanSize` can destabilise the protocol

## Vulnerability Details
According to protocol team they plan to roll out the protocol with `minLoanSize = 0` and adjust that number if needs be. 
This can be a big issue because there will be no incentive for liquidators to liquidate small underwater positions given the gas cost to do so would not make economic sense based on the incentive they would receive.
It also opens up a cheap attack path for would be attackers whereby they can borrow many small loans which will go underwater as they accrue interest but will not be liquidated.

## Impact
Can push the entire protocol into an underwater state.
Underwater debt would first be covered by Protocol reserves and where they arent sufficient, lenders will bear the responsibility of the uneconomical clean up of bad debt so both the protocol and lenders stand to lose out.

## Tools Used
Manual Review

## Recommendations
Close the vulnerability by implementing a relaistic `minLoanSize` which will incentivise liquidators to clean up bad debt.





## Assessed type

Other
DecreaseLiquidity with block.timestamp as Deadline can Cause Problems in 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#L1064-L1067


# Vulnerability details

## Impact
In the _sendPositionValue function when the contract calls the **decreaseLiquidity** function from uniswap the contract is sending the block timestamp as deadline which allows nodes to execute the tx whenever they decide to do it, this can affect the liquidation process.
 
## Proof of Concept

The _sendPositionValue used to liquidate a position is in charge of interacting with uniswap protocol to decrease the liquidity and collect the fees needed to pay to the liquidator, this function is calling the **decreaseLiquidity** function from uniswap and sending block.timestamp as a deadline, this is an un recommended practice that can bring problems in the liquidation process.

let's imagine that a loan becomes liquidable because the price of the assets drops, then a user has a bot that tries to liquidate as soon as a loan becomes liquidable and calls the liquidate function to liquidate the loan, everything is calculated in the function and the tx is sent to the node, but as the deadline of the tx is set to block.timestamp this means that the node can execute the tx whenever they decide, so the time between the liquidator sending the tx and this tx being executed on-chain could be different.

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

So these scenarios may happen:

1. The price has recovered when the Tx is executed and the liquidation reverts, so the loan is not liquidable anymore and the liquidator just loses the transaction costs.

2. The loan debt is greater than the liquidator expected and he is unable to pay it or he ends up paying more than expected something that could be bad for the liquidator.

## Tools Used
Manual Code Review

## Recommended Mitigation Steps
it's recommended to always allow the users to define the deadline for the Tx.


## Assessed type

MEV
NFT can get stuck forever, if `create` was called and the vault was passed as the receiver
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#L429-L477


# Vulnerability details

## Impact
Creating a position while passing the vault as the receiver, blocks the NFT from being withdrawn. When attempting to repay it, the TX will revert with `WrongContract` error, from `from == address(this)` in `onERC721Received`. Causing NFT to be stuck forever in the vault. 

## Proof of Concept

```solidity
function testSelfReceiveStuckNFT() public {
    vault.setLimits(1e6, 150e6, 150e6, 150e6, 150e6);

    vm.startPrank(WHALE_ACCOUNT);
    USDC.approve(address(vault), type(uint256).max);
    vault.deposit(100e6, WHALE_ACCOUNT);
    vm.stopPrank();

    address USER_1 = TEST_NFT_ACCOUNT;
    uint256 USER_1_NFT = TEST_NFT;

    vm.startPrank(USER_1);
    NPM.approve(address(vault), USER_1_NFT);
    vault.create(USER_1_NFT, address(vault));
    vm.stopPrank();

    assertEq(vault.ownerOf(USER_1_NFT), address(vault));

    vm.prank(USER_1);
    vm.expectRevert(IErrors.WrongContract.selector);
    vault.repay(USER_1_NFT, 0, false);
}
```

## Tools Used

Manual review

## Recommended Mitigation Steps

Make sure that the receiver is never the vault itself, add something similar to the following in `onERC721Received`:

```
address owner = abi.decode(data, (address));
if (owner == address(this)) {
    revert();
}
```


## Assessed type

Token-Transfer
An attacker can cause a DoS when a user repays their loan
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#L954-L1014


# Vulnerability details

## Impact

The user will be denied from repaying their loan in full and retrieving their collateral. The attacker can exploit this vulnerability repeatedly and inexpensively — particularly on layer 2 networks. The victim will be denied service for as long as the attacker wishes to keep performing the attack. In extreme cases this could lead to the user accumulating more interest subsequently resulting in an unhealthy position.

## Proof of Concept

To repay a loan users will call `V3Vault::repay()`. In this function there is a check to ensure users do not overpay, [V3Vault.sol#L972-L975](https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Vault.sol#L972-L975):

```solidity
// fails if too much repayed
if (shares > currentShares) {
    revert RepayExceedsDebt();
}
```

An attacker can front-run users who intend to repay their loan in full and pass a dust `amount`, causing the above check to fail and the transaction to revert. Add the following test case that demonstrates this to `test/integration/V3Vault.t.sol`:

```solidity
function test_Repay() public {
    _deposit(10000000, WHALE_ACCOUNT);

    // Borrow
    vm.startPrank(TEST_NFT_ACCOUNT);
    NPM.approve(address(vault), TEST_NFT);
    vault.create(TEST_NFT, TEST_NFT_ACCOUNT);
    ( , , uint256 collateralValue, , ) = vault.loanInfo(TEST_NFT);
    vault.borrow(TEST_NFT, collateralValue);
    vm.stopPrank();

    // Attacker front-runs with a small fraction of 1 USDC
    address attacker = makeAddr("attacker");
    deal(address(USDC), attacker, 1);
    vm.startPrank(attacker);
    USDC.approve(address(vault), 1);
    vault.repay(TEST_NFT, 1, false);
    vm.stopPrank();

    // User is denied repaying thier loan in full
    vm.startPrank(TEST_NFT_ACCOUNT);
    USDC.approve(address(vault), collateralValue);
    vm.expectRevert(IErrors.RepayExceedsDebt.selector);
    vault.repay(TEST_NFT, collateralValue, false);
    vm.stopPrank();
}
```

## Tools Used

Foundry

## Recommended Mitigation Steps

There are several potential ways to mitigate this and the best option will depend upon the sponsors preference, such as:

1. Have a seperate function that is dedicated to repaying a loan in full.
2. Restrict the access in `repay()` — limiting who can repay ones loan.
3. Allow the user to overpay and then return the excess funds.


## Assessed type

DoS