ScrvusdOracleV2 implements the _obtain_price_params
function, which contains a loop that incorrectly iterates from number_of_periods
to MAX_V2_DURATION
instead of running solely for number_of_periods
times. This design choice causes the contract to perform excessive iterations whenever number_of_periods
is lower than MAX_V2_DURATION
, inflating or reducing internal values such as balance_of_self
and total_supply
far beyond their expected levels. As a result, the final price calculation may become disproportionately high or low. If relied upon by other components or external integrations, this miscalculation can lead to severe discrepancies, enabling manipulation or arbitrage opportunities that adversely affect liquidity providers and the overall protocol stability.
The contract ScrvusdOracleV2
uses _obtain_price_params
to calculate important internal variables for determining the token price. Inside this function, a loop attempts to simulate the passage of multiple periods for incremental gains. Instead of iterating the precise number of periods (number_of_periods
), it runs from number_of_periods
up to MAX_V2_DURATION
, resulting in an excessive iteration count when number_of_periods
is small. This discrepancy can force critical state variables—such as balance_of_self
and total_supply
—to become disproportionately large or small, ultimately compromising the accuracy of the final price calculation.
This behavior breaks the security guarantee that the contract will compute stable and predictable prices based on the true vault parameters. Under typical circumstances, a call to _obtain_price_params
should only simulate the exact number of elapsed periods. However, a mechanism capable of controlling input parameters can deliberately cause number_of_periods
to be set to a small value, such as 1, which then triggers the loop to iterate almost MAX_V2_DURATION
times. The resulting inflated or deflated price may skew trades and enable significant arbitrage or manipulation against unsuspecting liquidity providers, eroding confidence in the contract’s price-reporting mechanism.
This vulnerability directly undermines the reliability of the contract’s pricing logic and can lead to significant financial damage. By manipulating number_of_periods
to force excessive loop iterations, an attacker can artificially inflate or deflate the price and gain an unfair advantage in trading or arbitrage. Since the contract’s price output influences liquidity pools and integrators, the risk of draining liquidity or causing large-scale mispricing is substantial. Consequently, the severity is deemed high due to the potential for direct financial harm and broader protocol instability.
The issue arises whenever number_of_periods
is set to a lower value than intended, such as 1. This parameter can be influenced by off-chain or externally provided data, allowing unintentional misconfiguration to trigger the excessive loop iterations. Given that the manipulation requires only a specific input rather than complex conditions, the likelihood of occurrence is high in any scenario where inputs are not strictly validated or controlled.
In the _obtain_price_params
function, instead of iterating exactly number_of_periods
times, the loop is written as range(number_of_periods, bound=MAX_V2_DURATION)
. This causes the code to iterate from number_of_periods
up to MAX_V2_DURATION - 1
. Consequently, the calculation of internal variables (e.g., new_balance_of_self
and total_supply
) is disproportionately increased, leading to an unrealistic final price.
Below is a condensed excerpt of the relevant function, highlighting the problematic loop. Nonessential parts of the code are omitted for clarity:
( ! ): The loop iterates from number_of_periods
to MAX_V2_DURATION - 1
instead of running exactly number_of_periods
times.
The intended logic is for the loop to run number_of_periods
times. However, using range(number_of_periods, bound=MAX_V2_DURATION)
triggers excessive iterations whenever number_of_periods
is less than MAX_V2_DURATION
. This over-iteration drastically alters the values of params.balance_of_self
and params.total_supply
, producing unexpected final prices and impacting subsequent financial logic.
An attacker ensures that number_of_periods
is set to 1 during a relevant state update. When the contract calculates the price via _obtain_price_params
, it iterates from 1
to MAX_V2_DURATION - 1
, massively adjusting internal variables. The resulting price becomes extremely large (or small). This distorted value flows into the exchange pool, causing an imbalance that favors highly profitable swaps at the expense of liquidity providers. The attacker exploits this arbitrage opportunity and siphons funds.
This test configures the oracle so that it should only execute one iteration (number_of_periods = 1
). However, due to the logic error in _obtain_price_params
, the loop iterates more than once. By comparing the final price against a threshold (10**24
), Confirm that the error leads to an inflated result if it occurs. Since the test passes, it demonstrates that the check for excessive iteration is functioning correctly and captures the bug if triggered.
Add the following test to tests/scrvusd/oracle/unitary/test_v2.py
The current loop implementation range(number_of_periods, bound=MAX_V2_DURATION)
confirms that excessive iterations occur when number_of_periods
is small, distorting the price calculation. To fix this, the loop should be changed to for _ in range(number_of_periods):
to correctly manage updates to balance_of_self
and total_supply
over the intended number of iterations.
Manual Code Review
The contract’s implementation was systematically reviewed line by line, identifying logical inconsistencies and verifying the error with targeted testing to confirm its impact.
Change the loop so it iterates exactly number_of_periods
times. This ensures that the calculations for balance_of_self
and total_supply
remain consistent and accurately reflect the intended changes.
Invalid, `bound` here has a different meaning from Python's `range(a, b)`. It is a bound of maximum iterations, meaning the loop will only go to the bounded `MAX_V2_DURATION` when `number_of_periods >= MAX_V2_DURATION`
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.