Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
Submission Details
Impact: medium
Likelihood: high

One global s_earnTimer.

Author Revealed upon completion

Description

  • The Snowman Merkle Airdrop allow users to claim one free snow token once a week by checking if one week has passed since the token have been claimed. The issue is this variable is global and not user-specific. Only one user can claim the token.

function earnSnow() external canFarmSnow {
if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) {
revert S__Timer();
}
_mint(msg.sender, 1);
s_earnTimer = block.timestamp; // <@
}

Risk

Likelihood:

  • High:

    Anyone user can invoke the function, no special permissions required.

Impact:

  • Medium:

    It denies other users from claiming their free weekly snow token. Whenever someone calls the earnSnow or buySnow functions, the timer is reset for everyone. Meaning legitimate users essentially lose their free weekly token and may abandon the protocol.


Proof of Concept

The following code calls the function earnSnow from the contract Snow.sol as "ashley" and then as "jerry". The balance of "Ashley" increases by one whereas jerry's balance remains 0.

Replace the "testCanEarnSnow()" function in TestSnow.t.sol with this code. Then run forge test --match-contract TestSnow -vvv

function testCanEarnSnow() public {
vm.prank(ashley);
snow.earnSnow();
vm.expectRevert();
vm.prank(jerry);
snow.earnSnow();
assert(snow.balanceOf(jerry) == 0);
assert(snow.balanceOf(ashley) == 1);
vm.prank(ashley);
vm.expectRevert(); // Should revert because 1 week has not passed
snow.earnSnow();
vm.warp(block.timestamp + 1 weeks);
vm.prank(ashley);
snow.earnSnow(); // Should not revert as 1 week has passed
assert(snow.balanceOf(ashley) == 2);
}

Recommended Mitigation

Change the type of s_earnTimer from uint256 to mapping(address => uint256) so the timer is set for each user individually instead of one global variable that resets for everyone once someone executes the earnSnow function or buys a snow token.

Snow.sol:30

- uint256 private s_earnTimer;
+ mapping(address => uint256) private s_earnTimer;

Snow.sol:88

- s_earnTimer = block.timestamp;
+ s_earnTimer[msg.sender] = block.timestamp;

Snow.sol:93-100

- function earnSnow() external canFarmSnow {
- if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) {
- revert S__Timer();
- }
- _mint(msg.sender, 1);
- s_earnTimer = block.timestamp;
- }
+ function earnSnow() external canFarmSnow {
+ if (s_earnTimer[msg.sender] != 0 && block.timestamp < (s_earnTimer[msg.sender] + 1 weeks)) {
+ revert S__Timer();
+ }
+ _mint(msg.sender, 1);
+ s_earnTimer[msg.sender] = block.timestamp;
+ }

Support

FAQs

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