DatingDapp

AI First Flight #6
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

[M-02] First match drains entire user balance, leaving subsequent matches with 0 ETH

Description

matchRewards() takes the user's entire userBalances on the first match, not just the 1 ETH associated with that specific like. Users who like multiple people and match with the first one send all their accumulated ETH to that single MultiSig.

Vulnerability Details

// src/LikeRegistry.sol, line 50-67
function matchRewards(address from, address to) internal {
uint256 matchUserOne = userBalances[from]; // @> takes ALL accumulated ETH
uint256 matchUserTwo = userBalances[to]; // @> takes ALL accumulated ETH
userBalances[from] = 0; // @> zeroes entire balance
userBalances[to] = 0;
uint256 totalRewards = matchUserOne + matchUserTwo;
uint256 matchingFees = (totalRewards * FIXEDFEE) / 100;
uint256 rewards = totalRewards - matchingFees;
totalFees += matchingFees;
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
require(success, "Transfer failed");
}

A user who likes 5 people (5 ETH total) matches with the first one. That match's MultiSig receives 5 ETH from the user's side instead of 1 ETH. The remaining 4 potential matches deploy MultiSig wallets with 0 ETH from the user's side, even though each of those 4 people paid 1 ETH to like them.

Risk

Likelihood:

  • Any user who likes more than one person and matches with any of them triggers this. Liking multiple people is the expected usage pattern for a dating app. The order of matches is unpredictable, so which match gets the windfall is effectively random.

Impact:

  • Users who paid 1 ETH to like someone and later match get a MultiSig with 0 ETH from the other side. Their 1 ETH contribution goes to a shared wallet but they receive nothing back from the person they matched with. The first match receives a disproportionate windfall of all accumulated ETH.

PoC

The test shows Alice liking three users (3 ETH total). When Bob matches first, the MultiSig receives Alice's entire 3 ETH balance plus Bob's 1 ETH. When Charlie matches second, Alice's balance is already 0, so Charlie's MultiSig only gets Charlie's 1 ETH with nothing from Alice's side.

function testFirstMatchDrainsBalance() public {
// Alice likes Bob, Charlie, Dave (3 ETH total)
vm.prank(alice);
likeRegistry.likeUser{value: 1 ether}(bob);
vm.prank(alice);
likeRegistry.likeUser{value: 1 ether}(charlie);
vm.prank(alice);
likeRegistry.likeUser{value: 1 ether}(dave);
// userBalances[alice] should be 3 ETH (assuming H-01 fix applied)
// Bob likes Alice -> match, Alice's ENTIRE 3 ETH goes to this MultiSig
vm.prank(bob);
likeRegistry.likeUser{value: 1 ether}(alice);
// MultiSig gets 3 + 1 = 4 ETH (minus fees), should only get 1 + 1 = 2 ETH
// Charlie likes Alice -> match, but userBalances[alice] is now 0
vm.prank(charlie);
likeRegistry.likeUser{value: 1 ether}(alice);
// MultiSig gets 0 + 1 = 1 ETH, Charlie's match is unfairly underfunded
}

Recommendations

Track ETH per like pair instead of per user. Each match should only transfer the fixed 1 ETH from each side, so all matches are funded equally regardless of order. This removes the dependency on cumulative balance and ensures each counterparty gets fair value.

- mapping(address => uint256) public userBalances;
+ uint256 constant LIKE_AMOUNT = 1 ether;
function matchRewards(address from, address to) internal {
- uint256 matchUserOne = userBalances[from];
- uint256 matchUserTwo = userBalances[to];
- userBalances[from] = 0;
- userBalances[to] = 0;
- uint256 totalRewards = matchUserOne + matchUserTwo;
+ uint256 totalRewards = LIKE_AMOUNT * 2;
uint256 matchingFees = (totalRewards * FIXEDFEE) / 100;
uint256 rewards = totalRewards - matchingFees;
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 16 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!