In the _extractParametersFromProof
function, the code extracts each storage value by computing the key as
for every parameter. This works correctly for the last parameter (“balanceOf(self)”) since that value is in a mapping (and the key for a mapping entry is computed as keccak256(abi.encode(key, slot))). However, for the other parameters (such as total_debt
, total_idle
, totalSupply
, etc.), these are plain storage variables stored at fixed slots (e.g. 21, 22, 20, …).
For plain storage variables in Ethereum, the key in the account’s storage trie is not the hash of the slot index; it is simply the 32‑byte (left‑padded) representation of the slot number. For example, for a variable stored at slot 21 the correct key should be:
—not
Consequences:
Because the verifier uses the wrong key derivation:
A genuine state proof (for example, one obtained from an Ethereum archive node via eth_getProof
) will include storage entries keyed by the plain slot (e.g. 0x000…15
for slot 21).
The verifier, however, will look for a key equal to keccak256(abi.encode(21))
, which is entirely different.
As a result, the extracted value for each plain variable will either be incorrect (likely defaulting to zero) or the proof will fail the check (if the node isn’t found).
This means that unless the scrvUSD vault was implemented in an unconventional way (storing even plain variables under hashed keys), the verifier will not be able to extract the correct parameters from the proof. Worse, if an attacker can craft a proof for the hashed key instead of the actual storage key, they might be able to manipulate the values that eventually get passed to the price oracle.
Impact:
can lead either to a failure in updating the price (if the proof is rejected) or to potential exploitation if an attacker can supply a custom state proof for the hashed key.
Proof of Concept:
Expected Behavior:
For a plain storage variable (e.g. total_debt
at slot 21), the standard is that its value is stored under the key
in the account’s storage trie.
What the Code Does:
The contract computes the key as
which yields a completely different 32‑byte value.
Implications:
A correct state proof (from a compliant Ethereum node) will provide the storage entry under bytes32(21)
.
The verifier will then call:
and fail to find a matching key.
This mismatch means the verifier might either revert (failing the update) or—if not properly handled—allow an attacker to supply a specially crafted proof that “proves” a value for the wrong key.
Tools Used:
manual.
Recommendation:
For Plain Variables:
Change the key derivation to use the proper padded representation of the slot. For example, replace
with
for parameters that are plain (non-mapping) storage variables.
See primary comments in issue #23
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.