DatingDapp

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

No Fees Are Realized And No Rewards Are Sent To MultiSigWallet When Users Like Themselves

Summary

Per the README, when there is a mutual likeness between two users, they are meant to get 90% of all their previous like payments pooled into a shared MultiSigWallet, and the protocol gets a 10% fee.

But with current setup, none of these is realized; neither 90% funds, or 10% fees.

Vulnerability Details

The LikeRegistry::userBalances is not updated with the payment of each caller of the LikeRegistry::likeUser() function.

Impact

This means that there are no funds sent to the shared MultiSigWallet, and the protocol realizes no fees.

PoC

I added the following getter function to the LikeRegistry contract to enable us get the totalFees the protocol realizes:

function getTotalFees() external view returns (uint256) {
return totalFees;
}

Here is the test that validates this finding:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console2} from "forge-std/Test.sol";
import {LikeRegistry} from "../src/LikeRegistry.sol";
import {SoulboundProfileNFT} from "../src/SoulboundProfileNFT.sol";
contract TestLikeRegistry is Test {
LikeRegistry registry;
SoulboundProfileNFT nft;
address bale = address(0x1);
address sarah = address(0x2);
address vendor = address(0x3);
function setUp() public {
nft = new SoulboundProfileNFT();
registry = new LikeRegistry(address(nft));
// bale and sarah mint profiles
vm.prank(bale);
nft.mintProfile("Bale", 27, "ipfs://profileImage");
vm.prank(sarah);
nft.mintProfile("Sarah", 24, "ipfs://profileImage");
deal(bale, 1 ether);
deal(sarah, 1 ether);
}
function testNoFeesAsWellAsNoFundsInMultiSIgWalletForLovers() public {
// bale likes sarah
vm.prank(bale);
registry.likeUser{value: 1 ether}(sarah);
// sarah likes bale, thus creating a match
vm.prank(sarah);
registry.likeUser{value: 1 ether}(bale);
// verify totalFees
uint256 totalFees = registry.getTotalFees();
assert(totalFees == 0);
vm.prank(nft.owner());
vm.expectRevert();
registry.withdrawFees();
}
receive() external payable {}
}

Run the testNoFeesAsWellAsNoFundsInMultiSIgWalletForLovers test and with the -vvvv flag, and read the traces to see that there are no funds sent to the newly created MultiSIgWallet:

Traces:
[713486] TestLikeRegistry::testNoFeesAsWellAsNoFundsInMultiSIgWalletForLovers()
├─ [0] VM::prank(ECRecover: [0x0000000000000000000000000000000000000001])
│ └─ ← [Return]
├─ [37536] LikeRegistry::likeUser{value: 1000000000000000000}(SHA-256: [0x0000000000000000000000000000000000000002])
│ ├─ [2627] SoulboundProfileNFT::profileToToken(ECRecover: [0x0000000000000000000000000000000000000001]) [staticcall]
│ │ └─ ← [Return] 1
│ ├─ [2627] SoulboundProfileNFT::profileToToken(SHA-256: [0x0000000000000000000000000000000000000002]) [staticcall]
│ │ └─ ← [Return] 2
│ ├─ emit Liked(liker: ECRecover: [0x0000000000000000000000000000000000000001], liked: SHA-256: [0x0000000000000000000000000000000000000002])
│ └─ ← [Stop]
├─ [0] VM::prank(SHA-256: [0x0000000000000000000000000000000000000002])
│ └─ ← [Return]
├─ [639534] LikeRegistry::likeUser{value: 1000000000000000000}(ECRecover: [0x0000000000000000000000000000000000000001])
│ ├─ [627] SoulboundProfileNFT::profileToToken(SHA-256: [0x0000000000000000000000000000000000000002]) [staticcall]
│ │ └─ ← [Return] 2
│ ├─ [627] SoulboundProfileNFT::profileToToken(ECRecover: [0x0000000000000000000000000000000000000001]) [staticcall]
│ │ └─ ← [Return] 1
│ ├─ emit Liked(liker: SHA-256: [0x0000000000000000000000000000000000000002], liked: ECRecover: [0x0000000000000000000000000000000000000001])
│ ├─ emit Matched(user1: SHA-256: [0x0000000000000000000000000000000000000002], user2: ECRecover: [0x0000000000000000000000000000000000000001])
│ ├─ [483834] → new MultiSigWallet@0xffD4505B3452Dc22f8473616d50503bA9E1710Ac
│ │ └─ ← [Return] 2193 bytes of code
│ ├─ [55] MultiSigWallet::receive() 👈🏾👈🏾👈🏾
│ │ └─ ← [Stop]
│ └─ ← [Stop]
├─ [291] LikeRegistry::getTotalFees() [staticcall]
│ └─ ← [Return] 0
├─ [2441] SoulboundProfileNFT::owner() [staticcall]
│ └─ ← [Return] TestLikeRegistry: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]
├─ [0] VM::prank(TestLikeRegistry: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496])
│ └─ ← [Return]
├─ [0] VM::expectRevert(custom error 0xf4844814)
│ └─ ← [Return]
├─ [2619] LikeRegistry::withdrawFees()
│ └─ ← [Revert] revert: No fees to withdraw
└─ ← [Stop]

Tools Used

  • Foundry

Recommendations

Update the userBalances of each user after they like another user:

function likeUser(address liked) external payable {
...
+ userBalances[msg.sender] += msg.value;
likes[msg.sender][liked] = true;
emit Liked(msg.sender, liked);
...
}

Here is a test that confirms that this mitigation works:

function testNoFeesAsWellAsNoFundsInMultiSIgWalletForLovers() public {
// bale likes sarah
vm.prank(bale);
registry.likeUser{value: 1 ether}(sarah);
// sarah likes bale, thus creating a match
vm.prank(sarah);
registry.likeUser{value: 1 ether}(bale);
// verify totalFees
uint256 totalFees = registry.getTotalFees();
assert(totalFees == 0.2 ether);
vm.prank(nft.owner());
registry.withdrawFees();
}
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.