Snowman Merkle Airdrop

AI First Flight #10
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: medium
Likelihood: high
Invalid

SnowmanAirdrop.sol MESSAGE_TYPEHASH has typo "addres" instead of "address"

Description (Root + Impact)

Description:
The MESSAGE_TYPEHASH constant at line 49 contains a typo: "addres" instead of "address". This causes the computed EIP-712 hash to differ from what any standard wallet or frontend would generate.
Impact:

  • Standard EIP-712 wallet implementations will generate different hashes

  • Signatures created by legitimate wallets will fail verification

  • The protocol may be completely unusable without a matching frontend bugfix

  • Users cannot claim their airdrops using standard signing tools

Root Cause (Solidity box)

// @> In SnowmanAirdrop.sol:49, there is a typo
bytes32 private constant MESSAGE_TYPEHASH = keccak256("SnowmanClaim(addres receiver, uint256 amount)");
// ^^^^^^ MISSING 's' - should be "address"
//
// Correct version should be:
// bytes32 private constant MESSAGE_TYPEHASH = keccak256("SnowmanClaim(address receiver, uint256 amount)");

Risk

Likelihood:

  • Affects 100% of signature verification attempts

  • Any standard EIP-712 implementation will produce wrong hash

  • Guaranteed mismatch between frontend and contract
    Impact:

  • All legitimate claim attempts using standard tools will fail

  • Protocol is effectively unusable without custom frontend

  • Users lose trust in the protocol

Proof of Concept (Solidity box)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Test, console2} from "forge-std/Test.sol";
contract TypeHashTypoPOC is Test {
function testM01_TypeHashTypo() public {
// Step 1: Calculate the CORRECT EIP-712 typehash (what wallets generate)
bytes32 correctTypehash = keccak256("SnowmanClaim(address receiver, uint256 amount)");
console2.log("Step 1 - Correct typehash (standard EIP-712):");
console2.logBytes32(correctTypehash);
// Step 2: Calculate the BUGGY typehash (what the contract uses)
bytes32 buggyTypehash = keccak256("SnowmanClaim(addres receiver, uint256 amount)");
console2.log("Step 2 - Buggy typehash (contract has typo 'addres'):");
console2.logBytes32(buggyTypehash);
// Step 3: Demonstrate they are NOT equal
assertNotEq(correctTypehash, buggyTypehash, "Typehashes should not match due to typo");
console2.log("Step 3 - Hashes DO NOT match!");
// Step 4: Show the consequence
console2.log("");
console2.log("CONSEQUENCE:");
console2.log("- Wallet signs with correct 'address'");
console2.log("- Contract verifies with buggy 'addres'");
console2.log("- Signature verification ALWAYS fails");
console2.log("- Protocol is UNUSABLE with standard wallets");
}
}

Steps to reproduce:

  1. Hash the correct EIP-712 type string with "address"

  2. Hash the buggy type string with "addres" (missing 's')

  3. Compare the two hashes - they are different

  4. Any signature created with correct string fails verification
    Run command: forge test --match-test testM01_TypeHashTypo -vvv

Recommended Mitigation (diff box)

contract SnowmanAirdrop is EIP712, ReentrancyGuard {
// ... existing code ...
- bytes32 private constant MESSAGE_TYPEHASH = keccak256("SnowmanClaim(addres receiver, uint256 amount)");
+ bytes32 private constant MESSAGE_TYPEHASH = keccak256("SnowmanClaim(address receiver, uint256 amount)");
// ... rest of contract ...
}

Mitigation explanation:

  1. Fix the typo: change "addres" to "address"

  2. This is a single character fix ('s' was missing)

  3. After fix, contract typehash will match standard EIP-712 implementations

  4. Signatures from MetaMask, Ledger, and other wallets will work correctly

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 10 days 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!