Summary
The claim function in the Airdrop contract presents a vulnerability wherein divorced individuals can erroneously claim rewards. This vulnerability stems from the function's failure to accurately verify the divorce status of the associated soulmate. Consequently, divorced users can exploit this flaw to claim rewards intended for non-divorced soulmates.
Vulnerability Details
In the claim function, the line:
if (soulmateContract.isDivorced()) revert Airdrop__CoupleIsDivorced();
This line checks if the caller's address (msg.sender) is divorced according to the Soulmate contract. However, the isDivorced() function in the Soulmate contract does not take any arguments, and it always checks the divorce status for msg.sender.
Since msg.sender in the Airdrop contract is the address that is calling the claim function, not necessarily the soulmate address associated with the claimed NFT, this check does not accurately determine if the soulmate associated with the claimed NFT is divorced.
function claim() public {
if (soulmateContract.isDivorced()) revert Airdrop__CoupleIsDivorced();
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;
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
);
}
function testClaimDivorced() public {
address alice = address(0x1);
address bob = address(0x2);
vm.startPrank(alice);
uint256 aliceTokenId = soulmateContract.mintSoulmateToken();
vm.stopPrank();
vm.startPrank(bob);
uint256 bobTokenId = soulmateContract.mintSoulmateToken();
vm.stopPrank();
assertEq(soulmateContract.soulmateOf(alice), bob);
assertEq(soulmateContract.soulmateOf(bob), alice);
vm.startPrank(alice);
vm.warp(block.timestamp + 200 days + 1 seconds);
soulmateContract.getDivorced();
bool isDivorced = soulmateContract.isDivorced();
assertTrue(isDivorced, "Alice should be divorced.");
bytes4 expectedRevertReason = bytes4(keccak256("Airdrop__CoupleIsDivorced()"));
vm.expectRevert(expectedRevertReason);
airdropContract.claim();
uint256 aliceBalance = loveToken.balanceOf(alice);
assertEq(aliceBalance, 0, "Alice should not have received tokens after divorce");
vm.stopPrank();
}
Impact
The vulnerability poses a significant risk to the fairness and integrity of the reward distribution system. By exploiting this flaw, divorced users can claim rewards illegitimately, leading to unfair allocation and potential financial losses.
Tools Used
Manual Review
Recommendations
Modify the claim function in the Airdrop contract to accurately check the divorce status of the soulmate associated with the claimed NFT.
Here possible fix for this:
add or modify the isDivorced()
as:
- function isDivorced() public view returns (bool) {
+ function isDivorced(address soulmate) public view returns (bool) {
- return divorced[msg.sender];
+ return divorced[soulmate];
}
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
);
}