DatingDapp

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

MultiSigWallet has no mechanism to revoke approvals or cancel transactions — approved but unexecutable transactions create permanent deadlock

Root + Impact

Description

  • The MultiSigWallet allows owners to submit transactions, approve them, and execute them. However, once an owner approves a transaction, there is no way to revoke that approval. If a transaction is approved by both owners but cannot be executed (e.g., due to insufficient balance or the recipient being a contract that reverts), the transaction is stuck forever in an approved-but-unexecuted state.

    Additionally, there is no way to cancel a transaction. The transactions array only grows, and each entry can only be marked executed = true through successful execution. This means:

    1. A malicious owner can spam low-value or impossible transactions, polluting the array.

    2. There's no nonce or expiry mechanism — old transactions remain valid indefinitely.

    3. If user relationships sour (likely in a dating context), one owner can refuse to cooperate, permanently locking funds.

// No revoke or cancel functions exist in MultiSig.sol
// Once approved, the only state transition is: approved → executed (via successful call)
// If execution fails, there is no path to recover

Risk

Likelihood:

  • In a dating application context, matched users becoming adversarial after a failed relationship is a realistic and expected scenario.

Impact:

  • Funds in the MultiSig wallet can be permanently locked if one owner becomes uncooperative.

  • No emergency recovery mechanism exists.

Proof of Concept

This test deploys a MultiSig with 2 ETH and has Alice submit and approve a valid 1 ETH transaction. Bob simply never approves. Alice has no mechanism to cancel, revoke, or withdraw — her funds are permanently locked unless Bob cooperates.

function testM03_NoRevokeOrCancel() public {
MultiSigWallet wallet = new MultiSigWallet(alice, bob);
(bool ok,) = payable(address(wallet)).call{value: 2 ether}("");
require(ok);
// Alice submits a valid transaction
vm.prank(alice);
wallet.submitTransaction(alice, 1 ether);
// Alice approves
vm.prank(alice);
wallet.approveTransaction(0);
// Bob refuses to approve — funds are stuck
// Alice cannot cancel transaction #0
// Alice cannot withdraw funds in any other way
// The 2 ETH is permanently locked unless Bob cooperates
}

Recommended Mitigation

Add functions to revoke approvals and cancel transactions. Consider adding a time-lock mechanism where, if one party is unresponsive for an extended period, the other party can withdraw their portion.

+ function revokeApproval(uint256 _txId) external onlyOwners {
+ require(_txId < transactions.length, "Invalid transaction ID");
+ Transaction storage txn = transactions[_txId];
+ require(!txn.executed, "Already executed");
+ if (msg.sender == owner1) {
+ require(txn.approvedByOwner1, "Not approved");
+ txn.approvedByOwner1 = false;
+ } else {
+ require(txn.approvedByOwner2, "Not approved");
+ txn.approvedByOwner2 = false;
+ }
+ }
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 3 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!