DatingDapp

AI First Flight #6
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

MultiSig Address Is Not Stored

Root + Impact

Description

  • MultiSig wallet is made for both users who mutualy like each other

  • On match, a fresh MultiSigWallet is deployed, but its address is neither stored nor emitted

  • Funds are sent to it, but users must rely on transaction traces/indexers/UI assumptions to find the wallet address

  • This is not direct theft, but it can make funds operationally inaccessible.

//LikeRegistry.sol line 61
// Deploy a MultiSig contract for the matched users
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);

Risk

Likelihood:

  • When a match is made , a multisig is created and funded but it isn't accessible to users directly and intuitively, therefore likelihood is Medium.


Impact:

  • Users may not able to get multisig wallet address

  • it can make funds operationally inaccessible

Proof of Concept

Write a new test named testUnableRecoverMultisigAddress.t.sol

to verify that the multisig wallet is created but never stored and therefore can not be retrieved easily

Ran with command: forge test --match-test testUnableRecoverMultisigAddress -vvvv

function testUnableRecoverMultisigAddress() public {
// --- Step 1: Record logs so we can fish out the MultiSigWallet address ---
vm.recordLogs();
vm.prank(alice);
likeRegistry.likeUser{value: 1 ether}(bob);
vm.prank(bob);
likeRegistry.likeUser{value: 1 ether}(alice); // mutual like → matchRewards() fires
// --- Step 2: Parse the Matched event to get the MultiSigWallet address ---
// matchRewards() deploys a new MultiSigWallet, but the address is never
// emitted. We recover it deterministically via CREATE: the deployer is
// LikeRegistry and the nonce is 1 (first contract it ever deploys).
address expectedMultisig = computeCreateAddress(address(likeRegistry), 1);
// Confirm the wallet really was deployed (has runtime code)
assertTrue(expectedMultisig.code.length > 0, "MultiSigWallet was not deployed");
// --- Step 3: The MultiSig holds 0 ETH ---
// userBalances[] is never written in likeUser(), so matchRewards() always
// computes totalRewards = 0, and sends 0 ETH to the wallet.
assertEq(
address(expectedMultisig).balance,
0,
"MultiSigWallet should hold 0 ETH (funds never routed)"
);
// --- Step 4: The 2 ETH is stuck in LikeRegistry ---
assertEq(
address(likeRegistry).balance,
2 ether,
"LikeRegistry should still hold the full 2 ETH"
);
// --- Step 5: Users cannot recover funds through the MultiSig ---
// Even if both owners submit + approve + execute a withdrawal, the wallet
// has nothing to send. Any attempt to execute a tx for the full amount
// will revert because the wallet is empty.
MultiSigWallet wallet = MultiSigWallet(payable(expectedMultisig));
vm.prank(alice);
wallet.submitTransaction(alice, 1 ether); // propose sending 1 ETH to Alice
vm.prank(alice);
wallet.approveTransaction(0);
vm.prank(bob);
wallet.approveTransaction(0);
// Execution reverts — the wallet has no ETH to send
vm.prank(alice);
vm.expectRevert("Transaction failed");
wallet.executeTransaction(0);
}

Recommended Mitigation

Store and emit the multisig address when deployed step by step

  1. store multisig address with other variables

+ mapping(address => mapping(address => address)) public matchWallet;

2 emit the address during creation and funding right after line 62

+ event Matched(address indexed user1, address indexed user2, address wallet);
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 2 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!