feat: implement v2 symmetric freeze/cancel emergency mechanism - #89
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements #71: the symmetric freeze/cancel emergency mechanism for an already-active v2 round. Note the issue itself states this design "needs maintainer sign-off before merging" and was never specified in
docs/src/V2_RESOLUTION.md; the one explicit open question in the issue (whethercancel_roundshould require a grace period after a pause starts) was resolved directly in conversation before implementing: instant is what's built here, since cancellation only ever runs before a terminal outcome locks, so there's no real "winner" to preempt, and any position that doesn't get to reveal in time still gets its full principal back through cancellation's no-forfeiture refund regardless.set_paused_v2(paused): blocks newassert_outcomecalls. Deliberately narrower than v1's pause: an already-active round's registration, reveal, resolution, settlement, and withdrawal all continue normally while paused, since blocking them would strand capital already locked into that round rather than protect it.cancel_round(id): callable only while paused, and only whileterminal_causeis stillNotYetDecided(Pending, orRegistration/Revealwith no strict majority reached yet). Refunds every already-funded position its exact principal, no forfeiture, no reward. Fails outright, not as a no-op, withRoundAlreadyDecidedonceterminal_causeis set by any means (a real outcome, or an earlier cancellation), making it structurally impossible for this call to alter an already-decided result.Pendingcancellation refunds the asserter's bond directly, since noResolution/Positionexists yet at that phase. ARegistration/Revealcancellation instead sets a newTerminalCause::AdminCancelledand closes the round toResolved; every position (including any that already revealed) then recovers its principal through the existingsettle/withdrawpath from [Feature] V2: implement settlement and forfeiture distribution #69/[Feature] V2: implement credit accrual and owner-authorized withdrawal #70, with no special-casing needed there:settlement_pooltreatsAdminCancelledasrecipient_weight = eligible_total, soforfeited_poolis always 0 and every position is a recipient.RoundCancelledevent, distinct fromResolved, so indexers can always tell a cancellation apart from a real outcome.Test plan
cargo fmt --check,cargo clippy --workspace --all-targets -- -D warnings, andcargo testpass locallycargo build -p tholos --target wasm32v1-none --releaseandcargo build --workspace --target wasm32v1-none --releasepass locallymdbook build docspasses locallyCONTRACT.mdupdated if the public interface changed — not applicable,tholos-v2isn't documented there yet (tracked separately)scripts/testnet-smoke.shrun against testnet — not run; this only touchestholos-v2, which isn't deployed yetset_paused_v2(blocks new assertions, doesn't block an active round's full lifecycle through settlement/withdrawal), andcancel_roundacross all three cancellable phases (Pendingdirect refund,Registrationwith a third-party position,Revealwith one already-revealed and one never-revealed position, confirming neither gets a reward or forfeits anything), plus its failure modes (not paused, nonexistent assertion, already decided via strict majority/optimistic timeout/uncontested finalize/a prior cancellation).