DatingDapp

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

`MultiSigWallet` is 2-of-2 with no recovery, so a matched pair's pooled ETH is permanently lockable by one uncooperative or key-loss owner

Description

Normal behavior: a matched pair's pooled date funds should be spendable, and each party should retain a way to recover at least their own contribution even if the relationship breaks down — since the two owners are strangers matched by an app, not a pre-existing trusted pair.

Specific issue: MultiSigWallet is a strict 2-of-2 with no recovery of any kind. executeTransaction requires BOTH approvedByOwner1 && approvedByOwner2, and the contract has no owner-change, no timelock fallback, no unilateral or time-delayed withdrawal, and no split-refund. The owners are fixed at construction (set by LikeRegistry.matchRewards to the two matched users) with no setter:

function executeTransaction(uint256 _txId) external onlyOwners {
...
require(txn.approvedByOwner1 && txn.approvedByOwner2, "Not enough approvals"); // @> 2-of-2, no fallback
...
}
// no setOwner / recover() / timelock / split — owners are immutable after the constructor

The result is that the pooled ETH is trapped whenever the two parties do not both actively cooperate: if either owner disagrees about the recipient or amount, loses/never had access to their key, abandons the app, or simply never signs, neither party — nor the protocol owner — can ever retrieve the funds, not even the portion they personally contributed. On a dating app, a matched pair going cold or one side ghosting is a normal, expected outcome, so this is not a rare edge case. The rest of the wallet is sound (access control, the 2-of-2 approval logic, checks-effects-interactions ordering with executed set before the external call, and replay protection are all correct) — the gap is purely the absence of any escape hatch for the held funds.

Risk

Likelihood:

  • A matched pair failing to both cooperate (disagreement, ghosting, lost key, abandoned account) is a common lifecycle outcome for strangers matched by an app; it needs no attacker.

Impact:

  • Permanent lock of the matched pair's entire pooled ETH, with no way for either contributor or the owner to recover even their own share. Bounded to a fund-lock (no attacker profit), hence Medium.

Proof of Concept

test/MultiSigAudit.t.sol::test_FundsPermanentlyLocked_OnDisagreement and ::test_LostKey_NoUnilateralWithdraw (both PASS): each owner approves only their own tx / one owner never acts; both executeTransaction calls revert "Not enough approvals" and the ETH is stuck.

Recommended Mitigation

Add a timelocked unilateral withdrawal, a 50/50 split-refund callable by either owner, or an owner-recovery mechanism.

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!