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

Potential Incorrect Price Calculations Due to Unchecked Storage Slot Existence

Summary

ScrvusdVerifierV1::_extractParametersFromProof function fails to check the existence of storage slots when extracting parameters from Ethereum state proofs, allowing missing slots to default to 0. This can result in incorrect price calculations, including potential division-by-zero errors

Vulnerability Details

https://github.com/CodeHawks-Contests/2025-03-curve/blob/198820f0c30d5080f75073243677ff716429dbfd/contracts/scrvusd/verifiers/ScrvusdVerifierV1.sol#L83

Location: ScrvusdVerifierV1._extractParametersFromProof

  • Root Cause: The function retrieves seven parameters (total_debt, total_idle, total_supply, full_profit_unlock_date, profit_unlocking_rate, last_profit_update, balance_of_self) from storage slots using Verifier.extractSlotValueFromProof. If a slot is missing or uninitialized, the returned SlotValue has exists = false and value = 0. The code assigns slot.value to the params array without verifying slot.exists, relying on an assumption (noted in a comment: “Slots might not exist, but typically we just read them”) that slots are present.

for (uint256 i = 1; i < PROOF_CNT; i++) {
Verifier.SlotValue memory slot = Verifier.extractSlotValueFromProof(
keccak256(abi.encode(PARAM_SLOTS[i])),
account.storageRoot,
proofs[i].toList()
);
// Slots might not exist, but typically we just read them.
params[i - 1] = slot.value;
}

The issue is Triggered when a proof omits a slot or the scrvUSD contract has uninitialized slots, possible due to malformed proofs or unexpected contract state.

Impact

  • Missing slots (e.g., total_debt = 0 total_idle = 0) can yield a total_assets of 0, resulting in a price of 0. A low total_supply could inflate prices unrealistically.

  • Inaccurate prices could lead to arbitrage losses for liquidity providers in pools like USDC/scrvUSD.

  • Though restricted by the PRICE_PARAMETERS_VERIFIER role, a trusted prover could exploit this with incomplete proofs, subtly affecting pool balances.

Tools Used

Manual Review

Recommendations

  • total_supply, total_debt, total_idle must be non-zero for meaningful prices.

  • full_profit_unlock_date, profit_unlocking_rate, last_profit_update, and balance_of_self might tolerate 0 as defaults.

for (uint256 i = 1; i < PROOF_CNT; i++) {
Verifier.SlotValue memory slot = Verifier.extractSlotValueFromProof(
keccak256(abi.encode(PARAM_SLOTS[i])),
account.storageRoot,
proofs[i].toList()
);
+ if (i <= 3) { // total_debt, total_idle, total_supply
+ require(slot.exists, "Critical slot missing");
+ }
params[i - 1] = slot.value;
}
Updates

Lead Judging Commences

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

[invalid] finding-slot-not-check-verifierv1-v2

- Looking at the OOS `StateProofVerifier` and `MerklePatriciaProofVerifier` contract that extracts the slot, the `exists` flag will be flagged as true as long as a non-zero length value is returned as seen [here](https://github.com/curvefi/curve-xdao/blob/3ff77bd2ccc9c88d50ee42d2a746fc7648c7ff2c/contracts/libs/StateProofVerifier.sol#L133C13-L136). From the `MerklePatriciaProofVerifier.extractProofValue`, the minimum length returned will be 1 as represenetd by `bytes(0)`. So this seems to be purely a sanity check that might not even be required. - A slot with zero values is only allowed when the proof provided by the prover correctly proofs that such values are included within the Merkle-Patricia-Tree. The values fetched from mainnet from the V3Vault stored in the merkle trie is likely checked before hand and aggregated into the MerkleTree.

Support

FAQs

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