DatingDapp

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

No Transaction Cancellation Mechanism Leads to Permanent Lock

Root + Impact

Description

  • Describe the normal behavior in one or more sentences

  • The transaction is supposed to be cancelled if need be

  • Explain the specific issue or problem in one or more sentences

  • Once a transaction is submitted, there is no way to cancel it

// No cancel function exists
function submitTransaction(address _to, uint256 _value) external onlyOwners {
transactions.push(Transaction(_to, _value, false, false, false));
// Once pushed, cannot be removed or cancelled
}// Root cause in the codebase with @> marks to highlight the relevant section

Risk

Likelihood:

  • Reason 1 // Describe WHEN this will occur (avoid using "if" statements)

  • Medium risk to high risk

  • Reason 2

Impact:

  • Impact 1

  • Funds locked if owners disagree

  • No recovery mechanism for mistaken transactions

  • Wallet unusable if malicious owner submits large transaction

  • Impact 2

Proof of Concept

// Owner1 accidentally submits wrong transaction
vm.prank(owner1);
wallet.submitTransaction(wrongAddress, 100 ether);
// Owner1 realizes mistake but cannot cancel
// Owner2 refuses to approve
// Transaction stuck forever, 100 ETH effectively locked
// Meanwhile, new legitimate transaction created
vm.prank(owner1);
wallet.submitTransaction(correctAddress, 50 ether);
// But if wallet only has 100 ETH total, legitimate tx cannot execute
// because pending tx "reserves" those funds conceptually

Recommended Mitigation

- remove this code
+ add this code
event TransactionCancelled(uint256 indexed txId);
function cancelTransaction(uint256 _txId) external onlyOwners {
require(_txId < transactions.length, "Invalid transaction ID");
Transaction storage txn = transactions[_txId];
require(!txn.executed, "Cannot cancel executed transaction");
// Require both owners to cancel OR allow individual cancellation
// Option 1: Either owner can cancel (safer)
txn.executed = true; // Mark as "cancelled" (reuse field)
emit TransactionCancelled(_txId);
}
// Option 2: Both owners must agree to cancel
bool public cancelledByOwner1;
bool public cancelledByOwner2;
mapping(uint256 => bool) public cancelledByOwner1Map;
mapping(uint256 => bool) public cancelledByOwner2Map;
function cancelTransaction(uint256 _txId) external onlyOwners {
require(_txId < transactions.length, "Invalid transaction ID");
Transaction storage txn = transactions[_txId];
require(!txn.executed, "Cannot cancel executed transaction");
if (msg.sender == owner1) {
cancelledByOwner1Map[_txId] = true;
} else {
cancelledByOwner2Map[_txId] = true;
}
// If both cancelled, mark as executed (cancelled)
if (cancelledByOwner1Map[_txId] && cancelledByOwner2Map[_txId]) {
txn.executed = true;
emit TransactionCancelled(_txId);
}
}
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!