DatingDapp

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

[H-1] Variable `LikeRegistry::userBalance` doesn't actually map to user balance and is always 0, completely disrupting **all** protocol functionality

Summary

The contract LikeRegistry.sol relies on the variable userBalance to keep track of the user balance whenever they like another user, but currently has no way to update it other than when it gets reset to 0, therefore it's 0 at all times.

Impact

The following functionality is rendered useless:

  • LikeRegistry::matchRewards: Since totalRewards is 0, the multiSigWallet created will have 0 funds.

  • LikeRegistry::withdrawFees: Since matchingFees is 0, the owner will never be able to withdraw fees.

This makes every fund placed into LikeRegistry.sol be permanently stuck. The contract MultiSig.sol becomes useless since all newly deployed wallets will always have 0 funds. Lastly, the contract SoulboundProfileNFT.sol can still be used but only for the fun of having the soulboundNFT, there wouldn't be further motivation to use it.

Proof of Concept

Add console import to LikeRegistry.sol:

import { console } from "forge-std/Test.sol";

Add all these console logs to view what numbers are coming from matchRewards:

function matchRewards(address from, address to) internal {
uint256 matchUserOne = userBalances[from];
+ console.log("matchUserOne balance:", matchUserOne);
uint256 matchUserTwo = userBalances[to];
+ console.log("matchUserTwo balance:", matchUserTwo);
userBalances[from] = 0;
userBalances[to] = 0;
uint256 totalRewards = matchUserOne + matchUserTwo;
uint256 matchingFees = (totalRewards * FIXEDFEE ) / 100;
+ console.log("matchingFees:", matchingFees);
uint256 rewards = totalRewards - matchingFees;
+ console.log("rewards:", rewards);
totalFees += matchingFees;
+ console.log("totalFees:", totalFees);
// 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}("");
require(success, "Transfer failed");
}

Make testLikeRegistry.t.sol and paste this in:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console} from "forge-std/Test.sol";
import {LikeRegistry} from "../src/LikeRegistry.sol";
import {SoulboundProfileNFT} from "../src/SoulboundProfileNFT.sol";
contract LikeRegistryTest is Test {
SoulboundProfileNFT soulboundNFT;
LikeRegistry likeRegistry;
address user1 = address(0x123);
address user2 = address(0x456);
function setUp() public {
vm.deal(user1, 10 ether);
vm.deal(user2, 10 ether);
soulboundNFT = new SoulboundProfileNFT();
likeRegistry = new LikeRegistry(address(soulboundNFT));
}
modifier userLikedByLiker() {
vm.startPrank(user2);
soulboundNFT.mintProfile("Bob", 23, "ipfs://profileImage");
vm.stopPrank();
vm.startPrank(user1);
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
likeRegistry.likeUser{value: 1 ether}(user2);
vm.stopPrank();
_;
}
function testMatchRewards() public userLikedByLiker {
// Act/Assert
vm.startPrank(user2);
// likeUser() will call matchRewards()
likeRegistry.likeUser{value: 1 ether}(user1);
vm.stopPrank();
}
}

Viewing the logs from this test it can be seen that every value is 0.

Recommendations

Considering changing the userBalance mapping:

- mapping(address => uint256) public userBalances;
+ mapping(address => mapping(address => uint256)) public userMatchBalance;

Add this line in the LikeRegistry::likeUser function:

function likeUser(
address liked
) public 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;
+ userMatchBalance[msg.sender][liked] = msg.value;
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);
}
}

And change the LikeRegistry::matchRewards function to use the new variable:

function matchRewards(address from, address to) internal {
- uint256 matchUserOne = userBalances[from];
+ uint256 matchUserOne = userMatchBalance[from][to];
console.log("matchUserOne balance:", matchUserOne);
- uint256 matchUserTwo = userBalances[to];
+ uint256 matchUserTwo = userMatchBalance[to][from];
console.log("matchUserTwo balance:", matchUserTwo);
- userBalances[from] = 0;
+ userMatchBalance[from][to] = 0;
- userBalances[to] = 0;
+ userMatchBalance[to][from] = 0;
uint256 totalRewards = matchUserOne + matchUserTwo;
uint256 matchingFees = (totalRewards * FIXEDFEE ) / 100;
console.log("matchingFees:", matchingFees);
uint256 rewards = totalRewards - matchingFees;
console.log("rewards:", rewards);
totalFees += matchingFees;
console.log("totalFees:", totalFees);
// 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}("");
require(success, "Transfer failed");
}

Now by running the test, the logs are correctly populated with values.

This is the reasoning:

mapping(address user1 => mapping(address user2 => uint256 balance)) public userMatchBalance;

This mapping leads to the balance user1 has placed when liking user2.

When Bob likes Alice:

  • His matching balance gets updated to 1

  • Alice => Bob balance remains as 0, since she hasn't liked him back yet

Now Bob likes Megan:

  • Bob => Megan updates from 0 to 1

  • Megan likes him back

  • Megan => Bob updates from 0 to 1

  • A multiSigWallet is created

  • Bob => Megan gets updated to 0

  • Megan => Bob gets updated to 0

  • Bob => Alice remains as 1

A few days later Alice finds Bob and likes him:

  • since Bob => Alice is still 1, a multiSigWallet is created with both rewards

  • Bob => Alice updates from 1 to 0

  • Alice => Bob updates from 1 to 0

This way it checks the balance of matched users, and won't update the balance to 0 for all future matches.

Updates

Appeal created

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