Summary
The contract LikeRegistry.sol
relies on the variable userBalance
to keep track of the user balance whenever they like another user, but currently has no way to update it other than when it gets reset to 0, therefore it's 0 at all times.
Impact
The following functionality is rendered useless:
LikeRegistry::matchRewards
: Since totalRewards is 0, the multiSigWallet created will have 0 funds.
LikeRegistry::withdrawFees
: Since matchingFees is 0, the owner will never be able to withdraw fees.
This makes every fund placed into LikeRegistry.sol
be permanently stuck. The contract MultiSig.sol
becomes useless since all newly deployed wallets will always have 0 funds. Lastly, the contract SoulboundProfileNFT.sol
can still be used but only for the fun of having the soulboundNFT, there wouldn't be further motivation to use it.
Proof of Concept
Add console
import to LikeRegistry.sol
:
import { console } from "forge-std/Test.sol";
Add all these console logs to view what numbers are coming from matchRewards:
function matchRewards(address from, address to) internal {
uint256 matchUserOne = userBalances[from];
+ console.log("matchUserOne balance:", matchUserOne);
uint256 matchUserTwo = userBalances[to];
+ console.log("matchUserTwo balance:", matchUserTwo);
userBalances[from] = 0;
userBalances[to] = 0;
uint256 totalRewards = matchUserOne + matchUserTwo;
uint256 matchingFees = (totalRewards * FIXEDFEE ) / 100;
+ console.log("matchingFees:", matchingFees);
uint256 rewards = totalRewards - matchingFees;
+ console.log("rewards:", rewards);
totalFees += matchingFees;
+ console.log("totalFees:", totalFees);
// Deploy a MultiSig contract for the matched users
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
// Send ETH to the deployed multisig wallet
(bool success, ) = payable(address(multiSigWallet)).call{value: rewards}("");
require(success, "Transfer failed");
}
Make testLikeRegistry.t.sol
and paste this in:
pragma solidity ^0.8.19;
import {Test, console} from "forge-std/Test.sol";
import {LikeRegistry} from "../src/LikeRegistry.sol";
import {SoulboundProfileNFT} from "../src/SoulboundProfileNFT.sol";
contract LikeRegistryTest is Test {
SoulboundProfileNFT soulboundNFT;
LikeRegistry likeRegistry;
address user1 = address(0x123);
address user2 = address(0x456);
function setUp() public {
vm.deal(user1, 10 ether);
vm.deal(user2, 10 ether);
soulboundNFT = new SoulboundProfileNFT();
likeRegistry = new LikeRegistry(address(soulboundNFT));
}
modifier userLikedByLiker() {
vm.startPrank(user2);
soulboundNFT.mintProfile("Bob", 23, "ipfs://profileImage");
vm.stopPrank();
vm.startPrank(user1);
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
likeRegistry.likeUser{value: 1 ether}(user2);
vm.stopPrank();
_;
}
function testMatchRewards() public userLikedByLiker {
vm.startPrank(user2);
likeRegistry.likeUser{value: 1 ether}(user1);
vm.stopPrank();
}
}
Viewing the logs from this test it can be seen that every value is 0.
Recommendations
Considering changing the userBalance
mapping:
- mapping(address => uint256) public userBalances;
+ mapping(address => mapping(address => uint256)) public userMatchBalance;
Add this line in the LikeRegistry::likeUser
function:
function likeUser(
address liked
) public 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;
+ userMatchBalance[msg.sender][liked] = msg.value;
emit Liked(msg.sender, liked);
// 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);
}
}
And change the LikeRegistry::matchRewards
function to use the new variable:
function matchRewards(address from, address to) internal {
- uint256 matchUserOne = userBalances[from];
+ uint256 matchUserOne = userMatchBalance[from][to];
console.log("matchUserOne balance:", matchUserOne);
- uint256 matchUserTwo = userBalances[to];
+ uint256 matchUserTwo = userMatchBalance[to][from];
console.log("matchUserTwo balance:", matchUserTwo);
- userBalances[from] = 0;
+ userMatchBalance[from][to] = 0;
- userBalances[to] = 0;
+ userMatchBalance[to][from] = 0;
uint256 totalRewards = matchUserOne + matchUserTwo;
uint256 matchingFees = (totalRewards * FIXEDFEE ) / 100;
console.log("matchingFees:", matchingFees);
uint256 rewards = totalRewards - matchingFees;
console.log("rewards:", rewards);
totalFees += matchingFees;
console.log("totalFees:", totalFees);
// Deploy a MultiSig contract for the matched users
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
// Send ETH to the deployed multisig wallet
(bool success, ) = payable(address(multiSigWallet)).call{value: rewards}("");
require(success, "Transfer failed");
}
Now by running the test, the logs are correctly populated with values.
This is the reasoning:
mapping(address user1 => mapping(address user2 => uint256 balance)) public userMatchBalance;
This mapping leads to the balance user1 has placed when liking user2.
When Bob likes Alice:
His matching balance gets updated to 1
Alice => Bob balance remains as 0, since she hasn't liked him back yet
Now Bob likes Megan:
Bob => Megan updates from 0 to 1
Megan likes him back
Megan => Bob updates from 0 to 1
A multiSigWallet is created
Bob => Megan gets updated to 0
Megan => Bob gets updated to 0
Bob => Alice remains as 1
A few days later Alice finds Bob and likes him:
since Bob => Alice is still 1, a multiSigWallet is created with both rewards
Bob => Alice updates from 1 to 0
Alice => Bob updates from 1 to 0
This way it checks the balance of matched users, and won't update the balance to 0 for all future matches.