The _raw_price function in the scrvUSD oracle contract is susceptible to a division-by-zero error if the total_supply parameter becomes zero. This can occur when the update_price function accepts invalid parameters without validation, leading to a revert that halts all price queries and renders the oracle unusable. This undermines the oracle’s reliability, critical for stableswap-ng pools.
The vulnerable code is in the _raw_price function:
vyper
@view
def _raw_price(ts: uint256, parameters_ts: uint256) -> uint256:
parameters: PriceParams = self._obtain_price_params(parameters_ts)
return self._total_assets(parameters) * 10**18 // self._total_supply(parameters, ts)
Division by Zero Risk:
_total_supply returns p.total_supply - self._unlocked_shares(...). If total_supply is set to 0 via update_price and no shares are unlocked, the result is 0.
No check ensures _total_supply > 0, causing a revert on division.
Source of Issue:
update_price sets price_params.total_supply = _parameters[2] without validation:
vyper
self.price_params = PriceParams(
total_debt=_parameters[0],
total_idle=_parameters[1],
total_supply=_parameters[2], # No check
...
)
Initial state sets total_supply = 1, but updates can override this.
PoC
Division by Zero in _raw_price
Objective
Trigger a division-by-zero revert in _raw_price by setting total_supply to 0, disabling the oracle.
Prerequisites
Control of the PRICE_PARAMETERS_VERIFIER role in the scrvUSD oracle contract (e.g., via DAO exploit or test setup).
Deployed scrvUSD oracle contract.
Exploit Scenario
An attacker with verifier access submits parameters with total_supply = 0, causing all price queries to revert.
Proof of Concept Steps
Setup: Deploy the oracle and grant PRICE_PARAMETERS_VERIFIER to an attacker-controlled address.
Exploit Contract: Create a Solidity test contract to call update_price with malicious parameters.
solidity
Execution:
Deploy ExploitDivisionByZero with the oracle address.
Call exploit() to update parameters.
Attempt check()—it reverts due to division by zero in _raw_price.
Result: All price functions (price_v0, price_v1, price_v2) fail, breaking dependent pools.
Outcome
The oracle becomes unusable until fixed, demonstrating a denial-of-service attack.
Oracle Failure: All price functions (price_v0, v1, v2) rely on _raw_price, so a revert disables the oracle, breaking dependent stableswap-ng pools.
Funds at Risk: Pools relying on accurate pricing could freeze or misbehave, potentially locking liquidity provider funds.
Denial of Service: An attacker with PRICE_PARAMETERS_VERIFIER access could intentionally set total_supply = 0, though this requires role compromise.
Manual Review
Add a validation check in update_price to prevent total_supply from being zero:
vyper
@external
def update_price(_parameters: uint256[ALL_PARAM_CNT], _ts: uint256, _block_number: uint256) -> uint256:
access_control._check_role(PRICE_PARAMETERS_VERIFIER, msg.sender)
assert self.last_block_number <= _block_number, "Outdated"
assert _parameters[2] > 0, "Total supply cannot be zero" # Add this
...
Note that `total_supply` and `profit_unlocking_rate` is initially set to 1 and 0 respectively when the `ScrvusdOracleV2.vy` is deployed 1. `total_supply` and `profit_unlocking_rate` is part of the price param updates within `update_price`, which must have gone through verification via the OOS `StateProofVerifier` contract, so there is no evidence that a 0 supply is allowed either via a 0 supply update or an extremely high `profit_unlocking_rate`. 2. Since price is retrieved via values retrived from the V3Vault, if there is no supply, there is arguably no price to be posted. As such, reverting is arguably the correct choice since a 0 price value is not expected from scrvUSD, which is a stable coin.
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.