SNARKeling Treasure Hunt

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

Claimed Event Emits msg.sender Instead of recipient, Breaking Off-Chain Tracking

Author Revealed upon completion

Finding 3: Claimed Event Emits msg.sender Instead of recipient, Breaking Off-Chain Tracking

Root + Impact

Wrong address emitted in event + off-chain monitoring fails to identify actual payout recipient

Description

  • The Claimed event is designed to log which address received the reward payout for a given treasure. Off-chain indexers, subgraphs, and monitoring systems rely on this event to track which participants have been paid and to reconcile on-chain payouts with real-world treasure finders.

  • The Claimed event emits msg.sender (the address that submitted the transaction) instead of recipient (the address that actually received the ETH reward). Since the protocol intentionally allows a claimer to specify a different recipient address (the recipient is bound into the ZK proof as a public input), the event consistently logs the wrong address whenever the caller and recipient differ.

event Claimed(bytes32 indexed treasureHash, address indexed recipient); // @> event signature declares "recipient"
function claim(bytes calldata proof, bytes32 treasureHash, address payable recipient) external nonReentrant() {
// ...
(bool sent, ) = recipient.call{value: REWARD}(""); // @> ETH is sent to `recipient`
require(sent, "ETH_TRANSFER_FAILED");
emit Claimed(treasureHash, msg.sender); // @> emits `msg.sender` instead of `recipient`
}

Risk

Likelihood:

  • This occurs on every claim where the participant submits the proof on behalf of a different recipient address — a core use case the protocol is explicitly designed to support

  • Any off-chain system consuming the Claimed event to build payout records receives the wrong address on every such claim

Impact:

  • Off-chain indexers and subgraphs record the transaction submitter as the payout recipient instead of the actual ETH receiver, corrupting payout audit trails

  • Treasure hunt organizers cannot accurately reconcile which real-world participants received rewards, undermining the transparency of the hunt

  • Front-end dashboards displaying "who received rewards" show incorrect data to users

Proof of Concept

function testClaimedEventEmitsWrongAddress() public {
(
bytes memory proof,
bytes32 treasureHash,
address payable recipient
) = _loadFixture();
// recipient is a third party (not msg.sender)
// msg.sender in this test is address(this) from the test contract
vm.prank(participant);
vm.expectEmit(true, true, false, true);
emit Claimed(treasureHash, participant); // This currently PASSES — event emits msg.sender (participant)
hunt.claim(proof, treasureHash, recipient);
// But the actual ETH was sent to `recipient`, not `participant`
// The event is misleading — it says `participant` got paid, but `recipient` got the ETH
assertEq(recipient.balance, INITIAL_PARTICIPANT_BALANCE + 10 ether); // recipient got the ETH
// An off-chain indexer reading the event would think `participant` received funds
}

Recommended Mitigation

event Claimed(bytes32 indexed treasureHash, address indexed recipient);
function claim(...) external nonReentrant() {
// ...
- emit Claimed(treasureHash, msg.sender);
+ emit Claimed(treasureHash, recipient);
}

Support

FAQs

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

Give us feedback!