Skip to content

Fix #4: Replace global fee pot with per-market fee ledger - #120

Open
funds0033-cmyk wants to merge 3 commits into
SPulse-Org:mainfrom
funds0033-cmyk:fix/issue-4-per-market-fee-ledger
Open

Fix #4: Replace global fee pot with per-market fee ledger#120
funds0033-cmyk wants to merge 3 commits into
SPulse-Org:mainfrom
funds0033-cmyk:fix/issue-4-per-market-fee-ledger

Conversation

@funds0033-cmyk

Copy link
Copy Markdown
Contributor

Closes #4

Problem

AccumulatedFees was a single global i128 shared across all markets:

  • cancel_market subtracted an estimate from that global number, so cancelling one large market could wipe out fees earned by unrelated markets.
  • withdraw_fees drained the entire pot, including fees still backing open markets.
  • Empty-side principal and platform fees could end up mixed in the same bucket.

There was no way to answer "how much of this belongs to market X?"

New storage model

Invariant: AccumulatedFees == Σ MarketFees(i) + LegacyFees

Key What it stores
MarketFees(market_id) Genuine fees that market earned (platform + unpaid referral + dust)
LegacyFees Old global balance, unsplittable after upgrade (market_id = 0)
AccumulatedFees Cached total only — no longer used for cancel/withdraw
ForfeitedPool(market_id) Empty-side user principal (from #3) — never mixed into fees
FeeLedgerMigrated One-shot flag preventing migration from running twice

Behavior changes

  • place_bet — credits the market's own ledger, bumps the cached global total by the same amount.
  • cancel_market — zeros only that market's ledger; other markets untouched (previously decremented the global pot by an estimate, or zeroed it entirely).
  • resolve_market — two-sided: dust credited to that market's ledger. Empty-side: that market's fees are locked into ForfeitedPool.locked_fees (from its own ledger, not estimated off the global pot); principal stays in ForfeitedPool and fees return to the market's ledger after the dispute window via finalize_zero_side / first claim.
  • withdraw_fees(caller, recipient, market_id) — new market_id argument; pays out only that ledger. Market must be resolved and not cancelled (FeesNotSettled = 29 otherwise). market_id = 0 withdraws the legacy bucket. Cannot drain another market in the same call.
  • request_withdraw_fees(..., amount, market_id) — same scoping; the 20% cap applies to that market's fees, not the global total. Pending requests store market_id so execution only debits that entry.

Migration

Old AccumulatedFees can't be split across markets retroactively. On first fee access after upgrade (or via admin migrate_fee_ledger):

  1. Copy the current global scalar into LegacyFees
  2. Set FeeLedgerMigrated
  3. Leave AccumulatedFees unchanged

New bets go to MarketFees(id); cancel/withdraw on a new market never touches the legacy bucket. Fresh deploys set FeeLedgerMigrated = true and LegacyFees = 0 in initialize.

New public API

  • get_market_fees(market_id) — that market's ledger (0 = legacy)
  • get_legacy_fees() — equivalent to get_market_fees(0)
  • migrate_fee_ledger(admin) — explicit upgrade helper (also runs lazily)

Tests

  • test_cancel_preserves_unrelated_market_fees
  • test_withdraw_fees_scoped_to_one_market
  • test_reject_withdraw_fees_from_open_market
  • test_legacy_fee_migration_does_not_double_count
  • Existing withdraw tests updated to pass market_id and resolve the market before withdrawal (fees are only withdrawable post-settlement).

Branch: fix/issue-4-per-market-fee-ledger

funds0033-cmyk and others added 3 commits August 18, 2026 11:13
…e ledger

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the per-market fee ledger and forfeited-pool path, take main's pause ABI, and assign unique error codes so pause, duration, dispute, and unsettled-fee errors no longer collide.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[CRITICAL] AccumulatedFees is a global fungible pool with no per-market provenance — cross-market contamination on cancel

1 participant