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

Lack of Validation in `update_price` Allows Inconsistent Asset-Supply Relationship, Leading to Incorrect Price Calculations

Summary

The update_price function in ScrvusdOracleV2.vy does not validate the logical consistency of the parameters it receives. This allows a malicious prover to submit mismatched values (e.g., total_idle + total_debt != total_supply), leading to incorrect price calculations and potential manipulation of the oracle.

Vulnerability Details

The update_price function accepts an array of parameters (_parameters) that represent key metrics of the scrvUSD vault, such as total_debt, total_idle, and total_supply. These parameters are used to calculate the price of scrvUSD. However, the function does not verify that these parameters are logically consistent.

  • total_assets should equal total_idle + total_debt.

  • total_supply should equal total_assets

https://github.com/CodeHawks-Contests/2025-03-curve/blob/198820f0c30d5080f75073243677ff716429dbfd/contracts/scrvusd/oracles/ScrvusdOracleV2.vy#L316-L318

Add this function to tests/scrvusd/oracle/unitary/test_v2.py:

def test_asset_supply_relationship_vulnerability(soracle, verifier):
"""
Test to demonstrate the vulnerability in the oracle where:
- total_assets != total_idle + total_debt
- total_supply != total_assets
"""
# Set initial valid parameters
total_idle = 100
total_debt = 50
total_assets = total_idle + total_debt # 150
total_supply = 150
ts_initial = boa.env.evm.patch.timestamp
parameters_valid = [total_debt, total_idle, total_supply, ts_initial + 7 * 86400, 0, ts_initial, 0]
# Update the oracle with valid parameters
with boa.env.prank(verifier):
soracle.update_price(parameters_valid, ts_initial, 1)
# Fetch the raw price to ensure the update succeeded
raw_price = soracle.raw_price()
expected_price = (total_assets * 10**18) // total_supply
assert raw_price == expected_price, f"Expected {expected_price}, got {raw_price}"
# Test invalid parameters: total_assets != total_idle + total_debt
invalid_total_idle = 120 # Inconsistent with total_debt
parameters_invalid_assets = [total_debt, invalid_total_idle, total_supply, ts_initial + 7 * 86400, 0, ts_initial, 0]
with boa.env.prank(verifier):
# The oracle should reject this update, but it currently accepts it
soracle.update_price(parameters_invalid_assets, ts_initial, 2)
# Fetch the raw price after the invalid update
raw_price_after_invalid_assets = soracle.raw_price()
# The raw price is now incorrect because the oracle accepted invalid parameters
incorrect_price = (invalid_total_idle + total_debt) * 10**18 // total_supply
assert raw_price_after_invalid_assets == incorrect_price, (
f"Expected incorrect price {incorrect_price}, got {raw_price_after_invalid_assets}"
)
# Test invalid parameters: total_supply != total_assets
invalid_total_supply = 140 # Inconsistent with total_assets
parameters_invalid_supply = [total_debt, total_idle, invalid_total_supply, ts_initial + 7 * 86400, 0, ts_initial, 0]
with boa.env.prank(verifier):
# The oracle should reject this update, but it currently accepts it
soracle.update_price(parameters_invalid_supply, ts_initial, 3)
# Fetch the raw price after the invalid update
raw_price_after_invalid_supply = soracle.raw_price()
# The raw price is now incorrect because the oracle accepted invalid parameters
incorrect_price_supply = total_assets * 10**18 // invalid_total_supply
assert raw_price_after_invalid_supply == incorrect_price_supply, (
f"Expected incorrect price {incorrect_price_supply}, got {raw_price_after_invalid_supply}"
)
# Print results for debugging
print(f"Valid raw price: {raw_price}")
print(f"Raw price after invalid total_assets: {raw_price_after_invalid_assets}")
print(f"Raw price after invalid total_supply: {raw_price_after_invalid_supply}")

Then run: pytest -s tests/scrvusd/oracle/unitary/test_v2.py

output:

.Valid raw price: 1000000000000000000
Raw price after invalid total_assets: 1133333333333333333
Raw price after invalid total_supply: 1071428571428571428

Impact

The lack of validation in update_price has the following consequences:

  1. Price Manipulation:

    • Attackers can submit inconsistent parameters to artificially inflate or deflate the price.

    • This could lead to arbitrage opportunities or draining of stableswap pools.

  2. System-Wide Impact:

    • The flaw affects all integrations relying on the oracle, potentially breaking the economic assumptions of the entire ecosystem.

it can only be called by a trusted verifier.

Tools Used

Recommendations

Add Validation in update_price:

  • Enforce the following invariants:

    • total_assets == total_idle + total_debt

    • total_supply == total_assets

  • Revert the transaction if these conditions are not met.

Updates

Lead Judging Commences

0xnevi Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope
Assigned finding tags:

[invalid] finding-missing-proof-content-validation

- 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

Support

FAQs

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