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

Unvalidated Timestamp Usage Leading to Minor Price Discrepancies

Summary

The verifyScrvusdByStateRoot function in the ScrvusdVerifierV1 contract uses a timestamp from params[5] (labeled last_profit_update) to update prices, assuming it’s always correct. If this timestamp is wrong or outdated, it could slightly skew the smoothed price reported by the oracle, though the impact is small.

Vulnerability Details

How It Works

  • Timestamp Source: The function uses params[5] (scrubUSD’s last_profit_update) as the timestamp for price updates.

  • Assumption: The code assumes last_profit_update is a valid proxy for the block timestamp.

  • Risk: No validation ensures last_profit_update is reasonable (e.g., not set to a future time).

Example Scenario

  1. Manipulated Timestamp: A bug in scrvUSD sets last_profit_update to 2030-01-01 (future).

  2. Oracle Reaction: The smoothing formula calculates price changes as if years have passed, but max_price_increment limits the adjustment to small increments per second.

    _price_v0 Uses self.price_params_ts

    self.price_params_ts and self.price_params.last_profit_update are both set to params[5] (from verifyScrvusdByStateRoot).

    Both values originate from params[5], which is unvalidated in ScrvusdVerifierV1.sol.

    _raw_price calculates an incorrect price based on distorted parameters.

  3. Result: The oracle price lags slightly until the timestamp catches up.

Vulnerable Function

params[5] is used directly as the timestamp (ts) in _updatePrice, with no validation.

https://github.com/CodeHawks-Contests/2025-03-curve/blob/198820f0c30d5080f75073243677ff716429dbfd/contracts/scrvusd/verifiers/ScrvusdVerifierV1.sol#L79

/// @param _block_number Number of the block to use state root hash
/// @param _proof_rlp The state proof of the parameters
function verifyScrvusdByStateRoot(
uint256 _block_number,
bytes memory _proof_rlp
) external returns (uint256) {
bytes32 state_root = IBlockHashOracle(BLOCK_HASH_ORACLE).get_state_root(_block_number);
uint256[PARAM_CNT] memory params = _extractParametersFromProof(state_root, _proof_rlp);
// Use last_profit_update as the timestamp surrogate
return _updatePrice(params, params[5], _block_number);
}

Oracle Price Update Logic

In ScrvusdOracleV2.vy, update_price stores the future timestamp in price_params.last_profit_update:

https://github.com/CodeHawks-Contests/2025-03-curve/blob/198820f0c30d5080f75073243677ff716429dbfd/contracts/scrvusd/oracles/ScrvusdOracleV2.vy#L321-L324

last_profit_update=_parameters[5],
...
self.price_params_ts = _ts

Impact

Limited Damage: The smoothing mechanism in _smoothed_price caps how much the price can shift, so even a bad timestamp won’t cause wild swings—just minor inaccuracies.

Normal Case: Usually, last_profit_update is probably close to correct, so this won’t often be a problem.

Outcome: The worst case is small price discrepancies, not a major financial hit or system breakdown.

Tools Used

Recommendations

Add a sanity check to reject invalid timestamps:

require(params[5] <= block.timestamp, "Invalid future timestamp");

Use the block header’s timestamp as a fallback:

uint256 ts = params[5] > block.timestamp ? block.timestamp : params[5];
Updates

Lead Judging Commences

0xnevi Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

[invalid] finding-missing-proof-content-validation

- See [here]([https://github.com/CodeHawks-Contests/2025-03-curve?tab=readme-ov-file#blockhash-oracle)](https://github.com/CodeHawks-Contests/2025-03-curve?tab=readme-ov-file#blockhash-oracle) on how it is used to verify storage variable - All state roots and proofs must be verified by the OOS `StateProofVerifier` inherited as `Verifier` (where the price values and params are extracted), so there is no proof that manipulating timestamp/inputs can affect a price update - It is assumed that the OOS prover will provide accurate data and the OOS verifier will verify the prices/max unlock time to be within an appropriate bound/values - There is a account existance check in L96 of `ScrvusdVerifierV1.sol`, in which the params for price updates are extracted from

Support

FAQs

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