Skip to content

[Feature] V2: implement symmetric freeze/cancel emergency mechanism #71

Description

@collinsezedike

Summary

V2's much larger in-flight blast radius (potentially dozens of real third-party positions locked into one round, across a multi-phase lifecycle, settled by meaningfully more complex math than v1's fixed-committee vote) means v1's "block new assertions only" pause isn't enough: if a bug is found in registration, reveal, or settlement logic while real capital is already locked into an active round, blocking new assertions does nothing to protect it. This issue designs and implements a real freeze/cancel mechanism for already-active v2 rounds. This was explicitly out of scope for the v1-style pause and was never designed in docs/src/V2_RESOLUTION.md; the shape below is a starting proposal, not a settled spec, and needs maintainer sign-off before merging.

Proposed shape

Before a terminal outcome is locked (assertion is Pending, Registration, or Reveal with no strict majority reached yet): admin may cancel the round. Cancellation refunds every already-funded position (asserter, disputer, and any third-party positions) their exact principal, no forfeiture, no reward, as if the round never happened. This is the safe phase to cancel in, since no side has been declared right yet, refunding everyone equally can't be read as picking a winner.

After a terminal outcome is locked (strict majority reached, or the optimistic timeout default has fired): the admin cannot cancel or alter the outcome. Settlement and withdrawal for that round continue exactly as normal, even during a pause. Allowing an admin to unwind an already-decided result would reintroduce exactly the kind of "pause can choose a winner" risk v1's pause design already explicitly avoids, just at the settlement stage instead of the dispute stage.

New assertions: still blocked during a pause, same as v1.

What to build

  • set_paused_v2(admin, paused: bool), blocking new assert_outcome calls while paused (same semantics as v1's set_paused).
  • cancel_round(admin, id: u64), callable only while paused and only while the assertion's terminal_cause is not yet set. Refunds every funded position's exact principal via the same credit-then-withdraw path as normal settlement (see [Feature] V2: implement credit accrual and owner-authorized withdrawal #70), so the reentrancy and accounting guarantees stay identical. Sets a distinct terminal_cause = AdminCancelled so it's clearly distinguishable from a real dispute outcome in history/events.
  • Reject cancel_round outright (not just a no-op) if terminal_cause is already set, to make it structurally impossible for this call to alter a decided outcome.
  • Emit a distinct event for cancellation, separate from normal settlement events, so indexers can tell the two apart.

Open question for maintainer review

Should cancel_round be available the instant a pause starts, or only after some grace period (giving in-flight reveals a chance to land first, since a reveal transaction submitted right before a pause could otherwise be preempted by a cancellation)? Needs a decision before implementation, not left to the implementer's judgment given the security sensitivity here.

Depends on

#64 (assertion/policy state), #70 (shares the credit/withdrawal path for refunds).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthardSecurity-sensitive or high-complexity change

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions