DatingDapp

First Flight #33
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: low
Invalid

Locked Funds Vulnerability in LikeRegistry When User Sends ETH Directly To The Contract.

Description

The LikeRegistry contract contains a critical vulnerability where ETH sent directly to the contract becomes permanently locked. While the contract includes a receive() function to accept ETH, it lacks any mechanism to withdraw these funds, as the only withdrawal function withdrawFees() is limited to fee collection.

function withdrawFees() external onlyOwner {
require(totalFees > 0, "No fees to withdraw");
uint256 totalFeesToWithdraw = totalFees;
totalFees = 0;
(bool success,) = payable(owner()).call{value: totalFeesToWithdraw}("");
require(success, "Transfer failed");
}

Impact:

  • Permanent loss of user funds sent directly to contract

  • No recovery mechanism for mistaken transfers

  • Contract balance can diverge from tracked fees

  • Owner cannot recover excess funds

Proof of Concept:

The following test demonstrates how funds can become permanently locked:

function testLockedFunds() public {
vm.deal(user, 5 ether);
// User accidentally sends ETH directly to contract
vm.prank(user);
(bool sent,) = address(likeRegistry).call{value: 2 ether}("");
require(sent, "Failed to send ETH");
// Verify contract received ETH
assertEq(address(likeRegistry).balance, 2 ether);
// No fees tracked, so this reverts
vm.expectRevert("No fees to withdraw");
likeRegistry.withdrawFees();
// Even if there were fees, it would only withdraw fee amount
// Simulate some fees
vm.mockCall(
address(likeRegistry),
abi.encodeWithSelector(likeRegistry.totalFees.selector),
abi.encode(1 ether)
);
// 1 ETH still locked in contract
assertEq(address(likeRegistry).balance, 2 ether);
// No way to withdraw remaining balance
vm.stopPrank();
}
function testMultipleLockedScenarios() public {
vm.deal(user, 5 ether);
vm.deal(user2, 5 ether);
// Create profiles
vm.prank(user);
soulboundNFT.mintProfile("Alice", 25, "ipfs://Alice");
vm.prank(user2);
soulboundNFT.mintProfile("Bob", 28, "ipfs://Bob");
// Scenario 1: Overpaid transaction
vm.prank(user);
likeRegistry.likeUser{value: 2 ether}(user2); // Overpaid, 1 excess is locked
// Scenario 2: Direct transfer
vm.prank(user2);
(bool sent,) = address(likeRegistry).call{value: 1 ether}("");
require(sent, "Failed to send ETH");
// Total locked funds
assertEq(address(likeRegistry).balance, 3 ether);
// Neither users nor owner can recover funds
vm.expectRevert();
(sent,) = address(likeRegistry).call{value: 0}(
abi.encodeWithSignature("withdrawBalance()")
);
}

Fix Recommendation:

  • Add refund function for direct transfers:

mapping(address => uint256) public directDeposits;
receive() external payable {
directDeposits[msg.sender] += msg.value;
emit DirectDepositReceived(msg.sender, msg.value);
}
function withdrawDirectDeposit() external {
uint256 amount = directDeposits[msg.sender];
require(amount > 0, "No direct deposits");
directDeposits[msg.sender] = 0;
(bool success,) = payable(msg.sender).call{value: amount}("");
require(success, "Refund failed");
emit DirectDepositWithdrawn(msg.sender, amount);
}

Tools Used

Foundry Testing Framework
Manual Review

Updates

Appeal created

n0kto Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

invalid_receive_function

Not the best design, but if you send money accidentally, that's a user mistake. Informational.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.