Summary
In some chains, 1 of the 2 Verifier
's functions will not work as they are not implemented in the BLOCK_HASH_ORACLE
.
Vulnerability Details
The ScrvusdVerifierV1
and ScrvusdVerifierV2
contracts implement 2 different ways to update the oracle:
By block hash
By state root
function verifyScrvusdByBlockHash(
bytes memory _block_header_rlp,
bytes memory _proof_rlp
) external returns (uint256) {
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"
);
..
function verifyScrvusdByStateRoot(
uint256 _block_number,
bytes memory _proof_rlp
) external returns (uint256) {
@> bytes32 state_root = IBlockHashOracle(BLOCK_HASH_ORACLE).get_state_root(_block_number);
..
Each on of them calls a different function of the BLOCK_HASH_ORACLE
contract, get_block_hash(...)
or get_state_root(...)
. However, this contract doesn't implement both functions. Looking at the Block Hash Oracle code we can see that OptimismBlockHashOracle.vy implements the get_block_hash(...)
function but not the get_state_root(...)
one:
@view
@external
def get_block_hash(_number: uint256) -> bytes32:
"""
@notice Query the block hash of a block.
@dev Reverts for unknown block numbers or if not supported.
@param _number Number of the block to look for.
"""
block_hash: bytes32 = self.block_hash[_number]
if block_hash == empty(bytes32) and _number == convert(staticcall L1_BLOCK.number(), uint256):
# try fetching current data
block_hash = staticcall L1_BLOCK.hash()
assert block_hash != empty(bytes32)
return block_hash
@view
@external
def get_state_root(_number: uint256) -> bytes32:
"""
@notice Query the state root hash of a block.
@dev Reverts for unknown block numbers or if not supported.
@param _number Number of the block to look for.
"""
raise "NotImplemented"
SonicBlockHashOracle.vy and TaikoBlockHashOracle.vy don't implement the get_block_hash(...)
function but do the get_state_root(...)
one.
Impact
1 of the 2 functions of the verifiers will always revert, allowing only 1 way to update the oracle.
Tools Used
Manual review
Recommendations
Gracefully revert in the verifiers if the function is not implemented or not allow calling specific functions in specific chains.