xanoutas/solidity-security-findings huggingface dataset:
No check if Arbitrum L2 sequencer is down in Chainlink feeds.
Audit: 2023-07-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph-audit/blob/a3b45512580f8a76be45c19f635689f48c0128c3/contracts/oracle/implementations/SGOracle.sol#L51


# Vulnerability details

## Impact

Stale Price from the oracle.

## Proof of Concept

Using Chainlink in L2 chains such as Arbitrum requires to check if the sequencer is down to avoid prices from looking like they are fresh although they are not.

The bug could be leveraged by malicious actors to take advantage of the sequencer downtime.

There is no check in `SGOracle` that the sequencer is down:

```solidity
File: SGOracle.sol

    function _get() internal view returns (uint256 _maxPrice) {
        uint256 lpPrice = (SG_POOL.totalLiquidity() *
            uint256(UNDERLYING.latestAnswer())) / SG_POOL.totalSupply();

        return lpPrice;
    }

```
[Link to code](https://github.com/Tapioca-DAO/tapioca-periph-audit/blob/a3b45512580f8a76be45c19f635689f48c0128c3/contracts/oracle/implementations/SGOracle.sol#L51)

## Tools Used

VS Code

## Recommended Mitigation Steps

It is recommended to follow the code example of Chainlink:
https://docs.chain.link/data-feeds/l2-sequencer-feeds#example-code


## Assessed type

Oracle
V3Vault is Vulnerable to Inflation Due to Donation Attacks
Audit: 2024-03-revert-lend-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Summary
The contract does not have an explicit check or enforcement for a minimum total supply threshold. If the `globalLendLimit` and `dailyLendIncreaseLimitMin` are set to low values, an attacker can potentially exploit the contract when the total supply is low enough, leading to a "donation attack".

## Proof of Concept
### Scenario
1. The contract is deployed with a low `globalLendLimit` and `dailyLendIncreaseLimitMin`.
2. During the initial stages of the contract when the total supply is low, an attacker can deposit a large amount of assets (within the limits) and receive a significant portion of the total shares.
3. The attacker can then strategically withdraw or redeem their shares, causing a significant reduction in the total supply.
4. With a very low total supply, the attacker can manipulate the exchange rates between assets and shares by making small deposits or withdrawals, potentially benefiting from the distorted exchange rates.

## Impact

Subsequent user deposits can be effectively stolen or significantly diminished due to the inflated exchange rates caused.

## Tools Used
Manual

## Recommended Mitigation Steps
Introduce an explicit minimum total supply threshold in the contract. This threshold should be set to a reasonable value based on the expected usage and risk profile of the lending protocol.

Add a new constant for the minimum total supply threshold:

```solidity
uint256 public constant MIN_TOTAL_SUPPLY = 1_000 * 10 ** decimals(); // Example: 1,000 tokens
```

Modify the `_deposit` function to check the minimum total supply threshold before minting new shares:

```solidity
function _deposit(address receiver, uint256 amount, bool isShare, bytes memory permitData)
    internal
    returns (uint256 assets, uint256 shares)
{
    // ...
    shares = _convertToShares(assets, newLendExchangeRateX96, Math.Rounding.Down);

    // Check if the new total supply after minting would be below the minimum threshold
    uint256 newTotalSupply = totalSupply() + shares;
    if (newTotalSupply < MIN_TOTAL_SUPPLY) {
        revert TotalSupplyBelowMinimum();
    }

    _mint(receiver, shares);
    // ...
}
```

Modify the `_withdraw` function to check the minimum total supply threshold before burning shares:

```solidity
function _withdraw(address receiver, address owner, uint256 amount, bool isShare)
    internal
    returns (uint256 assets, uint256 shares)
{
    // ...
    shares = _convertToShares(amount, newLendExchangeRateX96, Math.Rounding.Up);

    // Check if the new total supply after burning would be below the minimum threshold
    uint256 newTotalSupply = totalSupply() - shares;
    if (newTotalSupply < MIN_TOTAL_SUPPLY) {
        revert TotalSupplyBelowMinimum();
    }

    _burn(owner, shares);
    // ...
}
```

Add a new error event for the `TotalSupplyBelowMinimum` case:

```solidity
error TotalSupplyBe
Borrower can prevent being liquidated.
Audit: 2024-03-revert-lend-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Summary

The liquidation can be prevented by the token owner without any problem. This would lead to the borrowers being unliquiditable.

## Description

In the `V3Vault.sol` contract, the `liquidate` function has a major flaw. This is due to the owner of the NFT receiving the NFT via a safeTransfer which makes an external call the owner itself allowing him to revert the entire transaction(by reverting inside the `onERC721Received` function). 
The transfer itself happens on the `_cleanupLoan` function after removing the token from the owner in the lastTokenIndex and deleting the loan itself. 

## Impact

Since this would make the loans unliquiditable the protocol would not be used, since no one would want to lend money and not be able to recover it if the borrower does not wish to repay.

## Proof Of Concept

```
Alice position state: Liquidatable.
```
Bob sees that Alice can be liquidated, and calls `liquidate`

Expected result: 
```
Alice position state: Liquidated.
Bob is rewarded for calling liquidate.
```

Actual result if Alice wishes:

```
Alice position state: Liquidatable.
Bob is not rewarded since the liquidate call reverts.
```

## Mitigation Steps

I would recommend to make the owner claim the NFT after a liquidation occurs.


## Assessed type

DoS
Wrong number of ticks returned, which can lead to unexpected revert
Audit: 2024-03-revert-lend-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/automators/Automator.sol#L180-#L191


# Vulnerability details

## Vulnerability details
Function `Automator#_getTWAPTick()` is used to gets twap tick from pool history:

    function _getTWAPTick(IUniswapV3Pool pool, uint32 twapPeriod) internal view returns (int24, bool) {
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = 0; // from (before)
        secondsAgos[1] = twapPeriod; // from (before)

        // pool observe may fail when there is not enough history available
        try pool.observe(secondsAgos) returns (int56[] memory tickCumulatives, uint160[] memory) {
            return (int24((tickCumulatives[0] - tickCumulatives[1]) / int56(uint56(twapPeriod))), true); //<---
        } catch {
            return (0, false);
        }
    }

But it do not round to negative infinity like in [original uniswap function](https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/OracleLibrary.sol#L36):

        // Always round to negative infinity
        if (tickCumulativesDelta < 0 && (tickCumulativesDelta % secondsAgo != 0)) arithmeticMeanTick--;

So it might return wrong result in `_hasMaxTWAPTickDifference()`:

    function _hasMaxTWAPTickDifference(IUniswapV3Pool pool, uint32 twapPeriod, int24 currentTick, uint16 maxDifference)
        internal
        view
        returns (bool)
    {
        (int24 twapTick, bool twapOk) = _getTWAPTick(pool, twapPeriod);
        if (twapOk) {
            return twapTick - currentTick >= -int16(maxDifference) && twapTick - currentTick <= int16(maxDifference); //<---
        } else {
            return false;
        }
    }
Which will make `_validateSwap()` return wrong result, in the worst case, it will be reverted despite it should not:

    function _validateSwap(
        bool swap0For1,
        uint256 amountIn,
        IUniswapV3Pool pool,
        uint32 twapPeriod,
        uint16 maxTickDifference,
        uint64 maxPriceDifferenceX64
    ) internal view returns (uint256 amountOutMin, int24 currentTick, uint160 sqrtPriceX96, uint256 priceX96) {
        // get current price and tick
        (sqrtPriceX96, currentTick,,,,,) = pool.slot0();

        if (!_hasMaxTWAPTickDifference(pool, twapPeriod, currentTick, maxTickDifference)) {  //<---
            revert TWAPCheckFailed();
        }
      .  .  .  .  .  .

## Impact
In the worst case, swap will be unintentionally reverted when using `_validateSwap()` function

## Tools Used
Manual review

## Recommended Mitigation Steps
Adjust function like below:

    function _getTWAPTick(IUniswapV3Pool pool, uint32 twapPeriod) internal view returns (int24, bool) {
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = 0; // from (before)
        secondsAgos[1] = twapPeriod; // from (before)

        // pool observe may fail when there is not enough history available
        try pool.observe(secondsAgos) returns (int56[] me
The `Swapper::_routerSwap` function does not check whether the amount of tokens received from the swap is equal to the expected amount of tokens.
Audit: 2024-03-revert-lend-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/utils/Swapper.sol#L73-L118


# Vulnerability details

## Impact
The `Swapper::_routerSwap` function does not check whether the amount of tokens received from the swap is equal to the expected amount of tokens. This can result in a loss of funds for the user if the swap returns less tokens than expected.


## Proof of Concept
```solidity
    function _routerSwap(RouterSwapParams memory params)
        internal
        returns (uint256 amountInDelta, uint256 amountOutDelta)
    {
        if (params.amountIn != 0 && params.swapData.length != 0 && address(params.tokenOut) != address(0)) {
            uint256 balanceInBefore = params.tokenIn.balanceOf(address(this));
            uint256 balanceOutBefore = params.tokenOut.balanceOf(address(this));

            // get router specific swap data
            (address router, bytes memory routerData) = abi.decode(params.swapData, (address, bytes));

            if (router == zeroxRouter) {
                ZeroxRouterData memory data = abi.decode(routerData, (ZeroxRouterData));
                // approve needed amount
                SafeERC20.safeApprove(params.tokenIn, data.allowanceTarget, params.amountIn);
                // execute swap
                (bool success,) = zeroxRouter.call(data.data);
                if (!success) {
                    revert SwapFailed();
                }
                // reset approval
                SafeERC20.safeApprove(params.tokenIn, data.allowanceTarget, 0);
            } else if (router == universalRouter) {
                UniversalRouterData memory data = abi.decode(routerData, (UniversalRouterData));
                // tokens are transfered to Universalrouter directly (data.commands must include sweep action!)
                SafeERC20.safeTransfer(params.tokenIn, universalRouter, params.amountIn);
                IUniversalRouter(universalRouter).execute(data.commands, data.inputs, data.deadline);
            } else {
                revert WrongContract();
            }

            uint256 balanceInAfter = params.tokenIn.balanceOf(address(this));
            uint256 balanceOutAfter = params.tokenOut.balanceOf(address(this));

            amountInDelta = balanceInBefore - balanceInAfter;
            amountOutDelta = balanceOutAfter - balanceOutBefore;

            // amountMin slippage check
            if (amountOutDelta < params.amountOutMin) {
                revert SlippageError();
            }

            // event for any swap with exact swapped value
            emit Swap(address(params.tokenIn), address(params.tokenOut), amountInDelta, amountOutDelta);
        }
    }
```
## Tools Used

Manual Review
## Recommended Mitigation Steps

The `_routerSwap` function should check whether the amount of tokens received from the swap is equal to the expected amount of tokens, and revert the transaction if it is not. Here is an example of how this can be done:
```solidity
functio
If 2 tokens have different decimals, price returned from `_getReferencePoolPriceX96()` can be wrong
Audit: 2024-03-revert-lend-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Vulnerability detail
Function `_getReferencePoolPriceX96()` is used to return price of the pool:

    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();
        } else {
            uint32[] memory secondsAgos = new uint32[](2);
            secondsAgos[0] = 0; // from (before)
            secondsAgos[1] = twapSeconds; // from (before)
            (int56[] memory tickCumulatives,) = pool.observe(secondsAgos); // pool observe may fail when there is not enough history available (only use pool with enough history!)
            int24 tick = int24((tickCumulatives[0] - tickCumulatives[1]) / int56(uint56(twapSeconds)));
            sqrtPriceX96 = TickMath.getSqrtRatioAtTick(tick);
        }

        return FullMath.mulDiv(sqrtPriceX96, sqrtPriceX96, Q96);
    }
It is assumed that both tokens have same decimals. If a pool have 2 tokens that have decimals (example: weth/usdc), price return will be bigger alot than it should. Issue that related to different decimals is also discussed [at here](https://stackoverflow.com/questions/74555451/uniswap-v3-what-does-price-mean-at-a-given-tick)

## Impact
Price will be wrongly returned, lead to unexpected result

## Tools Used
Manual review

## Recommended Mitigation Steps
Checking decimals of both tokens in the pool and adjust the price returned based on them


## Assessed type

Other
`AutoCompound::execute` function that can allow an attacker to steal funds from the contract
Audit: 2024-03-revert-lend-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/transformers/AutoCompound.sol#L101-L193


# Vulnerability details

## Impact
The `_checkApprovals` function is called after the `_poolSwap` function. The `_poolSwap` function performs a swap of tokens using the UniswapV3 pool, and it can potentially transfer tokens to the attacker's address. If the attacker has previously approved the `AutoCompound` contract to spend their tokens, the `_checkApprovals` function will not revert, allowing the attacker to steal the tokens.




## Proof of Concept


```solidity
// if a swap is requested - check TWAP oracle
if (amountIn > 0) {
    IUniswapV3Pool pool = _getPool(state.token0, state.token1, state.fee);
    (state.sqrtPriceX96, state.tick,,,,,) = pool.slot0();

    // how many seconds are needed for TWAP protection
    uint32 tSecs = TWAPSeconds;
    if (tSecs > 0) {
        if (!_hasMaxTWAPTickDifference(pool, tSecs, state.tick, maxTWAPTickDifference)) {
            // if there is no valid TWAP - disable swap
            amountIn = 0;
        }
    }
    // if still needed - do swap
    if (amountIn > 0) {
        // no slippage check done - because protected by TWAP check
        (state.amountInDelta, state.amountOutDelta) = _poolSwap(
            Swapper.PoolSwapParams(
                pool, IERC20(state.token0), IERC20(state.token1), state.fee, params.swap0To1, amountIn, 0
            )
        );
        state.amount0 =
            params.swap0To1 ? state.amount0 - state.amountInDelta : state.amount0 + state.amountOutDelta;
        state.amount1 =
            params.swap0To1 ? state.amount1 + state.amountOutDelta : state.amount1 - state.amountInDelta;
    }
}

uint256 rewardX64 = totalRewardX64;

state.maxAddAmount0 = state.amount0 * Q64 / (rewardX64 + Q64);
state.maxAddAmount1 = state.amount1 * Q64 / (rewardX64 + Q64);

// deposit liquidity into tokenId
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;
}
```

## Tools Used

Manual Review
## Recommended Mitigation Steps

The `_checkApprovals` function should be called before the `_poolSwap` function. This will ensure that the contract has the necessary approvals before performing any token transfers. Additionally, the `_checkApprovals` function should revert if the caller does not have the necessary approvals.


```solidity
// if a swap is requested - check TWAP oracle
if (amountIn > 0) {
    _checkApprovals(state.token0, state.token1);

    IUniswap
_getReferencePoolPriceX96 will show incorrect price for negative ticks cause it doesn't round up for negative ticks.
Audit: 2024-03-revert-lend-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Attacker will take advantage of inacurate prices returned for negative ticks.

## Proof of Concept

The [_getReferencePoolPriceX96](https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Oracle.sol#L369-L374)
below does not take into account negative ticks.

```solidity
            uint32[] memory secondsAgos = new uint32[](2);
            secondsAgos[0] = 0; // from (before)
            secondsAgos[1] = twapSeconds; // from (before)
            (int56[] memory tickCumulatives,) = pool.observe(secondsAgos); // pool observe may fail when there is not enough history available (only use pool with enough history!)
     @>     int24 tick = int24((tickCumulatives[0] - tickCumulatives[1]) / int56(uint56(twapSeconds))); //@note here can be negative 
            sqrtPriceX96 = TickMath.getSqrtRatioAtTick(tick);
```
This oversight poses a risk, especially when `(tickCumulatives[1] - tickCumulatives[0]) % secondsAgo != 0`, potentially resulting in a tick value that is larger than it should be. Such discrepancies can create opportunities for price manipulations and arbitrage.

To address this issue, it's crucial to ensure that negative tick values are properly handled and rounded down to avoid inaccuracies in determining the tick value. This correction will enhance the accuracy and reliability of the pricing mechanism, reducing the potential for exploitation and manipulation in the system.


## Tools Used

Manual review

## Recommended Mitigation Steps
Copy the uniswap implementation and add this line.
```
if (tickCumulatives[1] - tickCumulatives[0] < 0 && (tickCumulatives[1] - tickCumulatives[0]) % secondsAgo != 0) tick --;
```


## Assessed type

Math
Underflow could happened when calculating Uniswap V3 position's fee growth and can cause operations to revert
Audit: 2024-03-revert-lend-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

When operations need to calculate Uniswap V3 position's fee growth, it used similar function implemented by uniswap v3. However, according to this known issue : https://github.com/Uniswap/v3-core/issues/573.  The contract is implicitly relies on underflow/overflow when calculating the fee growth, if underflow is prevented, some operations that rely on fee growth will revert.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

It can be observed that current implementation of getFeeGrowthInside not allow underflow/overflow to happen when calculating feeGrowthInside0X128 and feeGrowthInside1X128, because the contract used solidity >=0.8.0.

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


This could impact crucial operations that rely on this call, such as liquidation, and could revert unexpectedly. This behavior is quite often, especially for pools that use lower fees.





## Tools Used

Use unchecked when calculating feeGrowthInside0X128 and feeGrowthInside1X128.

## Recommended Mitigation Steps


## Assessed type

Context
A malicious actor can create a position with a recipient contract that does not follow the IERC721Receiver standard, hence being unliquidatable whilst still having access to the arbitrage the funds in the NFT via the V3Vault::transfrom function
Audit: 2024-03-revert-lend-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The vault could end up in a loss

## Proof of Concept
A malicious actor bob would call V3Vault::create to create a position with a recipient that is a contract, howvever this countract would not support the IERC721Receiver interface, this contract would be set as the owner of the position.  Bob could then take a loan via this contract. If the position ever becomes undercollaterized and a liquidator attempts to liquidate,the call would revert because the V3Vault::cleanupLoan which is called by the liquidate functions uses safeTransferFrom which expects the reciver to implement IERC721Receiver if they are a contract. bob could also make profits from the funds in the nft by arbitraging them using the V3Vault::transfrom fuction

## Tools Used
Manual analysis 

## Recommended Mitigation Steps
An address check could be implemened in the V3Vault::onERC721Received function to ensure that the owner is not a function 




## Assessed type

ERC721