The Airdrop::claim function allows people who don't have a soulmate to claim LoveTokens. This issue arises from a lack of proper validation to ensure that only people with a soulmate can execute the Airdrop::claim function.
In the README for Airdrop::claim function is said:
claim: Allows only those with a soulmate to collect 1 LoveToken per day.
But the Airdrop::claim function does not perform a check to verify the ownership of a Soulmate Token before allowing the claim:
The function calculates the number of days since the relationship is started:
uint256 numberOfDaysInCouple = (block.timestamp - soulmateContract.idToCreationTimestamp(soulmateContract.ownerToId(msg.sender))) /daysInSecond;
The function also assumes that the soulmateContract.ownerToId(msg.sender) will return a valid id. If the function is called from person who doesn't have a soulmate, the return id for this person will be 0, because his address doesn't exist in the ownerToId mapping. Therefore, the return value will be 0 and this person will be able to claim LoveTokens.
Also, if a person is waiting for a soulmate, he/she already has a valid id, but doesn't have a soulmate and a minted token. If a person is waiting at least 1 day for a soulmate, he/she can claim a LoveToken.
This vulnerability allows any user without soulmate to claim LoveTokens. We already said that the return value from soulmateContract.ownerToId(msg.sender) for a non existing address in the ownerToId mapping will be 0.
If there is at least one couple in the protocol, their token id is equal to 0. So there is a creation timestamp associated with that id. Therefore, if at least one day has passed since the start of the first couple's relationship, the person/persons without soulmate can claim 1 LoveToken for every day that this couple is in relationship.
The claim tokens are assigned to msg.sender: uint256 amountAlreadyClaimed = _claimedBy[msg.sender];. So the claimed tokens from the people without soulmate will not steal the tokens for the couple with id 0, but will reduce or drain the total amount of tokens in the airdropVault.
The following test test_UserWithoutSoulmateClaim shows the described issue. You can add the test in the test file AirdropTest.t.sol and execute it with the command: forge test --match-test "test_UserWithoutSoulmateClaim".
Another case is when there are no couples in the protocol. That is the worst case scenario, because the function soulmateContract.idToCreationTimestamp will return 0. And the numberOfDaysInCouple would be calculated as the entire time since the Unix epoch (January 1, 1970), divided by the number of seconds in a day. This would result in a large number representing the total number of days since the Unix epoch. In that case the person without soulmate can claim large number of LoveTokens. If many users make this (or one user with different addresses), the total supply of airdropVault will be drained.
The following test function test_UserWithoutSoulmateClaimThereAreNoCoupleCreated demonstrates this case. The time of calling the function is 12 February 2024 5:32:59 PM. So the claimed tokens should be 19765.
Additionally, if a person is waiting for his/her soulmate more than 1 day, he/she can claim LoveToken, 1 token for every day of waiting. He/she will have a valid id, but doesn't have a soulmate and a minted token. The following test shows that Charlie is waiting from 2 days for a soulmate and can claim 2 LoveTokens:
VS Code, Foundry
Add a check in Airdrop::claim function to ensure that people without a soulmate can not claim LoveTokens.
One possible way will be to check if the address of the returned id is associated with the address of msg.sender and if the msg.sender has a soulmate:
In order the function to work correctly the Soulmate::idToOwner should be public and should be added to the ISoulmate interface. Then if you execute again the described test functions, they will fail:
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.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.