Formal Verification of Governance Timelocks & Multisigs
Governance mechanisms control protocol parameters and treasury assets. Logical flaws in timelocks or multisig validation rules can lead to protocol takeovers.
1. What is Formal Verification?
Formal verification mathematically proves that a smart contract behaves according to a formal specification. Unlike fuzzing, which tests millions of random inputs, formal verification evaluates all possible state transitions.
2. Formally Specifying a Timelock in CVL
Certora Verification Language (CVL) is used to specify invariants. Below is a CVL specification that verifies proposal state boundaries in a timelock.
Rule: Proposals cannot be executed before the timelock expiration
// CVL specification
rule proposalTimelockExecution {
env e;
method f;
calldataarg args;
uint256 proposalId;
// Pre-condition: Proposal is queued and timelock has not expired
require getProposalState(proposalId) == ProposalState.Queued;
require e.block.timestamp < getProposalExecutionTime(proposalId);
// Call any state-modifying function
invoke f(e, args);
// Post-condition: The state of the proposal cannot be Executed
assert getProposalState(proposalId) != ProposalState.Executed,
"Proposal executed before timelock expired";
}
3. Common Vulnerability Categories
Our formal verification sweeps identify several common failure categories in multisigs and timelocks:
A. Reentrancy in Queue Operations
If the proposal execution logic triggers callbacks prior to changing proposal states, an attacker can reenter and execute the same proposal multiple times.
B. Chain ID Replay Vulnerabilities
Multi-signature verification logic that omits the block.chainid parameter can be replayed on fork networks or alternative chains:
// Vulnerable verification
function verifySignature(bytes32 txHash, bytes memory signature) public view returns (bool) {
// VULNERABILITY: txHash does not include block.chainid or nonce
address signer = recoverSigner(txHash, signature);
return isOwner[signer];
}
C. Constructor Initialization Overrides
Constructor functions that fail to lock initializers can allow an attacker to hijack the contract post-deployment by calling initializations directly.
D. Threshold Drift
A multisig can become unsafe if owner removal changes the denominator without revalidating the threshold. For example, a 3-of-5 wallet that removes two owners but leaves the threshold at 3 becomes permanently unable to execute. The opposite failure is worse: lowering the threshold before owner changes are finalized can briefly create a 1-of-N authorization window.
function removeOwner(address owner) external onlyWallet {
isOwner[owner] = false;
ownerCount -= 1;
require(threshold <= ownerCount, "threshold exceeds owners");
require(threshold >= MIN_THRESHOLD, "threshold too low");
}
E. Operation Hash Ambiguity
Governance systems commonly identify queued operations by hashing target, value, calldata, predecessor, and salt. If any field is omitted, two different operations can collide into the same queue slot. Formal specs should prove operation identity is injective over the fields the protocol claims to bind.
rule operationHashBindsTargetAndData {
address target1; address target2;
bytes data1; bytes data2;
uint256 value;
bytes32 predecessor; bytes32 salt;
require target1 != target2 || keccak256(data1) != keccak256(data2);
bytes32 h1 = hashOperation(target1, value, data1, predecessor, salt);
bytes32 h2 = hashOperation(target2, value, data2, predecessor, salt);
assert h1 != h2, "operation hash does not bind target/data";
}
4. Specification Design for Governance
A useful formal verification campaign separates safety, liveness, and authority properties:
| Property class | Question | Example assertion |
|---|---|---|
| Safety | What must never happen? | A queued proposal cannot execute before eta. |
| Authority | Who may cause state changes? | Only valid quorum signatures can queue or execute. |
| Replay resistance | Can an old approval be reused? | Executed nonces cannot become executable again. |
| Liveness | Can valid governance still operate? | A threshold-valid proposal can reach Executed after delay. |
| Upgrade safety | Can implementation changes bypass policy? | Proxy upgrades must pass through the same timelock. |
Safety properties are usually easiest to prove. Liveness properties require more care because solvers must reason about the existence of a valid path, not only the absence of an invalid one. In practice, we combine formal rules with bounded model tests for liveness-critical flows.
5. Modeling Signatures Correctly
Signature verification is the most common source of false confidence. A spec that treats isValidSignature() as a trusted oracle will miss replay and domain-separation failures. The model should include:
- The EIP-712 domain separator, including
chainIdand verifying contract. - A nonce or operation ID that is consumed exactly once.
- Strict signer uniqueness, so one signer cannot submit duplicate signatures to meet threshold.
- Sorted signature assumptions, if the implementation depends on ordering.
- Contract-wallet signatures through EIP-1271, including failure and revert paths.
rule signerCannotCountTwice {
env e;
bytes32 op;
address signer;
require isOwner(signer);
require signatureCount(op, signer) == 1;
submitSignature(e, op, signer);
assert signatureCount(op, signer) == 1,
"duplicate signer increased approval weight";
}
6. Timelock State Machine
Every operation should move through a single monotonic path:
Unset -> Waiting -> Ready -> Done
Cancellation is the only valid side branch, and canceled operations must not become ready unless they are explicitly requeued with a fresh salt or nonce. The important invariant is monotonicity: no external call, upgrade, or role change should move Done or Canceled back into an executable state.
7. Verification Workflow
- Freeze the governance threat model: assets controlled, roles, quorum, delay, emergency powers.
- Write a state-machine diagram before writing specs.
- Add invariants for early execution, replay, duplicate signers, threshold bounds, and operation hash binding.
- Model malicious target contracts that reenter during execution.
- Run the same specs against upgradeable proxy deployments and initialized implementation contracts.
- Pair solver results with fuzzing for gas-limit and calldata-shape edge cases.
8. Auditor Checklist
- Operation hashes bind target, value, calldata, predecessor, salt, and chain context.
- Nonces or operation IDs are consumed once and cannot be replayed cross-chain.
- Duplicate signatures cannot increase approval weight.
- Threshold cannot exceed owner count or fall below the documented security floor.
- Queue, cancel, and execute state transitions are monotonic.
- External calls during execution cannot reenter queue or execution paths.
- Proxy upgrades and role changes are themselves governed by the timelock.
9. Conclusion
Governance verification is high leverage because a single timelock or multisig bug can dominate every other protocol control. The practical goal is not to prove that governance is benevolent; it is to prove that even valid administrators must pass through the exact delay, quorum, replay, and upgrade rules users were promised.