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

Precision Loss in Price Calculations Due to Non-Standard Division Ordering

Summary

The ScrvusdOracleV2.vy contract suffers from significant precision loss in price calculations due to non-standard division ordering in multiple functions. This can lead to incorrect price calculations, especially for large numbers or when dealing with small price differences.

Vulnerability Details

In ScrvusdOracleV2.vy, several price calculation functions perform division operations in a way that can lead to precision loss:

  1. In _raw_price():

return self._total_assets(parameters) * 10**18 // self._total_supply(parameters, ts)
  1. In price inversion calculations:

return self._price_v1() if _i == 0 else 10**36 // self._price_v1()
  1. In _smoothed_price():

max_change: uint256 = (
self.max_price_increment * (block.timestamp - self.last_update) * last_price // 10**18
)

Critical issues:

  1. Division before multiplication in price calculations

  2. Potential for truncation in intermediate results

  3. Non-standardized scaling factors across calculations

  4. Missing precision guards for edge cases

  5. Inconsistent rounding behavior

Impact

  1. Financial Impact:

    • Incorrect price calculations leading to mispriced assets

    • Accumulating errors in long-term price tracking

    • Potential for arbitrage due to precision mismatches

  2. System Risks:

    • Loss of funds due to incorrect liquidations

    • Unfair trading opportunities

    • System instability during high-value transactions

Proof of Concept

def demonstrate_precision_loss():
# Deploy oracle
oracle = ScrvusdOracleV2.deploy()
# Case 1: Large numbers
large_assets = 10**30
large_supply = 10**12
# Current implementation
price1 = large_assets * 10**18 // large_supply # Overflow risk
# Case 2: Small numbers
small_assets = 100
small_supply = 10**18
# Current implementation
price2 = small_assets * 10**18 // small_supply # Truncation to 0
# Case 3: Price inversion precision loss
price = 10**18 # 1.0 in 18 decimals
inverted = 10**36 // price # May lose precision
re_inverted = 10**36 // inverted # Does not return to original value
assert price != re_inverted # Precision loss demonstrated

Tools Used

  • Manual code review

  • Mathematical analysis

Recommendations

  1. Implement Safe Price Calculation Library:

# @notice Library for safe price calculations with precision handling
@view
def _safe_price_calc(
numerator: uint256,
denominator: uint256,
scale: uint256
) -> uint256:
"""
@notice Safely calculate price with proper scaling
@param numerator The top number in division
@param denominator The bottom number in division
@param scale The desired decimal scale
"""
assert denominator != 0, "Division by zero"
# Handle small numbers
if numerator < scale:
return (numerator * scale) // denominator
# Handle large numbers
return (numerator // denominator) * scale
  1. Implement Price Normalization:

@view
def _normalize_price(
price: uint256,
input_decimals: uint256,
output_decimals: uint256
) -> uint256:
"""
@notice Normalize price between different decimal scales
"""
if input_decimals == output_decimals:
return price
if input_decimals > output_decimals:
diff: uint256 = input_decimals - output_decimals
return price // 10**diff
diff: uint256 = output_decimals - input_decimals
return price * 10**diff
  1. Add Precision Guards:

struct PrecisionConfig:
min_price: uint256
max_price: uint256
decimals: uint256
@view
def _check_price_precision(
price: uint256,
config: PrecisionConfig
) -> bool:
"""
@notice Verify price is within acceptable precision bounds
"""
assert price >= config.min_price, "Price below minimum"
assert price <= config.max_price, "Price above maximum"
# Check significant digits
significant_digits: uint256 = 0
temp: uint256 = price
while temp > 0:
temp = temp // 10
significant_digits += 1
assert significant_digits <= config.decimals, "Too many significant digits"
return True
  1. Implement Revised Price Calculations:

@view
def _raw_price(ts: uint256, parameters_ts: uint256) -> uint256:
"""
@notice Price replication with improved precision
"""
parameters: PriceParams = self._obtain_price_params(parameters_ts)
total_assets: uint256 = self._total_assets(parameters)
total_supply: uint256 = self._total_supply(parameters, ts)
# Use safe price calculation
return self._safe_price_calc(
total_assets,
total_supply,
10**18
)
@view
def _invert_price(price: uint256) -> uint256:
"""
@notice Safely invert price maintaining precision
"""
assert price != 0, "Cannot invert zero price"
# Use normalized calculation
PRICE_SCALE: constant(uint256) = 10**18
return self._safe_price_calc(
PRICE_SCALE * PRICE_SCALE,
price,
PRICE_SCALE
)
  1. Add Price Validation Checks:

@view
def _validate_price_calculation(
price: uint256,
previous_price: uint256
) -> bool:
"""
@notice Validate price calculation results
"""
# Configuration
config: PrecisionConfig = PrecisionConfig({
min_price: 10**12, # 0.000001 in 18 decimals
max_price: 10**24, # 1,000,000 in 18 decimals
decimals: 18
})
# Basic validation
assert self._check_price_precision(price, config)
# Check for reasonable changes
if previous_price > 0:
max_change: uint256 = previous_price * 2 # 100% change
min_change: uint256 = previous_price // 2 # 50% change
assert price <= max_change, "Price change too large"
assert price >= min_change, "Price change too small"
return True

These improvements provide:

  • Safe mathematical operations

  • Consistent precision handling

  • Proper decimal scaling

  • Validation checks

  • Protection against edge cases

The implementation should use all these mechanisms together to ensure accurate and safe price calculations while maintaining precision throughout all operations.

Updates

Lead Judging Commences

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

[invalid] finding-precision-loss

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.

Support

FAQs

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