The _smoothed_price
function in the scrvUSD oracle contract is designed to limit rapid price changes by applying a maximum price increment over time. However, an analysis reveals that it only caps price increases, not decreases. This asymmetry allows for potentially unlimited downward price adjustments in a single update, which could lead to unexpected behavior in dependent systems relying on stable or predictable pricing. While this may be an intentional design choice, it introduces a risk of sharp price drops that could affect trading, liquidations, or other price-sensitive operations.
The _smoothed_price
function calculates a smoothed price based on a last_price
and a raw_price
, using a max_change
value derived from self.max_price_increment * (block.timestamp - self.last_update) * last_price // 10**18
. The logic is intended to ensure that price changes do not exceed this maximum change per update, providing a linear approximation of price smoothing.
The key logic is as follows:
Condition: unsafe_sub(raw_price + max_change, last_price) > 2 * max_change
checks if the difference between raw_price
and last_price
exceeds max_change
in either direction.
Behavior:
If raw_price > last_price
and exceeds last_price + max_change
, it returns last_price + max_change
, effectively capping upward movement.
If raw_price < last_price
, it evaluates raw_price + max_change - last_price
. If this is greater than 2 * max_change
, it returns last_price - max_change
. However, if raw_price
is significantly lower, the condition fails, and raw_price
is returned directly, uncapped.
Price Increase:
last_price = 100
, raw_price = 120
, max_change = 10
raw_price + max_change - last_price = 120 + 10 - 100 = 30 > 20 (2 * max_change)
Returns last_price + max_change = 110
, capping the increase.
Price Decrease (Small):
last_price = 100
, raw_price = 95
, max_change = 10
raw_price + max_change - last_price = 95 + 10 - 100 = 5 < 20 (2 * max_change)
Returns raw_price = 95
, allowing the decrease.
Price Decrease (Large):
last_price = 100
, raw_price = 70
, max_change = 10
raw_price + max_change - last_price = 70 + 10 - 100 = -20 + 10 = -10 < 20 (2 * max_change)
Returns raw_price = 70
, allowing an uncapped drop.
The condition fails to enforce a lower bound (e.g., last_price - max_change
) for significant decreases, meaning any downward adjustment is applied fully if it doesn’t trigger the cap logic incorrectly. This asymmetry suggests that the smoothing mechanism is incomplete for downward movements.
The lack of a cap on price decreases could have the following impacts:
Financial Risk: Dependent contracts, might assume price stability in both directions. A sudden, uncapped price drop could trigger liquidations, arbitrage opportunities, or loss of funds if not anticipated.
Manual Code Review
To address this potential vulnerability, consider the following:
Symmetric Smoothing:
Modify _smoothed_price
to cap both increases and decreases explicitly:
This ensures price changes are bounded in both directions, aligning with the stated goal of eliminating sharp changes.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.