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

Missing Validation on Slot Existence Enables Zero-Value Parameters to Affect Oracle Pricing

Summary

The ScrvusdVerifierV1 contract extracts key financial parameters from state proofs and uses them to update the price of scrvUSD through an oracle. However, the implementation fails to validate whether each storage slot proof actually exists before accepting its value. As a result, zero values can be inadvertently or maliciously introduced into the oracle’s price calculation, potentially distorting prices and allowing for manipulative attacks on dependent liquidity pools.

Vulnerability Details

In the _extractParametersFromProof() function, the contract processes an array of Merkle Patricia Trie (MPT) proofs and extracts storage slot values corresponding to critical scrvUSD vault parameters such as total_debt, total_idle, totalSupply, and last_profit_update. The function uses Verifier.extractSlotValueFromProof() to obtain a SlotValue struct (presumably containing a value and an exists flag). However, the contract whether the exists flag is false, and the slot's value (which defaults to 0 when the slot doesn't exist) to the params array without any validation.

The code section in question;

// Extract slot values
uint256[PARAM_CNT] memory params;
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;
}

Impact

This oversight allows proofs that omit valid slot values to pass unchecked, resulting in potentially zeroed parameters being submitted to the oracle’s update_price() function. Since the oracle pricing relies on these parameters, feeding it incorrect values—especially zeros—could lead to inaccurate price updates.

Tools Used

Manual Review

Recommendation

After extracting each SlotValue from the proof, explicitly check the exists flag

// Extract slot values
uint256[PARAM_CNT] memory params;
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;
+ require(slot.exists);
}
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.