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 -
 
    
    
    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
        );
    }
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();
        
        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
        );
    }