Skip to content

fix(#406): timelock admin raffle cancellation to prevent rug of ticket buyers#543

Merged
Otaiki1 merged 2 commits into
crackedstudio:masterfrom
Heazzy500:fix/issue-406-admin-cancel-timelock
Jun 30, 2026
Merged

fix(#406): timelock admin raffle cancellation to prevent rug of ticket buyers#543
Otaiki1 merged 2 commits into
crackedstudio:masterfrom
Heazzy500:fix/issue-406-admin-cancel-timelock

Conversation

@Heazzy500

Copy link
Copy Markdown
Contributor

Overview

This PR closes a security hole in contracts/raffle-instance where an admin could cancel a raffle with sold tickets instantly, returning the prize to the creator while ticket buyers were left to chase individual refunds — an immediate rug of ticket holders.

It introduces a 48-hour timelock for admin-initiated cancellations of raffles that have already sold tickets (matching the factory's TIMELOCK_DELAY_SECONDS). The cancel is now scheduled first (emitting a CancelScheduled event) and only executed after the notice period, while ticket holders may refund immediately the moment a cancel is scheduled.

Related Issue

Closes #406

Changes

🔒 Timelocked Admin Cancellation

  • [MODIFY] contracts/raffle-instance/src/lib.rscancel_raffle

  • When CancelReason::AdminCancelled is used on a raffle with tickets_sold > 0, the cancel is now scheduled instead of executed: status stays Active, the prize stays locked, and PendingAdminCancel (a cancel_at timestamp = now + 48h) is stored.

  • Admin cancels of raffles with zero tickets sold remain immediate (no buyers to protect).

  • [ADD] contracts/raffle-instance/src/lib.rsexecute_admin_cancel

  • Executes a previously scheduled admin cancel, but only after the timelock has elapsed.

  • Returns CancelTimelockActive if called before cancel_at, and CancelNotScheduled if no cancel is pending.

  • [ADD] contracts/raffle-instance/src/lib.rsget_pending_cancel

  • Read-only getter returning the scheduled cancel_at timestamp (or None) for indexers/UIs.

  • [MODIFY] contracts/raffle-instance/src/lib.rsrefund_ticket

  • Ticket holders can now refund immediately once a cancel is scheduled, not just after it executes.

  • [MODIFY] contracts/raffle-instance/src/lib.rswipe_storage

  • Cleans up the new PendingAdminCancel key on teardown.

🧱 New State, Events & Errors

  • [ADD] contracts/raffle-instance/src/events.rsCancelScheduled

  • Emitted when an admin schedules a cancel; carries creator, scheduled_by, tickets_sold, cancel_at, and timestamp.

  • [MODIFY] contracts/raffle-instance/src/lib.rs

  • DataKey::PendingAdminCancel — stores the executable-at timestamp while a cancel is pending.

  • ADMIN_CANCEL_TIMELOCK_SECONDS = 172_800 (48 hours) — matches the factory's TIMELOCK_DELAY_SECONDS.

  • New errors: CancelTimelockActive = 64, CancelNotScheduled = 65.

🧪 Tests

  • [ADD] contracts/raffle-instance/src/lib.rs (test module)
  • admin_cancel_with_sold_tickets_requires_timelock — scheduling works, exactly one CancelScheduled event fires, executing immediately and 1s before the deadline both fail with CancelTimelockActive, and it succeeds at the deadline.
  • ticket_holders_refund_immediately_after_cancel_scheduled — refunds rejected while Active, allowed instantly once scheduled.
  • execute_admin_cancel_without_schedule_fails — returns CancelNotScheduled.
  • admin_cancel_with_no_tickets_is_immediate — zero-ticket admin cancel stays instant.
  • [MODIFY] test_wipe_storage_removes_all_keys — updated to the schedule → wait → execute_admin_cancel flow.

Verification Results

cargo build -p raffle-instance   ✅ Finished (existing repo warnings only)
cargo test  -p raffle-instance   ✅ 20 passed; 0 failed
cargo test  --workspace          ✅ raffle-instance 20/20, raffle-factory 19/19, all green
Acceptance Criteria Status
Admin-initiated cancellation of raffles with sold tickets requires a timelock
A CancelScheduled event is emitted when an admin schedules a cancel
Ticket holders can refund immediately once a cancel is scheduled (not just after it executes)
Test: scheduling then executing a cancel within the timelock window fails
Admin cancel of a raffle with zero tickets sold stays immediate
Build passes successfully
Test suite passes successfully

🤖 Generated with Claude Code

Heazzy500 and others added 2 commits June 30, 2026 09:10
… rug of ticket buyers

An admin could cancel a raffle with sold tickets immediately, returning the
prize to the creator while buyers had to individually claim refunds. This
adds a 48h timelock (matching the factory's TIMELOCK_DELAY_SECONDS) for admin
cancels of raffles with sold tickets.

- cancel_raffle now schedules (not executes) an admin cancel when tickets are
  sold: status stays Active, prize stays locked, and a CancelScheduled event
  is emitted with the cancel_at timestamp.
- New execute_admin_cancel runs the actual cancel only after the timelock
  elapses (CancelTimelockActive / CancelNotScheduled errors otherwise).
- refund_ticket lets holders refund immediately once a cancel is scheduled.
- Admin cancels with zero tickets sold remain immediate.
- Adds get_pending_cancel getter and wipes PendingAdminCancel in wipe_storage.
- Tests cover scheduling, timelock enforcement, immediate refunds, and the
  no-schedule / zero-ticket paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Otaiki1 Otaiki1 merged commit cdf2228 into crackedstudio:master Jun 30, 2026
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.

[SECURITY] Admin can cancel a raffle at any time without timelock — immediate rug of ticket buyers

2 participants