DatingDapp

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

[H-02] Only the funds used to like a user should be sent to multiSigWallet

Description:

Currently, when a match occurs, the funds allocated for liking multiple users are combined into a single multiSig wallet for the first match.

For example, if User A likes three users (User B, User C, and User D), the first match (e.g., between User A and User B) creates a vault that incorrectly includes all the funds User A has used to like other users. As a result:

Future vaults for User A’s matches with User C and User D will receive fewer funds than they should because User A has already contributed all their funds to the first vault.
User A effectively benefits from the funds of Users C and D, as they have ownership of the first vault without having contributed the correct amount specific to that match.
This issue arises because the userBalances mapping only tracks total user funds without differentiating whom the funds were intended for when the like action was performed.

In the function LikeRegistry::matchRewards(), once a vault is created, the total rewards amount (excluding the protocol fee) is transferred to it. However, this amount is incorrectly derived from the entire balance of both matched users, regardless of how many users they previously liked.

// Added to track user-specific multisig wallets
mapping(address => mapping(address => address)) public userMultiSigWallets;
@> mapping(address => uint256) public userBalances;
function matchRewards(address from, address to) internal {
uint256 matchUserOne = userBalances[from];
uint256 matchUserTwo = userBalances[to];
userBalances[from] = 0;
userBalances[to] = 0;
uint256 totalRewards = matchUserOne + matchUserTwo;
uint256 matchingFees = (totalRewards * FIXEDFEE) / 100;
uint256 rewards = totalRewards - matchingFees;
totalFees += matchingFees;
// Deploy a MultiSig contract for the matched users
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
// Track the multisig wallet for both users
userMultiSigWallets[from][to] = address(multiSigWallet);
userMultiSigWallets[to][from] = address(multiSigWallet);
// Send ETH to the deployed multisig wallet
@> (bool success, ) = payable(address(multiSigWallet)).call{
value: rewards
}("");
require(success, "Transfer failed");
}

Impact:

Critical. This issue misallocates funds every time a user likes more than one person. Some vaults receive more funds than they should, while others receive less. This misallocation affects fairness and could lead to users benefiting from funds they didn't contribute correctly.

Proof of Concept:

In the test testLikedMatchedUser(), three users are involved:

  1. Alice (User A)

  2. Bob (User B)

  3. Fred (User C)

Alice likes both Bob and Fred, but when a match occurs between Alice and Bob, the vault incorrectly locks 3 ETH instead of only the 2 ETH that should be associated with their match. This means funds allocated for Alice → Fred are stuck in the vault.

LikeRegistry likeRegistry;
SoulboundProfileNFT soulboundNFT;
address user = address(0x123);
address user2 = address(0x456);
address user3 = address(0x789);
address owner = address(this); // Test contract acts as the owner
uint256 public constant STARTING_USER_BALANCE = 10e18;
function setUp() public {
soulboundNFT = new SoulboundProfileNFT();
likeRegistry = new LikeRegistry(address(soulboundNFT));
vm.deal(user, STARTING_USER_BALANCE);
vm.deal(user2, STARTING_USER_BALANCE);
vm.deal(user3, STARTING_USER_BALANCE);
}
function testLikedMatchedUser() public {
vm.startPrank(user);
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
vm.stopPrank();
vm.startPrank(user3);
soulboundNFT.mintProfile("Fred", 32, "ipfs://profileImage");
vm.stopPrank();
vm.startPrank(user2);
soulboundNFT.mintProfile("Bob", 30, "ipfs://profileImage");
likeRegistry.likeUser{value: 1 ether}(user);
assertEq(likeRegistry.userBalances(user2), 1 ether);
vm.stopPrank();
vm.startPrank(user);
likeRegistry.likeUser{value: 1 ether}(user3);
assertEq(likeRegistry.userBalances(user), 1 ether);
//match here
likeRegistry.likeUser{value: 1 ether}(user2);
assertEq(
likeRegistry
.userMultiSigWallets(address(user), address(user2))
.balance,
2.7e18
);
}

Recommended Mitigation:

Instead of tracking a single balance per user, funds should be mapped per liked user to ensure correct allocation upon matching.
Modify the userBalances mapping to track who the funds were meant for, preventing unintended fund allocation when matches occur.

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

Then, update the matchRewards() function to only transfer funds allocated for the matched user, rather than the entire user balance.

This ensures that when User A matches with User B, only the funds allocated for that specific like are transferred to the vault, preserving fairness and preventing misallocation.

Updates

Appeal created

n0kto Lead Judge 5 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_several_match_lead_to_multisig_with_no_funds

Likelihood: Medium, if anyone has 2 matches or more before reliking. Impact: Medium, the user won't contribute to the wallet.

mnedelchev_ Auditor
5 months ago
n0kto Lead Judge
5 months ago
n0kto Lead Judge 5 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_several_match_lead_to_multisig_with_no_funds

Likelihood: Medium, if anyone has 2 matches or more before reliking. Impact: Medium, the user won't contribute to the wallet.

Support

FAQs

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