xanoutas/solidity-security-findings huggingface dataset:
A2-security - Liquidators can manipulate RepaidDebt calculation to seize excess collateral from users being liquidated
Audit: 2024-08-sentiment-v2-judging
Severity: HIGH

A2-security

High

# Liquidators can manipulate RepaidDebt calculation to seize excess collateral from users being liquidated

## Summary

- A  vulnerability in the liquidation process allows malicious liquidators to seize all collateral. The issue stems from a discrepancy between debt calculation in `RiskModule.sol`'s `_validateSeizedAssetValue` function and actual repayment in `PositionManager.sol`. By exploiting duplicate pool entries and `type(uint256).max` in the `debtData` array, attackers can trick the system into allowing seizure of more collateral than justified by the repaid debt. 

## Vulnerability Detail
- Sentiment protocol's liquidation process involves two key steps: validating the liquidation parameters and executing the actual repayment. The protocol aims to ensure that liquidators cannot seize more collateral than they're entitled to based on the debt they repay.

Let's examine the relevant parts of the code in detail:

1. Liquidation Validation:

In the RiskModule contract, the `_validateSeizedAssetValue` function calculates the expected debt repayment :

```solidity
function _validateSeizedAssetValue(address position, DebtData[] calldata debtData, AssetData[] calldata assetData, uint256 discount) internal view {
    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);
    }
    // ... rest of the function
}
```

- Notice that this function allows the use of `type(uint256).max` as a special value to indicate repayment of the full borrowed amount from a pool. When `amt == type(uint256).max`, it fetches the full borrowed amount for that Position using `pool.getBorrowsOf(poolId, position)` which always return the full borrowed amount of this position. 
```js
    function getBorrowsOf(uint256 poolId, address position) public view returns (uint256) {
        PoolData storage pool = poolDataFor[poolId];
        (uint256 accruedInterest,) = simulateAccrue(pool);
        // [ROUND] round up to enable enable complete debt repayment
        return _convertToAssets(borrowSharesOf[poolId][position], pool.totalBorrowAssets + accruedInterest, pool.totalBorrowShares, Math.Rounding.Up);
    }
```
2. Actual Debt Repayment:
The actual repayment occurs in the `PositionManager` contract:

```solidity 
function _repayPositionDebt(address position, DebtData[] memory debtData) internal {
    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);
        po
Obsidian - By inflating the value of a pool share, a malicious actor can steal a large amount of funds
Audit: 2024-08-sentiment-v2-judging
Severity: HIGH

Obsidian

High

# By inflating the value of a pool share, a malicious actor can steal a large amount of funds

### Summary

By inflating the value of a pool share, a malicious actor can steal a large amount of funds. An attacker can frontrun depositors to steal funds, or frontrun a SuperPool reallocation to steal funds.

### Root Cause

[`Pool.deposit()`](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/25a0c8aeaddec273c5318540059165696591ecfb/protocol-v2/src/Pool.sol#L309) rounds DOWN the number of shares minted to the user. `Pool.withdraw()` rounds UP the number of shares burned from the user.

A classical vault inflation/donation attack involves donating assets to the pool via token transfers. However the classic attack is not possible in this protocol since `totalDepositAssets` are tracked internally via a storage variable.

However, through exploiting the rounding directions on `deposit()` and `withdraw()` , a malicious actor can still inflate the value of a single share by a significant amount (1e0 share = 12.15e18 assets). Then, after the next deposit occurs (either a normal deposit, or a SuperPool reallocation), the malicious actor can withdraw their share for profit.

### Internal pre-conditions

`pool.interestFee == 0` 

### External pre-conditions

_No response_

### Attack Path

1. Attacker deposits `1e18` into a pool that was newly initialised
2. Attacker deposits `1e17` assets as collateral, and borrows `0.05e18` assets
3. After at least 1 block (so interest accrues on this loan), the attacker repays the loan + interest
4. Attacker withdraws `pool.getAssetsOf(fixedRatePool, attacker) - 2` 
    - This achieves the pre-condition of `totalAssets = 2, totalShares = 1`
5. Now the attacker deposits `2*totalAssets -  1` , and receives 1 share in return
6. Then the attacker withdraws that share, converting it into 1 asset
7. Steps 5-6 are repeated, so that `totalAssets` increases while `totalSupply` remains at 1 due to step 6
    - This is effectively ‘manual donation’ to the pool, allowing the share value to be inflated, without the need to send tokens in (since total assets are stored as storage variables rather than token balances)
    - After 40 iterations, `totalAssets` can be increased to ~ 3^40 (12e18)
8. Once the value of 1 share has been inflated sufficiently, the next depositor’s deposit will round the number of shares to 1
    - For example, if `totalAssets = 12e18`  and the depositor deposits `20e18` , the new state is `totalAssets = 32e18` and `totalShares = 2`
    - The attacker can then withdraw their 1 share for `totalAssets/2`  = `16e18`
    - Their profit is `16e18` - `12e18` = `4e18` , which is a profit of `33%`

### Impact

Loss of funds for the depositor who is frontran by the attacker

The maximum potential profit for the attacker is `50%` , per victim depositor

- This is the case where the depositor deposits m`2 * 3^n - 1`  where `n` is the number of iterations of step 5-6

### PoC

Add the following test
Brenzee - Liquidator can seize the whole collateral or collateral up to 200% of debt value during liquidation
Audit: 2024-08-sentiment-v2-judging
Severity: HIGH

Brenzee

High

# Liquidator can seize the whole collateral or collateral up to 200% of debt value during liquidation

### Summary

Incorrect assumption in `RiskModule._validateSeizedAssetValue` function opens up the possibility for user to lose all of the collateral when Liquidator liquidates the unhealthy position.

### Root Cause

The `debtRepaidValue` in [`RiskModule._validateSeizedAssetValue`](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/main/protocol-v2/src/RiskModule.sol#L136-L145) can be inflated by passing specific `debtData` parameters during `PositionManager.liquidate`.

This is possible due to 2 reasons:
1. Multiple debt data entries for the same poolId can be passed
2. `type(uint256).max` can be used that indicates the whole debt should be repaid.

If in debt data the following values are passed:
```solidity
        DebtData[] memory debts = new DebtData[](2);
        debts[0] = DebtData({ poolId: poolId, amt: pool.getBorrowsOf(poolId, position) - 1 });
        debts[1] = DebtData({ poolId: poolId, amt: type(uint256).max });
```
then the `debtRepaidValue` will be inflated close to 200%.

### Internal pre-conditions

1. User's position has to be unhealthy

### External pre-conditions

_No response_

### Attack Path

1. User deposits collateral and borrows assets.
2. Liquidator monitors the position and liquidates the position with the following `debtData` and `assetData`:
```md
debtData = [max borrow - 1 wei, max borrow] = 199.99...% of total borrowed (but when being repaid, 100% is repaid instead)
assetData = ltv > 50% ? user's whole collateral : collateral equal to 200% of the debt value
```
3. Liquidator successfully repays 100% of user's debt and receives more in reward than anticipated.

### Impact

User suffers a bigger loss during liquidation than anticipated, liquidator gains more reward than anticipated.

The loss for user depends on the average LTV value and liquidation discount
- if LTV 50%-98%, user will lose all of the collateral.
- if LTV 10%-50%, user will lose collateral equal to 200% of the debt value

### PoC

Add the following test code under `test` folder and run
```shell
with logs:
forge test --mc "LiquidationAuditTest" -vv

without logs:
forge test --mc "LiquidationAuditTest"
```

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import { BaseTest } from "../BaseTest.t.sol";
import "forge-std/console2.sol";

import { MockERC20 } from "../mocks/MockERC20.sol";
import { Pool } from "src/Pool.sol";
import { PositionManager } from "src/PositionManager.sol";
import { Action, AssetData, DebtData } from "src/PositionManager.sol";
import { RiskEngine } from "src/RiskEngine.sol";
import { RiskModule } from "src/RiskModule.sol";
import { FixedPriceOracle } from "src/oracle/FixedPriceOracle.sol";

contract LiquidationAuditTest is BaseTest {
    Pool pool;
    address position;
    RiskEngine riskEngine;
    PositionManager positionManager;
    address public liquidator = makeAddr("liquidato
cawfree - Red Stone Oracle Can Time Travel
Audit: 2024-08-sentiment-v2-judging
Severity: HIGH

cawfree

High

# Red Stone Oracle Can Time Travel

## Summary

The [`RedstoneCoreOracle`](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/25a0c8aeaddec273c5318540059165696591ecfb/protocol-v2/src/oracle/RedstoneOracle.sol) can be atomically manipulated repeatedly back and forth between different observations within the validity period to yield different price readings upon demand.

## Vulnerability Detail

The [`RedstoneCoreOracle`](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/25a0c8aeaddec273c5318540059165696591ecfb/protocol-v2/src/oracle/RedstoneOracle.sol#L12C10-L12C28) requires callers to manually update and cache the oracle price via the [`updatePrice`](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/25a0c8aeaddec273c5318540059165696591ecfb/protocol-v2/src/oracle/RedstoneOracle.sol#L48C14-L48C25) function:

```solidity
function updatePrice() external {
    // values[0] -> price of ASSET/USD
    // values[1] -> price of ETH/USD
    // values are scaled to 8 decimals

    uint256[] memory values = getOracleNumericValuesFromTxMsg(dataFeedIds);

    assetUsdPrice = values[0];
    ethUsdPrice = values[1];

    // RedstoneDefaultLibs.sol enforces that prices are not older than 3 mins. since it is not
    // possible to retrieve timestamps for individual prices being passed, we consider the worst
    // case and assume both prices are 3 mins old
    priceTimestamp = block.timestamp - THREE_MINUTES;
}
```

Although here we correctly consider the worst-case staleness for newly-submitted observation (and the inter-observation timestamps themselves are validated to be consistent between both readings), there are are no protections against repeatedly calling [`updatePrice`](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/25a0c8aeaddec273c5318540059165696591ecfb/protocol-v2/src/oracle/RedstoneOracle.sol#L48C14-L48C25) using valid data during the result validity period (for example, two different observations which took place within the same validity period) - even if that data has been seen before.

This means it is possible to call [`updatePrice`](https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/25a0c8aeaddec273c5318540059165696591ecfb/protocol-v2/src/oracle/RedstoneOracle.sol#L48C14-L48C25) with one valid observation, immediately call it with a second valid observation, and then update again to revert back to the original observation in an effort to manipulate price.

### Proof of Concept

This proof of concept is split into two sections - for quick verification, judges need only focus on the first part, whereas the second part provides instructions on how to recreate mock payloads locally.

#### Example Observations (default)

1. Add the following file (i.e. `Sherlock.t.sol`) to the [`protocol-v2/test`](https://github.com/sherlock-audit/2024-08-sentiment-v2/tree/main/protocol-v2/test) directory:

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import {Test, conso