xanoutas/solidity-security-findings huggingface dataset:
hash - User's can seize more assets during liquidation by using type(uint).max
Audit: 2024-08-sentiment-v2-judging
Severity: HIGH

hash

High

# User's can seize more assets during liquidation by using type(uint).max

## Summary
User's can seize more assets during liquidation than what should be actually allowed by replaying the repayment amount using type(uint).max 

## Vulnerability Detail
The liquidators are restricted on the amount of collateral they can seize during a liquidation

Eg:
if a position has 1e18 worth debt, and 2e18 worth collateral, then on a liquidation the user cannot seize 2e18 collateral by repaying the 1e18 debt, and they are limited to seizing for ex. 1.3e18 worth of collateral (depends on the liquidation discount how much profit a liquidator is able to generate)

The check for this max seizable amount is kept inside `_validateSeizedAssetValue`

[link](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/25a0c8aeaddec273c5318540059165696591ecfb/protocol-v2/src/RiskModule.sol#L129-L145)
```solidity
    function _validateSeizedAssetValue(
        address position,
        DebtData[] calldata debtData,
        AssetData[] calldata assetData,
        uint256 discount
    ) internal view {
        // compute value of debt repaid by the liquidator
        uint256 debtRepaidValue;
        uint256 debtLength = debtData.length;
        for (uint256 i; i < debtLength; ++i) {
            uint256 poolId = debtData[i].poolId;
            uint256 amt = debtData[i].amt;
            if (amt == type(uint256).max) amt = pool.getBorrowsOf(poolId, position);
            address poolAsset = pool.getPoolAssetFor(poolId);
            IOracle oracle = IOracle(riskEngine.getOracleFor(poolAsset));
            debtRepaidValue += oracle.getValueInEth(poolAsset, amt);
        }

        .....

        uint256 maxSeizedAssetValue = debtRepaidValue.mulDiv(1e18, (1e18 - discount));
        if (assetSeizedValue > maxSeizedAssetValue) {
            revert RiskModule_SeizedTooMuch(assetSeizedValue, maxSeizedAssetValue);
        }
```

But the `_validateSeizedAssetValue` is flawed as it assumes that the value `type(uint256).max` will result in the liquidator repaying the current `pool.getBorrowsOf(poolId, position)` value. In the actual execution, an attacker can repay some amount earlier and then use `type(uint256).max` on the same pool which will result in a decreased amount because debt has been repaid earlier

Eg:
getBorrows of position = 1e18
user passes in 0.9e18 and type(uint).max as the repaying values
the above snippet will consider it as 0.9e18 + 1e18 being repaid and hence allow for more than 1.9e18 worth of collateral to be seized
but during the actual execution, since 0.9e18 has already been repaid, only 0.1e18 will be transferred from the user allowing the user

### POC Code
Apply the following diff and run `testHash_LiquidateExcessUsingDouble`. It is asserted that a user can use this method to seize the entire collateral of the debt position even though it results in a much higher value than what should be actually allowed

```diff
diff --git a/protocol-v2/test/int
valuevalk - Attacker could update RedstoneOracle's price to an older one, to liquidate another position or benefit himself
Audit: 2024-08-sentiment-v2-judging
Severity: HIGH

valuevalk

High

# Attacker could update RedstoneOracle's price to an older one, to liquidate another position or benefit himself

## Summary
In the **RedstoneOracle**, a malicious actor could update the price with an older one to benefit themselves or cause losses to other users of the protocol.

## Vulnerability Detail

Redstone oracle uses an "on-demand" model, which allows for lower fees as the price is not constantly pushed. Instead, anyone can submit off-chain signed data that can be validated with the Redstone SDK. Check out the docs [here](https://docs.redstone.finance/docs/get-started/models/redstone-core).

In the **RedstoneOracle**, we are missing any validation on the actual `timestamp` of the price that’s submitted via `calldata` to the `updatePrice()` function.  
Thus, we could submit a price with an older timestamp.

## Impact
This issue has multiple impacts:
- If there was an older price that makes a certain position unhealthy, a malicious actor could submit it and liquidate the position.
- Since oracles are also used to check if a position is below `Pool.minDebt`, the malicious actor could use an older price to borrow less than `Pool.minDebt` compared to the real price. 
- Avoid liquidation of his position, if the price favours his LTV.

## Proof of Concept
All of the oracles in the protocol provide the `getValueInEth` function, and one of its main purposes is to ensure that the `LTV` ratio is healthy. Thus, someone being able to use an older price can take advantage of this.

`PositionManager.liquidate()` is validating the position health using an oracle -> [liquidate code snippet.](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/main/protocol-v2/src/PositionManager.sol#L435)  and [validate code snippet](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/main/protocol-v2/src/RiskModule.sol#L117)

 isPositionHealthy is using `_getPositionDebtData` and `_getPositionAssetData` which use `getAssetValue` and `getDebtValueForPool`. And the values are in ETH, which depends on the Oracle.
 
```solidity
    function getDebtValueForPool(address position, uint256 poolId) public view returns (uint256) {
        address asset = pool.getPoolAssetFor(poolId);
        IOracle oracle = IOracle(riskEngine.getOracleFor(asset));
@>>     return oracle.getValueInEth(asset, pool.getBorrowsOf(poolId, position));
    }
```
```solidity
    function getAssetValue(address position, address asset) public view returns (uint256) {
        IOracle oracle = IOracle(riskEngine.getOracleFor(asset));
        uint256 amt = IERC20(asset).balanceOf(position);
@>>     return oracle.getValueInEth(asset, amt);
    }
```

In the [RedstoneDefaultsLib.sol](https://github.com/redstone-finance/redstone-oracles-monorepo/blob/94ac46f41be52ee9132bede9d13897f5922c800d/packages/evm-connector/contracts/core/RedstoneDefaultsLib.sol#L18-L39), which is part of the Oracle SDK, we don't have any checks comparing an old ti
serial-coder - A liquidator can seize more or even all collateral assets from all liquidatable positions
Audit: 2024-08-sentiment-v2-judging
Severity: HIGH

serial-coder

High

# A liquidator can seize more or even all collateral assets from all liquidatable positions

## Summary

A liquidator can execute the `PositionManager::liquidate()` to liquidate a liquidatable position. The liquidator must repay debt assets (`debtRepaidValue` represented in ETH) for a profit from seizing the liquidating position's collateral assets at a discounted price.

To validate that a liquidator must not seize the liquidating position's collateral assets (`assetSeizedValue` represented in ETH) too much (beyond the discounted price), the `liquidate()` will trigger the `RiskModule::_validateSeizedAssetValue()` to validate that the `assetSeizedValue` must not exceed the maximum seizable asset value, `maxSeizedAssetValue`. 

However, I discovered that the process of computing the `maxSeizedAssetValue` variable in the `_validateSeizedAssetValue()` can be manipulated, opening up a vulnerability for a liquidator to seize collateral assets more than they should or even all collateral assets.

## Vulnerability Detail

The root cause of the vulnerability is an approach in which the `_validateSeizedAssetValue()` simulates the `debtRepaidValue` variable. With the "[`amt == type(uint256).max`](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/25a0c8aeaddec273c5318540059165696591ecfb/protocol-v2/src/RiskModule.sol#L140-L141)" option (`@1` in the snippet below), a liquidator can manipulate the input data to increase the `debtRepaidValue` to be larger than the actual repayable value.

Let's consider the following `debtData` input array as an example:
- `debtData[0]: { poolId: <same poolId>, amt: 0.99e18 }`
- `debtData[1]: { poolId: <same poolId>, amt: type(uint256).max }`

Assume that the position's total debt value is `1e18`. Therefore, when setting the input `amt` == `type(uint256).max` will represent `1e18`.

When processing the above `debtData` input array, therefore, in the first loop iteration, `amt` == `0.99e18`, whereas in the second iteration, `amt` == `1e18` (no deducting the value from the first iteration's `amt`).

As you can see, the [`debtRepaidValue` variable](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/25a0c8aeaddec273c5318540059165696591ecfb/protocol-v2/src/RiskModule.sol#L144) (`@2`) will be larger than the actual repayable value since the sum of the two `amts` will be `1.99e18`, more significant than the total debt value, which is `1e18` only. Because the (simulated) `debtRepaidValue` was manipulated to be larger than the actual value, the resulting [`maxSeizedAssetValue` variable](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/25a0c8aeaddec273c5318540059165696591ecfb/protocol-v2/src/RiskModule.sol#L156-L157) (`@3`) will also be larger than the actual value. Hence, the liquidator can seize the liquidating position's collateral assets more than they should.

```solidity
    // FILE: https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/main/protocol-v2/src/RiskModule.sol
EgisSecurity - Exploiter can always bypass `LIQUIDATION_DISCOUNT` and always seize all collateral
Audit: 2024-08-sentiment-v2-judging
Severity: HIGH

EgisSecurity

High

# Exploiter can always bypass `LIQUIDATION_DISCOUNT` and always seize all collateral

### Summary

Protocol integrates [LIQUIDATION_DISCOUNT](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/0b472f4bffdb2c7432a5d21f1636139cc01561a5/protocol-v2/src/RiskModule.sol#L33) variable, which will be hardcoded to 10% regarding the [README](https://audits.sherlock.xyz/contests/349). This means that when a user is liquidatable, the party which is repaying his debt can seize maximum collateral valued 110% of the value of the debt. 
Example:
If we have a liquidatable debt, which is worth $1000 and we have $1500 collateral against it, the liquidator can get at most $1100 collateral if he repays all $1000 debt.
The following is validated by [_validateSeizedAssetValue](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/0b472f4bffdb2c7432a5d21f1636139cc01561a5/protocol-v2/src/RiskModule.sol#L129-L160) function.
A vulnerability, which combines providing the same debt to be repaid and `if (amt == type(uint256).max) amt = pool.getBorrowsOf(poolId, position)` can lead to user manipulating `_validateSeizedAssetValue` to believe user is repaying larger amount, which result in the ability to seize any amount of collateral by the liquidator.

### Root Cause

The root cause is a combination of a few things:
- possibility for the liquidator to provide the same loan in `debtData` to `liquidate` function
- The way `type(uint256).max` passed as `amt` is treated

If we take a look into how `_validateSeizedAssetValue` calculate `debtRepaidValue`, we can see that if `type(uint256).max` is passed, we call `pool.getBorrowsOf(poolId, position)`:
```solidity
        for (uint256 i; i < debtLength; ++i) {
            uint256 poolId = debtData[i].poolId;
            uint256 amt = debtData[i].amt;
            if (amt == type(uint256).max) amt = pool.getBorrowsOf(poolId, position);
            address poolAsset = pool.getPoolAssetFor(poolId);
            IOracle oracle = IOracle(riskEngine.getOracleFor(poolAsset));
            debtRepaidValue += oracle.getValueInEth(poolAsset, amt);
        }

```
We do the following for each element inside `debtData`. If we two items in `debtData` with the same pool id and  `type(uint256).max`, function will calculate `debtRepaidValue` to be X2 the value of the debt.
Later in the `liquidate` flow when we [repay the debt](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/0b472f4bffdb2c7432a5d21f1636139cc01561a5/protocol-v2/src/PositionManager.sol#L484-L500), we repeat the approach with `type(uint256).max` value:
```solidity
        for (uint256 i; i < debtDataLength; ++i) {
            uint256 poolId = debtData[i].poolId;
            address poolAsset = pool.getPoolAssetFor(poolId);
            uint256 amt = debtData[i].amt;
            if (amt == type(uint256).max) amt = pool.getBorrowsOf(poolId, position);
            // transfer debt asset from the liquidator to the pool
            IERC20(poolAsset).s
AlexCzm - An attacker can pick up a new price that is older than the current stored one
Audit: 2024-08-sentiment-v2-judging
Severity: HIGH

AlexCzm

Medium

# An attacker can pick up a new price that is older than the current stored one

### Summary

Contract implementation uses the current `block.timestamp - 3 minutes` as the timestamp for when a price was observed rather than using actual observation timestamp associated with the price itself. This allows an attacker to set the price to an older value and to be considered fresher. 

### Root Cause

1. Redstone's `validateTimestamp` allows a user to pick up a price from the interval [3 minutes in the past, 1 minute in the future];[ See timestamp validation [here](https://github.com/redstone-finance/redstone-oracles-monorepo/blob/94ac46f41be52ee9132bede9d13897f5922c800d/packages/evm-connector/contracts/core/RedstoneDefaultsLib.sol#L28-L31)
The call chain is like :
`RedstoneCoreOracle.updatePrice` ->`RedstoneConsumerNumericBase.getOracleNumericValuesFromTxMsg` ->`validateTimestamp`
2. The `RedstoneCoreOracle.updatePrice`  has no extra time validation.
3. The attack is possible becasue a signed payload calldata (containing price and timestamp associated with it) must pe passed to `getOracleNumericValuesFromTxMsg` which is called from our `updatePrice` function.

### Internal pre-conditions

_No response_

### External pre-conditions

_No response_

### Attack Path

First we need to understand how prices are updated in Redstone oracle context. 
From their [docs](https://docs.redstone.finance/docs/get-started/data-formatting-processing#context) we can see that package of  prices and their associated timestamp are injected into a user tx. After the package is validated prices can be consumed. 

`
Transferring data to a blockchain requires packing an extra payload to a user’s transaction and processing the 
message on the blockchain. Said differently, the data that is put on the blockchain, such as a cryptocurrency’s price,
is inserted into part of the data that makes up a user’s transaction. 
`

1. Redstone payload is appended as calldata when `updatePrice` is called, before prices are consumed (before `getValueInEth` is called, ideally in same tx).  The payload contains [price, timestamp] = [p1, 100] that passes the (-3,+1) minutes [validation](https://github.com/redstone-finance/redstone-oracles-monorepo/blob/94ac46f41be52ee9132bede9d13897f5922c800d/packages/evm-connector/contracts/core/RedstoneDefaultsLib.sol#L27-L33)
2. An attacker can pick up an older payload (eg. [price, timestamp] = [p2, 98]) that also passes the timestampValidation, but with an older price (prices) than the curent saved one. (saved in `assetUsdPrice`, `ethUsdPrice`). Attacker call `updatePrice` with this old payload.
3. Next attacker calls `getValueInEth` (from borrowing, liquidate, etc). Stale price check passes since the `priceTimestamp = block.timestamp - THREE_MINUTES;`  is bigger than `block.timestamp - STALE_PRICE_THRESHOLD` used in if stale check.

### Impact

Asset prices up to 3 minutes old can be consumed by an
HHK - No greater `timestamps` check in `RedstoneCoreOracle`
Audit: 2024-08-sentiment-v2-judging
Severity: HIGH

HHK

High

# No greater `timestamps` check in `RedstoneCoreOracle`

### Summary

The `RedstoneCoreOracle` doesn't enforce that the new `timestamps` of the price update is higher than the previous one.
This can lead to the oracle returning a more recent price, then an older price, then a recent one again leading to not reflecting most recent market conditions.

### Root Cause

The function [`updatePrice`](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/0b472f4bffdb2c7432a5d21f1636139cc01561a5/src/oracle/RedstoneOracle.sol#L48-L48) in the `RedstoneCoreOracle` contract is in charge of updating the price stored for the corresponding assets.

Redstone API allows to update the price using a price that can be up to 3 minutes in the past.

The function [`updatePrice`](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/0b472f4bffdb2c7432a5d21f1636139cc01561a5/src/oracle/RedstoneOracle.sol#L48-L48) always assume that the price passed was from 3 minutes ago.
Comments inside the function indicates that it is not possible to extract the timestamp from the payload. This is partially wrong, there is way to check the timestamps of the price update. See the [redstone oracle adapter from Euler](https://github.com/euler-xyz/euler-price-oracle/blob/dda7da3c641e3bf98dd9c97c19a1c8b8a6c32e21/src/adapter/redstone/RedstoneCoreOracle.sol#L78).

The function assumes that each updates was from 3 minutes ago which can lead to wrong ordering of price updates. For example the function can be update with a price from 5 seconds ago, then be called again but this time with a price from 3 minutes ago, then again be called with a price from 5 seconds ago.

This creates attack path where a malicious user can play with the oracle by updating it back and forth with prices from the last 3 minutes to potentially borrow more than allowed or liquidate a user that shouldn't be.

For example:
The price of a collateral just went up by 2%, a user just borrowed at max LTV using the latest price available for the Redstone oracle. 
A malicious liquidator can come and update the price back to 3 minutes ago before the collateral price went up by 2%, opening the user's position to liquidation and liquidate him.

### Internal pre-conditions

- A user borrowing close to max LTV

### External pre-conditions

- Price volatility of a few percent in 3 minutes, which is common for crypto markets.

### Attack Path

- A user borrows close to max LTV using a collateral that just went up in price and updated the Redstone oracle.
- A liquidator see the transaction, just after the other user borrowed the assets he updates the Redstone oracle back to the price of 3 minutes ago.
- The user is now at risk of liquidation.
- The Liquidator liquidates the user and make a profits using an older price.

### Impact

- A malicious liquidator can liquidate positions that are healthy

### PoC

_No response_

### Mitigation

Consider extracting the price timestamps like [done by Euler](https://github.co
sl1 - Liquidator can bypass `_validateSeizedAssetValue()` check seizing all of the position's assets.
Audit: 2024-08-sentiment-v2-judging
Severity: HIGH

sl1

High

# Liquidator can bypass `_validateSeizedAssetValue()` check seizing all of the position's assets.

## Summary
During liquidation, `validateLiquidation()` function of RiskModule is invoked to ensure a position is unhealthy and restrict liquidator to seizing only an allowed amount of assets. However, it's possible to trick the function into thinking that the amount of debt repaid is greater than it is in practice.
## Vulnerability Detail
Consider the following scenario:

A pool facilitates lending of assetA and accepts assetB as a collateral. 
The price of both assets is 1 ETH and assetB has an LTV of 0.5.
 Liquidation discount is set to 20%.
1. Bob deposits 200 tokens of assetB worth 200 ETH into his position.
2. Bob borrows 100 tokens of assetA worth 100 ETH.
3. The `minReqAssetValue` of Bob's position is `100 * 1 / 0/.5 = 200 ETH` , so his position is healthy.
4. The price of assetB drops from 1 ETH to 0.9 ETH, putting Bob's asset value to 180 ETH, making his position liquidatable.

Under normal conditions, if liquidator were to repay full debt worth 100 ETH, the `maxSeizedAssetValue` would be set to `100 ETH * 100% / (100% - 20%) = 125 ETH`, as can be seen in the `_validateSeizedAssetValue()`.
[RiskModule.sol#L156-L159](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/0b472f4bffdb2c7432a5d21f1636139cc01561a5/protocol-v2/src/RiskModule.sol#L156-L159)
```solidity
// max asset value that can be seized by the liquidator
// 100e18 * 1e18 / 0.8e18 = 125e18
uint256 maxSeizedAssetValue = debtRepaidValue.mulDiv(
    1e18,
    (1e18 - discount)
);
if (assetSeizedValue > maxSeizedAssetValue) {
    revert RiskModule_SeizedTooMuch(
        assetSeizedValue,
        maxSeizedAssetValue
    );
}
```
Instead of that liquidator crafts a `debtData` parameter passed to the `liquidate()` function in the following way:
* `debtData` is an array containing two `DebtData` structs.
* `debtData[0].poolId` is set to ID of the pool.
* `debtData[0].amt` is set to 99 tokens of assetA.
* `debtData[1].poolId` is set to ID of the pool.
* `debtData[1].amt` is set to `type(uint256).max`.

With this `debtData` parameter `_validateSeizedAssetValue()` will calculate the amount of debt repaid as 199 ETH, since for the first element of the array debt value will be `99 * 1 ETH`, and for the second element debt value will be `pool.getBorrowsOf(poolId, position) * 1 ETH`.
[RiskModule.sol#L138-L145](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/0b472f4bffdb2c7432a5d21f1636139cc01561a5/protocol-v2/src/RiskModule.sol#L138-L145)
```solidity
for (uint256 i; i < debtLength; ++i) {
    uint256 poolId = debtData[i].poolId;
    uint256 amt = debtData[i].amt;
@>>    if (amt == type(uint256).max)
@>>        amt = pool.getBorrowsOf(poolId, position);
    address poolAsset = pool.getPoolAssetFor(poolId);
    IOracle oracle = IOracle(riskEngine.getOracleFor(poolAsset));
    debtRepaidValue += oracle.getValueInEth(poolAsset, amt);
}
```
Subsequently, the `maxSeizedA