MultiSig Timelock

First Flight #55
Beginner FriendlyWallet
100 EXP
View results
Submission Details
Impact: high
Likelihood: low
Invalid

L01. Fixed Confirmation Threshold Allows Governance Deadlock When Signer Count Drops Below Quorum

Root + Impact

Description

  • Under normal operation, the protocol requires a fixed minimum of REQUIRED_CONFIRMATIONS = 3 signer approvals before a transaction can be executed.

  • The protocol also allows the owner to revoke signing roles, reducing the total number of active signers at any time.

  • The issue arises because the required confirmation threshold is fixed and not validated against the current signer count, which allows the system to enter a state where new transactions can never reach quorum.

  • Transactions that already reached quorum before signer removal remain executable, but any transaction proposed after the signer count drops below the required confirmations becomes permanently unexecutable, causing a liveness failure.

// REQUIRED_CONFIRMATIONS is fixed
uint256 private constant REQUIRED_CONFIRMATIONS = 3;
// @> No validation that signer count >= REQUIRED_CONFIRMATIONS
function revokeSigningRole(address _account) external nonReentrant onlyOwner {
...
s_signerCount -= 1;
}
// @> Execution always requires REQUIRED_CONFIRMATIONS, regardless of signer count
if (txn.confirmations < REQUIRED_CONFIRMATIONS) {
revert MultiSigTimelock__InsufficientConfirmations(
REQUIRED_CONFIRMATIONS,
txn.confirmations
);
}

Risk

Likelihood:

  • The owner can reduce the signer set at any time without restriction.

  • Governance changes or key rotations naturally cause signer removal during protocol operation.

Impact:

  • New transactions become permanently unexecutable once signer count drops below quorum.

  • Funds can become locked indefinitely with no recovery path.


Proof of Concept

This PoC demonstrates a permanent deadlock scenario for new transactions:

  1. The signer count is reduced to fewer than REQUIRED_CONFIRMATIONS.

  2. A new transaction is proposed.

  3. All remaining signers confirm the transaction.

  4. Execution is attempted and always fails because quorum is mathematically unreachable.

Already-confirmed transactions are not affected, but future transactions are permanently blocked.

PoC Code

function testDeadlockForNewTransactionWhenSignerCountBelowQuorum() public {
// Reduce signer count from 5 to 2
vm.prank(owner);
multiSigTimelock.revokeSigningRole(signer5);
vm.prank(owner);
multiSigTimelock.revokeSigningRole(signer4);
vm.prank(owner);
multiSigTimelock.revokeSigningRole(signer3);
assertEq(multiSigTimelock.getSignerCount(), 2);
// Propose a new transaction
vm.prank(owner);
uint256 txnId = multiSigTimelock.proposeTransaction(
recipient,
1 ether,
hex""
);
// Both remaining signers confirm
vm.prank(owner);
multiSigTimelock.confirmTransaction(txnId);
vm.prank(signer2);
multiSigTimelock.confirmTransaction(txnId);
// Execution is permanently impossible
vm.prank(owner);
vm.expectRevert(
MultiSigTimelock.MultiSigTimelock__InsufficientConfirmations.selector
);
multiSigTimelock.executeTransaction(txnId);
}

Recommended Mitigation

The protocol must enforce that the signer count can never fall below the required quorum, or dynamically adapt the quorum.

Option 1: Enforce Minimum Signer Count

Function: revokeSigningRole

Function revokeSigningRole
- allow signer removal unconditionally
+ require s_signerCount - 1 >= REQUIRED_CONFIRMATIONS

Option 2: Make Required Confirmations Configurable

Function: setRequiredConfirmations(uint256)

Function setRequiredConfirmations
- fixed constant REQUIRED_CONFIRMATIONS
+ state variable with validation:
require(newValue > 0 && newValue <= s_signerCount)

Both approaches prevent the protocol from entering an unrecoverable execution deadlock.

Updates

Lead Judging Commences

kelechikizito Lead Judge 4 days ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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

Give us feedback!