Description
-
The protocol tracks ETH deposits in a single userBalances[user] mapping per address. When a user likes multiple people, all deposits accumulate into this single balance.
-
When any one match triggers, matchRewards() reads and drains the user's entire userBalances to that single MultiSigWallet, leaving zero for subsequent matches. This means deposits intended for other potential matches are redirected to whichever match fires first.
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;
}
Risk
Likelihood:
-
This occurs whenever a user likes multiple people -- a natural and expected usage pattern in a dating app
-
The first mutual match drains all accumulated deposits regardless of how many other pending likes exist
Impact:
-
Deposits meant for one match are sent to a different match's MultiSig -- the wrong pair receives the excess ETH
-
Subsequent matches receive 0 rewards because the balance was already fully drained
-
Users lose ETH intended for other matches with no recourse
Proof of Concept
function testAggregatedBalanceDrain() public {
vm.prank(alice);
profileNFT.mintProfile("Alice", 25, "img");
vm.prank(bob);
profileNFT.mintProfile("Bob", 26, "img");
vm.prank(carol);
profileNFT.mintProfile("Carol", 27, "img");
vm.prank(alice);
likeRegistry.likeUser{value: 1 ether}(bob);
vm.prank(alice);
likeRegistry.likeUser{value: 1 ether}(carol);
vm.prank(bob);
likeRegistry.likeUser{value: 1 ether}(alice);
vm.prank(carol);
likeRegistry.likeUser{value: 1 ether}(carol);
}
Recommended Mitigation
- mapping(address => uint256) public userBalances;
+ mapping(address => mapping(address => uint256)) public pairDeposits;
function likeUser(address liked) external payable {
// ...
- userBalances[msg.sender] += msg.value;
+ pairDeposits[msg.sender][liked] += msg.value;
// ...
}
function matchRewards(address from, address to) internal {
- uint256 matchUserOne = userBalances[from];
- uint256 matchUserTwo = userBalances[to];
- userBalances[from] = 0;
- userBalances[to] = 0;
+ uint256 matchUserOne = pairDeposits[from][to];
+ uint256 matchUserTwo = pairDeposits[to][from];
+ pairDeposits[from][to] = 0;
+ pairDeposits[to][from] = 0;
// ...
}