Using last_profit_update as a timestamp surrogate introduce avoidable inaccuracies.
The vulnerability occur in ScrvusdVerifierV1.sol within the verifyScrvusdByStateRoot function:
2. The params[5] correspond to the last_profit_update value extracted from the scrvUSD vault storage. This value represent the timestamp when the vault profit was last updated.
3. The _updatePrice function pass params[5] as ts (timestamp) to the oracle update_price:
4. The oracle use ts in _raw_price to compute the scrvUSD price:
there's a mismatched timestamps because the last_profit_update from the vault’s storage is not the actual block timestamp. It reflect the last time the vault profit was processed which lag behind the true block timestamp for example:
- If the vault hasn’t processed profits for days, the last_profit_update will be stale.
- The block actual timestamp when parameters are valid will differ significantly.
example scenario
- If last_profit_update is artificially old (1 week ago), the oracle will compute prices as if 1 week has passed, even if the actual block is recent.
- This create a price lags allowing arbitrageurs to exploit the pool by minting/redeeming scrvUSD at incorrect rates.
- The oracle then incorrectly assume time has not progressed since last_profit_update leading to:
Under/Over-Unlocked Shares: Miscalculate unlocked shares, skewing total_supply and raw_price.
Conditions:
last_profit_update = T (1 week ago).
Current block timestamp = T + 1 week.
Execution:
The verifier pass ts = T to the oracle.
The oracle compute _total_supply using ts = T leading to incorrect unlocked shares and price.
Mismatched Timestamps
The oracle uses a stale ts (T) instead of the actual block timestamp (T + 1 week).
Incorrect Price
The reported price lags behind the true market value enabling arbitrage.
- Actor exploit the price lag to mint/redeem scrvUSD at incorrect rates draining liquidity from stableswap-ng pools.
- Liquidity pool suffer losses as arbitrageurs profit from the mispricing.
1. Use the block actual timestamp instead of last_profit_update.
2. Validate Timestamp Freshness:
Ensure the timestamp is recent (1 hour of the current block):
- Sponsor Comments - State root oracles usually do not provide block.timestamp, so it's simply not available. That is why last_profit_update is intended. - In `update_price`, this value must be a future block, meaning this update is a state checked and allowed by the OOS verifier contracts. The impact is also increasingly limited given price is smoothen and any updates via the block hash `verifyScrvusdByBlockHash` can also update the prices appropriately, meaning the price will likely stay within safe arbitrage range aligning with protocol logic
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.