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

Incorrect Storage Slot Calculation for balanceOf in ScrvusdVerifierV1

Summary

In ScrvusdVerifierV1.sol, there's a critical error in how the storage slot for balanceOf(self) is calculated: #L32-L40

uint256[PROOF_CNT] internal PARAM_SLOTS = [
// ...
uint256(keccak256(abi.encode(18, SCRVUSD))) // balanceOf(self)
];

The correct way to calculate a mapping's storage slot in Solidity is

keccak256(abi.encode(key, mappingSlot))

Where

  • key is the mapping key (in this case, the SCRVUSD address itself)

  • mappingSlot is the slot number of the mapping itself (18)

However, the contract reverses this order.

Vulnerability Details

Tthe verifier contracts ScrvusdVerifierV1 and V2, they both use storage proofs to verify parameters from Ethereum. V1 seems to handle multiple parameters related to the vault's state, like total_debt, total_idle. V2 adds a new function to verify the profit_max_unlock_time parameter. The verifiers interact with a BlockHashOracle to get Ethereum block hashes or state roots, ensuring the data's authenticity.

The ScrvusdOracleV2 contract in Vyper is the oracle that calculates the price using these verified parameters. It has different versions (v0, v1, v2) which approximate the price with increasing sophistication. The update_price function is crucial here, as it updates the oracle's state with new parameters from the verifiers. There's also a smoothing mechanism to prevent abrupt price changes, which is important for preventing MEV and manipulation.

The verifiers rely on storage proofs, which are secure if implemented correctly. However, the proofs are constructed and verified. For example, in the _extractParametersFromProof function in V1, they loop through proofs starting from index 1. The PARAM_SLOTS array. The first element is 0 (account proof filler), then slots 21, 22, 20, 38, 39, 40, and the keccak of (18, SCRVUSD). Because balanceOf is a mapping, so the key is the address, and the slot is determined by keccak(abi.encode(address, slot)). But in the code, PARAM_SLOTS[7] is keccak(abi.encode(18, SCRVUSD)).

In Solidity, for a mapping like balanceOf, the storage slot is computed as keccak(KEY, slot_number). Here, the slot for the mapping is 18, and the key is SCRVUSD's address. So the actual slot would be keccak(abi.encodePacked(SCRVUSD, 18)), but the code uses keccak(abi.encode(18, SCRVUSD)). The order here matters. Solidity's mapping storage layout computes the slot as keccak(abi.encode(key, mappingSlot)), so if the mapping is at slot 18, then each key's storage is keccak(abi.encode(key, 18)).

But in the code, they use keccak(abi.encode(18, SCRVUSD)), which is keccak(18, address), which would be different. So that's a problem because the order of the encode is (18, SCRVUSD) instead of (SCRVUSD, 18). That would result in an incorrect storage slot for balanceOf(self). That's an error because the balanceOf would be read from the wrong slot, leading to incorrect parameters being passed to the oracle.

Impact

  1. The verifier will read from the wrong storage slot, retrieving arbitrary data instead of the actual balanceOf(self) value

  2. The ScrvusdOracleV2 will receive invalid parameters, causing it to calculate incorrect prices for scrvUSD

  3. Vulnerable Liquidity Pools: Any stableswap-ng pools relying on this oracle will have incorrect price information, potentially allowing attackers to:

    • Extract value through arbitrage

    • Drain liquidity from one side of the pool

    • Manipulate the market to their advantage

  4. This vulnerability affects all chains where the oracle system is deployed (except Ethereum), creating a systemic risk across multiple blockchains

Recommendations

Correct the slot calculation to

uint256(keccak256(abi.encode(SCRVUSD, 18))) // balanceOf(self) - correct order
Updates

Lead Judging Commences

0xnevi Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

[invalid] finding-ScrvusdVerifierV1-incorrect-storage-slot-balanceOf-compute

- Per sponsor comments, verified slot is vyper, solidity contract only verifies it. - Vyper computes storage slots different from solidity as seen [here](https://ethereum.stackexchange.com/questions/149311/storage-collision-in-vyper-hashmap)

Support

FAQs

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