DatingDapp

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

Incorrect Pooling of All Historical ETH on Match

Summary

The LikeRegistry contract incorrectly pools all historical ETH sent by users (from previous likes) into a shared multisig wallet upon a mutual match, instead of only pooling the 1 ETH sent for that specific match. This violates the protocol’s intended behavior, where only the 1 ETH from each user’s like to the other should be pooled. As a result, users risk losing all previously sent ETH to unintended matches, leading to significant financial loss and protocol misuse.


Vulnerability Details

Location

  • Contract: LikeRegistry.sol

  • Functions: likeUser, matchRewards

Root Cause

  1. Incorrect ETH Tracking:

    • The userBalances mapping tracks the cumulative ETH sent by a user for all likes, not per-recipient.

    • When a mutual match occurs, the entire userBalances of both users is pooled, not just the 1 ETH sent for the matched like.

  2. Protocol Logic Flaw:

    • The protocol intends to pool only the 1 ETH from the mutual likes (e.g., Alice sends 1 ETH to like Bob, Bob sends 1 ETH to like Alice).

    • Current implementation pools all ETH sent by Alice and Bob for all previous likes, including those unrelated to the match.

Code Snippet

// In LikeRegistry.sol
function matchRewards(address from, address to) internal {
uint256 matchUserOne = userBalances[from]; // Cumulative ETH from all likes
uint256 matchUserTwo = userBalances[to]; // Cumulative ETH from all likes
userBalances[from] = 0;
userBalances[to] = 0;
uint256 totalRewards = matchUserOne + matchUserTwo;
// ... sends totalRewards to multisig ...
}

Impact

Severity: Critical

  • Financial Loss: Users lose all ETH sent for previous likes to an unintended match. For example:

    • Alice spends 3 ETH liking 3 users.

    • Bob spends 2 ETH liking 2 users.

    • When Alice and Bob match, 5 ETH (3 + 2) is pooled (minus fees), even though only 1 ETH each was intended.

  • Protocol Misuse: The system’s core value proposition (“genuine connections”) is undermined, as matches drain unrelated funds.

  • Loss of Trust: Users will avoid the platform once they realize their entire ETH balance is at risk per match.


Proof of Concept (PoC)

Expected vs Actual Behavior

  • Expected: Only 1 ETH from Alice (to Bob) and 1 ETH from Bob (to Alice) are pooled (total 2 ETH - 10% fee = 1.8 ETH).

  • Actual: All 3 ETH (Alice’s 2 ETH + Bob’s 1 ETH) are pooled, resulting in 2.7 ETH sent to the multisig.


Recommended Mitigation*

Fix: Track ETH Per Like, Not Per User

Modify the contract to track ETH per like recipient instead of cumulatively:

// Replace userBalances with a nested mapping: user => recipient => amount
mapping(address => mapping(address => uint256)) public likeAmounts;
function likeUser(address liked) external payable {
require(msg.value == 1 ether, "Must send exactly 1 ETH");
// ... existing checks ...
likeAmounts[msg.sender][liked] += msg.value; // Track 1 ETH per recipient
emit Liked(msg.sender, liked);
// Check for mutual like
if (likeAmounts[liked][msg.sender] > 0) {
// Pool only the 1 ETH from each user's like to the other
uint256 totalRewards = likeAmounts[msg.sender][liked] + likeAmounts[liked][msg.sender];
uint256 fee = (totalRewards * FIXEDFEE) / 100;
uint256 rewards = totalRewards - fee;
// Reset the tracked ETH for this pair
likeAmounts[msg.sender][liked] = 0;
likeAmounts[liked][msg.sender] = 0;
// Deploy multisig and send rewards
MultiSigWallet multiSig = new MultiSigWallet(msg.sender, liked);
(bool success,) = address(multiSig).call{value: rewards}("");
require(success, "Transfer failed");
}
}

Additional Changes

  • Remove the userBalances mapping entirely.

  • Update matchRewards to handle per-recipient tracking.


Updates

Appeal created

n0kto Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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