AirDropper

AI First Flight #5
Beginner FriendlyDeFiFoundry
EXP
View results
Submission Details
Impact: low
Likelihood: high
Invalid

MerkleRootUpdated event is declared but never emitted — dead code that implies a non-existent merkle root update mechanism

Root + Impact

Description

  • MerkleAirdrop declares event MerkleRootUpdated(bytes32 newMerkleRoot). The existence of this event signals to integrators and auditors that a function exists to update the Merkle root — a critical parameter that determines which addresses are eligible to claim.

  • No function in MerkleAirdrop emits MerkleRootUpdated. The merkle root is stored in bytes32 private immutable i_merkleRoot, which is set at construction and can never change. The event is unreachable dead code that misleads every reader about the contract's actual capabilities.

// src/MerkleAirdrop.sol
bytes32 private immutable i_merkleRoot; // @> immutable — cannot be updated
event MerkleRootUpdated(bytes32 newMerkleRoot); // @> declared but never emitted
// No setMerkleRoot(), no updateRoot(), no function that emits this event

Risk

Likelihood:

  • Every reader of the contract sees the event and reasonably concludes an update path exists.

Impact:

  • Integrators building dashboards or monitoring that listen for MerkleRootUpdated will never receive it, causing silent failures in their off-chain systems.

  • The presence of the event creates false confidence that the Merkle root can be corrected if errors are found in the airdrop list — in reality, any correction requires a full redeployment.

Proof of Concept

The test records all logs emitted during a full claim cycle and confirms that no event with the MerkleRootUpdated topic is ever produced. It also reads i_merkleRoot before and after the claim to confirm the value is unchanged — proving the root is immutable and the event can never fire.

function testMerkleRootUpdatedNeverEmitted() public {
vm.recordLogs();
// Full claim cycle: fund, claim, collect fees
vm.prank(alice);
airdrop.claim{value: FEE}(alice, ALICE_AMOUNT, aliceProof);
vm.prank(owner);
airdrop.claimFees();
Vm.Log[] memory logs = vm.getRecordedLogs();
bytes32 updatedTopic = keccak256("MerkleRootUpdated(bytes32)");
for (uint256 i = 0; i < logs.length; i++) {
assertNotEq(logs[i].topics[0], updatedTopic, "MerkleRootUpdated was emitted");
}
// Root unchanged — immutable, no setter exists
assertEq(airdrop.getMerkleRoot(), ROOT);
}

No log with the MerkleRootUpdated topic appears across the entire lifecycle, confirming the event is unreachable dead code.

Recommended Mitigation

Remove the unused event, or replace immutable with a state variable and implement the setter the event implies:

- bytes32 private immutable i_merkleRoot;
+ bytes32 private s_merkleRoot;
constructor(bytes32 merkleRoot, IERC20 airdropToken) Ownable(msg.sender) {
- i_merkleRoot = merkleRoot;
+ s_merkleRoot = merkleRoot;
i_airdropToken = airdropToken;
}
+ function setMerkleRoot(bytes32 newMerkleRoot) external onlyOwner {
+ s_merkleRoot = newMerkleRoot;
+ emit MerkleRootUpdated(newMerkleRoot);
+ }

If root mutability is not desired, simply delete the event declaration to eliminate the misleading signal.

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 3 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!