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

Shared block number state in `ScrvusdOracleV2` between verifiers causes frequent update failures and ingestion of stale data

Summary

Using a single block number state variable for updates of both price parameters and profit max unlock time creates synchronization issues where one verifier can unfortunately block the other, leading to stale data and incorrect pricing.

Vulnerability Details

First, note the snippet below from ScrvusdOracleV2::update_price

# 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

And also from ScrvusdOracleV2::update_profit_max_unlock_time

# 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

As seen, both verifiers (ScrvusdVerifierV1 for price parameters and ScrvusdVerifierV2 for profit max unlock time) update the same last_block_number state variable in the oracle. This shared state creates a critical synchronization issue, as any update from either verifier increases the minimum block number required for future updates from either verifier, this would then mean that if we attempt to update the oracle for valid data on the price it would fail on the oracle level as Outdated, where as the price might have not been updated for a long time causing longer ingestion of stale data.

Textual POC:

  • Price is updated at block 1000

  • Profit max unlock time is updated at block 1150 30 minutes later.

  • Any valid proof of price update from block 1000 up to 1149 will be rejected by the oracle and claimed as Outdated, where as in real sense this price update is fresher than the current one stored and should be accepted.

Impact

As hinted under Vulnerability Details, this shared state variable breaks the valid flow for price updates, as it creates a scenario where:

  1. If the price parameter verifier (ScrvusdVerifierV1) consistently runs with newer blocks than the profit max unlock time verifier (ScrvusdVerifierV2), the latter will frequently encounter reverts with the "Outdated" error, having a state on the destination chain, where our profit evolution logic is wrong and stale, which affects pricing.

  2. This blocking effect means one of the two crucial state variables (either price parameters or profit max unlock time) will become stale and outdated.

  3. Since the profit max unlock time is essential for accurate price calculations, having stale unlock time data while price parameters are updated (or vice versa) leads to incorrect pricing.

  4. As updates fail more frequently, the protocol becomes vulnerable to price manipulation and arbitrage opportunities across chains due to the divergence.

This effectively means provers even lose funds as any attempt at updating price requires tx fees.

Tools Used

Manual review

Recommendations

Separate the block number tracking for each type of update:

# In ScrvusdOracleV2.vy
+ last_price_block_number: public(uint256) # For price parameters
+ last_unlock_time_block_number: public(uint256) # For profit max unlock time
@external
def update_price(
_parameters: uint256[ALL_PARAM_CNT], _ts: uint256, _block_number: uint256
) -> uint256:
access_control._check_role(PRICE_PARAMETERS_VERIFIER, msg.sender)
- assert self.last_block_number <= _block_number, "Outdated"
- self.last_block_number = _block_number
+ assert self.last_price_block_number <= _block_number, "Outdated"
+ self.last_price_block_number = _block_number
# Rest of function...
@external
def update_profit_max_unlock_time(_profit_max_unlock_time: uint256, _block_number: uint256) -> bool:
access_control._check_role(UNLOCK_TIME_VERIFIER, msg.sender)
- assert self.last_block_number <= _block_number, "Outdated"
- self.last_block_number = _block_number
+ assert self.last_unlock_time_block_number <= _block_number, "Outdated"
+ self.last_unlock_time_block_number = _block_number
# Rest of function...

This separation allows each verifier to operate independently on its own update schedule, preventing one verifier from blocking the other and ensuring both price parameters and profit max unlock time can be kept up-to-date.

Updates

Lead Judging Commences

0xnevi Lead Judge
3 months ago
0xnevi Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

bauchibred Submitter
3 months ago
0xnevi Lead Judge
3 months ago
0xnevi Lead Judge 2 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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