MultiSig Timelock

First Flight #55
Beginner FriendlyWallet
100 EXP
Submission Details
Impact: high
Likelihood: high

Full-gas external calls plus no cancel path create stuck-transaction DoS

Author Revealed upon completion

Scope
src/MultiSigTimelock.sol: _executeTransaction

Root + Impact

Description

  • Normal behavior: Failed executions should be cancellable.

  • Issue: The contract forwards all gas to txn.to and has no way to cancel a failing transaction. A malicious target can consume all gas or revert unconditionally; the transaction stays pending forever, wasting signer attempts and blocking operational queues.

(bool success,) = payable(txn.to).call{value: txn.value}(txn.data);
if (!success) { revert MultiSigTimelock__ExecutionFailed(); } // no state change, cannot mark canceled

Risk

Likelihood:

  • Reason 1 // Targets may change code after proposal

  • Reason 2 // Attackers can propose self-reverting contracts intentionally

Impact:

  • Impact 1 // Signers repeatedly pay gas to retry an unexecutable tx

  • Impact 2 // Operational backlog as dashboards treat the item as “ready” yet un-runnable

Proof of Concept

Explanation: Propose a call to a contract whose fallback always reverts. After three confirmations, every executeTransaction attempt reverts and cannot be canceled, leaving the item permanently clogging the queue.

// self-reverting target traps the proposal indefinitely

Recommended Mitigation

Explanation: Add a cancelTransaction function to tombstone failed items and optionally include per-call gas limits to reduce griefing surface.

+ function cancelTransaction(uint256 txnId) external onlyRole(SIGNING_ROLE) { s_transactions[txnId].executed = true; }
+ // store an optional gas limit and use it in the call

Status: Valid (DoS)


Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!