The ScrvusdVerifierV2 contract incorrectly calculates storage slot keys for fixed storage variables by hashing them, leading to verification of incorrect state values across chains and potential manipulation of oracle data.
In the _extractPeriodFromProof function of ScrvusdVerifierV2.sol, the code incorrectly calculates the storage slot key for the profit_max_unlock_time variable using keccak256(abi.encode(PERIOD_SLOT)):
This approach is fundamentally incorrect for accessing direct state variables in Ethereum's storage model. The profit_max_unlock_time variable is a simple state variable stored at slot 37 in the scrvUSD contract. According to Ethereum's storage layout specifications:
Simple state variables are stored directly at their assigned slots (0, 1, 2, etc.)
The correct way to access a direct slot is using bytes32(uint256(PERIOD_SLOT))
Hashing with keccak256 is only used for accessing elements in mappings or dynamic arrays
By using the hashed slot computation, the verifier retrieves data from an entirely unrelated storage location (the hash of 37) rather than slot 37 where the actual profit_max_unlock_time value is stored.
Impact: HIGH - The incorrect slot calculation breaks the fundamental cross-chain verification mechanism, causing the system to verify and use entirely incorrect data. This directly compromises the oracle's ability to accurately report the profit_max_unlock_time value, which determines how quickly profits are unlocked in the system.
Likelihood: CERTAIN - This is not a probabilistic issue but a deterministic one. The code as written absolutely retrieves data from the wrong storage location every time it executes.
Why Current Tests Pass
If the off-chain proof generator and on-chain verifier both assume the incorrect hashed slot, they can produce matching proofs.
This self-consistent “lie” means tests pass without actually verifying the real on-chain state.
The scrvUSD contract on Ethereum has profit_max_unlock_time set to 604800 (7 days) at storage slot 37
An off-chain prover generates a state proof for slot 37 (correctly)
When the proof is submitted to verifyPeriodByStateRoot, the function calculates keccak256(abi.encode(37)), which results in a completely different storage location
The function attempts to verify the proof against this incorrect location, which either fails (best case) or succeeds with a different value (worse case)
If the hashed location happens to contain some other value (like 0 or a very small number), the profit_max_unlock_time will be incorrectly verified and set to this arbitrary value
This causes profits to unlock too quickly or too slowly, breaking the economic model of the protocol
The issue breaks the entire purpose of the cross-chain verification system, as it verifies the wrong value.
Replace the hashing of the slot with the direct slot access method. Specifically:
Additionally, ensure that the off-chain proof generation is also adjusted to create proofs for the direct slot, not the hashed slot.
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.