The ScrvusdOracleV2.vy::\_raw\_price
uses integer division (//) to compute the price as (self._total_assets(parameters) * 10**18) // self._total_supply(parameters, ts). This operation truncates fractional remainders, resulting in a loss of precision in the calculated price. While the impact is reduced in typical stableswap-ng pools with large values, it remains a notable design flaw that could affect accuracy in scenarios with smaller asset or supply values.
The _raw_price
function calculates the price per share by multiplying total\_assets
by 10**18 (to scale to 18 decimals) and then dividing by total_supply using Vyper’s integer division operator (//). Integer division discards any remainder, unlike floating-point division, which retains fractional parts.
This truncation occurs because Vyper lacks native fixed-point arithmetic support, unlike Solidity libraries such as PRBMath. The result feeds into price methods (_price_v0
, _price_v1
, _price_v2
), and while _smoothed_price mitigates sudden jumps, it doesn’t correct the underlying precision loss in _raw_price
. The POC test demonstrates this by comparing the contract’s output to a higher-precision calculation scaled by 10**36.
Add this function to tests/scrvusd/oracle/unitary/test_v2.py:
Then run: pytest -s tests/scrvusd/oracle/unitary/test_v2.py
output:
POC Output Analysis: The test output confirms the issue:
Expected price (integer division): 1960784313725490196
Raw price from oracle: 1960784313725490196
These two numbers are identical. Why? Because:
The test calculates (100 * 10**18) // 51 = 1960784313725490196 in Python, mimicking the contract’s logic.
The contract does the same calculation internally and returns the same value.
Higher precision price (scaled back from 10^36): 1.9607843137254902e+18 (1960784313725490196.0784313725490196…)
The vulnerability becomes clear when you look at the higher precision value (1960784313725490196.07843). This shows what the price could be if the contract didn’t truncate, highlighting the lost .07843 * 10^18.
The precision loss is small, and in typical pools with large values, the relative error becomes negligible. However, In edge cases with smaller pools (e.g., new or low-liquidity pools), this loss could accumulate, slightly skewing prices and affecting trading decisions or profit calculations. For instance, if a pool operates with total_assets = 100 and total_supply = 51, repeated small inaccuracies could mislead traders or liquidity providers.
Increase Scaling Factor: Multiply by a higher factor (e.g., 10**36) before division, then scale back, to retain more precision:
Document Precision Limits: Add comments or documentation noting the expected precision loss and advise pool integrators to account for this in edge cases.
All values will be scaled to a combined of 36 decimals before division (be it price-related values or totalSupply). Considering the 18 decimals of all values, no realistic values were presented in any duplicates to proof a substantial impact on precision loss.
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.