feat: add overflow-safe score and probability arithmetic - #1432
Merged
greatest0fallt1me merged 1 commit intoAug 30, 2026
Conversation
Implement ArithmeticUtils with checked primitives (checked_add, checked_mul, checked_mul_div, checked_accumulate) that return Error::Overflow (code 672) instead of panicking or silently wrapping. Fixed call sites: - bets.rs: calculate_implied_probability, calculate_payout_multiplier — raw outcome * 100 replaced with checked_mul_div (fallback 0) - queries.rs: calculate_payout — raw stake * total replaced with checked_mul_div; calculate_outcome_pool pool += stake replaced with checked_accumulate; calculate_implied_probabilities pool * 100 and pool1 + pool2 replaced with checked ops (fallback 50) - voting.rs: calculate_user_payout winning_total accumulation replaced with checked_accumulate (propagates Error::Overflow) - markets.rs: calculate_payout upgraded from InvalidInput to Error::Overflow; calculate_winning_stats uses saturating_add - utils.rs: NumericUtils::calculate_percentage, weighted_average, simple_interest use saturating ops with zero-denominator guard Added arithmetic_overflow_tests.rs with 36 tests covering: normal operation, i128::MAX boundary, overflow detection, negative- input rejection, zero-denominator, and idempotency/replay safety. Test results: 36/36 new tests pass; 703 total passing (up from 701); pre-existing 147 failures are unrelated SDK-compat issues.
|
@SupremeCarMart100 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
greatest0fallt1me
merged commit Aug 30, 2026
ef36c79
into
Predictify-org:master
2 of 3 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces raw integer arithmetic in score and probability calculation paths with checked or saturating
operations, eliminating the risk of silent wrapping or panics when adversarial or extreme stake values
are passed through the payout pipeline.
Adds a new ArithmeticUtils struct as a single source of truth for checked math primitives used across the
contract.
Changes
New: ArithmeticUtils in utils.rs
Four #[inline] primitives that return Err(Error::Overflow) (code 672) instead of panicking or silently
wrapping:
Fixed call sites
┌────────────┬───────────────────────────────┬────────────────────────────────────────────────────────┐
│ File │ Function │ Change │
├────────────┼───────────────────────────────┼────────────────────────────────────────────────────────┤
│ bets.rs │ calculate_implied_probability │ outcome * 100 → checked_mul_div, safe fallback 0 │
├────────────┼───────────────────────────────┼────────────────────────────────────────────────────────┤
│ bets.rs │ calculate_payout_multiplier │ total * 100 → checked_mul_div, safe fallback 0 │
├────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────┤
│ queries.rs │ calculate_payout │ stake * total_staked, share * 2 → checked_mul_div, │
│ │ │ propagates Error::Overflow │
├────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────┤
│ queries.rs │ calculate_outcome_pool │ pool += stake → checked_accumulate, propagates │
│ │ │ Error::Overflow │
├────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────┤
│ queries.rs │ calculate_implied_probabilities │ pool * 100, pool1 + pool2 → checked ops, safe │
│ │ │ fallback 50 │
├────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────┤
│ voting.rs │ calculate_user_payout │ winning_total += → checked_accumulate, propagates │
│ │ │ Error::Overflow │
├────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────┤
│ markets.rs │ calculate_payout │ already used checked_mul but mapped to InvalidInput; │
│ │ │ upgraded to Error::Overflow for precise diagnostics │
├────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────┤
│ markets.rs │ calculate_winning_stats │ winning_total += → saturating_add │
├────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────┤
│ utils.rs │ calculate_percentage │ raw * → saturating_mul; added zero-denominator guard │
├────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────┤
│ utils.rs │ weighted_average │ raw * / += → saturating_mul / saturating_add │
├────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────┤
│ utils.rs │ simple_interest │ chained raw * → saturating_mul chain │
└────────────┴─────────────────────────────────┴──────────────────────────────────────────────────────┘
Tests
New file: src/arithmetic_overflow_tests.rs — 36 tests across three modules:
i128::MAX boundary, overflow, negative-input rejection, zero-denominator, idempotency
simple_interest
stake×fee and share×pool, proportional split, idempotency
test result: ok. 36 passed; 0 failed
Full suite: 703 passed (up from 701); 147 pre-existing failures unchanged
Compatibility
infallible helpers (calculate_percentage, etc.) see saturating results rather than wrapping
Security & failure modes
corruption
inconsistent results
invariant that stakes and totals are always non-negative
closes #1410