MultiSig Timelock

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

Timelock Delay Can Be Bypassed via Transaction Splitting

Author Revealed upon completion

Timelock Delay Can Be Bypassed via Transaction Splitting

Description

  • The protocol uses a tiered timelock system where higher transaction values require longer delays. However, this safety mechanism can be circumvented by splitting a large transaction into multiple smaller transactions that each fall below the 1 ETH threshold (NO_TIME_DELAY). This allows signers to drain the entire contract balance immediately, even if it exceeds 100 ETH, by executing many small, instantaneous transactions.

Risk

Likelihood:

  • While it still requires signer consensus, it is trivial for signers to automate the proposal and execution of multiple small transactions to bypass the delay.

Impact:

  • It completely nullifies the security benefit of the timelock, which is intended to provide a reaction window for the community or external monitors.

Proof of Concept

// add to test/unit/MultiSigTimelockTest.t.sol
function testSplitAmountCanPassTimelock() public grantSigningRoles {
vm.deal(address(multiSigTimelock), 5 ether);
for (uint256 i = 0; i < 5; ++i) {
vm.prank(OWNER);
uint256 txnId = multiSigTimelock.proposeTransaction(SPENDER_ONE, 0.99 ether, hex"");
confirmTx(txnId);
vm.prank(OWNER);
multiSigTimelock.executeTransaction(txnId);
}
assertEq(SPENDER_ONE.balance, 4.95 ether);
}
function confirmTx(uint256 txnId) public {
vm.prank(OWNER);
multiSigTimelock.confirmTransaction(txnId);
vm.prank(SIGNER_TWO);
multiSigTimelock.confirmTransaction(txnId);
vm.prank(SIGNER_THREE);
multiSigTimelock.confirmTransaction(txnId);
}

Recommended Mitigation

Enforce a minimum time interval between transaction executions.

+ uint256 public lastExecutionTime;
+ uint256 public constant EXECUTION_COOLDOWN = 1 hours;
function _executeTransaction(...) internal {
+ require(block.timestamp >= lastExecutionTime + EXECUTION_COOLDOWN, "Rate limit exceeded");
+ lastExecutionTime = block.timestamp;
// ...
}

Support

FAQs

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

Give us feedback!