Skip to content

test(pm): early-exit headroom-clamp conservation replay (PR #124 review round 4) - #142

Merged
On1x merged 1 commit into
pmfrom
test/pm-early-exit-headroom
Aug 10, 2026
Merged

test(pm): early-exit headroom-clamp conservation replay (PR #124 review round 4)#142
On1x merged 1 commit into
pmfrom
test/pm-early-exit-headroom

Conversation

@chiliec

@chiliec chiliec commented Aug 10, 2026

Copy link
Copy Markdown
Member

Summary

Adds regression coverage for the F1/#300 early-exit deferred-claim settlement pass on the PM branch — the largest new consensus-money surface added since the PR #124 review round, and one that currently ships with no test coverage:

  • nothing under tests/ references deferred_claim / early_exit / reward_cap / pot_retained,
  • the CI-wired tests/pm/parimutuel_test.cpp (14 cases) exercises the pure split but never the claim pass,
  • tests/consensus_sim/scenarios/test_pm_lifecycle.cpp drives leverage-close / cancel but has no supply-conservation assertion (supply_conserved() exists in the harness but is invoked 0 times in the PM scenarios).

So the headroom clamp (499246e9), the pot_retained clamp (#141), and the paid-claims→forfeit_pool fold were all unguarded against regression.

What this adds

One replay case, tests/pm/replay/t15_early_exit_headroom.cpp, in the same standalone style as t14_f2_ledger (#130): it models the settle-pass verbatim from pm_evaluator.cpp:647-700 and settles through the real compute_settlement (linked from libraries/chain/pm/parimutuel.cpp, no node/DB build required).

It targets the claim that motivated 499246e9: the reward cap alone does not bound solvency, because per-market fees are capped only by oracle+creator+liquidity <= 100% at creation (pm_operations.cpp:64) with no chain-param guard, so a valid market can push fees near 100%.

  • A — bucket UNCLAMPED: default 33% cap + a valid 90%-fee market overdraws the pot → uncovered = 23000 charged to LP principal. Reachable with default params, not just extreme governance medians. This is the bug 499246e9 fixes.
  • B — bucket CLAMPED (real code): the bucket is capped to settlement headroom (33000 → 10000), uncovered = 0, the ledger balances — the early-exiter is haircut instead of LP principal taking the hit.
  • C — adversarial sweep: 1680 combinations of valid (fees, cap, forfeit, claim)0 unbalanced ledgers and 0 claim-induced uncovered regressions vs the no-claim baseline, i.e. paid claims are solvency-neutral. The 400 uncovered_hits in the sweep are the pre-existing negative-forfeit_pool case (the F1 LP-charge target), not the claim path — the test separates them explicitly.

The program exits non-zero on any conservation failure.

Build & run

tests/pm/replay/build.sh t15_early_exit_headroom
tests/pm/replay/out/t15_early_exit_headroom   # exit 0 = all checks pass

Not part of the CMake build (each case is a main()), consistent with the rest of tests/pm/replay/. Built and run against pm head dd6c5d1: all checks pass, and t10_conserve / t14_f2_ledger re-run unchanged.

Notes / open review items (no test here, flagged for the author)

  • gc_market (pm_evaluator.cpp ~L754) does not drop pm_deferred_claim_index. Currently safe because every terminal transition consumes or purges claims first (settle at L696, void via return_liquiditypurge_deferred_claims), but it's an invariant held across four call sites with no GC backstop — adding drop_range(pm_deferred_claim_index) there would be cheap defense-in-depth.
  • Prior B8 (past-dated CHAIN_HARDFORK_14_TIME on both testnet and mainnet) is still a release-gating item.

The F1/steemit#300 early-exit deferred-claim settlement pass shipped with no test
coverage — nothing under tests/ referenced deferred_claim / early_exit /
reward_cap, parimutuel_test.cpp never exercised the claim pass, and the
consensus_sim PM suite has no supply-conservation assertion.

Add t15_early_exit_headroom to tests/pm/replay/ (same standalone style as
t14_f2_ledger, #130): model the settle-pass verbatim from
pm_evaluator.cpp:647-700 and settle through the real compute_settlement.

  A. bucket UNCLAMPED: the default 33% cap + a VALID 90%-fee market (fees are
     capped only by oracle+creator+liquidity <= 100% at creation, no chain
     param) overdraws the pot -> uncovered=23000 charged to LP principal.
     Reachable with default params, not just extreme medians — the exact case
     499246e fixes.
  B. bucket CLAMPED (real code): capped to settlement headroom (33000->10000),
     uncovered=0, ledger balances, early-exiter haircut instead of LP hit.
  C. 1680-combo sweep of valid (fees, cap, forfeit, claim): 0 unbalanced
     ledgers, 0 claim-induced uncovered regressions vs the no-claim baseline —
     the clamp is solvency-neutral. The 400 uncovered_hits are the pre-existing
     negative-forfeit case (F1 LP-charge target), not the claim path.

Exits non-zero on any conservation failure. Not part of the CMake build (each
case is a main()), consistent with the rest of tests/pm/replay/. Built and run
against head dd6c5d1: all checks pass.
@On1x
On1x merged commit 7a4924a into pm Aug 10, 2026
2 checks passed
@chiliec

chiliec commented Aug 10, 2026

Copy link
Copy Markdown
Member Author

Added: two CI-wired consensus_sim cases (verified against a full local build)

Following the standalone t15 replay, I added two integration cases to tests/consensus_sim/scenarios/test_pm_lifecycle.cpp that run through the real evaluators + database + settlement cron (not the pure-math replay), closing the two gaps flagged in the round-4 review:

supply_conserved_binary_lifecycle — a full binary-market lifecycle (create → extra LP add → unequal two-sided bets → oracle resolve → parimutuel settle with a 6% fee schedule), asserting current_supply grows by exactly block inflation (CHAIN_DIGITAL_ASSET_ISSUED_PER_BLOCK × blocks). This is the PR's core safety claim — PM never mints or burns — asserted end-to-end for the first time. The 51-case suite checks local balances and the pure compute_settlement struct, but supply_conserved() in the harness is a stub invoked 0× in the PM scenarios.

early_exit_deferred_claim_paid_from_bucket — the F1/#300 early-exit deferred-claim path had zero integration coverage (grep deferred/early_exit/claim_paid across tests/ = 0). Opens a leveraged position, a whale bets the same side to push it into profit, betting closes so the settlement force-close records the bettor residual as an outcome-contingent deferred claim (asserts it's deferred, not refunded), then resolves the winning side and asserts the claim pays from the bounded early-exit bucket and every claim row is consumed at settlement.

Verification

Built the full chain + consensus_sim_tests target locally and ran both:

Running 2 test cases...
*** No errors detected

Observed values from the real run (in the test log):

  • supply case: delta=26000 == expected_inflation=26000 (blocks=26)
  • early-exit case: position closes profitable (cv=1232992 > obligation=1100000), bettor_received=132992 recorded as a deferred claim on side A, n=1 total=132992; at close the trader balance is unchanged (deferred), and after settlement it rises by exactly 132992 (paid from the bucket).

Both are appended to the existing pm_lifecycle_suite and auto-registered by Boost — no CMake change, they run under ctest/CI like the other 51 cases. Purely additive (273 lines, no existing code touched).

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