
Timelock Guard
A timelock is an essential tool in blockchain governance. It takes transactions and delays their execution while making them visible to everyone.
Timelocks have traditionally been used to reassure users that protocol owners cannot do anything undesired without giving users time to react and remove their assets. But that is not all you can do with a timelock.
Relatively recent hacks, like the one suffered by Bybit, could likely have been prevented with a timelock. In a scenario where a hacker manages to obtain signatures to execute a malicious transaction, a timelock would reveal that plan and offer an opportunity to counter it.
Unfortunately, the most popular timelocks in use, such as those from OpenZeppelin and Compound, suffer from several drawbacks that make them not great for protecting protocols against external attacks:
They require the timelock to have privileged permissions over the assets or protocol.
They add several steps to the governance process.
They do not make scheduled transactions easily visible, since only a hash identifier is stored.
They do not make scheduled transactions easy to cancel: cancellation requires the same permissions as scheduling, or a separate privileged actor.
Given these limitations, we decided at Optimism to design a timelock that would be simple, robust, easy to plug into any protocol governance structure, and that would allow us to easily detect and remove malicious transactions.
The Solution
Most governance actions originate in a Gnosis Safe Multisig, and one of the tools available in such multisigs are Gnosis Safe Guards.
A Gnosis Safe Guard is a smart contract plugin for Gnosis Safe Multisigs that allows the multisig owners to dictate some conditions that are required for any action executed from the multisig.
At heart, any timelock is a condition for execution. A transaction needs to have been revealed to the timelock a certain time before execution. A Gnosis Safe Guard is a perfect fit and allows a timelock function to be implemented as a plugin to a Gnosis Safe Multisig.

Like other timelocks, we implemented three functions: schedule, execute, and cancel. We complemented those with a very visible transaction queue, a dynamic cancellation threshold, and a mechanism to clear the timelock queue.
There are other things that we didn’t build. The Safe already does all the work to store and execute a transaction as part of its core function of enabling collective control of an account. The Safe also includes logic for batching transactions. By not needing to code this ourselves, we greatly reduce the complexity and burden of the timelock.
Let’s discuss the implementation in detail.
Schedule
To schedule a transaction in our Timelock, the user needs to have already collected the signatures for its execution in the Safe, and we reuse the Safe code to check the signatures and derive an identifier hash. The only new code is storing all the transaction data, along with a minimum execution time.
The safe.checkSignatures logic does all the work: it verifies that the signatures are valid and that there are enough of them to reach quorum. By reusing this code from the Safe, our timelock doesn’t need to have its own access control mechanism.
Execute
There is no standalone execute function. Instead, as a Safe Guard, the Timelock is consulted by the Safe before executing a transaction in the checkTransaction function. The main goal of this function is to revert if the transaction was not scheduled at least a defined time before execution.
There is no execute function, instead the timelock uses the execution hook of the guard
By inserting itself in the Safe execution, our timelock doesn’t require any additional work by signers after the scheduling and the wait. It works behind the scenes.
Cancel
In other timelocks, cancellation is often an afterthought, and ends up being implemented in a way that adds complexity and risk.
In our timelock, we wanted to make transaction cancellation easy for the Safe owners. In a crisis situation, it might be difficult to assemble a quorum of signers to cancel a malicious transaction. At the same time, we didn't want to create a parallel governance structure to manage cancellations.
We solved this by creating a no-op signCancellation function. This function does nothing, but Safe owners can create off-chain signatures allowing the Safe to execute it, the same as they would sign any other transaction from the multisig.
The multisig is not going to execute signCancellation, because it does nothing. Instead, we implemented a cancelTransaction function that uses the signatures for signCancellation to assess if enough signers wanted to cancel a transaction. This sounds complicated, but the implementation is very simple as it it built entirely from features already present in the Safe multisig code.
The cancelTransaction function was coded mainly as calls to Safe code.
Cancellation Threshold
In describing the cancelTransaction function, we skipped the question of how many signers need to assemble to cancel a transaction.
At the suggestion of some very sharp folks working on the same problem (samczsun, dvf), we decided to implement a dynamic cancellation threshold, which is by far the most complex part of the timelock. We thought it was worth it, though.
The number of signatures needed to cancel a transaction starts at 1, giving a crisis team the best possible chance of stopping an attack.
If the cancellation threshold were always 1, it would be too easy for an attacker to compromise a single key and permanently DoS the Safe. To avoid that, the cancellation threshold increases by one with each consecutive cancellation, and resets back to 1 with a successful execution.
Cancellation threshold management functions
A determined DoS attack would need a high number of leaked keys to be successful. We capped the cancellation threshold at min(quorum, blocking_threshold), so it never exceeds the quorum, and never exceeds the number of signatures required to permanently block executions from the Safe.
We decided that this should be the limit because if attackers hold a blocking_threshold of keys or a quorum of keys, we already have a more serious problem than them being able to abuse the timelock to DoS the Safe, and it is best to deal with those problems separately.
Pending Transactions
We wanted scheduled transactions to be clearly visible. Other timelocks just emit an event when a transaction is scheduled, but our experience is that monitoring often misses events or can be fooled to miss events.
For this reason, we store the hash identifiers of all pending transactions in an enumerable set. When queried, we return all data fields of each scheduled transaction, to make analysis as easy as possible.
Querying the timelock is very convenient for all monitoring purposes
We considered the added complexity reasonable because the pending transactions set is never consulted on-chain by the timelock itself. Instead, it is maintained as a completely parallel structure for the benefit of monitoring tools.
Resetting the Timelock
Even with all the precautions above, there are some scenarios that could overwhelm the timelock with unwanted transactions, such as those we discussed when working on the cancellation threshold.
In those scenarios, we would expect the protocol to skip the timelock and execute a pause. Then, the crisis management team would resolve the situation before allowing normal operations to resume.
To make a restart safer, we included a very simple switch to remove all pending transactions in a single call, by pointing the Timelock to a new configuration set.
We can reset the timelock with a single call
By making the configuration and pending transaction queue depend on a sequential identifier, we just need to increase it to immediately switch to a default configuration and empty transaction queue, without resorting to heavy cleaning loops.
Conclusion
The dominant timelock implementations are a good fit for protecting users from harmful governance actions, but fall short when protecting protocols against external attackers. Furthermore, they are a burden on already complex governance processes.
At Optimism, we implemented a timelock that integrates closely with Gnosis Safes and demands the minimal possible variation on existing governance processes. By reusing code from the Safe that it integrates with, the timelock becomes much simpler and more robust.
We also paid special attention to how the timelock would be used during a crisis situation, so that the timelock owners have the best possible chance to detect and stop a malicious actor.
Our timelock is public goods and was audited by Spearbit. You can use it in any way that would suit you, including forking it and modifying it. We would love to hear your feedback!
This module was a joint effort by several people at Optimism. The vision came from Kelvin Fichter, and John Mardlin put the code together. Josep Bové, Ethnical, Matt Solomon, and I helped in other capacities.
At Optimism, we are always looking for talented individuals that will help us on our mission to scale Ethereum and build the Superchain, pushing the edge on what is possible and finding smart solutions for complex problems. If that is something that interests you, we are hiring.