SNARKeling Treasure Hunt

First Flight #59
Beginner FriendlyGameFiFoundry
100 EXP
View results
Submission Details
Severity: low
Valid

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

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.

Updates

Lead Judging Commences

s3mvl4d Lead Judge 18 days ago
Submission Judgement Published
Validated
Assigned finding tags:

incorrect event parameter

The event is declared as event `Claimed(bytes32 indexed treasureHash, address indexed recipient);`, which clearly indicates that the second indexed field is meant to represent the reward recipient, but `claim()` emits `Claimed(treasureHash, msg.sender)` instead of `Claimed(treasureHash, recipient)`, even though the ETH transfer is sent to recipient and the proof itself is constructed around the public inputs (treasureHash, recipient). As a standalone finding, this is appropriately low severity because it is fundamentally an event/accounting inconsistency rather than a direct loss-of-funds issue: the core state transition and payout still follow the intended recipient, but off-chain consumers reading the event log will observe incorrect metadata about who was associated with the claim.

Support

FAQs

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

Give us feedback!