SNARKeling Treasure Hunt

First Flight #59
Beginner FriendlyGameFiFoundry
100 EXP
Submission Details
Impact: low
Likelihood: high

`Claimed` event indexes `msg.sender`, not the payout `recipient`

Author Revealed upon completion

Root + Impact

Description

  • Normal behavior: Events should match their signatures and NatSpec so indexers can rely on indexed fields. The Claimed event’s second parameter is named recipient, so listeners expect the payout address.

  • Problem: The contract pays recipient but emits msg.sender as the second indexed argument. The contract also forbids recipient == msg.sender (InvalidRecipient), so the indexed address is never the payout address. Off-chain systems that filter or display “recipient” from logs will show the claimer, not who received the ETH.

// @> event documents "recipient" as second indexed topic
event Claimed(bytes32 indexed treasureHash, address indexed recipient);
// @> payout goes to recipient
(bool sent, ) = recipient.call{value: REWARD}("");
// @> second topic is msg.sender, not recipient
emit Claimed(treasureHash, msg.sender);

Risk

Likelihood:

  • Every successful claim produces logs with this shape.

  • Explorers, subgraphs, and accounting pipelines often index Claimed without re-reading calldata.

Impact:

  • Misattribution of who was paid; reconciliation errors and misleading UI.

  • No direct on-chain fund loss; integrity and operational risk dominate.

Proof of Concept

Explanation: Compare the declared event, the transfer target, and the emit. The second indexed topic is always msg.sender (the participant), while ETH is sent to recipient (a different address whenever the participant uses a separate payout address).

Supporting code (contract, contest repo):

// contracts/src/TreasureHunt.sol
if (recipient == address(0) || recipient == address(this) || recipient == owner || recipient == msg.sender) revert InvalidRecipient();
// ...
(bool sent, ) = recipient.call{value: REWARD}("");
emit Claimed(treasureHash, msg.sender);

Supporting check (run any passing claim test and decode logs):

forge test --match-test testClaimHappyPath -vvvv

Inspect Claimed topics: topic2 matches msg.sender, not the recipient argument passed to claim.

Recommended Mitigation

Explanation: Align the event payload with its name and NatSpec. Emit the actual payee (recipient) as the second indexed field if that is what integrators should track. If both claimer and payee matter, add a separate indexed field or a second event; update frontend and indexer schemas accordingly.

- emit Claimed(treasureHash, msg.sender);
+ emit Claimed(treasureHash, recipient);

Optional: rename the parameter for clarity if you also emit msg.sender elsewhere, for example event Claimed(bytes32 indexed treasureHash, address indexed payee, address indexed claimer) if both are required.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!