DeFiLayer 1Layer 2
14,723 OP
View results
Submission Details
Severity: medium
Invalid

Timestamp Underflow in Oracle Price Calculation When Accessing Historical Prices

Summary

In ScrvusdOracleV2.vy, the _obtain_price_params function advances last_profit_update into the future when projecting price parameters. When calculating prices for timestamps that fall between the original and projected last_profit_update, an integer underflow occurs in the _unlocked_shares function, resulting in incorrect price calculations.

Vulnerability Details

The issue occurs in the _obtain_price_params function, where last_profit_update is advanced by number_of_periods * period:

def _obtain_price_params(parameters_ts: uint256) -> PriceParams:
# ...
number_of_periods: uint256 = min(
(parameters_ts - params.last_profit_update) // period,
self.max_v2_duration,
)
# ...
params.full_profit_unlock_date += number_of_periods * period
params.last_profit_update += number_of_periods * period
return params

Later, when calculating the raw price using these parameters, the _unlocked_shares function is called:

def _unlocked_shares(
full_profit_unlock_date: uint256,
profit_unlocking_rate: uint256,
last_profit_update: uint256,
balance_of_self: uint256,
ts: uint256,
) -> uint256:
# ...
if full_profit_unlock_date > ts:
# If we have not fully unlocked, we need to calculate how much has been.
unlocked_shares = profit_unlocking_rate * (ts - last_profit_update) // MAX_BPS_EXTENDED
# ...

When ts < last_profit_update, the subtraction (ts - last_profit_update) causes an integer underflow in Vyper, resulting in a very large number. When this value is multiplied by profit_unlocking_rate, it produces an incorrect result for unlocked_shares.

Example Scenario:

  1. Original last_profit_update = 1000

  2. After projection with number_of_periods = 4 and period = 604800 (1 week), last_profit_update = 3419200

  3. A user requests price at timestamp 2000000 (which is between 1000 and 3419200)

  4. In _unlocked_shares, (ts - last_profit_update) becomes (2000000 - 3419200), causing underflow

  5. This results in an extremely large unlocked_shares value

  6. The _total_supply function subtracts this from total_supply, potentially resulting in another underflow

  7. The final price calculation is completely incorrect

Impact

This vulnerability can lead to:

  1. Incorrect price reporting by the oracle, especially when querying prices for timestamps between the original and projected last_profit_update

  2. Arbitrage opportunities against stableswap pools using this oracle

  3. Losses for liquidity providers in these pools

  4. Potential MEV exploitation

As stated in the project README: "If not precise enough, this can lead to MEV in the liquidity pool, at a loss for the liquidity providers."

Tools Used

Manual review

Recommendations

  1. Add underflow protection in the _unlocked_shares function:

def _unlocked_shares(
full_profit_unlock_date: uint256,
profit_unlocking_rate: uint256,
last_profit_update: uint256,
balance_of_self: uint256,
ts: uint256,
) -> uint256:
unlocked_shares: uint256 = 0
if full_profit_unlock_date > ts:
if ts >= last_profit_update: # Add this check
unlocked_shares = profit_unlocking_rate * (ts - last_profit_update) // MAX_BPS_EXTENDED
else:
unlocked_shares = 0 # Or use an appropriate value for timestamps before last_profit_update
# ...
  1. Alternatively, modify the _obtain_price_params function to create a separate copy of parameters for future projections without advancing the original last_profit_update:

def _obtain_price_params(parameters_ts: uint256) -> PriceParams:
# ...
# Create a new copy of parameters for future projection
projected_params = params.copy()
projected_params.full_profit_unlock_date += number_of_periods * period
projected_params.last_profit_update += number_of_periods * period
return projected_params
  1. Consider a different approach to price projections that doesn't create this timestamp inconsistency, such as maintaining separate sets of parameters for different time periods.

Updates

Lead Judging Commences

0xnevi Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope
Assigned finding tags:

[invalid] finding-timestamp-underflow

This issues and duplicates are very similar to reasonings highlighted in issue #11. The timestamp variables are extracted and verified via the OOS `StateProofVerifier` contract inherited as `Verifier`. There is simply no concrete proof that the verifier allowed such an underflow to occur, representing stale price value updates.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.