Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

### [H-4] The ```SnowmanAirdrop::claimSnowman``` Function Logic Conflicts with Stated Documentation

[H-4] The SnowmanAirdrop::claimSnowman Function Logic Conflicts with Stated Documentation

Description

Documentation says that Third-Party Claims are Allowed means user(bob) can recieve the airdrop
on behalf of other user(alice).But SnowmanAirdrop::claimSnowman Function current Logic does
not allow Third-party Claims.

@>if (i_snow.balanceOf(receiver) == 0) {
revert SA__ZeroAmount();
}

Here we are checking the balance of snow tokens of receiver, instead of
msg.sender.This mismatch causes third-party claims to revert every time, as the balance of the receiver
is irrelevant in this scenario.

Impact:

1.Third-Party claims are reverted Everytime due to incorrect balance check,Breaking the
protcol functionality.

2.Mismatch Between users and function behaviour creates confusion for users.

3.Current Implemenation deviates from standard practices of Merkle-Tree Based airdrops.

Proof of Concept

User Scenario:

->Bob wants alice to claim his airdrop.

->Bob will call SnowmanAirdrop::claimSnowman Function with
receiver as alice.

->Bob will get reverted everytime due to incorrect balance Check
of receiver instead of the msg.sender (Bob)

->Since Alice does not hold Snow tokens (as the tokens are held by Bob),
the transaction reverts with the SA__ZeroAmount error.

function test_claimingonbehalfofother() public {
vm.startPrank(bob);
snow.approve(address(airdrop), type(uint).max);
snow.buySnow{value: 1e18}(1);
bytes32 messageHash = airdrop.getMessageHash(alice);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(alicekey, messageHash);
airdrop.claimSnowman(alice, PROOF, v, r, s);
vm.stopPrank();
/*
[118328] OwnTest::test_claimingonbehalfofother()
├─ [0] VM::startPrank(bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e])
│ └─ ← [Return]
├─ [26949] Snow::approve(SnowmanAirdrop: [0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77])
│ ├─ emit Approval(owner: bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e], spender: SnowmanAirdrop: [0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9], value: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77])
│ └─ ← [Return] true
├─ [58241] Snow::buySnow{value: 1000000000000000000}(1)
│ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e], value: 1)
│ ├─ [0] console::log("ether block") [staticcall]
│ │ └─ ← [Stop]
│ ├─ emit SnowBought(buyer: bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e], amount: 1)
│ └─ ← [Return]
├─ [4920] SnowmanAirdrop::getMessageHash(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6]) [staticcall]
│ ├─ [3284] Snow::balanceOf(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6]) [staticcall]
│ │ └─ ← [Return] 0
│ └─ ← [Revert] SA__ZeroAmount()
└─ ← [Revert] SA__ZeroAmount()

Recommended Mitigation

1.To Allow the current Documented Functionality,Check the snow tokens
Balance Of msg.sender(Bob),not reciever(alice).

2.This way users can claim their own airdrop and Third-Party Claims
also gets Executed as expected.

function claimSnowman(
address receiver,
bytes32[] calldata merkleProof,
uint8 v,
bytes32 r,
bytes32 s
) external nonReentrant {
if (receiver == address(0)) {
revert SA__ZeroAddress();
}
-(i_snow.balanceOf(receiver) == 0)
+if (i_snow.balanceOf(msg.sender) == 0) {
revert SA__ZeroAmount();
}
if (!_isValidSignature(receiver, getMessageHash(receiver), v, r, s)) {
revert SA__InvalidSignature();
}
-uint256 amount = i_snow.balanceOf(receiver);
+uint256 amount = i_snow.balanceOf(msg.sender);
bytes32 leaf = keccak256(
bytes.concat(keccak256(abi.encode(receiver, amount)))
);
if (!MerkleProof.verify(merkleProof, i_merkleRoot, leaf)) {
revert SA__InvalidProof();
}
-i_snow.safeTransferFrom(receiver, address(this), amount);
+i_snow.safeTransferFrom(msg.sender, address(this), amount);
s_hasClaimedSnowman[receiver] = true;
emit SnowmanClaimedSuccessfully(receiver, amount);
i_snowman.mintSnowman(receiver, amount);
}
// >>> INTERNAL FUNCTIONS
function _isValidSignature(
address receiver,
bytes32 digest,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (bool) {
(address actualSigner, , ) = ECDSA.tryRecover(digest, v, r, s);
return actualSigner == receiver;
}
// >>> PUBLIC VIEW FUNCTIONS
function getMessageHash(address receiver) public view returns (bytes32) {
-if (i_snow.balanceOf(receiver) == 0)
+if (i_snow.balanceOf(msg.sender) == 0) {
revert SA__ZeroAmount();
}
-uint256 amount = i_snow.balanceOf(receiver);
+uint256 amount = i_snow.balanceOf(msg.sender);
return
_hashTypedDataV4(
keccak256(
abi.encode(
MESSAGE_TYPEHASH,
SnowmanClaim({receiver: receiver, amount: amount})
)
)
);
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Appeal created

wolf_kalp Submitter
5 months ago
yeahchibyke Lead Judge
5 months ago
yeahchibyke Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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