Skip to content

test(pm): margin/leverage safety — bad-debt transfer, profit close, Sybil settlement throttle (PR #124) - #151

Merged
On1x merged 1 commit into
pmfrom
test/pm-margin-leverage-coverage
Aug 12, 2026
Merged

On1x merged 1 commit into
pmfrom
test/pm-margin-leverage-coverage

Conversation

@chiliec

@chiliec chiliec commented Aug 12, 2026

Copy link
Copy Markdown
Member

Summary

Margin/leverage safety coverage for the HF14 prediction markets, following the review of PR #124. Adds three CI-wired consensus_sim cases to tests/consensus_sim/scenarios/test_pm_lifecycle.cpp, all driven through the real evaluators + database + settlement cron (not the standalone replay math). Purely additive (+365 lines, no existing code touched); auto-registered by Boost, run under ctest/CI like the other cases.

leverage_bad_debt_is_transfer_not_mint

Forces genuine pool bad debt (pool_received < loan) via the cancel-bet cascade and asserts the loss is a zero-sum transfer, not a mint: current_supply moves by block inflation alone and pool free_balance stays ≥ 0. The pool is not always made whole — a fast adverse move sticks LPs with the loss — but the chain never emits tokens to cover a leveraged bettor.

leverage_profit_close_never_mints

Open → same-side whale → profitable voluntary close → resolve → settle. Asserts the #141 pot_retained clamp keeps forfeit_pool ≥ 0 and current_supply moves by block inflation alone (no mint on the profit path either).

leverage_sybil_settlement_is_cap_throttled

Documents a margin-abuse probe and its (working) defense. There is no per-market / per-account leverage-position count cap and no minimum loan (validate only requires loan>0, collateral>0), so a Sybil can open many cheap positions on one market — the test opens 12. The hoped-for exploit was that resolving such a market would liquidate all positions in one block via the uncapped settle_market → force_close_positions backstop (block-time DoS). It does not: process_pm_markets §2d force-closes open positions once betting is over, and that sweep shares the single per-block done < cap budget, so ≤ cap positions close per block (test observed max 5 == cap) before the uncapped backstop is ever reached. Not a single-block DoS. This is a regression guard — if the §2d throttle is removed, closures would exceed cap and the test fails.

Verification

Built the full chain + consensus_sim_tests target locally and ran all three:

Running 3 test cases...
bad-debt-transfer: pos.status=1 cv=272727 loan=1000000 pool_received=272727 pool_free=9272727 supply_delta=3000 inflation=3000
profit-close: forfeit_pool=0 supply_delta=14427000 inflation=14427000 pool_free=10100000
sybil-settle: opened 12 positions; max closed in a single block = 5 (cap=5); all closed=true
*** No errors detected

Note (defense-in-depth suggestion, not blocking)

The Sybil surface is throttled for safety (no single-block DoS, no mint), but a large position flood still creates a liquidation backlog: N positions take ~N/cap blocks to drain, during which their collateral/loans sit in limbo. A per-account position cap or a minimum loan size would bound that backlog as defense-in-depth. Filed as a review comment on #124.

… close, Sybil throttle (PR #124)

Three CI-wired consensus_sim cases in test_pm_lifecycle.cpp, driven through the REAL
evaluators + database + settlement cron, targeting the leverage/margin subsystem:

- leverage_bad_debt_is_transfer_not_mint: forces genuine pool bad debt
  (pool_received < loan) via the cancel-bet cascade and asserts the loss is a zero-sum
  TRANSFER, not a mint — current_supply moves by block inflation alone, pool free_balance
  stays >= 0. LPs bear leverage counterparty risk; the chain never emits tokens to cover it.

- leverage_profit_close_never_mints: open -> same-side whale -> profitable voluntary
  close -> resolve -> settle. Asserts the #141 pot_retained clamp keeps forfeit_pool >= 0
  and current_supply moves by block inflation alone (no mint on the profit path either).

- leverage_sybil_settlement_is_cap_throttled: documents an abuse probe. There is NO
  per-market/per-account leverage-position count cap and NO minimum loan (validate only
  requires loan>0, collateral>0), so a Sybil can open many cheap positions on one market
  (test opens 12). BUT the forced settlement is throttled: process_pm_markets §2d
  force-closes open positions once betting is over, sharing the single per-block
  `done < cap` budget, so <= cap positions close per block (test: max 5 == cap) BEFORE
  settle_market's uncapped force_close_positions backstop is reached. Not a single-block
  DoS. Regression guard: if the §2d throttle is removed, closures exceed cap and this fails.

Built the full chain locally and ran all three green: "*** No errors detected".
@chiliec chiliec mentioned this pull request Aug 12, 2026
@On1x
On1x merged commit d2741aa into pm Aug 12, 2026
2 checks passed
On1x added a commit that referenced this pull request Aug 12, 2026
…teemit#536)

Require loan >= pm_min_liquidity in the pm_leverage_open evaluator. validate()
only checks loan>0, so without a floor a Sybil could open unbounded near-zero
loan positions on one market, each consuming a slice of the capped leverage
fund and a settlement force-close slot -> a liquidation backlog that throttles
PM cron throughput for ~N/cap blocks (see the leverage_sybil_settlement_is_cap
_throttled coverage added in PR #151). A minimum loan bounds the global open
position count to fund_total / pm_min_liquidity, so the backlog is bounded by
construction. Reuses the existing median-voted pm_min_liquidity (no new chain
property or serialization change); governance can still raise the floor.

Adds the leverage_min_loan_floor_enforced consensus_sim case: a sub-floor loan
is rejected, a loan exactly at the floor is accepted (so PR #151's Sybil case,
which borrows exactly pm_min_liquidity, still opens).
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.

2 participants