[H-02] LikeRegistry::matchRewards pools each user's entire balance, letting a zero-contribution user gain 2-of-2 control over the counterparty's ETH
Description
uint256 matchUserOne = userBalances[from];
uint256 matchUserTwo = userBalances[to];
userBalances[from] = 0;
userBalances[to] = 0;
Risk
Likelihood: High
Impact: High
Proof of Concept
Run against LikeRegistry with only the H-01 one-liner added. Bob spends his balance matching Carol, then matches Alice with 1 ETH against her 5 ETH and blocks the 5.4 ETH forever.
function test_H02_zeroContributionGetsHalfControl() public {
vm.prank(bob); reg.likeUser{value: 1 ether}(carol);
vm.prank(carol); reg.likeUser{value: 1 ether}(bob);
vm.prank(alice); reg.likeUser{value: 5 ether}(bob);
vm.prank(bob); reg.likeUser{value: 1 ether}(alice);
MultiSigWallet m = MultiSigWallet(payable(vm.computeCreateAddress(address(reg), 2)));
assertEq(address(m).balance, 5.4 ether);
vm.prank(alice); m.submitTransaction(alice, 5.4 ether);
vm.prank(alice); m.approveTransaction(0);
vm.prank(alice); vm.expectRevert("Not enough approvals");
m.executeTransaction(0);
}
Recommended Mitigation
Track deposits per (liker, liked) pair and pool only the two deposits of the matched pair:
- mapping(address => uint256) public userBalances;
+ mapping(address => mapping(address => uint256)) public likeDeposit;
// likeUser
+ likeDeposit[msg.sender][liked] += msg.value;
// matchRewards
- uint256 matchUserOne = userBalances[from];
- uint256 matchUserTwo = userBalances[to];
+ uint256 matchUserOne = likeDeposit[from][to];
+ uint256 matchUserTwo = likeDeposit[to][from];
Also add a timeout-based 50/50 split to MultiSigWallet so neither party can hold the funds hostage.