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.