The ScrvusdVerifierV1.sol
priceparams
handling in verifyScrvusdByStateRoot()
is broken cause when extracting parameters from storage proofs, if the last_profit_update
or other storage slot is zero (which is a valid path), this zero value is passed as a timestamp surrogate to price calculations in the case of last_profit_update
and in the case of any other slot so is it passed
This zero value however for most of the slots breaks the oracle as it flaws price calculations, profit evolution, and accounting in ScrvusdOracleV2.sol
.
NB: This report can be made for most of the slots that are read, but in this report we would focus on
params[5]
¶ms[6]
, i.elast_profit_update
andbalance_of_self
First note: ScrvusdVerifierV1::verifyScrvusdByStateRoot()
params[5]
Going down the path of last_profit_update
first since it's specifically passed in as the "timestamp surrogate" when we use the stateroot verification path unlike the headers path where we pass the block timestamp. as seen we don't check if this value is non-zero, but from examining the parameter extraction logic, we see that these parameters can be zero, per documentation:
So when extracting, we explicitly acknowledge that "slots might not exist," but the code proceeds without validating that the slot value in this case timestamp value is non-zero.
As hinted earlier on, in contrast with the other verification method:
This method uses the actual block timestamp from the header, which is guaranteed to be non-zero
for a valid block header.
Also note that in the verifier that updates the profit max unlock time we actually ensure the extracted slot value exists, see this.
Now, this issue becomes severe when examining how this timestamp value is used in ScrvusdOracleV2.vy
:
params[6]
The same case can be made for the balance_of_self
value, when extracting, all these values in the param
are iterated upon in the array while being set without verification of them being non-zero and then sent directly to the update
Going into the oracle, if we do have the balance_of_self
value as 0
, the our unlocked shares calculation is effectively broken, cause we would easily return that 0
shares are unlocked, whereas all the shares should be unlocked:
Which would then go ahead and break the pricing logic, cause this is used as a denominator when getting the raw price, since we would have an overly inflated total_supply
:
End price is heavily deflated:
The oracle gets broken if any of the slots we pass are inexisted and/or their value is zero.
How broken the oracle gets is now dependent on what slot was passed while being non-existent, and the report shows two of these cases, i.e when last_profit_value
is 0 and also how the price would be incorrectly deflated in cases when balance_of_self
is inexistent/zero, cause if the full profits has already been unlocked, i.e full_profit_unlock_date < ts, so we should have the full balance returned, but we instead have 0 returned.
Other params can also lead to broken total assets which would also affect the prices, or even broken profit evolution and gain period calculation since any of the below could then be assumed to be zero where as it isn't:
Manual review
All slots that are used in the oracle should be validated to be non-zero while extracting the parameters from the proof.
Alternatively if we are only looking at the window from verifyScrvusdByStateRoot()
then this would be the fix
In general though, something like is done in the verifier that updates the profit max unlock time should be implemented.
- See [here]([https://github.com/CodeHawks-Contests/2025-03-curve?tab=readme-ov-file#blockhash-oracle)](https://github.com/CodeHawks-Contests/2025-03-curve?tab=readme-ov-file#blockhash-oracle) on how it is used to verify storage variable - All state roots and proofs must be verified by the OOS `StateProofVerifier` inherited as `Verifier` (where the price values and params are extracted), so there is no proof that manipulating timestamp/inputs can affect a price update - It is assumed that the OOS prover will provide accurate data and the OOS verifier will verify the prices/max unlock time to be within an appropriate bound/values - There is a account existance check in L96 of `ScrvusdVerifierV1.sol`, in which the params for price updates are extracted from
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.