DatingDapp

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

Incorrect ETH Accounting in LikeRegistry (Loss of Funds)

Summary

The LikeRegistry contract contains a critical vulnerability in its ETH accounting mechanism. When users send ETH via the likeUser function, the ETH is not properly tracked in the userBalances mapping. As a result, the matchRewards function, which relies on userBalances to calculate rewards for matched users, always calculates rewards as zero. This leads to all ETH sent by users being permanently stuck in the contract, rendering the matching and reward distribution mechanism non-functional.


Vulnerability Details

Location

  • Contract: LikeRegistry.sol

  • Functions: likeUser and matchRewards

Root Cause

  1. ETH Tracking Issue:

    • In the likeUser function, users send ETH (msg.value) to express a "like" for another user. However, the contract fails to update the userBalances mapping to reflect the ETH sent.

    • The userBalances mapping is used in the matchRewards function to calculate the rewards for matched users. Since userBalances is never updated, it remains zero for all users.

  2. Reward Calculation Issue:

    • The matchRewards function calculates rewards based on the sum of userBalances for the two matched users. Since userBalances is always zero, the calculated rewards are also zero.

    • As a result, no ETH is sent to the MultiSig contract, and all ETH sent by users remains stuck in the LikeRegistry contract.

Code Snippets

likeUser Function (Incorrect Implementation)

function likeUser(address liked) external payable {
require(msg.value >= 1 ether, "Must send at least 1 ETH");
require(!likes[msg.sender][liked], "Already liked");
require(msg.sender != liked, "Cannot like yourself");
require(profileNFT.profileToToken(msg.sender) != 0, "Must have a profile NFT");
require(profileNFT.profileToToken(liked) != 0, "Liked user must have a profile NFT");
likes[msg.sender][liked] = true;
emit Liked(msg.sender, liked);
// ETH is not tracked in userBalances
// userBalances[msg.sender] += msg.value; // Missing line
// Check if mutual like
if (likes[liked][msg.sender]) {
matches[msg.sender].push(liked);
matches[liked].push(msg.sender);
emit Matched(msg.sender, liked);
matchRewards(liked, msg.sender);
}
}

matchRewards Function (Incorrect Reward Calculation)

function matchRewards(address from, address to) internal {
uint256 matchUserOne = userBalances[from]; // Always zero
uint256 matchUserTwo = userBalances[to]; // Always zero
userBalances[from] = 0;
userBalances[to] = 0;
uint256 totalRewards = matchUserOne + matchUserTwo; // Always zero
uint256 matchingFees = (totalRewards * FIXEDFEE) / 100; // Always zero
uint256 rewards = totalRewards - matchingFees; // Always zero
totalFees += matchingFees; // Always zero
// Deploy a MultiSig contract for the matched users
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
// Send ETH to the deployed multisig wallet
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}(""); // Sends zero ETH
require(success, "Transfer failed");
}

Impact

Severity: Critical

  • Loss of Funds: All ETH sent by users via the likeUser function is permanently stuck in the LikeRegistry contract. Users receive no rewards for matches, and the contract's core functionality is broken.

  • Broken Functionality: The matching and reward distribution mechanism is non-functional, rendering the contract unusable for its intended purpose.

  • Reputation Damage: Users and stakeholders may lose trust in the platform due to the loss of funds and broken functionality.


Proof of Concept (PoC)

Steps to Reproduce

  1. Deploy the LikeRegistry contract and the SoulboundProfileNFT contract.

  2. User A mints a profile NFT and sends 1 ETH via likeUser to like User B.

  3. User B mints a profile NFT and sends 1 ETH via likeUser to like User A.

  4. Observe that:

    • The matchRewards function is triggered due to the mutual like.

    • The userBalances mapping for both users remains zero.

    • The matchRewards function calculates zero rewards and sends zero ETH to the MultiSig contract.

    • The 2 ETH sent by the users remains stuck in the LikeRegistry contract.

Expected vs Actual Behavior

  • Expected: ETH sent by users should be tracked in userBalances, and matched users should receive rewards via the MultiSig contract.

  • Actual: ETH is not tracked, and no rewards are distributed. All ETH remains stuck in the contract.


Recommended Mitigation

Fix

Update the likeUser function to properly track ETH in the userBalances mapping:

function likeUser(address liked) external payable {
require(msg.value >= 1 ether, "Must send at least 1 ETH");
require(!likes[msg.sender][liked], "Already liked");
require(msg.sender != liked, "Cannot like yourself");
require(profileNFT.profileToToken(msg.sender) != 0, "Must have a profile NFT");
require(profileNFT.profileToToken(liked) != 0, "Liked user must have a profile NFT");
likes[msg.sender][liked] = true;
emit Liked(msg.sender, liked);
// Track ETH sent by the user
userBalances[msg.sender] += msg.value;
// Check if mutual like
if (likes[liked][msg.sender]) {
matches[msg.sender].push(liked);
matches[liked].push(msg.sender);
emit Matched(msg.sender, liked);
matchRewards(liked, msg.sender);
}
}

Additional Recommendations

  1. Refund Excess ETH:

    • Ensure users send exactly 1 ETH or refund any excess ETH to prevent overpayment.

    require(msg.value == 1 ether, "Must send exactly 1 ETH");
  2. Add ETH Recovery Mechanism:

    • Implement a function to allow the contract owner to recover stuck ETH in case of emergencies.

    function recoverStuckETH() external onlyOwner {
    (bool success,) = payable(owner()).call{value: address(this).balance}("");
    require(success, "Transfer failed");
    }
Updates

Appeal created

n0kto Lead Judge 6 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.