The protocol intentionally supports third-party claiming — a recipient can sign an EIP-712 message and share it with any address to claim on their behalf. For this delegation model to be safe, the signature must be bounded in time and single-use: a deadline prevents the signature from being used indefinitely into the future, and a nonce ensures the signature is invalidated the moment it is used, preventing it from being replayed if the receiver re-acquires the same token balance.
The SnowmanClaim struct contains only receiver and amount — no nonce and no deadline. A signature produced once for a given (receiver, amount) pair is valid for the entire lifetime of the contract and for every future moment at which the receiver's balance equals the signed amount. Once a user signs a delegation message, there is no cryptographic mechanism to revoke, expire or invalidate it. Any third party who holds the signature can use it today, next month, or next year, and user's only recourse is to revoke her ERC20 approval which also prevents the user from self-claiming.
Likelihood:
Every user who delegates their claim to a third party (the protocol's documented feature) produces a signature that remains permanently valid — the delegation cannot be revoked except by revoking the ERC20 approval entirely, which also locks the user out of self-claiming
When a receiver re-acquires the same token amount after a previous claim (via earnSnow or buySnow), getMessageHash produces the identical digest as before — the previously issued signature passes verification again with no indication to the receiver that it is being reused
Impact:
A receiver who issued a delegation signature and later changes their mind (wanting to wait, hold their Snow tokens, or time their claim) cannot revoke that delegation; a third party who holds the old signature can execute the claim at any moment the balance matches, permanently overriding the receiver's current intent
Combined with the separate finding that s_hasClaimedSnowman is never checked before executing a claim, a single delegation signature captured once can be repeatedly replayed every time the receiver re-acquires their original token amount, draining their Snow balance on every cycle
The first test demonstrates the forced-timing attack. Alice signs a delegation at time T=0 intending to claim later on her own terms. She has no cryptographic mechanism to revoke the signature — her only option would be revoking her ERC20 approval, which also prevents self-claiming. The test warps 90 days forward and confirms that Bob can still use Alice's three-month-old signature to execute the claim on her behalf. The assertions confirm Alice's Snow balance is zero and she holds an NFT, both without her current consent.
To run: forge test --match-test test_DelegationSignatureCannotBeRevoked -vvvv
The second test demonstrates the replay attack. Bob consumes Alice's signature at T=0, draining her Snow. Alice re-earns 1 Snow token two weeks later — restoring the same balance she had when she originally signed. The critical assertion assertEq(digest, replayDigest) confirms that getMessageHash produces the byte-for-byte identical digest as before, because the struct contains only receiver and amount with no nonce increment. Bob replays the original signature against this identical digest and succeeds. The final assertion assertEq(nft.balanceOf(alice), 2) confirms Alice has been claimed against twice using a single signature she issued once.
To run: forge test --match-test test_ExpiredDelegationReplayedAfterTokenReacquisition -vvvv
Add a nonce to invalidate a signature after it is used, and a deadline to limit how far into the future the delegation remains valid. Both fields must be added to the SnowmanClaim struct, the MESSAGE_TYPEHASH type string, and the abi.encode call inside getMessageHash so that signers commit to them and callers cannot strip or forge them.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.