Description:
In LikeRegistry.sol
, we find the core logic of the protocol, where users can like each other and get matched. To perform a like, a user must pay at least 1 ETH by calling the function likeUser(address liked)
.
A crucial part of the protocol is the tracking of user payments using the userBalances()
mapping. This mapping records the amount of ETH each user has paid, which is later used to create an instance of MultiSigWallet.sol
and transfer the correct funds from both matched users to the MultisigWallet.
The issue arises because LikeRegistry::likeUser()
does not update the userBalances
mapping. As a result, the stored balance remains zero, even if users pay ETH to the protocol. This flaw has two major consequences:
Locked Funds – The ETH sent by users cannot be accessed, as the protocol relies on userBalances
to track amounts for MultiSigWallet
transfers.
Fee Withdrawal Failure – The protocol calculates fees based on userBalances
, meaning that if the balance remains zero, the fees will also be zero, preventing the contract owner from withdrawing them.
Ultimately, this breaks the entire financial flow of the protocol, leading to permanently locked funds and an inoperable fee system.
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);
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:
The impact of this issue is critical, as the funds paid by users will be permanently locked, and the core functionalities of the protocol will be disrupted.
The likelihood of this issue occurring is also high, as it will consistently affect every transaction.
Proof of Concept:
PoC
function test__audit__noTrackingOfUserBalancesCriticalError() public {
vm.prank(user);
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
vm.prank(user2);
soulboundNFT.mintProfile("David", 26, "ipfs://profileImage");
vm.prank(user);
likeRegistry.likeUser{value: 1 ether}(address(user2));
uint256 balancesOfUserPayed = likeRegistry.userBalances(address(user));
console.log(
"Balance of the userBalance(user) after first payment: ",
balancesOfUserPayed
);
uint256 balanceOfCOntract = (address(likeRegistry)).balance;
console.log(
"LikeRegistry balance after first payment: ",
balanceOfCOntract
);
assert(likeRegistry.likes(address(user), address(user2)) == true);
vm.prank(user2);
likeRegistry.likeUser{value: 1 ether}(address(user));
uint256 balanceOfCOntractAfterSendingToMultisig = address(likeRegistry)
.balance;
console.log(
"LikeRegistry balance after creatingand sending to multisig: ",
balanceOfCOntractAfterSendingToMultisig
);
assert(balanceOfCOntractAfterSendingToMultisig < balanceOfCOntract);
}
Recommended Mitigation:
To mitigate this issue we encourage you to add an update of the userBalances()
mapping inside the
LikeRegistry::likeUser()
function.
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);
if (likes[liked][msg.sender]) {
// e pushea tanto en uno como en otro array en mathces mapping
matches[msg.sender].push(liked);
matches[liked].push(msg.sender);
emit Matched(msg.sender, liked);
matchRewards(liked, msg.sender);
}
}