DatingDapp

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

`LikeRegistry::userBalances` are not updated, resulting in all funds getting locked in the `LikeRegistry` contract

Description

In LikeRegistry::likeUser, the userBalances are not updated to reflect the users msg.value. According to the protocol docs, "If the like is mutual, all their previous like payments (minus a 10% fee) are pooled into a shared multisig wallet, which both users can access for their first date". This suggests that userBalance should be updated each time a user pays 1 ETH and "likes" another user.

No where in the code is userBalances being updated. This has a significant impact on the intended flow and functionality of the protocol.

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);
}
}

Impact

User funds and owner fees will be stuck in the LikeRegistry contract with no way for the users or the owner to withdraw it.

Proof of Concept

  1. Users create profiles.

  2. Users send 1 ETH and "like" each other - userBalances remain 0.

  3. Calculations are based on 0 amounts in matchRewards and totalFees remains 0.

  4. No ETH will be transferred to the MultisigWallet.

  5. LikeRegistry contract balance is 2 ETH.

Place the following code in a new file testLikeRegistry.t.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "../src/LikeRegistry.sol";
import "../src/SoulboundProfileNFT.sol";
contract LikeRegistryTest is Test {
LikeRegistry likeRegistry;
SoulboundProfileNFT profileNFT;
address user = address(0x123);
address user2 = address(0x456);
function setUp() public {
profileNFT = new SoulboundProfileNFT();
likeRegistry = new LikeRegistry(address(profileNFT));
vm.deal(user, 10 ether);
vm.deal(user2, 10 ether);
}
function testUserBalances() public {
// Mint profiles for both users
vm.prank(user2);
profileNFT.mintProfile("Bob", 30, "ipfs://profileImage");
vm.startPrank(user);
profileNFT.mintProfile("Alice", 25, "ipfs://profileImage");
// check balances before liking
console.log("Before liking, User1's balance:", likeRegistry.userBalances(user));
console.log("Before liking, User2's balance:", likeRegistry.userBalances(user2));
// User likes user2, sending 1 ETH
likeRegistry.likeUser{value: 1 ether}(user2);
console.log("After User1 likes, User1's balance:", likeRegistry.userBalances(user));
console.log("After User1 likes, User2's balance:", likeRegistry.userBalances(user2));
vm.stopPrank();
// User2 likes user, sending 1 ETH
vm.prank(user2);
likeRegistry.likeUser{value: 1 ether}(user);
console.log("After mutual like, User1's balance:", likeRegistry.userBalances(user));
console.log("After mutual like, User2's balance:", likeRegistry.userBalances(user2));
// Check what happens in matchRewards
uint256 totalFees = likeRegistry.getTotalFees();
console.log("Total fees: ", totalFees);
// check contract address balance
console.log("Contract balance: ", address(likeRegistry).balance);
}
}

Add the following getter function to LikeRegistry.sol:

function getTotalFees() external view returns (uint256) {
return totalFees;
}

Tools Used

Manual review.

Recommendations

To prevent the funds getting locked in the contract, updating the userBalances mapping is essential.

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);
+ 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);
}
}
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.