SNARKeling Treasure Hunt

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

`Claimed` emits the caller instead of the payout recipient

Author Revealed upon completion

`Claimed` emits the caller instead of the payout recipient

Description

  • The `Claimed` event is the canonical on-chain record that should allow off-chain consumers to identify which treasure was claimed and who received the payout.

  • The implementation emits `msg.sender` in the `recipient` slot even though the ETH transfer is sent to the `recipient` function argument. This causes the event stream to misreport the beneficiary of each successful claim.

event Claimed(bytes32 indexed treasureHash, address indexed recipient);
function claim(bytes calldata proof, bytes32 treasureHash, address payable recipient) external nonReentrant {
// ...
(bool sent,) = recipient.call{value: REWARD}("");
require(sent, "ETH_TRANSFER_FAILED");
// @> the event logs the prover/caller, not the actual reward recipient
emit Claimed(treasureHash, msg.sender);
}

Risk

Likelihood: HIGH

  • Every successful claim where the caller and payout recipient differ produces an incorrect event.

  • The repository already supports this exact usage pattern by allowing a finder to bind a proof to a separate `recipient` address.

Impact: LOW

  • Indexers, analytics, and operational tooling that rely on the event stream will attribute payouts to the wrong address.

  • Incident response and payout accounting become less reliable because logs diverge from actual fund flows.

Proof of Concept

Add this test case to test file and run this test case

function testPoCLow_ClaimedEventLogsCallerInsteadOfRecipient() public {
(bytes memory proof, bytes32 treasureHash, address payable recipient) = _loadFixture();
vm.recordLogs();
vm.prank(PARTICIPANT);
hunt.claim(proof, treasureHash, recipient);
Vm.Log[] memory logs = vm.getRecordedLogs();
bytes32 claimedTopic = keccak256("Claimed(bytes32,address)");
bool found;
for (uint256 i = 0; i < logs.length; i++) {
if (logs[i].emitter == address(hunt) && logs[i].topics.length == 3 && logs[i].topics[0] == claimedTopic) {
found = true;
assertEq(logs[i].topics[1], bytes32(treasureHash));
assertEq(address(uint160(uint256(logs[i].topics[2]))), PARTICIPANT);
assertTrue(address(uint160(uint256(logs[i].topics[2]))) != recipient);
break;
}
}
assertTrue(found, "Claimed event not found");
}

Recommended Mitigation

Change the second params of emitting Claimed from msg.sender to recipient

- 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!