A critical vulnerability has been identified in the verifyScrvusdByBlockHash
function of the ScrvusdVerifierV1 contract. The function lacks a mechanism to prevent the resubmission of previously processed proofs, allowing for a proof replay attack.
The verifyScrvusdByBlockHash
function is designed to verify scrvUSD vault parameters from Ethereum and update the oracle on target chains. However, it only enforces that block numbers are processed in non-decreasing order (self.last_block_number <= _block_number
) but does not prevent processing the same block multiple times.
The vulnerability exists because:
The function does not track which specific block hashes have already been processed
It allows reprocessing the same block through _block_number == last_block_number
No nonce or other replay protection mechanism is implemented
An attacker identifies a significant gap between the current smoothed price and raw price. By repeatedly submitting the same valid proof transaction in consecutive blocks, they manipulate the time-dependent smoothing function. Each resubmission preserves the identical raw price target while resetting self.last_update
to the current timestamp. This forces recalculation of max_change
based on new block times, applying multiple adjustment steps toward the target price. The cumulative effect artificially expedites price movement, circumventing the protocol's rate-limiting mechanism designed to keep price changes below the arbitrage threshold of 0.5bps per block.
When scrvUSD price movements exceed the stableswap pool fee threshold (~1bps), arbitrage opportunities emerge. Attackers can detect when smoothed price is approaching but still significantly divergent from raw price, then execute proof replays to accelerate convergence past the arbitrage threshold. By deploying flash loans, they exploit the price discrepancy between the oracle-influenced stableswap pool price and the actual market price of scrvUSD on other venues. This extracts risk-free profit proportional to the pool's liquidity depth at the expense of liquidity providers.
The vulnerability enables precisely timed market manipulation through sandwich attacks. Attackers monitor pending vault parameter proof submissions, and when significant parameter changes appear, they front-run with trades in anticipation of price direction, inject repeated proof submissions of current parameters to accelerate price movement in their favor, then back-run with offsetting trades after price adjustment. This sophisticated attack extracts value from normal market operations without contributing any useful economic activity to the protocol.
By selectively targeting specific chains with the replay attack, attackers can create artificial price discrepancies between different blockchain implementations of the oracle. Accelerating price movement on Chain A through replay attacks while leaving Chain B to adjust at the normal rate creates exploitable arbitrage opportunities across chains. This undermines the core goal of maintaining consistent scrvUSD pricing across all supported networks and fragments liquidity across the protocol's cross-chain ecosystem.
When the oracle processes an update, the _smoothed_price
function implements a rate-limiting mechanism to prevent abrupt price changes. This security measure works by calculating a maximum allowable price movement (max_change
) per update, which is proportional to the time elapsed since the last update, the current price, and a configurable parameter called max_price_increment
:
The vulnerability arises from how this time-dependent formula behaves when processing identical updates repeatedly:
During normal operation, when a new legitimate proof is submitted with updated parameters:
The raw price reflects new vault parameters
self.last_update
is set to current block.timestamp
The smoothed price moves toward raw price, limited by max_change
When exploiting the replay vulnerability by submitting an identical proof:
The raw price remains unchanged (using same parameters)
However, self.last_update
is reset to the current timestamp
A new max_change
value is calculated based on time elapsed
The smoothed price can move further toward the same raw price target
The critical technical issue is that each resubmission creates a new discrete step in the smoothing algorithm, effectively partitioning what should be a continuous time-based approach into smaller increments. This allows an attacker to manipulate the rate of price convergence.
For example, if the target raw price is 10% higher than the current smoothed price, and the maximum allowed change is 1% per hour:
Normal: Price would take 10 hours to fully converge
Exploited: Attacker submits the same proof every 10 minutes, reaching full convergence in ~1.7 hours
This attack directly undermines the protocol's carefully calibrated risk parameters designed to ensure that price movements remain below the arbitrage threshold for AMM pools (typically 1bps). By bypassing this rate-limiting, attackers can create profitable arbitrage opportunities against pools using the oracle.
verifyScrvusdByStateRoot shares the same vulnerability as verifyScrvusdByBlockHash - it lacks protection against proof replays, allowing the same proof to be submitted multiple times.
This function is particularly interesting because it uses params[5] (last_profit_update) as the timestamp, meaning that replaying the same proof would always use the same timestamp value. However, the smoothing acceleration attack remains possible because self.last_update in the oracle would still be updated to the current block time when the proof is processed.
Track Processed Blocks: Implement a mapping of already processed block hashes:
Enforce Strict Ordering: Modify the requirement to strictly enforce increasing block numbers:
Add Replay Protection:
- Anything related to the output by the `BLOCK_HASH_ORACLE` is OOS per \[docs here]\(<https://github.com/CodeHawks-Contests/2025-03-curve?tab=readme-ov-file#blockhash-oracle>). - The PoC utilizes a mock `BLOCK_HASH_ORACLE`which is not representative of the one used by the protocol - Even when block hash returned is incorrect, the assumption is already explicitly made known in the docs, and the contract allows a subsequent update within the same block to update and correct prices - All state roots and proofs must be verified by the OOS `StateProofVerifier` inherited as `Verifier`, so there is no proof that manipulating block timestamp/block number/inputs can affect a price update - There seems to be a lot of confusion on the block hash check. The block hash check is a unique identifier of a block and has nothing to do with the state root. All value verifications is performed by the OOS Verifier contract as mentioned above
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.