Skip to content

feat(creator-keys): emergency platform pause with 24h timelocked resume (#1000) - #1008

Open
solaawojobi00-bit wants to merge 5 commits into
accesslayerorg:mainfrom
solaawojobi00-bit:fix/issue-1000-emergency-platform-pause
Open

solaawojobi00-bit wants to merge 5 commits into
accesslayerorg:mainfrom
solaawojobi00-bit:fix/issue-1000-emergency-platform-pause

Conversation

@solaawojobi00-bit

Copy link
Copy Markdown

Emergency platform pause for a platform-wide trading halt

Problem

There was no way for the multisig admins to halt all bonding-curve trading in one transaction while keeping a deliberate, delayed path back to live trading. The existing #784 global_pause / global_resume takes two separate transactions to activate, and resume has no delay. The per-key pause_with_expiry is time-boxed and belongs to the creator's own admin set, not the platform.

Scenario Before After
Exploit in progress, admins need every key halted now Two separate vote transactions (#784) One pause_platform call signed by 2+ admins
Admins lift the halt too soon under pressure global_resume takes effect as soon as the second vote lands resume_platform fails until 24h after queue_platform_resume
One key is compromised, the rest of the platform is healthy Creator-controlled, time-limited pause only Platform multisig set_key_pause_override with no expiry
Platform resumes while a key is still under investigation n/a The key override survives the platform resume
Indexers need to know who paused and when n/a plat_pau / plat_res carry actor + timestamp

Solution

A new emergency_pause module in creator-keys, exposed through seven new contract functions:

  • Multisig in one transaction: every write takes signers: Vec<Address>. It needs at least GLOBAL_PAUSE_THRESHOLD (2) distinct members of the existing global-pause admin set (set_global_pause_admins), and each of them must require_auth. Reusing that admin set means no new admin configuration.
  • Timelocked resume: queue_platform_resume records now + 86_400s. resume_platform fails with TimelockNotElapsed until that ledger timestamp is reached.
  • Per-key override: stored under a separate key. It is checked in the same guard as the platform flag but never cleared by platform resume, so the two states stay independent.
  • Trade guard: assert_trading_allowed(env, key_id) returns ContractError::GlobalTradingHalted (the error trade paths already use for halts). It is placed first in every buy/sell entrypoint and per order in the batch paths.

Changes

creator-keys/src/emergency_pause.rs (new)

  • EmergencyPauseError: its own #[contracterror] enum (Unauthorized, InsufficientSigners, DuplicateSigner, AlreadyPaused, NotPaused, ResumeAlreadyQueued, ResumeNotQueued, TimelockNotElapsed, Overflow). It is separate so it adds no variants to ContractError.
  • EmergencyPauseDataKey { PlatformPaused, ResumeEta, KeyPaused(Address) }: new keys only.
  • assert_multisig checks threshold, membership and duplicates before require_auth, so a repeated address is rejected cleanly rather than double-authorised.
  • pause_platform, queue_platform_resume, resume_platform, set_key_pause_override, the is_platform_paused / is_key_paused / resume_eta views, and assert_trading_allowed.

creator-keys/src/lib.rs

  • Contract entrypoints: pause_platform, queue_platform_resume, resume_platform, is_paused, get_platform_resume_eta, set_key_pause_override, is_key_paused.
  • emergency_pause::assert_trading_allowed(&env, &creator)? added to:
    • buy_keys_with_referrer (also covers buy_keys) and buy_key_with_referrer (also covers buy_key)
    • sell_key
    • the per-order loops of batch_buy, batch_buy_v2 and batch_sell

creator-keys/src/events.rs

  • plat_pau, plat_rq, plat_res and key_pau names, payload structs, *_DATA_FIELDS field-order constants and topic helpers, following docs/contract-event-conventions.md.

Docs

  • docs/emergency-pause-runbook.md: new section 9 covering the multisig platform halt, the blocked entrypoints, the timelock and the override semantics.
  • docs/contract-event-conventions.md: event table for the four new events.

Regression Tests

creator-keys/src/test_issue_1000.rs: 13 tests.

Acceptance criterion Test(s)
pause_platform halts trades on all keys immediately pause_platform_halts_buy_and_sell_on_all_keys (buy_key, buy_keys, sell_key, batch_buy, batch_sell, batch_buy_v2 across two keys)
Multisig admin only pause_platform_requires_every_signer_to_authorise, pause_platform_rejects_invalid_signer_sets, pause_platform_without_admin_set_is_unauthorized, pause_platform_twice_is_rejected
resume_platform blocked until 24h timelock elapses resume_platform_blocked_until_timelock_elapses (now, eta - 1, eta), resume_platform_requires_queue_and_multisig
is_paused checked correctly by bonding curve buy and sell resume_platform_blocked_until_timelock_elapses (trading halted while paused, restored after resume on both keys)
PlatformPaused event emitted with timestamp and actor pause_platform_emits_event_with_timestamp_and_actor, resume_platform_emits_event_with_timestamp_and_actor
Per-key pause works independently of platform pause state key_override_halts_only_that_key_while_platform_live, key_override_survives_platform_resume, clearing_key_override_does_not_bypass_platform_pause, key_override_requires_multisig

Testing

$ cargo fmt --all -- --check
(no output)

$ cargo clippy --workspace --all-targets -- -D warnings
    Finished `dev` profile [unoptimized] target(s) in 1m 49s

$ cargo test --workspace
test test_issue_1000::clearing_key_override_does_not_bypass_platform_pause ... ok
test test_issue_1000::key_override_requires_multisig ... ok
test test_issue_1000::key_override_survives_platform_resume ... ok
test test_issue_1000::key_override_halts_only_that_key_while_platform_live ... ok
test test_issue_1000::pause_platform_halts_buy_and_sell_on_all_keys ... ok
test test_issue_1000::pause_platform_emits_event_with_timestamp_and_actor ... ok
test test_issue_1000::pause_platform_without_admin_set_is_unauthorized ... ok
test test_issue_1000::pause_platform_requires_every_signer_to_authorise ... ok
test test_issue_1000::pause_platform_rejects_invalid_signer_sets ... ok
test test_issue_1000::pause_platform_twice_is_rejected ... ok
test test_issue_1000::resume_platform_blocked_until_timelock_elapses ... ok
test test_issue_1000::resume_platform_emits_event_with_timestamp_and_actor ... ok
test test_issue_1000::resume_platform_requires_queue_and_multisig ... ok
test result: ok. 409 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 21.63s

All 225 test result lines across the workspace are ok; 0 FAILED.

Checklist

  • Linked issue: Implement emergency pause contract for platform-wide trading halt #1000
  • Added creator-keys tests for every changed behavior, including failure paths for each reachable EmergencyPauseError variant and ContractError::GlobalTradingHalted
  • Ran cargo fmt --all -- --check, cargo clippy --workspace --all-targets -- -D warnings, and cargo test --workspace
  • Storage: additive only (new EmergencyPauseDataKey enum). No existing key or layout changes, so no migration needed; an unset flag reads as "not paused"
  • Events: new events follow docs/contract-event-conventions.md (lowercase symbol_short! names in events.rs, topic 0 = name, topic 1 = primary entity, documented field order); no existing event changed
  • Docs updated for the new public interface and events
  • Scope limited to the platform pause; no dependency, lockfile or generated-artifact changes

Notes for Reviewers

  • is_paused vs get_is_paused: the issue asks for is_paused(), so that name reports this platform halt only. The legacy single-admin protocol pause is still reported by get_is_paused; the runbook calls this out.
  • actor in events is the first entry in signers; every signer's auth is still required.
  • buyback is not guarded. It is a creator repurchase from their own key rather than a user buy/sell. Happy to add the one-line guard if you want it covered too.
  • Existing gap, left as is: batch_buy, batch_buy_v2 and batch_sell never checked the older per-key pause_with_expiry state. The new guard (platform + override) is enforced per order there, but I left the older check untouched to stay in scope.
  • The Add a global emergency pause function that halts all buy and sell operations across every creator key #784 global_pause / global_resume flow is unchanged and still works alongside this one.

Closes #1000

…me (accesslayerorg#1000)

Add an emergency_pause module letting the global-pause multisig halt all
bonding-curve buys and sells in one transaction, resume only after a
queued 24h timelock, and pause individual keys independently. Wire the
guard into every buy and sell entrypoint, including batch paths.
… halt (accesslayerorg#1000)

Move the platform pause event names and payloads into events.rs per the
event conventions, and document the multisig platform halt in the
emergency pause runbook and event conventions table.
…gency-platform-pause

# Conflicts:
#	creator-keys/src/events.rs
@drips-wave

drips-wave Bot commented Sep 27, 2026

Copy link
Copy Markdown

@solaawojobi00-bit Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

…gency-platform-pause

# Conflicts:
#	creator-keys/src/lib.rs
…gency-platform-pause

# Conflicts:
#	creator-keys/src/lib.rs
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.

Implement emergency pause contract for platform-wide trading halt

1 participant