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

State Root Trust Issue

Summary

The verifyScrvusdByStateRoot function in ScrvusdVerifierV1 trusts the blockhash oracle’s state root without validation, unlike verifyScrvusdByBlockHash. This partial mitigation relies on oracle reliability, leaving a gap if it fails.

Vulnerability Details

The vulnerable code is in verifyScrvusdByStateRoot:

function verifyScrvusdByStateRoot(uint256 _block_number, bytes memory _proof_rlp) external returns (uint256) {
bytes32 state_root = IBlockHashOracle(BLOCK_HASH_ORACLE).get_state_root(_block_number);
...
}
  • Blind Trust: No check against blockhash, unlike:

    require(block_header.hash == IBlockHashOracle(BLOCK_HASH_ORACLE).get_block_hash(block_header.number));

PoC

Objective

Feed a fake state root via a compromised blockhash oracle to misprice scrvUSD.

Prerequisites

  • Deployed ScrvusdVerifierV1 with a mock IBlockHashOracle.

  • Control over the oracle’s state root return value (test setup).

Exploit Scenario

An attacker uses a rigged blockhash oracle to supply a fake state root, passing invalid parameters.

Proof of Concept Steps

  1. Mock Oracle:

    solidity

    contract MockBlockHashOracle {
    function get_block_hash(uint256) external pure returns (bytes32) { return bytes32(0x123); }
    function get_state_root(uint256) external pure returns (bytes32) { return bytes32(0xdead); } // Fake root
    }
  2. Setup: Deploy ScrvusdVerifierV1 with MockBlockHashOracle.

  3. Exploit:

    • Craft a proof matching the fake state root 0xdead with skewed parameters (e.g., total_debt = 1000000).

    • Call verifyScrvusdByStateRoot(blockNumber, proof_rlp).

  4. Result: Oracle updates with inflated price, exploitable in pools.

Outcome

Proves reliance on oracle trust can lead to catastrophic mispricing.


Notes

  • These PoCs assume testnet control (e.g., role access, mock dependencies). In a live environment, exploits depend on compromising roles or oracles.

  • For a real contest, you’d deploy these on a fork (e.g., Foundry/Anvil) and log results (e.g., price changes, reverts).

Impact

Garbage Data: Incorrect state root feeds invalid parameters, mispricing scrvUSD.

  • Pool Exploit: Temporary mispricing could drain pools.

Tools Used

Manual Review

Recommendations

Cross-verify state root with blockhash in verifyScrvusdByStateRoot (requires block header input):

function verifyScrvusdByStateRoot(uint256 _block_number, bytes memory _proof_rlp, bytes memory _block_header_rlp) external returns (uint256) {
Verifier.BlockHeader memory block_header = Verifier.parseBlockHeader(_block_header_rlp);
require(block_header.hash == IBlockHashOracle(BLOCK_HASH_ORACLE).get_block_hash(_block_number));
bytes32 state_root = block_header.stateRootHash; # Use verified root
...
}
Updates

Lead Judging Commences

0xnevi Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope
Assigned finding tags:

[invalid] finding-block-number-no-input-check

- 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

Support

FAQs

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