SNARKeling Treasure Hunt

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

Claimed event emits the caller instead of the actual reward recipient

Root + Impact

Description

  • The Claimed event is intended to log the address that received the reward, but claim() emits msg.sender instead of recipient.

  • As a result, off-chain indexers, UIs, and monitoring tools will record the wrong payout recipient.

emit Claimed(treasureHash, msg.sender);

Risk

Likelihood:

  • This occurs on every successful claim where msg.sender differs from recipient

  • The contract explicitly requires recipient != msg.sender, so the emitted recipient is always wrong for valid claims.

Impact:

  • The actual ETH transfer still goes to the correct recipient, so funds are not directly lost because of this bug.

  • However, event consumers will see incorrect claim data, which can break frontends, analytics, accounting, and dispute resolution.

Proof of Concept

The PoC performs a valid claim from caller while sending the reward to a separate recipient. The transaction succeeds and ETH is transferred to recipient, but the emitted Claimed event records caller as the recipient.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import "forge-std/Test.sol";
import "forge-std/StdJson.sol";
import {TreasureHunt} from "../src/TreasureHunt.sol";
import {HonkVerifier} from "../src/Verifier.sol";
contract TreasureHuntWrongClaimedEventPoC is Test {
using stdJson for string;
HonkVerifier verifier;
TreasureHunt hunt;
address constant owner = address(0xDEADBEEF);
address constant caller = address(0xBEEF);
uint256 constant INITIAL_OWNER_BALANCE = 200 ether;
uint256 constant INITIAL_FUNDING = 100 ether;
event Claimed(bytes32 indexed treasureHash, address indexed recipient);
function setUp() public {
vm.deal(owner, INITIAL_OWNER_BALANCE);
vm.startPrank(owner);
verifier = new HonkVerifier();
hunt = new TreasureHunt{value: INITIAL_FUNDING}(address(verifier));
vm.stopPrank();
}
function _loadFixture()
internal
view
returns (
bytes memory proof,
bytes32 treasureHash,
address payable recipient
)
{
proof = vm.readFileBinary("contracts/test/fixtures/proof.bin");
string memory json = vm.readFile(
"contracts/test/fixtures/public_inputs.json"
);
bytes memory raw = json.parseRaw(".publicInputs");
bytes32[] memory inputs = abi.decode(raw, (bytes32[]));
treasureHash = inputs[0];
recipient = payable(address(uint160(uint256(inputs[1]))));
}
function testPoC_claimedEventEmitsCallerInsteadOfRecipient() public {
(
bytes memory proof,
bytes32 treasureHash,
address payable recipient
) = _loadFixture();
uint256 reward = hunt.REWARD();
uint256 recipientBalanceBefore = recipient.balance;
uint256 callerBalanceBefore = caller.balance;
vm.prank(caller);
// The reward is paid to `recipient`, but the contract emits `caller`
// as the recipient in the Claimed event.
vm.expectEmit(true, true, false, false);
emit Claimed(treasureHash, caller);
hunt.claim(proof, treasureHash, recipient);
// ETH was transferred to the real recipient.
assertEq(recipient.balance, recipientBalanceBefore + reward);
// The caller did not receive the reward.
assertEq(caller.balance, callerBalanceBefore);
// The event was wrong because it emitted `caller`, not `recipient`.
assertTrue(caller != recipient);
}
}

Recommended Mitigation

Emit the actual reward recipient instead of msg.sender.

- emit Claimed(treasureHash, msg.sender);
+ emit Claimed(treasureHash, recipient);
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!