SNARKeling Treasure Hunt

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

`Claimed` Event Emits `msg.sender` Instead of Actual Reward Recipient

Author Revealed upon completion

Root Cause + Impact

The Claimed event on line 111 emits msg.sender as the recipient, but the ETH reward is sent to the recipient parameter on line 107. Since the contract enforces recipient != msg.sender (line 86), the event always logs the wrong address. All off-chain indexers and dashboards display incorrect claim data.

Description

  • The Claimed event should record which address received the treasure reward, enabling off-chain systems (block explorers, leaderboards, analytics) to track the treasure hunt accurately.

  • The event emits msg.sender (transaction submitter) instead of recipient (actual ETH receiver). The contract explicitly forbids recipient == msg.sender on line 86, guaranteeing these are always different addresses. Every claim event in the contract's lifetime has the wrong recipient logged.

// TreasureHunt.sol
// Line 44: event definition names the second parameter "recipient"
event Claimed(bytes32 indexed treasureHash, address indexed recipient);
// Line 86: recipient and msg.sender are guaranteed to be different
if (recipient == address(0) || recipient == address(this) || recipient == owner || recipient == msg.sender) revert InvalidRecipient();
// Line 107: ETH goes to recipient
(bool sent, ) = recipient.call{value: REWARD}("");
// Line 111: but event logs msg.sender
@> emit Claimed(treasureHash, msg.sender);

Risk

Likelihood: Occurs on every successful claim() call — 100% of emitted events have the wrong address. The recipient != msg.sender check on line 86 guarantees the logged address is never the actual recipient.

Impact: Off-chain indexers, subgraphs, and block explorers attribute treasure claims to the wrong addresses. Leaderboards and analytics built on event data display entirely incorrect participant information. Cross-contract composability breaks: any downstream contract listening for Claimed events (airdrops, NFT rewards) operates on the wrong address. Audit trails are broken — on-chain event records contradict actual fund flows.

Proof of Concept

On any successful claim, the participant submits claim(proof, treasureHash, recipient) with recipient != msg.sender (enforced by line 86). Line 107 transfers 10 ETH to recipient, but line 111 emits Claimed(treasureHash, msg.sender). The indexed recipient topic in the event log therefore never matches the address that actually received the funds. This mismatch is systematic and 100% reproducible — not edge-case.

function testExploit_EventLogsWrongRecipient() public {
(bytes memory proof, bytes32 treasureHash, address payable recipient) = _loadFixture();
vm.startPrank(participant);
vm.expectEmit(true, true, false, false);
// Event will log participant (msg.sender), NOT recipient
emit Claimed(treasureHash, participant);
hunt.claim(proof, treasureHash, recipient);
vm.stopPrank();
assertTrue(participant != recipient);
}

Severity note: no direct loss of funds — ETH still flows to the intended recipient. The harm is data-integrity on a field whose only purpose is off-chain indexing. The mismatch is systematic rather than edge-case, making this a non-trivial correctness bug.

Recommended Mitigation

Emit recipient (the address that actually receives the ETH) instead of msg.sender:

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

If the protocol wants to preserve both the submitter and the recipient, extend the event signature to include both fields instead of overloading one.

Support

FAQs

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

Give us feedback!