Timelocks and the governance attacks they prevent

timelock
Table of Contents

A timelock is a smart contract that forces a fixed delay between a decision and its execution. A governance vote, an upgrade or an admin action is queued, becomes visible on-chain, and can only run once the delay has passed. That window is the whole point: it gives users time to exit and gives defenders time to cancel.

The word comes from bank vaults. A vault time lock is a clock inside the door that will not let it open before a preset hour, even for someone holding the right combination. It was built for one threat: robbers who kidnap the person who knows the combination. In 1875 a gang in Great Barrington, Massachusetts took a banker’s family hostage and demanded he open the vault. A time lock fitted days earlier meant he could not, and they left.

The smart contract version solves the same problem. Keys get stolen, votes get bought, and teams change their minds. A timelock does not make the key holder trustworthy. It makes their actions slow enough to see.

Quick Answer

A timelock in DeFi governance is a contract that holds a protocol’s admin rights and only executes a queued action after a minimum delay, often 2 days. It stops flash-loan votes, instant takeovers by a stolen key and silent rule changes. It does not stop malicious code nobody reads, a role that bypasses it, or a bug in the timelock itself.

Last updated October 2026.

One naming note before we start. A hashed timelock contract (HTLC) is a different primitive, used for payment channels and atomic swaps. This page is about the governance timelock.

On this page

  • How a timelock works
  • A minimal timelock in Solidity
  • The attacks a timelock is built to stop
  • What a timelock does not stop
  • A timelock delays your fixes too
  • What an auditor checks in a timelock
  • Frequently asked questions

How a timelock works

A timelock sits between whoever makes decisions and the contracts that hold value. The protocol’s contracts name the timelock as their owner or admin, so every privileged call has to pass through it. OpenZeppelin’s governance guide puts it plainly: “It is good practice to add a timelock to governance decisions. This allows users to exit the system if they disagree with a decision before it is executed.”

Every action goes through the same lifecycle.

  1. Queue: A proposer (a multisig, or a Governor contract after a successful vote) schedules an exact call: target, value and calldata. The timelock records the earliest time it may run.
  2. Wait: The action sits on-chain in plain view. Anyone can read what it will do.
  3. Execute: Once the delay has passed, an executor runs exactly the call that was queued, nothing else.
  4. Cancel: At any point before execution, a canceller can kill it.
  5. Expire: Some timelocks add a grace period. An action not executed in time goes stale and has to be queued again.

The two most used implementations make different choices.

Compound TimelockOpenZeppelin TimelockController
DelayAt least 2 days and at most 30, enforced in the contractA minimum set at deployment, changeable only through the timelock itself
Expiry14 day grace period, then the transaction is staleNone: a ready operation stays ready until executed or cancelled
Who can queueA single admin, typically the Governor contractAccounts holding the Proposer role
Who can executeThe adminExecutor role, or anyone if the role is left open
SourceTimelock.solOpenZeppelin API docs

OpenZeppelin’s guide adds a rule that is easy to miss: “it is the timelock that will execute proposals and thus the timelock that should hold any funds, ownership, and access control roles.” If the treasury or an upgrade key sits anywhere else, the delay protects nothing.

A minimal timelock in Solidity

The mechanism fits in about 40 lines. This version compiles cleanly under solc 0.8.26 and is written to be read, not deployed.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

// Illustrative only. In production, use an audited implementation
// such as OpenZeppelin's TimelockController.
contract MiniTimelock {
    uint256 public constant DELAY = 2 days;
    uint256 public constant GRACE_PERIOD = 14 days;

    address public immutable proposer;
    mapping(bytes32 => uint256) public readyAt;

    event Queued(bytes32 indexed id, address target, bytes data, uint256 readyAt);
    event Executed(bytes32 indexed id);
    event Cancelled(bytes32 indexed id);

    constructor(address proposer_) {
        proposer = proposer_;
    }

    function queue(address target, bytes calldata data, bytes32 salt) external returns (bytes32 id) {
        require(msg.sender == proposer, "not proposer");
        id = keccak256(abi.encode(target, data, salt));
        require(readyAt[id] == 0, "already queued");
        readyAt[id] = block.timestamp + DELAY;
        emit Queued(id, target, data, readyAt[id]);
    }

    function execute(address target, bytes calldata data, bytes32 salt) external {
        bytes32 id = keccak256(abi.encode(target, data, salt));
        uint256 t = readyAt[id];
        // Check readiness and clear the entry BEFORE the external call.
        require(t != 0 && block.timestamp >= t, "not ready");
        require(block.timestamp <= t + GRACE_PERIOD, "stale");
        delete readyAt[id];
        (bool ok, ) = target.call(data);
        require(ok, "call failed");
        emit Executed(id);
    }

    function cancel(bytes32 id) external {
        require(msg.sender == proposer, "not proposer");
        require(readyAt[id] != 0, "not queued");
        delete readyAt[id];
        emit Cancelled(id);
    }
}

The order of the checks in execute matters more than anything else in the file. The readiness check runs before the external call, and the entry is cleared before the call too. Get that order wrong and the call can re-enter the timelock while it runs. That is not a hypothetical, as the section on what a timelock does not stop shows.

The attacks a timelock is built to stop

A timelock is a defence against speed. Any attack that depends on acting before anyone can react gets harder when a mandatory delay sits in the way.

Flash-loan governance attacks

A flash loan lends any amount with no collateral, on the condition it is repaid in the same transaction. If voting power is counted from a live balance, a flash loan can buy a majority for a few seconds.

That is what happened to Beanstalk on 17 April 2022. The protocol’s own post-mortem records that the attacker “used a flash loan to exploit the protocol’s governance mechanism” and took about $77M in non-Beanstalk user assets. The rekt.news analysis puts total damage at $181M, with $76M kept by the attacker. The attacker borrowed 350M DAI, 500M USDC and 150M USDT from Aave, voted for their own proposal, and executed it through an emergency path, all in one transaction.

Beanstalk did have a delay. Rekt notes a delay of about a day on all governance actions, and the attacker submitted their proposals the day before. What it lacked was a delay between the vote and the execution. Rekt’s verdict: the attack was “made possible through the use of flash loans combined with the absence of a delay on proposal execution.”

A delay before execution breaks the flash-loan model, because the loan must be repaid in the transaction that borrowed it. Pair it with voting power taken from a past snapshot (OpenZeppelin’s ERC20Votes retrieves it “from past snapshots rather than current balance”) and borrowed tokens never count at all.

A stolen or rogue admin key

Without a timelock, whoever holds the admin key holds the protocol, instantly. With one, a compromised key can only queue an action, and the queue is public.

We rated exactly this gap Critical in our Adrena audit, a Solana perpetuals DEX reviewed between February and March 2026. Finding C1 was a missing timelock on admin operations with single-step authority transfer. Every privileged operation executed immediately, atomically and under one signature. Adrena replaced the transfer with a two-step process carrying a fixed 48 hour delay, which the current admin can cancel while it is pending. As the case study says, the protocol’s only Critical was “a governance timelock gap rather than a solvency bug”.

A team changing the rules under its users

The third threat is the protocol’s own team. An upgrade can swap the code users trusted for code they have never seen. OpenZeppelin’s post-mortem describes this benefit directly: a timelock “protects the community from the project’s governance itself, allowing members to exit the protocol if they disagree with any impending changes.”

This is why a proxy behind a timelock is a different risk from a proxy behind one private key, a point we make in rug pull red flags. The delay makes an upgrade visible before it lands; it does not make it safe.

What a timelock does not stop

A timelock buys time. It does not read code, it does not watch the queue and it does not check its own configuration. Four failure modes get past it.

AttackDoes the delay stop it?What else is needed
Flash-loan voteYes, if every path goes through the delaySnapshot voting power, no emergency bypass
Stolen admin keyIt buys a window to reactSomeone watching, and a canceller the attacker does not control
Rug by upgradeIt gives users time to exitA delay long enough to exit in
Malicious code in a proposalOnly if someone reads the code that will runReview the bytecode at the target, not the description
Bug in the timelock itselfNoAn audit of the timelock and its roles
Role that skips the timelockNoRemove it, or bound it tightly

Malicious code hidden in a proposal

In May 2023 an attacker took over Tornado Cash governance with a proposal that claimed to reuse the logic of an earlier one. It had an extra function that let the attacker self-destruct the proposal contract. After the vote, they used CREATE and CREATE2 to deploy different code at the very address governance had approved. The new code gave them 1.2M votes against about 700k legitimate ones.

Delay or no delay, the problem is the same: voters approved an address, not the code that would eventually sit there. A review only protects you if it checks the code at the moment it executes.

A bug in the timelock itself

In August 2021 a whitehat reported a critical flaw in OpenZeppelin’s TimelockController. According to OpenZeppelin’s post-mortem, executeBatch checked that an operation was ready only after running its calls. A malicious executor could submit a batch that granted itself the proposer role, set the minimum delay to zero, and scheduled the very batch being executed. By the time the check ran, the batch looked ready. The fix was a readiness check before execution, which is why MiniTimelock above checks first.

A role that skips the queue

OpenZeppelin warns that extra proposers or cancellers “can execute operations as the timelock (bypassing governance)” and can cancel approved proposals. Beanstalk’s emergency path was the same thing in another form. Any route around the delay is the route an attacker will take.

Nobody watching

A delay is only useful if someone reads the queue and can act on it. The window helps users who notice. It does nothing for a protocol whose team is asleep. Post-deployment monitoring, with detection tuned to the protocol’s own logic and a documented escalation path, is what turns a timelock’s delay into a response.

A timelock delays your fixes too

The same delay that slows an attacker slows you. Compound’s Proposal 62 executed on 29 September 2021 and “introduced a bug into the Comptroller contract that allowed users in certain markets to accrue and claim far more COMP than intended.” The fix, Proposal 64, could not skip governance. It executed on 8 October.

Teams handle this trade-off in three common ways:

  • A pause role, scoped narrowly. It can stop deposits or borrowing, but cannot move funds or upgrade code.
  • Shorter delays for low-risk parameters, longer ones for upgrades and treasury moves.
  • A canceller held by a separate party, such as a security council multisig, so a malicious action can be killed without a fast path to execute one.

Each of these is a deliberate hole in the delay. Each needs its limits written down and checked.

What an auditor checks in a timelock

A timelock review is mostly a review of who can do what, and how fast. The code is usually a well-known library. The configuration is where the findings are.

CheckThe question
OwnershipDoes the timelock hold every admin role, upgrade key and treasury, or does something sit outside it?
DelayIs the minimum delay long enough for a user to notice and exit? Can it be lowered without waiting?
RolesWho holds Proposer, Executor, Canceller and Admin? Did the deployer renounce Admin after setup?
BypassesIs there an emergency path, guardian or pause role? What exactly can it do?
Voting powerIs it read from a snapshot taken before the proposal, or from a live balance?
ExecutionDoes execution run exactly the queued calldata, against code that cannot change after approval?
UpgradesAfter an upgrade through the timelock, does the security work still describe the code?

That last row is the one teams forget. A timelocked upgrade still changes the code, and an audit covers one version. We made the argument in when a smart contract audit actually expires, and a timelock does not change it. If the properties above matter to you, they are also good candidates for formal verification.

A manual audit at Fidesium starts at $5,000, covers logic and economic risk line by line, and includes two rounds of fix verification. Our published reports are in the audit portfolio, and every audit is minted as an on-chain record. After launch, deterministic static analysis runs on every commit from $399 a month.

A timelock is only as strong as the roles and paths around it. Talk to the audit team if you want that configuration checked before your users rely on it.

Frequently asked questions

What is a timelock in a smart contract?

A timelock is a smart contract that enforces a minimum delay between scheduling an action and executing it. The protocol makes the timelock its owner or admin, so upgrades, parameter changes and treasury moves are queued publicly and can only run after the delay. Users get time to exit and defenders get time to cancel.

Why do DeFi protocols use timelocks?

They use them to protect users from fast attacks and from the protocol’s own governance. A delay stops a flash-loan vote from executing in one transaction, stops a stolen admin key from taking control instantly, and gives users a window to leave before a change they disagree with takes effect.

How long should a timelock delay be?

It should be long enough for users to notice a queued action and exit. Compound’s Timelock enforces a minimum of 2 days and a maximum of 30. Many protocols use longer delays for upgrades and treasury moves than for routine parameters. A delay shorter than the time it takes your users to react protects very little.

Would a timelock have stopped the Beanstalk hack?

A delay between the vote and execution would have broken that attack, because the flash loan had to be repaid in the transaction that borrowed it. Beanstalk had a delay of about a day on governance actions, but once it had passed, the borrowed votes were counted and the proposal executed in the same transaction.

Can a timelock be bypassed?

Yes. Extra proposers, guardians, emergency paths and pause roles can all act outside the delay if they are given too much power. A bug in the timelock can bypass it too, as OpenZeppelin’s 2021 TimelockController flaw showed. An audit checks every route around the delay.

What is the difference between a timelock and a multisig?

A multisig decides who must agree, by requiring several signatures. A timelock decides how long everyone waits before the decision runs. They are usually combined: a multisig or a Governor contract is the proposer, and the timelock holds the protocol’s admin rights and enforces the delay.

Is a timelock the same as a hashed timelock contract?

No. A hashed timelock contract (HTLC) is a payment primitive used in payment channels and atomic swaps. It releases funds when a secret is revealed before a deadline. A governance timelock delays privileged actions on a protocol. They share a name and very little else.

Share:

More Posts

Tell us your security needs