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

`Airdrop:: claim` has dependency on `Soulmate:: isDivorced` which will return false for divorced user, making them claim airdrop

Summary

Claim function allows divorced soulmates to claim airdrop

Vulnerability Details

Airdrop:: claim function is meant to reward soulmates for staying along, more time they will be along, more tokens they will be earning.
Here is the function -

// Both partner of a couple can claim their own token every days.
// Financiary dependency is important in a couple.
/// @notice Claim tokens. Every person who have a Soulmate NFT token can claim 1 LoveToken per day.
function claim() public {
// No LoveToken for people who don't love their soulmates anymore.
@> if (soulmateContract.isDivorced()) revert Airdrop__CoupleIsDivorced();
// 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
);
}

If we notice highlighted line, it call the function isDivorced on soulmate contract to check if caller is divorced or not. But this approach has flaw.

When the function is called on soulmate contract, msg.sender is the airdrop contract. so it will return false everytime, no matter if a couple is divorced or not. This will make divorced couples to claim the airdrop, which they was not supposed to.

POC

In existing AirdropTest.t.sol file, import following line

+ import {console2} from "forge-std/Test.sol";

then add the following test -

function testClaimWhenSoulmatesAreDivorced() public {
_mintOneTokenForBothSoulmates();
vm.startPrank(soulmate1);
soulmateContract.getDivorced();
console2.log("soulmate1 got divorce:", soulmateContract.isDivorced() );
vm.stopPrank();
vm.prank(soulmate2);
console2.log("soulmate2 got divorce:", soulmateContract.isDivorced() );
vm.stopPrank();
/// let's try to claim airdrop now
vm.startPrank(soulmate1);
vm.warp(block.timestamp + 7 days);
uint256 balanceBefore = loveToken.balanceOf(soulmate1);
airdropContract.claim();
uint256 balanceAfter = loveToken.balanceOf(soulmate1);
console2.log("love token claimed by soulmate1:", balanceAfter - balanceBefore);
vm.stopPrank();
vm.startPrank(soulmate2);
uint256 balanceBefore1 = loveToken.balanceOf(soulmate2);
airdropContract.claim();
uint256 balanceAfter1 = loveToken.balanceOf(soulmate2);
console2.log("love token claim by soulmate2:", balanceAfter1 - balanceBefore1);
vm.stopPrank();
}

Now, run the forge test --mt testClaimWhenSoulmatesAreDivorced -vv in your terminal. You'll get following output -

[⠢] Compiling...
[⠒] Compiling 1 files with 0.8.23
[⠢] Solc 0.8.23 finished in 1.89s
Running 1 test for test/unit/AirdropTest.t.sol:AirdropTest
[PASS] testClaimWhenSoulmatesAreDivorced() (gas: 401617)
Logs:
soulmate1 got divorce: true
soulmate2 got divorce: true
love token claimed by soulmate1: 7000000000000000000
love token claim by soulmate2: 7000000000000000000
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.08ms
Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)

Impact

Loss of love tokens, that could have been distributed to more soulmates for longer run.

Tools Used

Manual Review, Foundry

Recommendations

Here is recomendation to fix it -
In Soulmate contract update the isDivorced function as follows -

function isDivorced(address user) public view returns (bool) {
return divorced[user];
}

then update the claim function like below:

function claim() public {
// No LoveToken for people who don't love their soulmates anymore.
- if (soulmateContract.isDivorced()) revert Airdrop__CoupleIsDivorced();
+ if (soulmateContract.isDivorced(msg.sender)) revert Airdrop__CoupleIsDivorced();
// 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-isDivorced-wrong-check

Support

FAQs

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