In ScrvusdVerifierV1 , The storage slot for balance_of_self, defined as BALANCE_OF_SELF_SLOT = 18, does not correspond to the actual slot of balanceOf[address(this)] in the scrvUSD vault contract (Yearn V3 Vault at 0x0655977FEb2f289A4aB78af67BAB0d17aAb84367). Instead of extracting the vault’s self-balance from the correct mapping slot (keccak256(abi.encode(address(this), 7))), it retrieves the role_manager address from slot 18, misinterpreting it as a uint256 value. This error propagates to the ScrvusdOracleV2, inflating _unlocked_shares and causing extreme price distortions in the v2 price mode.
Root Cause:
In ScrvusdVerifierV1, BALANCE_OF_SELF_SLOT = 18 is used to fetch balance_of_self
The scrvUSD vault’s storage layout (Yearn V3 Vault, Vyper 0.3.7) shows:
balance_of: HashMap[address, uint256] at slot 7.
balanceOf[address(this)] at keccak256(abi.encode(SCRVUSD, 7)).
Slot 18 is role_manager: public(address), an unrelated variable.
Using slot 18 extracts the role_manager address (e.g., 0x1234...) as a uint256, typically a massive value (e.g., 1.23e40), instead of the vault’s balance.
This will occurs on every call to _extractParametersFromProof when fetching balance_of_self, invoked by verifyScrvusdByBlockHash or verifyScrvusdByStateRoot, which then calls update_price in ScrvusdOracle.
The Intended Behavior Is Suppose to be:
balance_of_self should reflect balanceOf[address(this)] from the vault, representing shares held by the vault itself (locked profits). This value adjusts _unlocked_shares to account for profit unlocking, ensuring _total_supply accurately reflects circulating shares for price calculations.
But The Actual Behavior:
Slot 18 (role_manager) is fetched instead, typically an address like 0x1234567890abcdef.... As a uint256, this is a massive number (e.g., 0x1234... ≈ 1.23e40).
Post-full_profit_unlock_date, _unlocked_shares = balance_of_self becomes this inflated value, causing _total_supply to underflow or approach zero, hyperinflating the price in _raw_price.
Example:
Vault state: total_supply = 1e21, total_idle = 1e18, total_debt = 0, balance_of_self = 2e20 (correct), full_profit_unlock_date passed.
The Correct way:
_unlocked_shares = 2e20.
_total_supply = 1e21 - 2e20 = 8e20.
Price = 1e18 * 1e18 // 8e20 = 1.25e15 (1.25 scrvUSD per share).
But The Actual (assume role_manager = 0x1234567890abcdef1234567890abcdef12345678):
balance_of_self = 0x1234567890abcdef1234567890abcdef12345678 ≈ 1.23e40.
_unlocked_shares = 1.23e40.
_total_supply = 1e21 - 1.23e40 underflows to near-zero (e.g., 1 due to Vyper’s uint256 bounds).
Price = 1e18 * 1e18 // 1 ≈ 1e36 (an absurd 1e18 scrvUSD per share).
balance_of_self is orders of magnitude larger than intended, causing _unlocked_shares to dominate _total_supply, reducing it to negligible values or triggering underflow artifacts.
Manual Review, Tenderly
Update BALANCE_OF_SELF_SLOT to the correct mapping base slot (7) and adjust extraction logic
Verify all PARAM_SLOTS against the vault’s layout
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.