Summary
The LikeRegistry::likeUser
function receives ETH from users but fails to track these balances in the userBalances
mapping. When matches occur, the matchRewards
function uses these untracked balances, effectively setting rewards to zero and causing users to lose their deposited ETH.
Vulnerability Details
In LikeRegistry.sol
, ETH is received but never tracked in the user's balance:
function likeUser(address liked) external payable {
require(msg.value >= 1 ether, "Must send at least 1 ETH");
require(!likes[msg.sender][liked], "Already liked");
likes[msg.sender][liked] = true;
}
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;
}
Proof Of Concept
function testMissingBalanceTracking() public {
uint256 initialContractBalance = address(registry).balance;
vm.startPrank(alice);
vm.deal(alice, 5 ether);
registry.likeUser{value: 5 ether}(bob);
assertEq(registry.userBalances(alice), 0, "Alice balance should be 0");
vm.stopPrank();
vm.startPrank(bob);
vm.deal(bob, 3 ether);
registry.likeUser{value: 3 ether}(alice);
assertEq(registry.userBalances(bob), 0, "Bob balance should be 0");
vm.stopPrank();
uint256 finalContractBalance = address(registry).balance;
assertEq(
finalContractBalance - initialContractBalance,
8 ether,
"Contract should have 8 ETH stuck"
);
vm.startPrank(bob);
address[] memory bobMatches = registry.getMatches();
assertEq(bobMatches.length, 1, "Match should be created");
assertEq(bobMatches[0], alice, "Bob should be matched with Alice");
assertEq(
address(registry).balance,
8 ether,
"ETH stuck in contract despite match"
);
vm.stopPrank();
}
Impact
High severity because:
All user funds sent with likes are permanently locked in the contract
Users never receive their rewards upon matching
Contract accumulates ETH with no way to distribute it
Core functionality of reward distribution is broken
No way to recover stuck funds
Tools Used
Recommendations
Add balance tracking in the likeUser
function:
function likeUser(address liked) external payable {
require(msg.value >= 1 ether, "Must send at least 1 ETH");
require(!likes[msg.sender][liked], "Already liked");
userBalances[msg.sender] += msg.value;
likes[msg.sender][liked] = true;
emit Liked(msg.sender, liked);
}
Add events for balance updates:
event BalanceUpdated(address indexed user, uint256 newBalance);
Add a withdrawal mechanism for unmatched funds:
function withdrawUnmatchedFunds() external {
uint256 amount = userBalances[msg.sender];
require(amount > 0, "No funds to withdraw");
userBalances[msg.sender] = 0;
(bool success,) = payable(msg.sender).call{value: amount}("");
require(success, "Transfer failed");
emit BalanceWithdrawn(msg.sender, amount);
}
Add proper balance tracking tests to ensure accurate accounting.