DatingDapp

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

Missing ETH Balance Tracking in LikeRegistry Leads to Zero Reward Pooling

Summary

The protocol requires users to pay 1 ETH when liking another profile. For mutual matches, all previously sent ETH (minus 10% fees) should be pooled into a MultiSig wallet. However, the LikeRegistry contract fails to track user ETH contributions in the userBalances mapping during the LikeRegistry::likeUser function. As a result, mutual matches create empty MultiSig wallets with 0 ETH, rendering the core reward mechanism non-functional.

Vulnerability Details

The issue arises due to the contract not updating the userBalances mapping after receiving ETH payments. Since these contributions are never recorded, the matchRewards function attempts to distribute rewards from an empty balance, preventing users from receiving their pooled ETH rewards.

src/LikeRegistry.sol#L31-L48

function likeUser(address liked) external payable {
require(msg.value >= 1 ether, "Must send at least 1 ETH");
// ...
likes[msg.sender][liked] = true;
emit Liked(msg.sender, liked); // ETH not tracked in userBalances
if (likes[liked][msg.sender]) {
// ...
matchRewards(liked, msg.sender); // Pulls from unupdated userBalances
}
}

To confirm the presence of this issue, the following test case verifies that userBalances is never updated after a like action.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "../src/SoulboundProfileNFT.sol";
import "../src/LikeRegistry.sol";
import "../src/MultiSig.sol";
contract DatingDappTest is Test {
SoulboundProfileNFT public profileNFT;
LikeRegistry public likeRegistry;
address public alice = makeAddr("alice");
address public bob = makeAddr("bob");
uint256 public constant LIKE_FEE = 1 ether;
function setUp() public {
profileNFT = new SoulboundProfileNFT();
likeRegistry = new LikeRegistry(address(profileNFT));
vm.prank(alice);
profileNFT.mintProfile("Alice", 25, "ipfs://alice");
vm.prank(bob);
profileNFT.mintProfile("Bob", 28, "ipfs://bob");
}
function testMissingBalanceTracking() public {
// Alice likes Bob (send 1 ETH)
vm.deal(alice, LIKE_FEE);
vm.prank(alice);
likeRegistry.likeUser{value: LIKE_FEE}(bob);
// Verify Alice's balance NOT updated
assertEq(
likeRegistry.userBalances(alice),
0,
"Alice's balance should be 0"
);
// Bob likes Alice (send 1 ETH)
vm.deal(bob, LIKE_FEE);
vm.prank(bob);
likeRegistry.likeUser{value: LIKE_FEE}(alice);
// Get Bob's matches (impersonate Bob)
vm.prank(bob);
address[] memory matches = likeRegistry.getMatches();
require(matches.length > 0, "No matches found for Bob");
address payable multiSigAddr = payable(matches[0]);
MultiSigWallet multiSig = MultiSigWallet(multiSigAddr);
// Verify MultiSig has 0 ETH despite mutual likes
assertEq(address(multiSig).balance, 0, "MultiSig should have 0 ETH");
assertEq(
likeRegistry.userBalances(bob),
0,
"Bob's balance should be 0"
);
}
}

Expected Test Output

$ forge test --mt testMissingBalanceTracking
[⠰] Compiling...
[⠔] Installing Solc version 0.8.26
[⠑] Successfully installed Solc 0.8.26
No files changed, compilation skipped
Ran 1 test for test/DatingDappTest.t.sol:DatingDappTest
[PASS] testMissingBalanceTracking() (gas: 719242)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 858.20µs (168.45µs CPU time)
Ran 1 test suite in 12.30ms (858.20µs CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

Impact

Matched users receive empty MultiSig wallets instead of pooled ETH rewards. Since user balances are never recorded, their ETH payments and potential fees are effectively lost, with no mechanism to recover the funds. This completely breaks the intended incentive structure, making the reward system non-functional.

Tools Used

Foundry

Recommendations

Modify the LikeRegistry::likeUser function to correctly track ETH contributions. The following fix ensures that user balances are updated properly:

function likeUser(address liked) external payable {
require(msg.value >= 1 ether, "Must send at least 1 ETH");
// ...
likes[msg.sender][liked] = true;
+ userBalances[msg.sender] += msg.value; // Track ETH contribution
emit Liked(msg.sender, liked);
if (likes[liked][msg.sender]) {
// ...
}
}

By adding userBalances[msg.sender] += msg.value;, the contract correctly tracks ETH contributions, ensuring mutual matches result in properly funded MultiSig wallets.

Updates

Appeal created

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