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.