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

Shared `last_block_number` state variable can cause price update blocking

Vulnerability Details

The last_block_number state variable is shared between update_profit_max_unlock_time and update_price functions. In case when one of the functions is called with a block number N, and the other function is called with a block number N-1, the second function will be blocked by the last_block_number check.

contracts/scrvusd/oracles/ScrvusdOracleV2.vy

@external
def update_price(
_parameters: uint256[ALL_PARAM_CNT], _ts: uint256, _block_number: uint256
) -> uint256:
access_control._check_role(PRICE_PARAMETERS_VERIFIER, msg.sender)
# Allowing same block updates for fixing bad blockhash provided (if possible)
# here the last_block_number can be less than _block_number passed to update_profit_max_unlock_time call
@> assert self.last_block_number <= _block_number, "Outdated"
self.last_block_number = _block_number
...
@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)
# Allowing same block updates for fixing bad blockhash provided (if possible)
# here the last_block_number can be greater than _block_number passed to update_price call
# and can be applied, which will block the update_price call
@> assert self.last_block_number <= _block_number, "Outdated"
self.last_block_number = _block_number

Impact

If update_profit_max_unlock_time and update_price functions are called in asynchroneous way, the other function can be blocked by the last_block_number. This may disrupt the oracle update process and lead to outdated price data.

PoC

Put the following code in a file tests/scrvusd/oracle/unitary/test_v2.py

def test_block_last_block_number(soracle, verifier):
ts = boa.env.evm.patch.timestamp
price_params = [3, 0, 2, ts + 7 * 86400, 0, 0, 0]
blocknumber = boa.env.evm.patch.block_number + 10
with boa.env.prank(verifier):
soracle.update_profit_max_unlock_time(
8 * 86400,
blocknumber,
)
# will revert, despite the data are not outdated
with boa.reverts():
soracle.update_price(
price_params,
ts,
blocknumber - 1,
)

Recommendations

Use separate last_block_number state variable for each update function.

@external
def update_price(
_parameters: uint256[ALL_PARAM_CNT], _ts: uint256, _block_number: uint256
) -> uint256:
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
+ assert self.last_price_block_number <= _block_number, "Outdated"
+ self.last_price_block_number = _block_number
Updates

Lead Judging Commences

0xnevi Lead Judge
6 months ago
0xnevi Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope
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.