DatingDapp

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

`LikeRegistry::likeUser` doesn't update user balances, which leads to direct loss of user funds

Summary

The contract never updates userBalances after receiving the ETH, meaning the funds are never properly tracked or transferred. Instead, the ETH just sits in the contract with no way for users to claim it.

// LikeRegistry.sol
mapping(address => uint256) public userBalances;

Vulnerability Details

The matchRewards function reads the userBalances to calculate the starting funds of the multisig. But because the mapping is never updated, the rewards variable used in .call will always be zero.

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);
// Send ETH to the deployed multisig wallet
(bool success, ) = payable(address(multiSigWallet)).call{
value: rewards
}("");
require(success, "Transfer failed");
}

Impact

User funds are permanently locked, because there is no other method to withdraw them from the contract. The MultiSig wallets receive 0 ETH, making them useless.

Proof of Code: Copy this code in a new .t.sol file. The main test tracks the funds movements in the console. It shows that the MultiSig::executeTransaction function reverts due to out-of-funds.

WARNING: LikeRegistry::matchRewards doesn't emit an event when creating the multisig. You need to check in the terminal the address of the multisig created and set it to the multiSig variable in case it creates a different address.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "../src/SoulboundProfileNFT.sol";
import "../src/MultiSig.sol";
import "../src/LikeRegistry.sol";
contract ProofOfCode is Test {
SoulboundProfileNFT soulboundNFT;
MultiSigWallet multiSigWallet;
LikeRegistry likeRegistry;
address user = address(0x123);
address user2 = address(0x456);
function setUp() public {
soulboundNFT = new SoulboundProfileNFT();
likeRegistry = new LikeRegistry(address(soulboundNFT));
vm.deal(user, 1 ether);
vm.deal(user2, 1 ether);
}
function testMultiSigFails() public {
// Balance checks BEFORE calling likeUser
console.log(
"Balance of LikeRegistry BEFORE calling likeUser: %s",
address(likeRegistry).balance
);
console.log(
"Balance of User1 BEFORE calling likeUser: %s",
address(user).balance
);
console.log(
"Balance of User2 BEFORE calling likeUser: %s",
address(user2).balance
);
vm.prank(user);
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
vm.startPrank(user2);
soulboundNFT.mintProfile("Bob", 25, "ipfs://profileImage");
likeRegistry.likeUser{value: 1 ether}(user);
vm.stopPrank();
vm.startPrank(user);
likeRegistry.likeUser{value: 1 ether}(user2);
// LikeRegistry doesn't emit an event when creating the multisig
// You need to check in the terminal the address of the multisig and set it to the next variable
address payable multiSig = payable(
0xffD4505B3452Dc22f8473616d50503bA9E1710Ac
);
// Balance checks AFTER calling likeUser
console.log(
"Starting balance of Mulisig: %s",
address(multiSig).balance
);
console.log(
"Balance of LikeRegistry AFTER calling likeUser: %s",
address(likeRegistry).balance
);
console.log(
"Balance of User1 AFTER calling likeUser: %s",
address(user).balance
);
console.log(
"Balance of User2 AFTER calling likeUser: %s",
address(user2).balance
);
MultiSigWallet(multiSig).submitTransaction(
address(likeRegistry),
1 ether
);
MultiSigWallet(multiSig).approveTransaction(0);
vm.stopPrank();
vm.startPrank(user2);
MultiSigWallet(multiSig).approveTransaction(0);
// executeTransaction reverts because the multisig is out of funds
vm.expectRevert();
MultiSigWallet(multiSig).executeTransaction(0);
vm.stopPrank();
}
}

Tools Used

Foundry - Slither

Recommendations

The developers must update userBalances in likeUser(). This ensures that users' funds are properly tracked and later transferred when a match happens:

function likeUser(address liked) external payable {
// HERE ARE THE CHECKS
likes[msg.sender][liked] = true;
+ userBalances[msg.sender] += msg.value;
emit Liked(msg.sender, liked);
// FUNCTION CONTINUES
}
Updates

Appeal created

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