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

Silent Parameter Skew in ScrvusdVerifierV1

Summary

The ScrvusdVerifierV1 contract’s _extractParametersFromProof function silently accepts zero values for missing storage slot proofs, skewing vault parameters like total_supply or total_debt. This can lead to inaccurate scrvUSD price updates, enabling arbitrage or pool imbalances without detection.

Vulnerability Details

The vulnerable code is in _extractParametersFromProof:

solidity

function _extractParametersFromProof(bytes32 stateRoot, bytes memory proofRlp) internal view returns (uint256[PARAM_CNT] memory) {
RLPReader.RLPItem[] memory proofs = proofRlp.toRlpItem().toList();
require(proofs.length == PROOF_CNT, "Invalid number of proofs");
...
for (uint256 i = 1; i < PROOF_CNT; i++) {
Verifier.SlotValue memory slot = Verifier.extractSlotValueFromProof(...);
params[i - 1] = slot.value; # No slot.exists check
}
return params;
}
  • Silent Zero Values:

    • Verifier.extractSlotValueFromProof returns slot.value = 0 if the slot isn’t proven, without flagging slot.exists.

    • No validation ensures all parameters are non-zero or present.

  • Contrast: ScrvusdVerifierV2 checks slot.exists, showing intent to avoid this.

PoC

Silent Parameter Skew in ScrvusdVerifierV1

Objective

Skew scrvUSD pricing by submitting a partial proof that zeros out total_supply, underpricing the asset.

Prerequisites

  • Deployed ScrvusdVerifierV1 and scrvUSD oracle.

  • Ability to craft RLP-encoded proofs (e.g., via off-chain script or test tooling).

  • Control of proof submission (assume verifier role or test environment).

Exploit Scenario

An attacker submits a proof missing the total_supply slot, causing it to default to 0, which misprices scrvUSD and enables arbitrage.

Proof of Concept Steps

  1. Setup: Deploy ScrvusdVerifierV1 linked to the oracle.

  2. Craft Malicious Proof:

    • Use an off-chain tool (e.g., Python with rlp library) to create an RLP proof with only 7 items (instead of 8), omitting the total_supply slot (slot 20).

    • Example pseudo-code:

      python

      import rlp
      proofs = [
      rlp.encode(account_proof), # Account proof
      rlp.encode(slot_proof_21), # total_debt
      rlp.encode(slot_proof_22), # total_idle
      # Skip total_supply (slot 20)
      rlp.encode(slot_proof_38), # full_profit_unlock_date
      rlp.encode(slot_proof_39), # profit_unlocking_rate
      rlp.encode(slot_proof_40), # last_profit_update
      rlp.encode(slot_proof_balance) # balance_of_self
      ]
      proof_rlp = rlp.encode(proofs)
  3. Exploit:

    • Call verifyScrvusdByBlockHash with a valid block header and the malformed proof_rlp.

    • total_supply defaults to 0, triggering a division-by-zero revert or, if patched, a low price.

  4. Result: Oracle updates with skewed parameters, e.g., price drops to near-zero, allowing arbitrage in stableswap-ng pools.

Outcome

Demonstrates how silent skewing misprices scrvUSD, exploitable until corrected.

Impact

Mispricing: Zeroed parameters (e.g., total_supply = 0) distort price calculations, potentially triggering the division-by- zero issue or underpricing scrvUSD.

  • Arbitrage: Inaccurate prices in stableswap-ng pools allow profit extraction at liquidity providers’ expense.

  • Stealth Exploit: Silent failure makes this hard to detect without monitoring.

Tools Used

Manual Review

Recommendations

Require slot existence in _extractParametersFromProof:

solidity

function _extractParametersFromProof(bytes32 stateRoot, bytes memory proofRlp) internal view returns (uint256[PARAM_CNT] memory) {
RLPReader.RLPItem[] memory proofs = proofRlp.toRlpItem().toList();
require(proofs.length == PROOF_CNT, "Invalid number of proofs");
...
for (uint256 i = 1; i < PROOF_CNT; i++) {
Verifier.SlotValue memory slot = Verifier.extractSlotValueFromProof(...);
require(slot.exists, "Missing slot proof"); # Add this
params[i - 1] = slot.value;
}
return params;
}
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.