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

Linear Smoothing Vulnerable to Rapid Price Changes

Summary

The _smoothed_price function limits price adjustments to a fixed rate (max_price_increment), which cannot adapt to rapid market changes. During high volatility, the oracle price updates too slowly, creating a gap between the oracle and real market prices. Arbitrageurs exploit this gap, draining liquidity from the pool at the expense of LPs.

Vulnerability Details

The linear smoothing formula restricts price changes to:

allowed_change = (max_price_increment * time_elapsed * last_price) / 1e18
new_price = last_price ± allowed_change

This creates predictable, incremental adjustments that cannot match sudden market movements.

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

Example Exploit Scenario

  1. Real Price Surge: scrvUSD’s market price jumps 2% due to high demand.

  2. Oracle Lag: The oracle’s linear model only allows a 0.5% increase in the same timeframe.

  3. Arbitrage Execution:

    • Arbitrageurs buy scrvUSD from the pool at the outdated lower price (0.5% increase).

    • Sell it on external markets at the higher real price (2% increase).

  4. Result: The pool’s reserves are drained, and LPs lose funds.

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

def test_linear_smoothing_lag(soracle, verifier):
# Initialize oracle with a base price of 1.0 (1e18 wei)
ts = boa.env.evm.patch.timestamp
initial_params = [
0, # total_debt = 0
10**18, # total_idle = 1e18 (100%)
10**18, # total_supply = 1e18 (100 shares)
ts + 7*86400, # full_profit_unlock_date
0, 0, 0 # Other params
]
with boa.env.prank(verifier):
soracle.update_price(initial_params, ts, 1) # Pass block_number as 3rd positional arg
initial_price = soracle.price_v1() # Should be 1.0e18
assert initial_price == 10**18
# Simulate a 2% price increase in the underlying asset (total_idle jumps to 1.02e18)
new_ts = ts + 1 # 1 second later
new_params = [
0,
102 * 10**16, # total_idle = 1.02e18 (+2%)
10**18, # total_supply unchanged
new_ts + 7*86400,
0, 0, 0
]
# Update oracle with new parameters
with boa.env.prank(verifier):
soracle.update_price(new_params, new_ts, 2) # 2 as block_number
# Advance time by 1 second to trigger smoothing
boa.env.time_travel(seconds=1)
# Fetch updated smoothed price
updated_price = soracle.price_v1()
print(f"Updated Price: {updated_price}")
raw_price = soracle.raw_price() # Real price = 1.02e18
print(f"Raw Price: {raw_price}")
# Calculate expected smoothed price using linear model
max_increment = soracle.max_price_increment() # Default: 2e12 (0.02 bps/sec)
time_elapsed = 1 # Seconds since last update
allowed_change = (max_increment * time_elapsed * initial_price) // 10**18
expected_price = initial_price + allowed_change # 1e18 + 2e12 = 1.0000002e18
# Verify the oracle price lags significantly
assert updated_price == expected_price, "Linear smoothing did not cap price rise"
assert updated_price < raw_price, "Oracle price did not lag behind real price"
# Calculate arbitrage gap dynamically
arbitrage_gap = raw_price - expected_price
assert arbitrage_gap > 0
print(f"Arbitrage opportunity: {arbitrage_gap / 1e18}%")

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

output:

Updated Price: 1000002000000000000
Raw Price: 1020000000000000000
Arbitrage opportunity: 0.019998%

Impact

  • LPs suffer losses proportional to the arbitrage gap.

  • Repeated exploitation could reduce liquidity, destabilizing the pool.

Tools Used

Recommendations

Implement Exponential Smoothing

new_price = old_price + α * (real_price - old_price) # α = sensitivity factor (0 < α < 1)
  • Adjusts faster when the price gap is large.

  • Gradually stabilizes as the gap closes.

Or Dynamic max_price_increment Adjustment

  • Allow DAO votes to increase max_price_increment during volatility.

Updates

Lead Judging Commences

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

Support

FAQs

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