Description
Users' ETH payments in the LikeRegistry
contract are not tracked, risking permanent loss of funds when users like profiles. The contract receives ETH but does not record individual user contributions, creating a critical vulnerability where sent funds become effectively trapped.
Here is the affected code:
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
User-sent ETH becomes untraceable
There is no mechanism to recover or withdraw individual user funds
Potential permanent loss of user investments
Proof of Code
Create a folder for testing for the LikeRegistry.sol
contract
Paste this into the folder
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "../src/LikeRegistry.sol";
import "../src/SoulboundProfileNFT.sol";
contract LikeRegistryTest is Test {
LikeRegistry public likeRegistry;
SoulboundProfileNFT public profileNFT;
address alice = address(0x1);
address bob = address(0x2);
function setUp() public {
profileNFT = new SoulboundProfileNFT();
likeRegistry = new LikeRegistry(address(profileNFT));
vm.deal(alice, 10 ether);
vm.deal(bob, 10 ether);
vm.prank(alice);
profileNFT.mintProfile("Alice", 25, "ipfs://alice");
vm.prank(bob);
profileNFT.mintProfile("Bob", 28, "ipfs://bob");
}
function testLostFunds() public {
vm.prank(alice);
likeRegistry.likeUser{value: 1 ether}(bob);
assertEq(likeRegistry.userBalances(alice), 0);
assertEq(address(likeRegistry).balance, 1 ether);
vm.prank(bob);
likeRegistry.likeUser{value: 1 ether}(alice);
assertEq(address(likeRegistry).balance, 2 ether);
}
}
This test shows the vulnerability in a few simple steps:
Alice sends 1 ETH to like Bob
The contract accepts the ETH but doesn't track it in userBalances
Bob likes Alice back with 1 ETH
When they match, no funds get distributed because their balances show 0
Both users' ETH (2 ETH total) is now stuck in the contract forever
Tools Used
Manual Code Review
Solidity Static Analysis
Recommended Mitigation
Add userBalances[msg.sender] += msg.value;
to track user contributions.
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);
if (likes[liked][msg.sender]) {
matches[msg.sender].push(liked);
matches[liked].push(msg.sender);
emit Matched(msg.sender, liked);
matchRewards(liked, msg.sender);
}
}