From 14cc5b5916dc92662a83618dfd4a8ad2028d6700 Mon Sep 17 00:00:00 2001 From: Anatoly Piskunov Date: Fri, 30 Oct 2026 14:36:43 +0100 Subject: [PATCH] tests: pin the pm_lifecycle clock instead of starting at now() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The harness takes genesis from fc::time_point::now(), so every case in pm_lifecycle_suite silently changed the rules it ran under on the day a date-gated hardfork activated. HF16 (2026-10-31T00:00:00Z) requires a QC over the parent on every block, exempting only the activation block's child: a chain whose first block is past the fork date dies at block 3 with "No QC for head #2", before any of the 72 cases tests what it is about. Yesterday's failure in run 41054140379 was this class: the long leverage cases crossed the date inside one run (HARDFORK 16 at block 13712), so the same master commit was green in the morning and red at noon. Pin the case clock two days before HF16 — after HF15, before HF16, i.e. the rules that were live on 2026-10-30. Two days leave room for the longest case (~11 h 26 min of virtual time), and both bounds are compile-time constants, so the pair stays ordered forever. This is the same cure test_pm_audit_fixes.cpp already applied to its own time bomb. Voting for head, as the HF16-aware scenarios do, would not be enough here: after the fork the slot shortens to CHAIN_BLOCK_INTERVAL_HF16 (1 s), while this file's block-count arithmetic is written against 3 s blocks. Single-TU -fsyntax-only (testnet defines, nice/ionice): clean. --- .../scenarios/test_pm_lifecycle.cpp | 159 ++++++++++-------- 1 file changed, 87 insertions(+), 72 deletions(-) diff --git a/tests/consensus_sim/scenarios/test_pm_lifecycle.cpp b/tests/consensus_sim/scenarios/test_pm_lifecycle.cpp index 3aea8a5d08..1ffa87940f 100644 --- a/tests/consensus_sim/scenarios/test_pm_lifecycle.cpp +++ b/tests/consensus_sim/scenarios/test_pm_lifecycle.cpp @@ -32,6 +32,21 @@ using namespace graphene::chain; using namespace graphene::protocol; namespace { + // Wall clock is not a fixture here. The harness takes the genesis time from + // fc::time_point::now(), so a case that starts "now" silently changes the RULES it runs under + // on the day a date-gated hardfork activates: HF16 (2026-10-31T00:00:00Z) requires a QC over + // the parent on every block, and from that date on each case died at block 3 with + // "No QC for head #2" instead of testing what it is about (the first cut of + // test_pm_audit_fixes.cpp had exactly that time bomb and was fixed by pinning too). + // + // These cases test PM lifecycle under the rules that were live on 2026-10-30: after HF15, + // before HF16. Two days before the fork leaves room for the longest case (leverage interest + // runs ~11 h 26 min of virtual time), and keeps the pair ordered forever because both are + // compile-time constants. + fc::time_point_sec pm_lifecycle_start() { + return fc::time_point_sec(CHAIN_HARDFORK_16_TIME) - fc::days(2); + } + account_name_type next_validator(simulated_node& n) { return n.db().get_scheduled_validator(1); } fc::ecc::private_key key_for(const genesis_params& gp, const account_name_type& v) { @@ -305,7 +320,7 @@ BOOST_AUTO_TEST_SUITE(pm_lifecycle_suite) BOOST_AUTO_TEST_CASE(oracle_register_after_hf14) { auto gp = make_genesis_params(0x1234u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -341,7 +356,7 @@ BOOST_AUTO_TEST_CASE(oracle_register_after_hf14) { BOOST_AUTO_TEST_CASE(binary_market_lifecycle) { auto gp = make_genesis_params(0x2222u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -441,7 +456,7 @@ BOOST_AUTO_TEST_CASE(binary_market_lifecycle) { BOOST_AUTO_TEST_CASE(committee_dispute_flips_outcome) { auto gp = make_genesis_params(0x3333u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -558,7 +573,7 @@ BOOST_AUTO_TEST_CASE(committee_dispute_flips_outcome) { // and overturn the oracle. Without the lazy-pool credit her weight is ~0 → the dispute would uphold. BOOST_AUTO_TEST_CASE(committee_dispute_lazy_pool_voting_weight) { auto gp = make_genesis_params(0x3344u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -665,7 +680,7 @@ BOOST_AUTO_TEST_CASE(committee_dispute_lazy_pool_voting_weight) { // The non-refundable creation fee stays with the DAO, so the creator's net cost is just the fee. BOOST_AUTO_TEST_CASE(oracle_reject_refunds_liquidity_once) { auto gp = make_genesis_params(0x6666u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -732,7 +747,7 @@ BOOST_AUTO_TEST_CASE(oracle_reject_refunds_liquidity_once) { // flips to status 1, and the market's reserves/bets_sum are reversed by exactly the stake. BOOST_AUTO_TEST_CASE(bet_cancellation_reverses_cpmm) { auto gp = make_genesis_params(0x5555u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -825,7 +840,7 @@ BOOST_AUTO_TEST_CASE(bet_cancellation_reverses_cpmm) { // resolved_outcome=-1 / payout_status=3. Zero-sum: bettors made whole, oracle pays the fine. BOOST_AUTO_TEST_CASE(oracle_missed_refunds_and_slashes) { auto gp = make_genesis_params(0x4444u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -916,7 +931,7 @@ BOOST_AUTO_TEST_CASE(oracle_missed_refunds_and_slashes) { // in prediction_market_api, explicitly non-consensus — so it has no on-chain assertion.) BOOST_AUTO_TEST_CASE(lazy_active_market_penalty) { auto gp = make_genesis_params(0x7777u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -1019,7 +1034,7 @@ BOOST_AUTO_TEST_CASE(lazy_active_market_penalty) { // watch alloc.check_step advance one notch per step_dur, holding steady between gates. BOOST_AUTO_TEST_CASE(lazy_recall_is_time_gated) { auto gp = make_genesis_params(0x8888u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -1115,7 +1130,7 @@ BOOST_AUTO_TEST_CASE(lazy_recall_is_time_gated) { // (winner via forfeit_pool vs. lost) to confirm zero-sum is preserved. BOOST_AUTO_TEST_CASE(committee_dispute_upholds_oracle) { auto gp = make_genesis_params(0x9999u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -1247,7 +1262,7 @@ BOOST_AUTO_TEST_CASE(committee_dispute_upholds_oracle) { // a carve-out of the dispute fee (|pp|/10000), and the disputer is refunded the remainder. BOOST_AUTO_TEST_CASE(dispute_good_faith_oracle_not_slashed) { auto gp = make_genesis_params(0xAAAAu, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -1365,7 +1380,7 @@ BOOST_AUTO_TEST_CASE(dispute_good_faith_oracle_not_slashed) { // unlock (the lock is now enforced) and returns principal exactly once it elapses. BOOST_AUTO_TEST_CASE(lazy_deposit_lock_and_withdraw) { auto gp = make_genesis_params(0xB111u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -1462,7 +1477,7 @@ BOOST_AUTO_TEST_CASE(lazy_deposit_lock_and_withdraw) { // are emitted) plus the emergency penalty (penalty on profit retained in the pool). BOOST_AUTO_TEST_CASE(lazy_yield_and_emergency_penalty) { auto gp = make_genesis_params(0xB222u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -1578,7 +1593,7 @@ BOOST_AUTO_TEST_CASE(lazy_yield_and_emergency_penalty) { // the trader gets the remainder. Zero-sum: the trader's net loss equals the pool's interest gain. BOOST_AUTO_TEST_CASE(leverage_open_and_close) { auto gp = make_genesis_params(0xC111u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -1691,7 +1706,7 @@ BOOST_AUTO_TEST_CASE(leverage_open_and_close) { // shortfall (bad debt). The liquidated trader loses their collateral (gets nothing back). BOOST_AUTO_TEST_CASE(leverage_cancel_bet_cascade_bad_debt) { auto gp = make_genesis_params(0xC222u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -1808,7 +1823,7 @@ BOOST_AUTO_TEST_CASE(leverage_cancel_bet_cascade_bad_debt) { // the fee + reward carve-out (same canon as committee mode). A non-resolver cannot resolve. BOOST_AUTO_TEST_CASE(account_mode_dispute_resolver_flips) { auto gp = make_genesis_params(0xD111u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -1932,7 +1947,7 @@ BOOST_AUTO_TEST_CASE(account_mode_dispute_resolver_flips) { // refunded bettors as compensation. BOOST_AUTO_TEST_CASE(oracle_no_contest_refund_and_compensate) { auto gp = make_genesis_params(0xD222u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -2041,7 +2056,7 @@ BOOST_AUTO_TEST_CASE(oracle_no_contest_refund_and_compensate) { // outcome (not a refund) and the oracle is slashed for the wrongful no-contest. BOOST_AUTO_TEST_CASE(dispute_overrides_no_contest) { auto gp = make_genesis_params(0xD333u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -2150,7 +2165,7 @@ BOOST_AUTO_TEST_CASE(dispute_overrides_no_contest) { // penalised, and — the path this guards — the disputer's escrowed fee is returned. BOOST_AUTO_TEST_CASE(dispute_auto_close_refunds_fee) { auto gp = make_genesis_params(0xD444u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -2253,7 +2268,7 @@ BOOST_AUTO_TEST_CASE(dispute_auto_close_refunds_fee) { // rejected. BOOST_AUTO_TEST_CASE(position_transfer_changes_owner) { auto gp = make_genesis_params(0xE111u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -2361,7 +2376,7 @@ BOOST_AUTO_TEST_CASE(position_transfer_changes_owner) { // forfeited: a penalty stays in the pool and the remainder is refunded. BOOST_AUTO_TEST_CASE(batch_commit_reveal_and_forfeit) { auto gp = make_genesis_params(0xF111u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -2484,7 +2499,7 @@ BOOST_AUTO_TEST_CASE(batch_commit_reveal_and_forfeit) { // computed b, bettors back each outcome, the oracle resolves one, and only the winner is paid. BOOST_AUTO_TEST_CASE(lmsr_multi_market_lifecycle) { auto gp = make_genesis_params(0xF222u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -2578,7 +2593,7 @@ BOOST_AUTO_TEST_CASE(lmsr_multi_market_lifecycle) { // consensus gate lives in pm_place_bet (the field alone was previously not enforced). BOOST_AUTO_TEST_CASE(instant_bet_disabled_gate) { auto gp = make_genesis_params(0x5511u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -2649,7 +2664,7 @@ BOOST_AUTO_TEST_CASE(instant_bet_disabled_gate) { // force (the op field existed but was previously a no-op). BOOST_AUTO_TEST_CASE(dispute_bans_creator_from_new_markets) { auto gp = make_genesis_params(0x1401u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -2764,7 +2779,7 @@ BOOST_AUTO_TEST_CASE(dispute_bans_creator_from_new_markets) { // remainder, then a full withdrawal closes it. BOOST_AUTO_TEST_CASE(fractional_liquidity_withdrawal) { auto gp = make_genesis_params(0x1501u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -2832,7 +2847,7 @@ BOOST_AUTO_TEST_CASE(fractional_liquidity_withdrawal) { // market freezes the creator's own offer (0 here). Quote fields ride on pm_oracle_accept_market. BOOST_AUTO_TEST_CASE(oracle_fixed_fee_external_vs_self) { auto gp = make_genesis_params(0x5601u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -2942,7 +2957,7 @@ BOOST_AUTO_TEST_CASE(oracle_fixed_fee_external_vs_self) { // liquidity-fee bonus by principal × time-in-market: the earlier LP earns strictly more. BOOST_AUTO_TEST_CASE(time_weighted_lp_fee_split) { auto gp = make_genesis_params(0x0901u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -3024,7 +3039,7 @@ BOOST_AUTO_TEST_CASE(time_weighted_lp_fee_split) { // stamps the oracle's penalty_stamps counter (lazy-pool spam defence, §4.10). BOOST_AUTO_TEST_CASE(zero_volume_resolution_stamps_oracle) { auto gp = make_genesis_params(0x3801u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -3091,7 +3106,7 @@ BOOST_AUTO_TEST_CASE(zero_volume_resolution_stamps_oracle) { // pool loan clears, and the position becomes a plain parimutuel bet. BOOST_AUTO_TEST_CASE(leverage_convert_to_normal_bet) { auto gp = make_genesis_params(0x5201u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -3202,7 +3217,7 @@ BOOST_AUTO_TEST_CASE(leverage_convert_to_normal_bet) { // create (auto-accept). Also covers the offer→quote rule on the % fee. BOOST_AUTO_TEST_CASE(market_accepted_vop_and_quote) { auto gp = make_genesis_params(0x7A11u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -3303,7 +3318,7 @@ BOOST_AUTO_TEST_CASE(market_accepted_vop_and_quote) { // M2: dave + committee → creator mismatch (pinned to carol) → pending (0). BOOST_AUTO_TEST_CASE(auto_accept_resolver_and_creator_pins_gate_creation) { auto gp = make_genesis_params(0xA11Eu, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -3403,7 +3418,7 @@ BOOST_AUTO_TEST_CASE(auto_accept_resolver_and_creator_pins_gate_creation) { // each carrying the stake + the side it was on. BOOST_AUTO_TEST_CASE(payout_vop_per_bettor) { auto gp = make_genesis_params(0x7A22u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -3499,7 +3514,7 @@ BOOST_AUTO_TEST_CASE(payout_vop_per_bettor) { // settlement (status → 2 won / 3 lost) and emits pm_leverage_resolve with the outcome + leverage. BOOST_AUTO_TEST_CASE(leverage_resolve_vop_at_settlement) { auto gp = make_genesis_params(0x7A33u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -3612,7 +3627,7 @@ BOOST_AUTO_TEST_CASE(leverage_resolve_vop_at_settlement) { // cancel and still expects the position liquidated. BOOST_AUTO_TEST_CASE(leverage_disabled_keeps_liquidation_protection) { auto gp = make_genesis_params(0xC333u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -3728,7 +3743,7 @@ BOOST_AUTO_TEST_CASE(leverage_disabled_keeps_liquidation_protection) { // tables are authoritative, not hand-math. BOOST_AUTO_TEST_CASE(betting_odds_and_payout_table) { auto gp = make_genesis_params(0x0DD5u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -3965,7 +3980,7 @@ BOOST_AUTO_TEST_CASE(betting_odds_and_payout_table) { // 1 + (Σ_{j≠i} pool_j) x (1 - commission) / pool_i. BOOST_AUTO_TEST_CASE(betting_odds_and_payout_table_multi) { auto gp = make_genesis_params(0x0DD6u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -4178,7 +4193,7 @@ BOOST_AUTO_TEST_CASE(betting_odds_and_payout_table_multi) { // both sides; nothing restricts it) so bandwidth/funding are never the bottleneck. BOOST_AUTO_TEST_CASE(odds_drift_long_market_1000_bets) { auto gp = make_genesis_params(0x0DD7u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -4350,7 +4365,7 @@ BOOST_AUTO_TEST_CASE(odds_drift_long_market_1000_bets) { // favourite near x1.2, underdog ~x4-5. Shows the drift on a non-even market. BOOST_AUTO_TEST_CASE(odds_drift_binary_skewed) { auto gp = make_genesis_params(0x0DD8u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -4402,7 +4417,7 @@ BOOST_AUTO_TEST_CASE(odds_drift_binary_skewed) { // board generalises (odds_i = 1 + Σ_other x (1-comm) / pool_i) and drifts the same way. BOOST_AUTO_TEST_CASE(odds_drift_multi_1000) { auto gp = make_genesis_params(0x0DD9u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -4459,7 +4474,7 @@ BOOST_AUTO_TEST_CASE(odds_drift_multi_1000) { // dispute exists to rebut. BOOST_AUTO_TEST_CASE(oracle_rebuttal_and_decision_reason) { auto gp = make_genesis_params(0x58EEu, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -4569,7 +4584,7 @@ BOOST_AUTO_TEST_CASE(oracle_rebuttal_and_decision_reason) { // it early; a non-issuer is rejected; the creator can then open a market again. BOOST_AUTO_TEST_CASE(resolver_unban_lifts_ban) { auto gp = make_genesis_params(0x59BAu, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -4699,7 +4714,7 @@ BOOST_AUTO_TEST_CASE(resolver_unban_lifts_ban) { // clears it (banned_until → 0) and emits pm_ban_expired, after which the creator can act again. BOOST_AUTO_TEST_CASE(creator_ban_auto_expires) { auto gp = make_genesis_params(0x60AEu, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -4812,7 +4827,7 @@ BOOST_AUTO_TEST_CASE(creator_ban_auto_expires) { // (one per lifted ban, carrying the account and which ban(s) lifted). BOOST_AUTO_TEST_CASE(ban_expired_vop_emitted) { auto gp = make_genesis_params(0x61EEu, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -4927,7 +4942,7 @@ BOOST_AUTO_TEST_CASE(ban_expired_vop_emitted) { // #62 — No-contest stores its rationale on the market as decision_reason (readable via get_market). BOOST_AUTO_TEST_CASE(no_contest_records_decision_reason) { auto gp = make_genesis_params(0x62AAu, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -4984,7 +4999,7 @@ BOOST_AUTO_TEST_CASE(no_contest_records_decision_reason) { // #63 — pm_unban guards: lifting a ban that does not exist / is not active is rejected. BOOST_AUTO_TEST_CASE(unban_rejected_when_not_banned) { auto gp = make_genesis_params(0x63BBu, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -5036,7 +5051,7 @@ BOOST_AUTO_TEST_CASE(unban_rejected_when_not_banned) { // Death #5 — resolved + auto-paid. BOOST_AUTO_TEST_CASE(gc_resolved_market_after_retention) { auto gp = make_genesis_params(0x6C01u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -5094,7 +5109,7 @@ BOOST_AUTO_TEST_CASE(gc_resolved_market_after_retention) { // still ending with the whole cluster gone. BOOST_AUTO_TEST_CASE(gc_row_budget_spans_blocks) { auto gp = make_genesis_params(0x6C06u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -5211,7 +5226,7 @@ BOOST_AUTO_TEST_CASE(gc_row_budget_spans_blocks) { // FC_ASSERT(escrow == 0) rides along: a resume path that pays twice or drops a row trips it. BOOST_AUTO_TEST_CASE(settle_row_budget_spans_blocks) { auto gp = make_genesis_params(0x6C07u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -5354,7 +5369,7 @@ BOOST_AUTO_TEST_CASE(settle_row_budget_spans_blocks) { // stake back. BOOST_AUTO_TEST_CASE(void_refund_row_budget_spans_blocks) { auto gp = make_genesis_params(0x6C08u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -5485,7 +5500,7 @@ BOOST_AUTO_TEST_CASE(void_refund_row_budget_spans_blocks) { // epoch boundary while a partial pass is in flight. BOOST_AUTO_TEST_CASE(batch_queue_row_budget_spans_blocks) { auto gp = make_genesis_params(0x6C09u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -5646,7 +5661,7 @@ BOOST_AUTO_TEST_CASE(batch_queue_row_budget_spans_blocks) { // same block must therefore finalize in DIFFERENT blocks. BOOST_AUTO_TEST_CASE(dispute_tally_row_budget_defers_next) { auto gp = make_genesis_params(0x6C0Au, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -5792,7 +5807,7 @@ BOOST_AUTO_TEST_CASE(dispute_tally_row_budget_defers_next) { // where a voter overwrites an existing ballot and the counter must NOT move. BOOST_AUTO_TEST_CASE(dispute_ballot_counter_matches_rows) { auto gp = make_genesis_params(0x6C0Bu, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -5892,7 +5907,7 @@ BOOST_AUTO_TEST_CASE(dispute_ballot_counter_matches_rows) { // Death #1 — oracle rejects the market's terms. BOOST_AUTO_TEST_CASE(gc_oracle_reject_after_retention) { auto gp = make_genesis_params(0x6C02u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -5935,7 +5950,7 @@ BOOST_AUTO_TEST_CASE(gc_oracle_reject_after_retention) { // Death #2 — oracle never accepts; the accept window expires and the cron voids the market. BOOST_AUTO_TEST_CASE(gc_accept_window_expired_after_retention) { auto gp = make_genesis_params(0x6C03u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -5977,7 +5992,7 @@ BOOST_AUTO_TEST_CASE(gc_accept_window_expired_after_retention) { // Death #3 — oracle misses the resolution deadline; the cron refunds and closes the market. BOOST_AUTO_TEST_CASE(gc_oracle_missed_after_retention) { auto gp = make_genesis_params(0x6C04u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -6027,7 +6042,7 @@ BOOST_AUTO_TEST_CASE(gc_oracle_missed_after_retention) { // Death #4 — a dispute is filed but nobody votes; the anti-freeze auto-close voids the market. BOOST_AUTO_TEST_CASE(gc_dispute_auto_close_after_retention) { auto gp = make_genesis_params(0x6C05u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -6179,7 +6194,7 @@ namespace { // oldest idle market ate the whole budget every boundary and the bet stayed queued forever. BOOST_AUTO_TEST_CASE(batch_settle_idle_markets_do_not_starve) { auto gp = make_genesis_params(0xF331u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -6226,7 +6241,7 @@ BOOST_AUTO_TEST_CASE(batch_settle_idle_markets_do_not_starve) { // persisted cursor resumes past market 0, so market 1 settles within a couple of epochs. BOOST_AUTO_TEST_CASE(batch_settle_round_robin_prevents_busy_starvation) { auto gp = make_genesis_params(0xF332u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -6296,7 +6311,7 @@ BOOST_AUTO_TEST_CASE(batch_settle_round_robin_prevents_busy_starvation) { // current_supply grows by exactly block inflation across the full lifecycle (no PM mint/burn). BOOST_AUTO_TEST_CASE(supply_conserved_binary_lifecycle) { auto gp = make_genesis_params(0x5C01u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -6409,7 +6424,7 @@ BOOST_AUTO_TEST_CASE(supply_conserved_binary_lifecycle) { // their side wins, and (c) the market still conserves VIZ through settlement. BOOST_AUTO_TEST_CASE(early_exit_deferred_claim_paid_from_bucket) { auto gp = make_genesis_params(0x5E02u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -6564,7 +6579,7 @@ BOOST_AUTO_TEST_CASE(early_exit_deferred_claim_paid_from_bucket) { // price and cascade-liquidate) but asserts the money-supply invariant the other case omits. BOOST_AUTO_TEST_CASE(leverage_bad_debt_is_transfer_not_mint) { auto gp = make_genesis_params(0x5A31u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -6681,7 +6696,7 @@ BOOST_AUTO_TEST_CASE(leverage_bad_debt_is_transfer_not_mint) { // asserts current_supply moved by block inflation alone and forfeit_pool never went negative. BOOST_AUTO_TEST_CASE(leverage_profit_close_never_mints) { auto gp = make_genesis_params(0x5A32u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -6807,7 +6822,7 @@ BOOST_AUTO_TEST_CASE(leverage_profit_close_never_mints) { // the §2d throttle, closures per block would exceed cap and this test fails. BOOST_AUTO_TEST_CASE(leverage_sybil_settlement_is_cap_throttled) { auto gp = make_genesis_params(0x5A33u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -6926,7 +6941,7 @@ BOOST_AUTO_TEST_CASE(leverage_sybil_settlement_is_cap_throttled) { // above — which borrows exactly pm_min_liquidity — still opens; the floor bounds, not blocks). BOOST_AUTO_TEST_CASE(leverage_min_loan_floor_enforced) { auto gp = make_genesis_params(0x5A34u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -7000,7 +7015,7 @@ BOOST_AUTO_TEST_CASE(leverage_min_loan_floor_enforced) { // position's cancel-refund bypass the liquidity-growth cap. BOOST_AUTO_TEST_CASE(partial_transfer_inherits_entry_liquidity) { auto gp = make_genesis_params(0x7a11u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -7103,7 +7118,7 @@ BOOST_AUTO_TEST_CASE(partial_transfer_inherits_entry_liquidity) { // head, never pro-rata to later requests. BOOST_AUTO_TEST_CASE(lazy_withdraw_fifo_queue) { auto gp = make_genesis_params(0xF1F0u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -7286,7 +7301,7 @@ BOOST_AUTO_TEST_CASE(lazy_withdraw_fifo_queue) { // which the per-block bound below rejects. BOOST_AUTO_TEST_CASE(lazy_withdraw_queue_row_budget_spans_blocks) { auto gp = make_genesis_params(0xF1F1u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -7414,7 +7429,7 @@ BOOST_AUTO_TEST_CASE(lazy_withdraw_queue_row_budget_spans_blocks) { // fee_percent is capped by the median; a wrong signer cannot touch the profile. BOOST_AUTO_TEST_CASE(oracle_profile_update_and_insurance_gate) { auto gp = make_genesis_params(0x0A0Eu, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -7532,7 +7547,7 @@ BOOST_AUTO_TEST_CASE(oracle_profile_update_and_insurance_gate) { // "the floor works" (the trap the live dust probe fell into twice). BOOST_AUTO_TEST_CASE(min_bet_floor_blocks_row_spam) { auto gp = make_genesis_params(0x4320u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -7664,7 +7679,7 @@ BOOST_AUTO_TEST_CASE(min_bet_floor_blocks_row_spam) { // market or top it up later. BOOST_AUTO_TEST_CASE(min_liquidity_floor_applies_to_topups) { auto gp = make_genesis_params(0x4321u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -7742,7 +7757,7 @@ BOOST_AUTO_TEST_CASE(min_liquidity_floor_applies_to_topups) { // allocations) and the ban must still lapse, because sec 8 now counts against its own budget. BOOST_AUTO_TEST_CASE(ban_expiry_survives_saturated_cron_budget) { auto gp = make_genesis_params(0x69BAu, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL); @@ -7885,7 +7900,7 @@ BOOST_AUTO_TEST_CASE(ban_expiry_survives_saturated_cron_budget) { // returned, the income earned, and any F1 uncovered charge. Lazy-pool allocations are market-level only. BOOST_AUTO_TEST_CASE(lp_payout_vop_and_fee_counter) { auto gp = make_genesis_params(0x7B22u, 1); - fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec start(pm_lifecycle_start()); fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); if (hf > start) start = hf; start += fc::seconds(CHAIN_BLOCK_INTERVAL);