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

Unchecked Oracle Call Return Value in `ScrvusdVerifierV2.sol`

Summary

The ScrvusdVerifierV2.sol contract calls the update_profit_max_unlock_time function on the ScrvusdOracleV2.vy contract without checking whether the call success or reverts.

The Vyper-implemented oracle can revert under specific conditions :

Ex: outdated block number or insufficient role permissions.

But the verifier assumes success and returns the bool result directly. This can lead to failures, leaving the oracle misaligning the price growth rate used by the stableswap-ng pool.

Vulnerability Details

**Location : **

  • ScrvusdVerifierV2.verifyPeriodByBlockHash:

return IScrvusdOracleV2(SCRVUSD_ORACLE).update_profit_max_unlock_time(period, block_header.number);
  • ScrvusdVerifierV2.verifyPeriodByStateRoot:

return IScrvusdOracleV2(SCRVUSD_ORACLE).update_profit_max_unlock_time(period, _block_number);

Reason:

The verifier assumes the external call to update_profit_max_unlock_time succeeds without verifying the return value or handling potential reverts. The Vyper oracle can revert in two cases:

  1. Role Check Failure:

access_control._check_role(UNLOCK_TIME_VERIFIER, msg.sender) reverts if the caller lacks the UNLOCK_TIME_VERIFIER role (unlikely under normal operation but possible with misconfiguration).

  1. Outdated Block Number:

assert self.last_block_number <= _block_number reverts if the submitted _block_number is less than the stored last_block_number. (plausible with stale proofs or delays).

Impact

  • Stale Data: If the oracle reverts, its profit_max_unlock_time remains at the previous value, misaligning the price growth rate fed to the stableswap-ng pool.

Tools Used

  • Manual Code Review

Recommendations

  • Modify both functions to explicitly verify the oracle call’s succes

function verifyPeriodByBlockHash(
bytes memory _block_header_rlp,
bytes memory _proof_rlp
) external returns (bool) {
Verifier.BlockHeader memory block_header = Verifier.parseBlockHeader(_block_header_rlp);
require(block_header.hash != bytes32(0), "Invalid blockhash");
require(
block_header.hash == IBlockHashOracle(BLOCK_HASH_ORACLE).get_block_hash(block_header.number),
"Blockhash mismatch"
);
uint256 period = _extractPeriodFromProof(block_header.stateRootHash, _proof_rlp);
- return IScrvusdOracleV2(SCRVUSD_ORACLE).update_profit_max_unlock_time(period, block_header.number);
+ bool success = IScrvusdOracleV2(SCRVUSD_ORACLE).update_profit_max_unlock_time(period, block_header.number);
+ require(success, "Oracle update failed");
+ return success;
}
function verifyPeriodByStateRoot(
uint256 _block_number,
bytes memory _proof_rlp
) external returns (bool) {
bytes32 state_root = IBlockHashOracle(BLOCK_HASH_ORACLE).get_state_root(_block_number);
uint256 period = _extractPeriodFromProof(state_root, _proof_rlp);
- return IScrvusdOracleV2(SCRVUSD_ORACLE).update_profit_max_unlock_time(period, _block_number);
+ bool success = IScrvusdOracleV2(SCRVUSD_ORACLE).update_profit_max_unlock_time(period, _block_number);
+ require(success, "Oracle update failed");
+ return success;
}
Updates

Lead Judging Commences

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

Support

FAQs

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