DatingDapp

AI First Flight #6
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Severity: high
Valid

`LikeRegistry.likeUser` never credits `userBalances`, so match rewards are always 0 and every like-payment ETH is permanently locked

Description

Normal behavior: users pay >= 1 ETH to like a profile; on a mutual match, both users' accumulated like-payments (minus a 10% fee) are pooled into a shared MultiSig for their date.

Specific issue: likeUser is payable and enforces msg.value >= 1 ether, but it NEVER records the payment — userBalances[msg.sender] is only read (lines 51-52) and zeroed (53-54), never incremented. So at match time userBalances[from] == userBalances[to] == 0, matchRewards computes totalRewards = 0, funds the freshly deployed MultiSigWallet with call{value: 0}, and totalFees stays 0. The paid ETH accumulates in LikeRegistry with no exit: there is no user-withdraw, and withdrawFees reverts ("No fees to withdraw") because totalFees is 0. All deposited ETH is frozen forever and the entire reward/MultiSig subsystem is inert.

function likeUser(address liked) external payable {
require(msg.value >= 1 ether, "Must send at least 1 ETH");
...
likes[msg.sender][liked] = true; // @> payment received but userBalances[msg.sender] is NEVER credited
if (likes[liked][msg.sender]) { ... matchRewards(liked, msg.sender); }
}
function matchRewards(address from, address to) internal {
uint256 matchUserOne = userBalances[from]; // @> always 0
uint256 matchUserTwo = userBalances[to]; // @> always 0
...
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}(""); // @> sends 0
}

Risk

Likelihood:

  • Happens on every like/match on the intended happy path; no special conditions.

Impact:

  • 100% of user deposits are permanently locked (no withdraw path anywhere), and every matched pair's MultiSig is funded with 0 — the protocol's core value flow is fully bricked. Total loss of user funds (lock, not theft).

Proof of Concept

Two users mint profiles and like each other (2 ETH total). The test asserts the deployed MultiSig received 0, withdrawFees reverts, and 2 ETH is stuck in LikeRegistry with no recovery. test/LikeRegistryAudit.t.sol::testFundsPermanentlyLocked and test/SeamAudit.t.sol::test_F1_matchFundsMultisigWithZero_ethLockedForever (both PASS).

// alice and bob each likeUser{value:1 ether} each other -> mutual match
assertEq(address(registry).balance, 2 ether); // both stakes trapped in registry
assertEq(multisig.balance, 0); // match pooled nothing
vm.expectRevert("No fees to withdraw"); registry.withdrawFees();
assertEq(registry.userBalances(alice), 0); // never credited

Run: forge test --match-test testFundsPermanentlyLocked -vvv

Recommended Mitigation

Credit the payment on like, and add a way for funds to actually leave (the reward path, plus a refund/withdraw for unmatched stakes — see finding #2).

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

Lead Judging Commences

ai-first-flight-judge Lead Judge about 2 hours ago
Submission Judgement Published
Validated
Assigned finding tags:

[H-01] After the user calls the `likeUser` function, the userBalance does not increase by the corresponding value.

## Description User A calls `likeUser` and sends `value > 1` ETH. According to the design of DatingDapp, the amount for user A should be accumulated by `userBalances`. Otherwise, in the subsequent calculations, the balance for each user will be 0. ## Vulnerability Details When User A calls `likeUser`, the accumulation of `userBalances` is not performed. ```solidity 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); // 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); } } ``` This will result in `totalRewards` always being 0, affecting all subsequent calculations: ```solidity uint256 totalRewards = matchUserOne + matchUserTwo; uint256 matchingFees = (totalRewards * FIXEDFEE ) / 100; uint256 rewards = totalRewards - matchingFees; totalFees += matchingFees; ``` ## POC ```solidity function testUserBalanceshouldIncreaseAfterLike() public { vm.prank(user1); likeRegistry.likeUser{value: 20 ether}(user2); assertEq(likeRegistry.userBalances(user1), 20 ether, "User1 balance should be 20 ether"); } ``` Then we will get an error: ```shell [FAIL: User1 balance should be 20 ether: 0 != 20000000000000000000] ``` ## Impact - Users will be unable to receive rewards. - The contract owner will also be unable to withdraw ETH from the contract. ## Recommendations Add processing for `userBalances` in the `likeUser` function: ```diff 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; + userBalances[msg.sender] += msg.value; emit Liked(msg.sender, liked); [...] } ```

Support

FAQs

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

Give us feedback!