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

Unverified Slot Existence in ScrvusdVerifierV1 leading to oracle manipulation and denial of service

Summary

In ScrvusdVerifierV1.sol, the _extractParametersFromProof function does not check if storage slots exist. If a slot is uninitialized, its value defaults to 0, leading to incorrect parameter extraction and oracle manipulation.
A missing total_supply can result in a division-by-zero error, leading to contract failures or erroneous price calculations.

// 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;
}

Vulnerability Details

The issue here is that the code assigns params[i - 1] = slot.value without verifying slot.exists.

  • If total_supply (slot 20) is uninitialized, total_supply = 0, causing division by zero in _raw_price.

  • If balance_of_self (dynamic slot) is uninitialized, unlocked shares will be miscalculated.

This contrasts with the behavior in ScrvusdVerifierV2, which properly checks for slot existence:

// ScrvusdVerifierV2.sol
Verifier.SlotValue memory slot = Verifier.extractSlotValueFromProof(
keccak256(abi.encode(PERIOD_SLOT)),
account.storageRoot,
proofs[1].toList()
);
require(slot.exists);

Impact

  • Invalid parameters (e.g., zero total_supply) cause the oracle to compute erroneous prices, enabling arbitrage attacks or rendering the oracle unusable.

  • Example: An attacker could trigger a division-by-zero revert in _raw_price, halting price updates and disrupting dependent systems.

Tools Used

Manual Review

Recommendations

  • In ScrvusdVerifierV1, ensure slot.exists is true for all critical parameters. Revert if any slot is missing.

// ScrvusdVerifierV2.sol
Verifier.SlotValue memory slot = Verifier.extractSlotValueFromProof(
keccak256(abi.encode(PERIOD_SLOT)),
account.storageRoot,
proofs[1].toList()
);
require(slot.exists);
  • For non-critical parameters, use default values only if they don’t compromise security.

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.