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

Lack of freshness guarantees enables easy stale price rampage on destination chains

Summary

The oracle fails to enforce maximum age for block headers, allowing intentional use of outdated price data that breaks cross-chain price convergence and creates arbitrage opportunities.

Vulnerability Details

From ScrvusdOracleV2::update_price()
:

@external
def update_price(
_parameters: uint256[ALL_PARAM_CNT], _ts: uint256, _block_number: uint256
) -> uint256:
"""
@notice Update price using `_parameters`
@param _parameters Parameters of Yearn Vault to calculate scrvUSD price
@param _ts Timestamp at which these parameters are true
@param _block_number Block number of parameters to linearize updates
@return Absolute relative price change of final price with 10^18 precision
"""
access_control._check_role(PRICE_PARAMETERS_VERIFIER, msg.sender)
# Allowing same block updates for fixing bad blockhash provided (if possible)
assert self.last_block_number <= _block_number, "Outdated"
self.last_block_number = _block_number
# ... rest of the function

The oracle only verifies that each new block number is greater than or equal to the previously used block number. Similarly in the update_profit_max_unlock_time function:

assert self.last_block_number <= _block_number, "Outdated"
self.last_block_number = _block_number

As seen, the only check in the oracle during update of prices or max unlock time is that passed block is not older than the previous updated block in the oracle, however this easily allows for stale prices to be used in the destination chain, since if no updats happen for a day a prover can pass in a price for 12 hours ago and this would be accepted as a price on the destination chain where as this is a stale price.

Now, the README also hints that LayerZero could be used temporarily, for blockhash feeds with a "minimal delay is 64 blocks to avoid any potential mainnet reorg risks," but there is no actual on-chain enforcement of maximum age for block headers. This creates a serious vulnerability where stale data is accepted and used to update prices.

The layerzero integration aside, that is to say, if no updates happen on the destination chain for an extended period (e.g., a day or more), a prover can submit valid proof for a block that's 12+ hours old.

Impact

Since scrvUSD price generally increases over time (due to yield accrual), using old data will significantly undervalue the token on the destination chain, which breaks the convergence of prices accross destination chain and mainnet which is core to this protocol.

Arbitrageurs can then exploit this price divergence between chains and the magnitude of this attack increases with time - the longer between updates, the greater the potential divergence between real and reported prices.

The cross-chain nature of this protocol makes this particularly dangerous, as price synchronization relies entirely on timely proofs from Ethereum. The current implementation allows for arbitrarily old blocks to be used as long as they're newer than the last update.

Tool Used

Manual Review

Recommendation

Implement a maximum age check of the destination chain's block.timestamp against the timestamp from the verified block headers in both oracle update functions, this can then be explicitly required to not be older than 1 hour.

Updates

Lead Judging Commences

0xnevi Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Lack of quality
Assigned finding tags:

[invalid] finding-missing-sequencer-check-stale-price

I believe this to be at best informational severity as - The moment sequencer is up again, the price updates that retrieve storage values from mainnet will be pushed. To note, price updates are retrieved from storage proofs are retrieved from Ethereum scrvUSD contract, so the concept of the next updated price being outdated is not possible, given mainnet does not utilize sequencers. - There are no problems with small lags if used in liquidity pools due to fees. Fees generate spread within which price can be lagged. - All price updates are subjected to smoothing, and as you can see from the historical price movements as seen [here](https://coinmarketcap.com/currencies/savings-crvusd/), there is never a large discrepancy in prices (absolute terms), and even more unlikely given sequencer downtimes will unlikely be long. This small price changes can be safely arbitrage aligning with [protocol design](https://github.com/CodeHawks-Contests/2025-03-curve?tab=readme-ov-file#parameters) , along with the above mentioned fees - Combined with the above, the max price increments can be temporarily increased to more effectively match the most updated price.

Appeal created

bauchibred Submitter
3 months ago
bauchibred Submitter
3 months ago
0xnevi Lead Judge
2 months ago
0xnevi Lead Judge about 2 months ago
Submission Judgement Published
Invalidated
Reason: Lack of quality
Assigned finding tags:

[invalid] finding-missing-sequencer-check-stale-price

I believe this to be at best informational severity as - The moment sequencer is up again, the price updates that retrieve storage values from mainnet will be pushed. To note, price updates are retrieved from storage proofs are retrieved from Ethereum scrvUSD contract, so the concept of the next updated price being outdated is not possible, given mainnet does not utilize sequencers. - There are no problems with small lags if used in liquidity pools due to fees. Fees generate spread within which price can be lagged. - All price updates are subjected to smoothing, and as you can see from the historical price movements as seen [here](https://coinmarketcap.com/currencies/savings-crvusd/), there is never a large discrepancy in prices (absolute terms), and even more unlikely given sequencer downtimes will unlikely be long. This small price changes can be safely arbitrage aligning with [protocol design](https://github.com/CodeHawks-Contests/2025-03-curve?tab=readme-ov-file#parameters) , along with the above mentioned fees - Combined with the above, the max price increments can be temporarily increased to more effectively match the most updated price.

Support

FAQs

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