SNARKeling Treasure Hunt

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

Claimed event emits msg.sender instead of recipient, systematically misattributing every payout in on-chain event logs

Author Revealed upon completion

Description

Normal behavior: After a successful claim(), the Claimed event should emit (treasureHash, recipient) where recipient is the address that actually received the ETH reward. Off-chain indexers, explorers, and monitoring dashboards use this event to attribute payouts.

Specific issue: The event is declared with a recipient parameter but the emission uses msg.sender. Because claim() explicitly requires recipient != msg.sender at L86, the emitted "recipient" is guaranteed to be wrong on every successful claim — it's always the caller (e.g. an EOA that submitted the claim), never the actual payee.

// contracts/src/TreasureHunt.sol
// Declaration (L44):
event Claimed(bytes32 indexed treasureHash, address indexed recipient);
// Emission (L111), inside claim():
function claim(bytes calldata proof, bytes32 treasureHash, address payable recipient) external nonReentrant() {
...
if (recipient == address(0) || recipient == address(this) || recipient == owner || recipient == msg.sender) revert InvalidRecipient(); //@> L86: forces recipient != msg.sender
...
(bool sent, ) = recipient.call{value: REWARD}(""); //@> L107: pays `recipient`
require(sent, "ETH_TRANSFER_FAILED");
emit Claimed(treasureHash, msg.sender); //@> BUG: logs `msg.sender` instead of `recipient`
}

Risk

Likelihood: HIGH

Reason 1: Every successful claim triggers this bug — the event is always wrong because recipient != msg.sender is enforced.

Reason 2: Consumers of the event (subgraph indexers, analytics dashboards, monitoring tools) have no way to detect the discrepancy without cross-referencing the ETH transfer trace.

Impact: LOW

Impact 1: Off-chain data integrity is compromised. Every "who got paid" record in any external service that watches Claimed events is incorrect.

Impact 2: Accounting and dispute resolution become unreliable. If the hunt organizer needs to publish a list of winners for tax reporting, transparency, or dispute handling, the event log is systematically wrong and must be reconstructed from the ETH transfers.

No funds are misrouted — the ETH transfer at L107 goes to the correct address. Hence Low severity.

Proof of Concept

function test_claimed_event_misattributes_recipient() public {
// Setup: a legitimate claim.
address payable actualRecipient = payable(address(0xB055));
address caller = address(0x51EA1E4);
bytes32 h = bytes32(uint256(42));
bytes memory proof = hex"deadbeef";
vm.recordLogs();
vm.prank(caller);
hunt.claim(proof, h, actualRecipient);
Vm.Log[] memory logs = vm.getRecordedLogs();
// Find the Claimed event.
bytes32 claimedTopic = keccak256("Claimed(bytes32,address)");
for (uint256 i = 0; i < logs.length; i++) {
if (logs[i].topics[0] == claimedTopic) {
address emittedRecipient = address(uint160(uint256(logs[i].topics[2])));
// Event's "recipient" topic is msg.sender == caller, NOT actualRecipient:
assertEq(emittedRecipient, caller, "event logs msg.sender");
assertTrue(emittedRecipient != actualRecipient, "event recipient != actual recipient");
break;
}
}
// ETH was sent correctly:
assertEq(actualRecipient.balance, hunt.REWARD());
assertEq(caller.balance, 0); // caller received no ETH
}

Recommended Mitigation

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

The one-line fix makes the emitted recipient topic match the actual payee.

If the protocol also wants to track the caller (for audit trail purposes), extend the event with a caller field:

-event Claimed(bytes32 indexed treasureHash, address indexed recipient);
+event Claimed(bytes32 indexed treasureHash, address indexed recipient, address indexed caller);
...
- emit Claimed(treasureHash, msg.sender);
+ emit Claimed(treasureHash, recipient, msg.sender);

Support

FAQs

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

Give us feedback!