Summary
When a user (Alice) expresses interest in another user (Bob) by calling the LikeRegistry::likeUser
function, and Bob reciprocates, a mutual like is established. At this point, a shared multisig wallet is created, allowing both users to access the funds for their first date. However, due to a bug, the multisig wallet is currently created with a 0 balance instead of the expected amount.
Vulnerability Details:
When Alice expresses interest in Bob by calling LikeRegistry::likeUser
and sending 1e18 ETH. And when Bob reciprocates by calling LikeRegistry::likeUser
and sending 1e18 ETH, the mutual like is established, and the contract invokes the LikeRegistry::matchRewards
internal function to create a shared multisig wallet. This wallet should contain 18e17 ETH but is instead created with 0 ETH, preventing users from accessing their funds.
To reproduce the issue, add the following test case in the testSoulboundProfileNFT.t.sol
file.
import {LikeRegistry} from "../src/LikeRegistry.sol";
.
.
.
function setUp() public {
soulboundNFT = new SoulboundProfileNFT();
likeRegistry = new LikeRegistry(address(soulboundNFT));
vm.deal(user,10e18);
vm.deal(user2,10e18);
}
.
.
.
.
function testMultisigGenerate() public {
vm.prank(user);
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
vm.prank(user2);
soulboundNFT.mintProfile("Bob", 25, "ipfs://profileImage");
vm.prank(user);
likeRegistry.likeUser{value:1e18}(user2);
vm.prank(user2);
likeRegistry.likeUser{value:1e18}(user);
}
The following output shows that the multisig wallet is created, but without any balance:
[1014614] SoulboundProfileNFTTest::testLikedUserBalance()
├─ [0] VM::prank(0x0000000000000000000000000000000000000123)
│ └─ ← [Return]
├─ [166185] SoulboundProfileNFT::mintProfile("Alice", 25, "ipfs://profileImage")
│ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x0000000000000000000000000000000000000123, tokenId: 1)
│ ├─ emit ProfileMinted(user: 0x0000000000000000000000000000000000000123, tokenId: 1, name: "Alice", age: 25, profileImage: "ipfs://profileImage")
│ └─ ← [Stop]
├─ [0] VM::prank(0x0000000000000000000000000000000000000456)
│ └─ ← [Return]
├─ [144285] SoulboundProfileNFT::mintProfile("Bob", 25, "ipfs://profileImage")
│ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x0000000000000000000000000000000000000456, tokenId: 2)
│ ├─ emit ProfileMinted(user: 0x0000000000000000000000000000000000000456, tokenId: 2, name: "Bob", age: 25, profileImage: "ipfs://profileImage")
│ └─ ← [Stop]
├─ [0] VM::prank(0x0000000000000000000000000000000000000123)
│ └─ ← [Return]
├─ [31014] LikeRegistry::likeUser{value: 1000000000000000000}(0x0000000000000000000000000000000000000456)
│ ├─ [627] SoulboundProfileNFT::profileToToken(0x0000000000000000000000000000000000000123) [staticcall]
│ │ └─ ← [Return] 1
│ ├─ [627] SoulboundProfileNFT::profileToToken(0x0000000000000000000000000000000000000456) [staticcall]
│ │ └─ ← [Return] 2
│ ├─ emit Liked(liker: 0x0000000000000000000000000000000000000123, liked: 0x0000000000000000000000000000000000000456)
│ └─ ← [Stop]
├─ [0] VM::prank(0x0000000000000000000000000000000000000456)
│ └─ ← [Return]
├─ [639513] LikeRegistry::likeUser{value: 1000000000000000000}(0x0000000000000000000000000000000000000123)
│ ├─ [627] SoulboundProfileNFT::profileToToken(0x0000000000000000000000000000000000000456) [staticcall]
│ │ └─ ← [Return] 2
│ ├─ [627] SoulboundProfileNFT::profileToToken(0x0000000000000000000000000000000000000123) [staticcall]
│ │ └─ ← [Return] 1
│ ├─ emit Liked(liker: 0x0000000000000000000000000000000000000456, liked: 0x0000000000000000000000000000000000000123)
│ ├─ emit Matched(user1: 0x0000000000000000000000000000000000000456, user2: 0x0000000000000000000000000000000000000123)
│ ├─ [483835] → new MultiSigWallet@0xffD4505B3452Dc22f8473616d50503bA9E1710Ac
│ │ └─ ← [Return] 2193 bytes of code
│ ├─ [55] MultiSigWallet::receive()
│ │ └─ ← [Stop]
│ └─ ← [Stop]
└─ ← [Stop]
Impact
Users cannot access the funds they contributed during LikeRegistry::likeUser
, rendering the multisig wallet ineffective for its intended purpose.
Tools Used
. Foundry
Recommendations
Modify the LikeRegistry::likeUser
function to correctly update the userBalances
mapping:
likes[msg.sender][liked] = true;
+ userBalances[msg.sender] += 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);
}