The Airdrop::claim()
function distributes tokens to any caller, even to those awaiting a soulmate, or have never minted a soulmate.
The Airdrop::claim()
function is meant to give tokens to soulmates, however any user, even those who don't have a soulmate or never minted a soulmate via the function Soulmate::mintSoulmateToken()
can claim rewards. This can happen due to setting the numberOfDaysInCouple
local variable:
When numberOfDaysInCouple
is set, it initally calls Soulmate::ownerToId()
and passes msg.sender
as the argument. This mapping maps the address to a uint256:
Initially this mapping is set inside of Soulmate::mintSoulmateToken()
for users that have a soulmate. However for users without a soulmate, this mapping will return 0
by default.
When the function then calls the Soulmate::idToCreationTimestamp()
function to fetch the timestamp of when the soulmates were united, it will actually return the timestamp of the very first soulmate NFT minted OR if no one has minted, this will also return 0
.
If we assume a soulmate NFT has been minted, the function Airdrop::claim()
will then continue to do calculations based off of the first NFT minted and transfer tokens to the caller msg.sender
as if they were the first soulmate NFT minted.
In the case there has been no Soulmate NFTs minted, the Airdrop::claim()
function will set Airdrop::numberOfDaysInCouple
to the amount of days since block.timestamp
's inception: (block.timestamp - 0) / 3600 * 24
.
Any user can be rewarded with tokens without having actually minted a soulmate.
Imagine the following most-likely scenario:
User A calls Soulmate::mintSoulmateToken()
.
User B calls Soulmate::mintSoulmateToken()
and now NFT #0 has been minted.
10 days passes by since NFT #0 was minted.
User C (Attacker) calls Airdrop::claim()
.
Airdrop::claim()
checks the number of days passed. Since user C hasn't minted, it will set the days since NFT #0 was minted.
Airdrop::claim()
rewards user C with 10 tokens.
Here is a POC of the above scenario:
VS Code, Foundry
Add a new error and make a call to Soulmate::soulmateOf
to check if msg.sender
has a soulmate, if they don't revert the transaction. This will prevent those who never minted, in addition to those who are still waiting for a soulmate.
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.