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

Airdrop Exploitation by Waiting Unpaired Souls

Summary

Airdrop::claim allows unpaired participants, specifically those who have initiated the minting process but are not yet paired with a soulmate, to illegitimately access and claim love token rewards from the Airdrop Vault. This issue stems from a lack of adequate validation in the claim process, particularly failing to account for the state where soulmateContract.idToCreationTimestamp(soulmateContract.ownerToId(msg.sender)) returns zero, indicative of a user in a waiting state for pairing. This loophole can lead to significant unauthorized claims, undermining the fairness and integrity of the reward distribution framework.

Vulnerability Details

Airdrop::claim fails to account for users in a "waiting" state, identifiable when soulmateContract.idToCreationTimestamp(soulmateContract.ownerToId(msg.sender)) returns zero. The protocol's logic intends for only paired users to access these rewards; however, due to this oversight, any user who has merely initiated a minting request can exploit this loophole to claim rewards, bypassing the intended eligibility criteria.

Proof of code:

function test_soulInWaitingCanStealFundsFromAirdropVault() public {
uint256 balanceUser1;
uint256 balanceAirdropVault;
uint256 currentUnixEpoch = 1707922532;
console2.log("timestamp: ", block.timestamp); // 1 by default on anvil
vm.warp(block.timestamp + currentUnixEpoch);
console2.log("timestamp: ", block.timestamp);
vm.startPrank(user1);
soulmateContract.mintSoulmateToken();
airdropContract.claim();
vm.stopPrank();
balanceUser1 = loveToken.balanceOf(user1);
balanceAirdropVault = loveToken.balanceOf(address(airdropVault));
assertLt(0, balanceUser1);
assertGt(500_000_000 ether, balanceAirdropVault);
assertEq(balanceUser1 + balanceAirdropVault, 500_000_000 ether);
}

Impact

Unauthorized claims by unpaired users can lead to the depletion of resources in the Airdrop Vault, intended for legitimately paired users, undermining trust in the protocol's governance and potentially destabilizing its economic model

Tools Used

Manual review.

Recommendations

Implement an appropriate check in Airdrop::claim as follows:

function claim() public {
// No LoveToken for people who don't love their soulmates anymore.
if (soulmateContract.isDivorced()) revert Airdrop__CoupleIsDivorced();
+ require( soulmateContract.idToCreationTimestamp(soulmateContract.ownerToId(msg.sender)) != 0, "Unpaired souls cannot claim!");
// Calculating since how long soulmates are reunited
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.