DatingDapp

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

MultiSig requires both matched owners with no timeout or recovery, so one lost or uncooperative partner locks the matched funds forever

2-of-2 MultiSigWallet has no recovery, so a single uncooperative owner locks rewards forever

Description

The MultiSigWallet deployed per match requires BOTH owners to approve before any transaction executes (line 72), and provides no timeout, no single-owner recovery, and no way to rotate owners. If one matched partner loses their key, dies, or simply refuses to sign, the matched rewards are permanently locked.

function executeTransaction(uint256 _txId) external onlyOwners {
require(_txId < transactions.length, "Invalid transaction ID");
Transaction storage txn = transactions[_txId];
require(!txn.executed, "Transaction already executed");
require(txn.approvedByOwner1 && txn.approvedByOwner2, "Not enough approvals"); // @> both required, no fallback
txn.executed = true;

Risk

Likelihood: Low to medium. Lost keys, abandoned accounts, and disputes between former matches are ordinary occurrences in a consumer dating app, and any one of them is sufficient. There is no adversary needed — passive non-cooperation triggers the freeze.

Impact: High for the affected funds. Every match's reward ETH is sent to a fresh 2-of-2 wallet (LikeRegistry line 62-65). With strictly both approvals mandatory and no recovery path or owner change function, a single non-signing party renders that wallet's entire balance unrecoverable for both users. Funds are not stolen but are permanently frozen, which is an equivalent economic loss to the depositors.

Proof of Concept

One owner approves, the other never does; the balance can never leave the wallet.

function test_singleOwnerCanFreezeFunds() public {
// wallet funded with 2 ether, owners = (a, b)
vm.prank(a);
wallet.submitTransaction(a, 2 ether);
vm.prank(a);
wallet.approveTransaction(0); // only owner1 approves
vm.prank(a);
vm.expectRevert("Not enough approvals"); // owner2 never signs -> locked
wallet.executeTransaction(0);
}

Recommended Mitigation

Add a recovery mechanism: a timelocked single-owner withdrawal after a long inactivity window, or an owner-rotation/guardian function.

+ uint256 public constant RECOVERY_DELAY = 180 days;
+ function recoverAfterTimeout(address to, uint256 value) external onlyOwners {
+ require(block.timestamp > deployedAt + RECOVERY_DELAY, "too early");
+ (bool ok,) = payable(to).call{value: value}("");
+ require(ok, "recovery failed");
+ }
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!