DatingDapp

First Flight #33
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: high
Valid

Missing balance tracking in `LikeRegistry::likeUser` leads to permanent loss of user funds

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

  • Manual review

  • Foundry testing framework

Recommendations

  1. 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");
// Track user balance
userBalances[msg.sender] += msg.value;
likes[msg.sender][liked] = true;
emit Liked(msg.sender, liked);
}
  1. Add events for balance updates:

event BalanceUpdated(address indexed user, uint256 newBalance);
  1. 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);
}
  1. Add proper balance tracking tests to ensure accurate accounting.

Updates

Appeal created

n0kto Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_likeUser_no_userBalances_updated

Likelihood: High, always. Impact: High, loss of funds

Support

FAQs

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