Use Case

Timelocks are a governance mechanism in DeFi protocols that enforce a delay between when a governance action is proposed and when it can be executed. This delay allows the community to review and react to potentially harmful changes.

This assertion prevents:

  • Bypassing the timelock mechanism
  • Setting timelock delays that are too short
  • Changing the timelock admin without proper authorization
  • Executing governance actions before the timelock period has expired

This is important because:

  • Timelocks provide a buffer against malicious governance actions
  • Without proper enforcement, changes could be executed immediately
  • Effective timelock periods need to be of appropriate duration

Applicable Protocols

  • DeFi lending protocols that use timelocks for parameter updates
  • DEX protocols that require timelocked governance for pool parameter changes
  • Yield aggregators that use timelocks for strategy updates
  • Cross-chain bridges that implement timelocks for security parameters
  • DAOs that use timelocks for treasury management and protocol upgrades

Each of these protocol types relies on timelocks to:

  • Prevent flash loan attacks through parameter manipulation
  • Allow community review of significant protocol changes
  • Protect against governance attacks
  • Ensure proper security review periods for critical updates

Explanation

The assertion verifies timelock integrity by:

  1. Pre-state Verification

    • Captures the initial timelock state before any changes
    • Records whether the timelock is currently active
  2. Post-state Analysis

    • Compares the final timelock state after changes
    • Verifies that any changes maintain security parameters
  3. Parameter Validation

    • Ensures timelock delay is within acceptable bounds (1 day to 2 weeks)
    • Confirms timelock activation follows proper procedures

The assertion uses these cheatcodes:

  • forkPreState(): Captures the initial timelock state
  • forkPostState(): Analyzes the final timelock state
  • registerStorageChangeTrigger(): Monitors changes to the timelock storage slot

The implementation performs checks on:

  1. The timelock’s active status before and after the transaction
  2. The timelock delay parameters when the timelock is activated

This ensures that:

  • Timelock activations use appropriate delay periods
  • Security parameters are maintained during governance changes
  • Protocol changes follow proper governance procedures

For more information about cheatcodes, see the Cheatcodes Documentation.

Code Example

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

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

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;
}

contract TimelockVerificationAssertion is Assertion {
    IGovernance public governance;

    constructor(address _governance) {
        governance = IGovernance(_governance);
    }

    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 pre-state timelock information
        ph.forkPreState();
        bool preActive = governance.timelockActive();

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

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

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

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

Note: This code example is maintained in the Phylax Assertion Examples Repository. For a full examples with mock protocol code and tests please refer to the repository.

Testing

To test this assertion:

  1. Deploy a mock governance contract with timelock functionality
  2. Create test cases for:
    • Valid timelock activation with proper parameters
    • Invalid timelock activation with too short delay
    • Attempts to bypass timelock entirely
  3. Verify the assertion correctly identifies valid and invalid states

Assertion Best Practices

  • Combine this assertion with Owner Change for comprehensive governance security
  • Use appropriate delay bounds based on your protocol’s risk profile