fix: secure activity rewards against forged sessions and coin farming - #14
Open
Xaxxoo wants to merge 1 commit into
Open
fix: secure activity rewards against forged sessions and coin farming#14Xaxxoo wants to merge 1 commit into
Xaxxoo wants to merge 1 commit into
Conversation
Threat model: any registered address could call recordActivity() with arbitrary scores and collect DAILY_REWARD_COINS on every call, draining the reward pool. No session verification existed. Contract changes (MathBlocGame.sol): - Daily reward cap: base 10-coin reward is awarded at most once per player per day via dailyRewardClaimed mapping. Perfect-score bonus (20 coins) is still awarded per qualifying session. - EIP-712 session attestation: when owner sets a sessionSigner, every recordActivity call must carry a valid EIP-712 signature covering (player, score, correct, attempts, topic, nonce, deadline). Forged, expired, replayed, and cross-wallet attestations revert. When sessionSigner == address(0), signatures are not required (migration path for the existing deployment). - Per-player nonces prevent replay attacks. - Input bounds: score <= 1000, attempts <= 100, topic <= 32 bytes. - Rate limit: max 10 sessions per player per day. - Emergency pause: owner can pause/unpause all activity recording, registration, and CELO claims via OpenZeppelin Pausable. - Leaderboard empty-array fix (topN=0 or 0 players returns []). Infrastructure: - hardhat.config.ts: enable Cancun EVM target + viaIR for OZ v5.6 compatibility. - lib/useContract.ts: pass deadline=0 and empty signature to the new recordActivity ABI (no-op when signer is not configured). - contracts/scripts/daily-activity.ts: updated keeper call signature. Tests (35 passing): - Daily reward cap: single-day farming yields only 10 coins. - Input bounds: score > 1000, attempts > 100, topic > 32 bytes revert. - Rate limit: 11th same-day session reverts. - EIP-712: valid signature accepted; wrong signer, expired, replayed, and cross-wallet attestations revert; nonce increments correctly. - Emergency pause: registration, activity, and claims blocked. - Session signer management: owner-only set/disable. Closes DogStark#9 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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
Fixes #9
The existing
recordActivity()accepted arbitrary scores from any registered wallet and awardedDAILY_REWARD_COINSon every call — an attacker could farm 100 coins in minutes and drain the CELO reward pool. This PR adds five layers of defense:1. Daily reward cap
The base 10-coin reward is awarded at most once per player per day via a
dailyRewardClaimed[player][day]mapping. Perfect-score bonus (20 coins) is still awarded per qualifying session. This directly prevents same-day coin farming.2. EIP-712 session attestation
When the owner sets a
sessionSigner, everyrecordActivitycall must carry a valid EIP-712 signature covering(player, score, correct, attempts, topic, nonce, deadline). The following attack vectors are tested and revert:When
sessionSigner == address(0), no signature is required — this provides a migration path for the existing deployment.3. Input bounds
score <= 1000per sessionattempts <= 100per sessioncorrect <= attemptstopic.length <= 32bytes4. Rate limiting
Max 10 sessions per player per day via
dailySessionCountmapping.5. Emergency pause
Owner can
pause()/unpause()all activity recording, registration, and CELO claims using OpenZeppelin'sPausable. This provides an operational response mechanism for the deployed contract.Other changes
hardhat.config.ts: Cancun EVM target +viaIRfor OpenZeppelin v5.6 compatibilitylib/useContract.ts: passesdeadline=0and empty signature (no-op when signer not configured)contracts/scripts/daily-activity.ts: updated keeper call to new ABITest coverage (35 tests, all passing)
Migration notes
The new contract has a different ABI for
recordActivity(addsdeadlineandsignatureparameters). A new deployment is required. The migration path:sessionSignerasaddress(0)initially (existing behavior)setSessionSigner(backendAddress)to enable signature verificationTest plan
npx hardhat test— all 35 tests pass🤖 Generated with Claude Code