fix: enforce systemic configuration invariants (#100) - #108
Open
fredericklamar342-prog wants to merge 1 commit into
Open
fix: enforce systemic configuration invariants (#100)#108fredericklamar342-prog wants to merge 1 commit into
fredericklamar342-prog wants to merge 1 commit into
Conversation
Turns the implicit cross-constant interactions into explicit, machine-verified constraints and fixes the concrete interaction failures: - Fee group: NET_NUMERATOR is now DERIVED from the fee constants (single source of truth) plus a compile-time invariant matrix in every contract (fee, limits, timelock, TTL relationships). - Fee accounting: per-market MarketFees ledger + per-bet refundable tracking. Cancellation no longer reclaims via the naive net*200bps formula (which zeroed the accumulator and stole other markets' fees); cancel_refund releases exactly what the contract holds (net + platform + referral-if-unpaid) and drains fees per market, keeping Sigma(market fees) == AccumulatedFees. - Leaderboard: MinPoints/MinSlot cache now tracks a player whose points drop below the cached min, keeping evictions correct at MAX_TOP_PLAYERS. - Reward economics: hard PULSE supply cap bounds combined welcome-bonus and betting-reward minting. Closes SPulse-Org#100
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
The protocol's constants were each individually reasonable but their interactions were never constrained: fee bookkeeping could silently invert on cancellation, the leaderboard's min cache could be wrong at capacity, and combined welcome/bet minting had no bound. This PR makes the safe operating envelope explicit and machine-verified (compile-time invariant matrix + provenance ledgers + caps) and fixes the concrete interaction failures the issue identifies, rather than tuning any single constant.
Root Cause
Constants were hardcoded literals with implicit relationships (e.g.
NET_NUMERATOR == BPS_DENOM - TOTAL_FEE_BPSwas only true by coincidence of typing9_800), and accounting state was global:cancel_marketreclaimed fees withnet * 200bps / (10000 - 200bps)— treating ALL 2% as reclaimable even when the referrer had already been paid — and clamped the whole accumulator, so cancelling one referrer-backed market stole other markets' fees.cancel_refundrefunded gross, which the contract may no longer hold. The leaderboard min-cache could disagree with reality (issue #43 family), and PULSE minting was unbounded (issues #29/#46 family).Impact
AccumulatedFeescan never reconcile.MAX_TOP_PLAYERSand a stale min cache, a low-points player can displace a high-points player once the list fills.Solution
1. Single source of truth + compile-time invariant matrix (all four contracts)
NET_NUMERATORis now derived:BPS_DENOM - TOTAL_FEE_BPS— the cash-accounting identitynet + total_fee == denomcan no longer drift.0 < fees < denom,platform <= total,net + total == denomMIN_BET > 0,MAX_BETS_PER_USER > 0,MAX_MARKETS_PER_HOUR > 00 < MAX_WITHDRAWAL_BPS <= BPS_DENOM,WITHDRAW_DELAY_SECS > 00 < TTL_BUMP <= TTL_HIGH(persistent bump never outruns the instance key)WELCOME_BONUS_POINTS/TOKENS > 0,REFERRAL_BET_POINTS > 0MAX_SUPPLY > 0An unsafe combination now fails the compilation, not production.
2. Exact per-market fee accounting (Group 1 + the cancel interaction)
DataKey::MarketFees(market_id)ledger records exactly what each market contributed toAccumulatedFees(platform fee + referral fee when never paid out + swept pools/dust at resolution).BetEntry.refundabletracks per user the amount the contract actually holds (net + platform + referral iff unpaid).cancel_refundnow paysrefundable(not gross) and drains that bet's fee share fromAccumulatedFees— so refunds self-balance:Σ refundable == pool + MarketFees[market], and no cancellation can touch another market's fees.get_market_fees(market_id)gives full provenance:Σ_markets MarketFees == AccumulatedFees(fees + held + sweep).3. Leaderboard capacity invariant (Group 2)
update_top_playersnow also refreshesMinPoints/MinSlotwhen an in-list player's updated points drop below the cached min (the stale-cache hole): the min cache always equals the true minimum of the leaderboard.4. Bounded reward economics (Group 3/4)
MAX_SUPPLY(1e9 PULSE, 7 decimals) enforced inmintbefore any state change (SupplyCapExceeded). Welcome bonuses, betting rewards and referral flows are all bounded by it.Implementation Details
prediction_market/src/lib.rsNET_NUMERATOR+ compile-time invariant matrix;MarketFees(u64)ledger;BetEntry.refundable; exactcancel_refund(refundable + per-bet fee drain); removed thenet*200bpscancel clamp; sweep provenance inresolve_market;get_market_fees()view; renamesreward→add_pts+ direct PULSE mint inclaim(pre-existing baseline repair, see note).prediction_market/src/tests.rsleaderboard/src/lib.rsleaderboard/src/tests.rsreward_bonus→add_bonus_ptsstale calls repaired.pulse_token/src/lib.rsMAX_SUPPLY+SupplyCapExceeded+ mint guard.pulse_token/src/tests.rsreferral_registry/src/lib.rsreward_bonus→add_bonus_pts+ welcome mint repair.referral_registry/src/tests.rsset_token→set_token_contract(baseline compile repair).Baseline repair note (honest): upstream
maindid not compile because an earlier leaderboard rename left dangling callers (set_token/reward_bonus/reward). These mechanical repairs are included so the workspace is a runnable baseline; they are behavior-preserving and identical in scope to the baseline repairs on the sibling PRs.Security / Invariant Considerations
refundable == net + platform + held_referral; Σ over bettors ofrefundable == pool + MarketFees[m]; after full cancellation the contract returns exactly what it held andAccumulatedFeesis drained only by this market's amounts — provable (and tested) that no other market's fees can move.MAX_SUPPLY.MarketFees,BetEntry.refundable), no existing key reshaped; old refunds on upgraded contracts simply refund the exact held amount.Test Coverage
test_cancel_refund_isolates_market_fees— two markets: cancelling+refunding one never touches the other's fees.test_cancel_refund_respects_referrer_holdings— referrer-backed refund = gross − paid referral (99.5%); no-referrer = 100%; referrer not clawed back.test_fee_provenance_invariant_holds— Σ fees ==AccumulatedFees==get_market_fees(); refunds return exactly the held gross.test_min_bet_net_threshold_boundary— the net-vs-grossMIN_BETinteraction pinned exactly (gross yields net == MIN_BET accepted; one stroop below rejected).test_sweep_provenance_recorded— swept user principal is attributed to the market's fee ledger.test_min_cache_matches_true_minimum_at_capacity(leaderboard) — fill to 50, below-min rejected, above-min admitted, list stays sorted.test_mint_enforces_supply_cap/test_supply_cap_partial_is_allowed(pulse) — cap hit exactly, over-cap mint fails#9.Acceptance Criteria ↔ Evidence
const _: () = assert!(...)matrix in all 4 contractsTTL_BUMP <= TTL_HIGH+ existing TTL suitesWELCOME_* > 0asserts +MAX_SUPPLY+ cap testsValidation
cargo test --workspace→ 104/104 pass (leaderboard 6, prediction_market 65, pulse_token 18, referral_registry 15) — zero warnings, zero errors.cargo fmt: upstreammainis not rustfmt-clean; branch keeps style-consistent changes only (no repo-wide reformat).Regression Analysis
MIN_BET/fee/withdrawal/TTL/leaderboard existing suites unaffected.MIN_BETand the mock footprint limit) fixed minimally.Review Checklist
Issue Reference
Closes #100