Summary
The issue is located in function LikeRegistry::likeUser
where the user is paying at least 1 ether to like another user. Here we are suppose to add the amount payed into the balance of the user, but this is not being done.
Vulnerability Details
The vulnerability effect is located in LikeRegistry::matchRewards
where the mapping userBalances is used to calculate the amount of rewards and the matching fees.
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;
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
require(success, "Transfer failed");
}
POC
In order to prove the issue, we can validate that no fees are being collected.
For that, we need to make totalFees
public first
- uint256 totalFees;
+ uint256 public totalFees;
And run this test
contract LikeRegistryTest is Test {
LikeRegistry likeRegistry;
SoulboundProfileNFT soulboundNFT;
address user1 = address(1);
address user2 = address(2);
function setUp() public {
soulboundNFT = new SoulboundProfileNFT();
likeRegistry = new LikeRegistry(address(soulboundNFT));
}
function testLikeRegistry() public {
vm.prank(user1);
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
vm.prank(user2);
soulboundNFT.mintProfile("Bob", 26, "ipfs://profileImage");
vm.prank(user1);
vm.deal(user1, 1 ether);
likeRegistry.likeUser{value: 1 ether}(user2);
assertEq(likeRegistry.userBalances(user1), 0 ether);
vm.prank(user2);
vm.deal(user2, 1 ether);
likeRegistry.likeUser{value: 1 ether}(user1);
assertEq(likeRegistry.userBalances(user2), 0 ether);
assertEq(likeRegistry.totalFees(), 0 ether);
}
}
Impact
This is happening for every valid matcht and makes the Multisig to be empty. Also the fees collected are going to be zero.
Recommendations
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);
+ userBalances[msg.sender] += msg.value;
// 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);
}
}