Description
claim() emits Claimed(treasureHash, msg.sender) at line 111, but the payout at line 107 goes to recipient. Line 86 explicitly forbids recipient == msg.sender, so these are guaranteed different addresses on every successful claim. The event's indexed parameter is named recipient (line 44) yet contains the submitter. Any off-chain tool filtering Claimed by payee address points at the wrong person.
event Claimed(bytes32 indexed treasureHash, address indexed recipient);
(bool sent, ) = recipient.call{value: REWARD}("");
emit Claimed(treasureHash, msg.sender);
Risk
Likelihood: certain on every successful claim. Impact: indexers, dashboards, and incident-response tooling cannot attribute payouts to the real payee. Gas sponsors or relayers show up in the log instead of the winner. No fund loss.
Proof of Concept
pragma solidity ^0.8.27;
import "forge-std/Test.sol";
import {TreasureHunt} from "../src/TreasureHunt.sol";
import {IVerifier} from "../src/Verifier.sol";
contract PoC_L02 is Test {
event Claimed(bytes32 indexed treasureHash, address indexed recipient);
TreasureHunt hunt;
function setUp() public {
address v = address(0xABCDEF);
vm.etch(v, hex"00");
vm.deal(address(0xDEAD), 200 ether);
vm.prank(address(0xDEAD));
hunt = new TreasureHunt{value: 100 ether}(v);
vm.mockCall(v, abi.encodeWithSelector(IVerifier.verify.selector), abi.encode(true));
}
function test_event_logs_submitter_not_recipient() public {
address submitter = address(0x5E11DE8);
address payable realRecipient = payable(address(0xA11CE));
bytes32 h = bytes32(uint256(1));
vm.expectEmit(true, true, false, false);
emit Claimed(h, submitter);
vm.prank(submitter);
hunt.claim(hex"de", h, realRecipient);
assertEq(realRecipient.balance, 10 ether, "ETH goes to realRecipient");
}
}
Actual trace from the H-01 PoC (ten replays from 0x...BAD):
emit Claimed(treasureHash: 0x0354..., recipient: 0x0000000000000000000000000000000000000Bad)
[x10 — every event shows the attacker, never the real 0xA11CE* recipients that received ETH]
Recommended Mitigation
- emit Claimed(treasureHash, msg.sender);
+ emit Claimed(treasureHash, recipient);