TimeCurve: distributePrizes zero-prizePool early return — indexer semantics, events, replay (Finding 3 from #131) #133
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:open_issues
agents
agent:security_audit
agent:verify
area
timecurve
arena
arena-v2
audit
block:feature_review
block:security
bug
contracts
design
devops
docs
documentation
enhancement
epic
feature
frontend
indexer
low priority
megaeth
operations
ops
performance
QA
qa checklist
referrals
removal
security
smart-contracts
tech-debt
testing
time-arena
timecurve
visual-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
code/yieldomega#133
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parent
Split from #131 (Finding 3).
Problem
distributePrizesdoes:Effects:
prizesDistributedstays false — noPrizesDistributedevent. Downstream actors (indexer UX, ops runbooks treat distribution as canonical “reserve round closed”—seedocs/operations/final-signoff-and-value-movement.md) can wait indefinitely without a distinguishing signal between “not yet attempted / empty vault retry” vs “intentionally zero pool forever”.reservePodiumPayoutsEnabled— behavior differs from the non-empty path (policy/consistency gap).PodiumPoollet the owner calldistributePrizesagain later and route funds through category splits (owner-only, but unsolicited donations create a repeatable routing surface called out in the review).Constraint from existing tests
test_distributePrizes_empty_vault_is_retryablerequires that an empty podium vault does NOT setprizesDistributedso ops can refill and retry. Do not “fix” Finding 3 by blindly settingprizesDistributed = trueon zero without resolving this contradiction.Recommended fix (pick a coherent product story)
Deliver explicit on-chain signalling plus closed replay semantics without bricking refill:
prizePool == 0afterended(e.g.PrizePoolEmpty(address podiumPool)orPodiumSettlementSkipped(reason)) indexed fields as needed — distinct fromPrizesDistributed.PrizesDistributed= vault drained successfully; new event / state = “attempted settlement with zero balance” vs “distribution complete with zero disbursement”.podiumSettlementFinalized(ornonce) once owner acknowledges zero pool afterfinalizeWarbowPodium/preconditions OR block second successful distribution path — exact design requires product/legal signoff (must preserve retry when vault was griefed empty then restored per existing test).Alternative documentation-only path (if contracts unchanged): indexer treats
SaleEnded+ persisteddistributedPrizesattempts + subgraph rules — weaker on-chain guarantee.Acceptance criteria
onlyOwner distributePrizeswhenprizePool == 0: observer-visible outcome (event and/or documented state machine).docs/operations/final-signoff-and-value-movement.mdstays aligned with the chosen canonical signal (PrizesDistributedvs supplemental events).Verification checklist
forge test— extend/replacetest_distributePrizes_empty_vault_is_retryable/dustcases for new semantics.INV-INDEXER-112matrix cross-check).docs/testing/invariants-and-business-logic.md+ strategy pointers perdocs/testing/strategy.md.yieldomega-guardrailsskill: link if invariant added for prize distribution signals (#55 corridor).Repo touch list
contracts/src/TimeCurve.sol, possiblyPodiumPoolinterfacecontracts/test/TimeCurve.t.sol(prize griefing / retry section)indexer/event decode + schema if new eventdocs/operations/final-signoff-and-value-movement.md,invariants-and-business-logic.md,docs/indexer/design.mdif neededmentioned in issue #131
mentioned in commit
aa4907475ementioned in issue #129
mentioned in commit
15240cd774mentioned in commit
170d7b7823Verification update (agent)
On-chain (#133) status: Already on
mainprior to this pass —distributePrizeswith zeroPodiumPoolbalance requiresreservePodiumPayoutsEnabled, emitsPrizesSettledEmptyPodiumPool, setsprizesDistributed = true(noPrizesDistributed, no refill/retry). SeeTimeCurve.solarounddistributePrizes.This MR: documentation / agent-facing consistency only (commit on main): stale invariant rows that still described retryable empty vault, incorrect
INV-WARBOW-129-DIST, andverify-timecurve-post-end-gates-anvil.shmessaging were aligned with the shipped semantics.Checklist for @brouie
cast abi-event "PrizesSettledEmptyPodiumPool(address)"/ receipt shows indexedpodiumPoolon empty settlement txs.prizesDistributed()istrue; seconddistributePrizes()revertsTimeCurve: prizes done.forge test --match-test test_distributePrizes_empty -— includestest_distributePrizes_empty_vault_emits_empty_settlement_and_locksand gate revert test.cargo test roundtrip_prizes_settled_empty_podium_pool(decoder round-trip).Issue left open per maintainer request.
@PlasticDigits — verification PASS. walked your checklist:
PrizesSettledEmptyPodiumPool(address)event present inTimeCurve.solL199-200 with indexedpodiumPoolrecipient.distributePrizesempty-pool path at L841-870 setsprizesDistributed = true, requiresreservePodiumPayoutsEnabled(so it does NOT short-circuit the gate anymore), and emits cleanly without pulling slices.distributePrizes()after empty settlement revertsTimeCurve: prizes done— locked correctly.forge test --match-test test_distributePrizes_empty -green: 4/4 PASS —test_distributePrizes_empty_vault_emits_empty_settlement_and_locks,test_distributePrizes_empty_pool_reverts_when_reserve_podium_payouts_disabled(gate revert), plusempty_defended_forwards_20pct_to_protocolandempty_time_booster_forwards_time_slice_to_protocolcovering the partial-empty paths.cargo test roundtrip_prizes_settled_empty_podium_poolgreen. decoder picks up the event, persist branch inserts intoidx_timecurve_prizes_settled_empty_podium_pool(migration20260504193000),reorg.rsincludes the table in rollback list.docs/testing/invariants-and-business-logic.mdreads cleanly — empty-pool settlement is now terminal (no retry-on-refill), distinct fromPrizesDistributedfor non-empty path. INV-WARBOW-129-DIST + post-end-gates anvil script messaging are aligned.the previous
empty_vault_is_retryablesemantic is gone — replaced by terminal lock + dedicated event. cleaner indexer story than the old retry-on-refill model. donation-griefing surface closed.ready for close on your side.
mentioned in issue #160
mentioned in issue #207