fix(reward): scope replay guard by created_tx — allow legit repeat awards#53
Merged
Conversation
…ards The contract's reward() has no uniqueness check: the same (action, receiver, awarder) can legitimately be awarded again for a repeatable automatic action. The replay guard keyed on the trio alone would silently drop such a second award (never occurred in prod data, but allowed by the chain). Stamp rewards.created_tx (column added by backend migration 20260702120000) and key the guard on (trio + created_tx), falling back to trio-only for legacy NULL rows — identical to the old behavior for pre-column data, exact replay detection for everything new. DEPLOY ORDER: requires the backend migration to be live first (the insert now writes created_tx). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lucca65
marked this pull request as ready for review
July 2, 2026 11:40
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.
DRAFT until backend #344 is merged + deployed — this writes the
rewards.created_txcolumn that migration20260702120000creates. Deploying this first would fail every reward insert.Why
Contract audit finding:
reward()has no uniqueness check — the same(action, receiver, awarder)can legitimately be awarded repeatedly (repeatable automatic actions with usages). The replay guard shipped in #50 keys on the trio alone, so it would silently drop a legitimate second award. Never triggered in prod data (zero repeat trios exist), but it's a correctness hole against chain semantics.What
created_txon reward inserts (matching every other chain-written table).(action, receiver, awarder) AND (created_tx = this tx OR created_tx IS NULL):_processed_actionsledger (feat: persistent applied-action ledger — replay-safe reindex #52) remains the first line for anything indexed after it went live.Verified: criteria syntax (
or+ null →IS NULL) exercised against the dev DB with the migrated column; StandardJS clean.🤖 Generated with Claude Code