DatingDapp

First Flight #33
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: high
Valid

LikeRegistry::likeUser never update the userBalances losing the ether sent

Summary

The LikeRegistry::likeUser function fails to update a user's balance when Ether is sent, resulting in inaccurate tracking of funds. Additionally, the userBalances mapping is designed to track a single balance per address, whereas it should map balances for each like interaction between two parties.

Vulnerability Details

  • Problem with Updating userBalances:
    – When a match occurs and the contract is expected to forward all Ether collected by both partners to a new multisig wallet, the match function verifies the Ether amounts by checking the userBalances of each user.
    – Because likeUser never increments the corresponding balance entry, the userBalances remain at zero even though Ether was sent. As a result, the match function cannot accurately account for the funds held on behalf of the users.

  • Incorrect Mapping Structure:
    – The current implementation uses a single-level mapping:
    mapping(address => uint256) public userBalances;
    This design maps one balance to an address but doesn’t distinguish between multiple relationships (i.e. which user liked which other user).
    – To correctly track the funds associated with each “like” or match, the mapping should be nested:
    mapping(address => mapping(address => uint256)) public userBalances;
    With this change, the balance from user A to user B (and vice versa) can be tracked independently.

  • Missing Update Logic:
    – The likeUser function should update the mapping by adding the sent Ether amount to the balance corresponding to the sender and the target user. Without this update, any Ether sent is not recorded properly, leading to potential issues in future fund claims or in executing matching logic.

Impact

  • Funds Misaccounted:
    – Since the Ether sent through likeUser is not added to any internal ledger, it becomes impossible for the contract to later determine how much Ether each user contributed and the fund are irrecoverable.

  • Broken Match Functionality:
    – The match function relies on userBalances to verify the total funds that should be forwarded to a new multisig wallet. Without proper balance updates, a match result in an incorrect transfer of funds.

POC

function testMultisigDontGetTheMoney() public {
// Create profiles for two users.
vm.prank(user);
soulboundNFT.mintProfile("Alice", 18, "ipfs://profileImageAlice");
vm.prank(user2);
soulboundNFT.mintProfile("Bob", 65, "ipfs://profileImageBob");
// User sends 1 ether "like" to user2.
vm.prank(user);
likeRegistry.likeUser{value: 1 ether}(user2);
// User2 sends 1 ether "like" back to user.
// The contract is expected to match the likes and forward funds to a multisig wallet.
vm.prank(user2);
vm.recordLogs();
likeRegistry.likeUser{value: 1 ether}(user);
Vm.Log[] memory entries = vm.getRecordedLogs();
// read the multisig address from the event
// NOTE: the multisig address has been added to the Matched event
address multisigAddress = abi.decode(abi.encodePacked(entries[1].topics[3]), (address));
// Verify that the multisig wallet does not hold any Ether because the balances were not updated correctly
assertEq(multisigAddress.balance, 0);
}

Tools Used

  • Foundry

Recommendations

  • Change the Mapping Structure:
    – Replace the single-level mapping with a nested mapping to track funds per like between two parties:

- mapping(address => uint256) public userBalances;
+ mapping(address => mapping(address => uint256)) public userBalances;
  • Update the Balance in likeUser:
    – Modify the likeUser function to record the sent Ether amount into the proper balance entry. For instance:

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][target] += msg.value;
...
}
function matchRewards(address from, address to) internal {
- uint256 matchUserOne = userBalances[from];
- uint256 matchUserTwo = userBalances[to];
- userBalances[from] = 0;
- userBalances[to] = 0;
+ uint256 matchUserOne = userBalances[from][to];
+ uint256 matchUserTwo = userBalances[to][from];
+ userBalances[from][to] = 0;
+ userBalances[to][from] = 0;
uint256 totalRewards = matchUserOne + matchUserTwo;
Updates

Appeal created

n0kto Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_likeUser_no_userBalances_updated

Likelihood: High, always. Impact: High, loss of funds

Support

FAQs

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