Skip to main content

When to Use This Pattern

Ensures timelocks are correctly triggered for governance actions, enforcing delays between proposal and execution to allow community review. Critical for DeFi lending protocols, DEX protocols, yield aggregators, cross-chain bridges, and DAOs that use timelocks to prevent flash loan attacks, allow security review periods, and protect against governance attacks. Timelocks provide a buffer against malicious governance actions - without proper enforcement, changes could be executed immediately.

What This Pattern Checks

Verifies timelock integrity by comparing timelock state and parameters before and after transactions:
  • forkPreTx() / forkPostTx(): Compare timelock state before and after transaction
  • registerStorageChangeTrigger(): Monitor changes to timelock storage slot
  • Ensures timelock delay is within acceptable bounds (1 day to 2 weeks)
  • Confirms timelock activation follows proper procedures
The assertion performs both pre/post state comparison and parameter validation to ensure governance changes follow proper timelock procedures. For more information about cheatcodes, see the Cheatcodes Documentation.

Assertion Pattern

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

import {Assertion} from "credible-std/Assertion.sol";
import {PhEvm} from "credible-std/PhEvm.sol";

contract TimelockVerificationAssertion is Assertion {
    function triggers() external view override {
        // Register trigger for changes to the timelock storage slot
        registerStorageChangeTrigger(this.assertionTimelock.selector, bytes32(uint256(0)));
    }

    // Verify that a timelock is working as expected after some governance action
    function assertionTimelock() external {
        // Get the assertion adopter address
        IGovernance adopter = IGovernance(ph.getAssertionAdopter());

        // Get pre-state timelock information
        ph.forkPreTx();
        bool preActive = adopter.timelockActive();

        // If timelock was already active, no need to check further
        if (preActive) {
            return;
        }

        // Get post-state timelock information
        ph.forkPostTx();

        // If timelock is now active, verify all parameters
        if (adopter.timelockActive()) {
            // Verify timelock delay is within acceptable bounds
            bool minDelayCorrect = adopter.timelockDelay() >= 1 days;
            bool maxDelayCorrect = adopter.timelockDelay() <= 2 weeks;

            // Require all parameters to be correct
            require(minDelayCorrect && maxDelayCorrect, "Timelock parameters invalid");
        }
    }
}

interface IGovernance {
    struct Timelock {
        uint256 timelockDelay;
        bool isActive;
    }

    function timelockActive() external view returns (bool);
    function timelockDelay() external view returns (uint256);
    function activateTimelock() external;
    function setTimelock(uint256 _delay) external;
}
Full examples with tests available in the Phylax Assertion Examples Repository.