DatingDapp

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

Logic Error - Deployed MultiSig address is not stored or emitted, preventing user interaction

Root + Impact


Description

When a mutual match occurs in LikeRegistry.sol, the matchRewards function deploys a new MultiSigWallet contract. However, the address of this newly deployed contract is only stored in a local stack variable and is never saved to the contract's state or emitted in the Matched event.

Impact

  • Permanent Loss of Funds: Rewards are successfully transferred to the new MultiSigWallet address, but since that address is not recorded anywhere on-chain, the matched users (the owners) have no way to locate the contract to interact with it.

  • Locked Liquidity: Any ETH rewards sent to the wallet remain permanently locked because no one can call submitTransaction or approveTransaction without the contract address.

function matchRewards(address from, address to) internal {
uint256 matchUserOne = userBalances[from];
uint256 matchUserTwo = userBalances[to];
userBalances[from] = 0;
userBalances[to] = 0;
uint256 totalRewards = matchUserOne + matchUserTwo;
uint256 matchingFees = (totalRewards * FIXEDFEE) / 100;
uint256 rewards = totalRewards - matchingFees;
totalFees += matchingFees;
// 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");
}

Risk

Likelihood:

Users receive a Matched event but have no way to retrieve the address of the MultiSig contract. Any ETH sent to the MultiSig as a reward remains permanently inaccessible to the owners.

Proof of Concept

This Foundry test proves that while the system "performs" the match and moves funds, it fails to provide a way for users to "spot" the resulting contract. The test verifies that:

  1. The Matched event contains only user addresses and lacks the wallet address.

  2. The LikeRegistry balance drops to zero, confirming the funds were sent to an unrecorded destination.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "../src/LikeRegistry.sol";
import "../src/SoulboundProfileNFT.sol";
import "../src/MultiSig.sol";
contract LikeRegistryPoC is Test {
LikeRegistry registry;
SoulboundProfileNFT nft;
address user1 = makeAddr("Adarsh");
address user2 = makeAddr("User2");
function setUp() public {
nft = new SoulboundProfileNFT();
registry = new LikeRegistry(address(nft));
vm.deal(user1, 10 ether);
vm.deal(user2, 10 ether);
vm.prank(user1);
nft.mintProfile("Adarsh", 25, "ipfs://img1");
vm.prank(user2);
nft.mintProfile("User2", 24, "ipfs://img2");
}
function test_FundsAreLockedBecauseAddressIsLost() public {
vm.prank(user1);
registry.likeUser{value: 1 ether}(user2);
vm.recordLogs();
vm.prank(user2);
registry.likeUser{value: 1 ether}(user1);
Vm.Log[] memory entries = vm.getRecordedLogs();
// Verification: The 'Matched' event is missing the wallet address
assertEq(entries[1].topics.length, 3, "Event should have exactly 3 topics (missing wallet)");
// Verification: Funds are at an unrecorded destination
assertEq(address(registry).balance, 0, "Funds left registry but destination is unknown");
}
}

Recommended Mitigation

To fix this, we must ensure the deployment address is captured and persisted. The mitigation involves:

  1. State Storage: Adding a mapping to store the wallet address for each pair of users.

  2. Event Transparency: Updating the Matched event to include the new contract address.

  3. Balance Tracking: Correctly updating userBalances during the likeUser call so the rewards are accurately calculated.

+ mapping(address => mapping(address => address)) public getMultiSigWallet;
- event Matched(address indexed user1, address indexed user2);
+ event Matched(address indexed user1, address indexed user2, address multiSigAddress);
function matchRewards(address from, address to) internal {
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
+ address walletAddr = address(multiSigWallet);
+ getMultiSigWallet[from][to] = walletAddr;
+ getMultiSigWallet[to][from] = walletAddr;
+ emit Matched(from, to, walletAddr);
- (bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
+ (bool success,) = payable(walletAddr).call{value: rewards}("");
require(success, "Transfer failed");
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 8 days 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!