Temporal Tariff State Calculator Integer Precision Loss in Time-Weighted Rate Averaging
Problem Statement
The contracts/src/tariff/temporal_calculator.rs module computes time-weighted average rates for volumetric resource billing across temporal windows (peak/off-peak/Shoulder). The calculate_weighted_rate() function at line 72 performs sum(rate_i * duration_i) / total_duration where all values are u128. For a 30-day billing window with 15-minute granularity (2880 intervals), duration_i is stored in seconds. The product rate_i * duration_i can overflow u128 when rate_i has 18 decimal places (Soroban token standard) and duration_i = 2592000 (30 days in seconds). Specifically, rate = 500000000000000000000 (500 tokens at 18 decimals) × 2592000 = 1.296e27, which exceeds u128::MAX / 2, causing intermediate overflow before division. The truncated result produces a 15% error in the final weighted average for peak-period billing.
State Invariants & Parameters
MAX_DECIMALS: 18
MAX_TARIFF_INTERVALS: 2880 (30 days at 15-min granularity)
INTERVAL_DURATION_UNITS: seconds (u64)
u128::MAX: 3.4e38
- Current overflow threshold:
rate * duration > 1.7e38
- Invariant:
∀ intervals: Σ(rate_i × duration_i) / total_duration ∈ [min_rate, max_rate]
Affected Code Paths
contracts/src/tariff/temporal_calculator.rs:65-100 — Weighted rate calculation multiplication overflow
contracts/src/tariff/tariff_schedule.rs:40-80 — Schedule loading that feeds into calculator
contracts/src/tariff/tests/temporal_tests.rs — No overflow edge-case tests at max intervals
Resolution Blueprint
- Implement a
checked_weighted_average() using u128::checked_mul() with early return on overflow, falling back to a div-then-mul strategy: rate_i / total_duration * duration_i which maintains precision for rate_i / total_duration using Decimal128 with a 38-digit intermediate scale.
- Add a
Decimal128 wrapper struct in contracts/src/tariff/decimal128.rs that implements mul() and div() with 38-digit internal precision (stored as (u128 mantissa, i8 scale)) and rounds using ROUND_HALF_UP.
- Pre-validate interval data at tariff creation time:
require!(rate_i.checked_mul(duration_i).is_some(), "rate*interval overflow") — reject tariff schedules that exceed the safe computation range.
- Add property-based tests covering
rate ∈ [1, 10^18], duration ∈ [60, 2592000], and interval_count ∈ [1, 2880], verifying relative error < 10^-15.
- Update
contracts/docs/specs/tariff-precision.md with the decimal scaling strategy and overflow safety guarantees.
Labels
Complexity: Hardcore
Layer: Core-Engine
Type: Race-Condition
Temporal Tariff State Calculator Integer Precision Loss in Time-Weighted Rate Averaging
Problem Statement
The
contracts/src/tariff/temporal_calculator.rsmodule computes time-weighted average rates for volumetric resource billing across temporal windows (peak/off-peak/Shoulder). Thecalculate_weighted_rate()function at line 72 performssum(rate_i * duration_i) / total_durationwhere all values areu128. For a 30-day billing window with 15-minute granularity (2880 intervals),duration_iis stored in seconds. The productrate_i * duration_ican overflowu128whenrate_ihas 18 decimal places (Soroban token standard) andduration_i = 2592000(30 days in seconds). Specifically,rate = 500000000000000000000 (500 tokens at 18 decimals) × 2592000 = 1.296e27, which exceedsu128::MAX / 2, causing intermediate overflow before division. The truncated result produces a 15% error in the final weighted average for peak-period billing.State Invariants & Parameters
MAX_DECIMALS: 18MAX_TARIFF_INTERVALS: 2880 (30 days at 15-min granularity)INTERVAL_DURATION_UNITS: seconds (u64)u128::MAX: 3.4e38rate * duration > 1.7e38∀ intervals: Σ(rate_i × duration_i) / total_duration ∈ [min_rate, max_rate]Affected Code Paths
contracts/src/tariff/temporal_calculator.rs:65-100— Weighted rate calculation multiplication overflowcontracts/src/tariff/tariff_schedule.rs:40-80— Schedule loading that feeds into calculatorcontracts/src/tariff/tests/temporal_tests.rs— No overflow edge-case tests at max intervalsResolution Blueprint
checked_weighted_average()usingu128::checked_mul()with early return on overflow, falling back to a div-then-mul strategy:rate_i / total_duration * duration_iwhich maintains precision forrate_i / total_durationusingDecimal128with a 38-digit intermediate scale.Decimal128wrapper struct incontracts/src/tariff/decimal128.rsthat implementsmul()anddiv()with 38-digit internal precision (stored as(u128 mantissa, i8 scale)) and rounds usingROUND_HALF_UP.require!(rate_i.checked_mul(duration_i).is_some(), "rate*interval overflow")— reject tariff schedules that exceed the safe computation range.rate ∈ [1, 10^18],duration ∈ [60, 2592000], andinterval_count ∈ [1, 2880], verifying relative error <10^-15.contracts/docs/specs/tariff-precision.mdwith the decimal scaling strategy and overflow safety guarantees.Labels
Complexity: HardcoreLayer: Core-EngineType: Race-Condition