Summary
Although the likeUser
function is a payable function and can receive ether, it has no means to keep track of the payment made by users whenever they like another user's profile
Vulnerability Details
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);
}
}
The LikeRegistry
contract has a mapping userBalances
that maps the address of users to the amount deposited/paid which is meant to be updated whenever a user like another user's profile. But userBalances
was not updated in the likeUser
function
Impact
userBalances
was used in matchRewards
to get the amount paid by each user involved in a match and was used in calculating reward
and totalFees
. This results in severe disruption of the protocol's functionality as there would be no fee and no reward to send to the multiSig Wallet
Place the code below in testSoulboundProfileNFT.t.sol
:
PoC
function testWithdrawFees() public {
vm.prank(user);
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
vm.prank(user2);
soulboundNFT.mintProfile("Alex", 24, "ipfs://profileImage");
uint256 ownerInitialBalance = address(likeRegistry).balance;
console.log('Owners Initial Balance:', ownerInitialBalance);
vm.deal(user, 2 ether);
vm.deal(user2, 2 ether);
vm.prank(user);
likeRegistry.likeUser{value: 1 ether}(user2);
vm.prank(user2);
likeRegistry.likeUser{value: 1 ether}(user);
vm.prank(owner);
likeRegistry.withdrawFees();
uint256 fees = address(likeRegistry).balance;
assertEq(ownerInitialBalance, 0);
assertEq(fees, 0.2 ether);
}
`fees` returns 0 because there is no fee to withdraw
Tools Used
Manual Review
Recommendations
Update userBalances
mapping whenever a user likes another's profile
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");
+ userBalances[msg.sender] += msg.value;
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);
}
}