feat(events): pull-model prize claims for Single-release events#90
Conversation
select_winners now records prizes; each winner claims in their own transaction via the new claim_prize(event_id, position, op_id) entrypoint, so the winner count per event is bounded by the winner_distribution instead of one transaction's resource budget. Design decisions, in response to the constraints that sank PR #83: - No persisted-struct changes. Winner and EventRecord are untouched; all new state lives under appended DataKeys (EventPrizeAward, EventUnclaimedPrizes, EventPrizeBaseEscrow, EventPrizeClaimExpiry). A regression test writes a 1.2.0-shape Winner row and reads it back, so any future field addition to Winner turns CI red instead of bricking deployed rows. - Selection is batchable at 50/call: each position is awardable once (the award key is the replay lock) and amounts anchor to the escrow baseline captured at the first batch, so the sum of awards can never exceed the baseline. Pre-1.3.0 events keep one-shot semantics. - Liveness: a 90-day claim window anchored at selection time (not the event deadline, which typically passes before selection). Within the window, start_cancel rejects while prizes are unclaimed (O(1) counter, no row scans); after expiry, cancellation sweeps unclaimed amounts through the normal refund path. Escrow can never strand. - claim_prize releases funds first, then runs profile calls as best-effort try_ invocations, so a broken profile contract cannot block payouts. WinnerPaid still fires at the moment money moves, so indexers are unchanged; claim_milestone keeps its signature. - Version 1.3.0. No data migration needed (new keys only). The Error enum is now at the 50-case XDR cap, noted in BACKLOG. Tests: 13-test prize_claim suite (claim math with recipient + fee deltas, auth negatives per the #73 convention, double-claim/op-replay, cancel gate + window expiry sweep + refresh, Multi-release rejection, legacy row decode guard); existing Single-flow tests updated to claim after select. cargo test --workspace: 276 green. make build: boundless_events.wasm 51,276 bytes, under the 64 KB ceiling. Closes #61 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 52 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughThis change introduces version 1.3.0 single-release pull-model prize claims. Winner selection records anchored awards and claim metadata, recipients claim prizes separately, cancellation observes claim windows, and tests cover accounting, replay protection, batching, compatibility, and lifecycle behavior. ChangesSingle-release prize claim flow
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
contracts/events/src/event_ops.rs (1)
565-565: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winRedundant
set_eventwrite-back inselect_winners.
eventis fetched immutably at line 565 and is never mutated anywhere in this function (Single or Multi branch) — the binding itself had to change to non-mutprecisely because nothing assigns to it. Thestorage::set_event(env, event_id, &event)call at line 724 therefore persists an unchanged record. Since reads already refresh TTL (per thetouch_event_persistentconvention used by every getter in storage.rs), this write appears to be pure overhead on everyselect_winnersinvocation — counter to this PR's own goal of shaving per-call resource costs.♻️ Proposed fix
let winners_count = winners.len(); - storage::set_event(env, event_id, &event); evt::WinnersSelected {Also applies to: 622-650, 723-724
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@contracts/events/src/event_ops.rs` at line 565, Remove the redundant storage::set_event write-back from select_winners, since the event fetched through storage::get_event is never mutated in either winner-selection branch. Keep the immutable event binding and existing get_event call unchanged, relying on the getter’s TTL refresh behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@contracts/events/src/event_ops.rs`:
- Line 565: Remove the redundant storage::set_event write-back from
select_winners, since the event fetched through storage::get_event is never
mutated in either winner-selection branch. Keep the immutable event binding and
existing get_event call unchanged, relying on the getter’s TTL refresh behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d6b08370-a19d-4e3d-9565-39ef8533c560
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (17)
BACKLOG.mdcontracts/events/Cargo.tomlcontracts/events/src/admin.rscontracts/events/src/errors.rscontracts/events/src/event_ops.rscontracts/events/src/lib.rscontracts/events/src/storage.rscontracts/events/src/tests/admin.rscontracts/events/src/tests/bounty_pillar.rscontracts/events/src/tests/cancel_refund.rscontracts/events/src/tests/contributions.rscontracts/events/src/tests/cross_contract.rscontracts/events/src/tests/escrow_fee_math.rscontracts/events/src/tests/hackathon_pillar.rscontracts/events/src/tests/mod.rscontracts/events/src/tests/prize_claim.rscontracts/events/src/types.rs
Cut narration and version/PR tags from the #61 pull-model code down to comments that state a real constraint (reentrancy ordering, decode-compat trap, the selection-time window anchor). Add a comment-sparingly rule to CLAUDE.md and AGENTS.md so future changes hold the line.
…rror-enum cap Brings the two-step manager delegation up to current testnet (boundlessfi#90 pull-model claims, boundlessfi#84 snapshots) and makes it build and pass there. Error enum: testnet is at the 50-case contracterror cap, so a new PendingManagerMismatch variant would not compile. Instead generalize the two admin-rotation variants — PendingAdminMismatch/PendingAdminExpired -> PendingRotationMismatch/PendingRotationExpired (discriminants 12/13 unchanged, so no on-chain code change) — and share them across both two-step rotations. accept_manager now distinguishes no-pending (Mismatch) from expired (Expired), which is more precise than the original single-variant usage. Conflict resolution: - types.rs / storage.rs / event_ops.rs: PendingManager keys and helpers ordered after the 1.3.0 prize keys; append-only, no reorder. - cross_contract.rs: kept the new manager suite; the accepted-manager test now claims the prize before asserting Completed, since under the pull model select_winners records rather than pays. Verified: cargo test 216 events + 66 profile green; make build OK (events wasm 56,202 bytes, < 64 KB); fmt + clippy clean.
Summary
Implements the pull/claim payout model chosen for #61:
select_winnersrecords prizes, and each winner claims individually via the newclaim_prize(event_id, position, op_id)entrypoint. The per-event winner ceiling is now bounded by thewinner_distribution, not by one transaction's resource budget.This is the in-house replacement for the approach explored in #83, designed around the constraints that review surfaced there.
How each #83 review finding is addressed
Winnerbricks deployed rows (verifiedUnexpectedSizetrap)Winner/EventRecorduntouched. All new state lives in appended DataKeys (EventPrizeAward,EventUnclaimedPrizes,EventPrizeBaseEscrow,EventPrizeClaimExpiry). A regression test writes a 1.2.0-shape row and reads it back, so any futureWinnerfield addition turns CI red.simulateTransactionbefore raising the constant.prize_claimsuite: claim math with recipient and fee-account deltas, auth negatives (#73 convention) plus a positiveenv.auths()assertion, double-claim, op-replay, unawarded-position, Multi-release rejection, cancel gate, window-expiry sweep, window refresh, top-up residual math, and the legacy-row decode guard.claim_milestonesignature,WinnerPaidremoval)claim_milestoneunchanged.WinnerPaidstill fires when money moves (now insideclaim_prize), so indexers keep working. The only additive change is the new entrypoint.migrate()dispatch intentionally stays empty.Behavior changes for the backend
select_winnersno longer pays or completes the event; the event completes when the final prize claim drains escrow. Anything keying offCompletedimmediately after selection must switch to claim-driven flow.claim_prize(event_id, position, op_id)— the recorded recipient signs.Verification
cargo test --workspace: 276 tests green (210 events + 66 profile), including the 13 new prize-claim testscargo fmt --checkcleanmake build:boundless_events.wasm= 51,276 bytes (64 KB ceiling holds)Closes #61
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
claim_prize, available during a 90-day claim window.Bug Fixes