Hoodi Testnet

Contract

0x69E8e916c4A19F42C13C802abDF2767E1fB4F059

Overview

ETH Balance

0 ETH

More Info

Multichain Info

N/A
Transaction Hash
Method
Block
From
To

There are no matching entries

Please try again later

Advanced mode:
Parent Transaction Hash Method Block
From
To
View All Internal Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
TimelockedGovernance

Compiler Version
v0.8.26+commit.8a97fa7a

Optimization Enabled:
Yes with 200 runs

Other Settings:
cancun EvmVersion

Contract Source Code (Solidity Standard Json-Input format)

// SPDX-FileCopyrightText: 2024 Lido <[email protected]>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {ITimelock} from "./interfaces/ITimelock.sol";
import {IGovernance} from "./interfaces/IGovernance.sol";

import {ExternalCall} from "./libraries/ExternalCalls.sol";

/// @title Timelocked Governance
/// @notice A contract that serves as the interface for submitting and scheduling the execution of governance proposals.
contract TimelockedGovernance is IGovernance {
    // ---
    // Errors
    // ---

    error CallerIsNotGovernance(address caller);
    error InvalidGovernance(address governance);
    error InvalidTimelock(ITimelock timelock);

    // ---
    // Immutable Variables
    // ---

    address public immutable GOVERNANCE;
    ITimelock public immutable TIMELOCK;

    // ---
    // Main Functionality
    // ---

    /// @notice Initializes the TimelockedGovernance contract.
    /// @param governance The address of the governance contract.
    /// @param timelock The address of the timelock contract.
    constructor(address governance, ITimelock timelock) {
        if (governance == address(0)) {
            revert InvalidGovernance(governance);
        }
        if (address(timelock) == address(0)) {
            revert InvalidTimelock(timelock);
        }
        GOVERNANCE = governance;
        TIMELOCK = timelock;
    }

    /// @notice Submits a proposal to the timelock.
    /// @param calls An array of ExternalCall structs representing the calls to be executed in the proposal.
    /// @param metadata A string containing additional information about the proposal.
    /// @return proposalId The id of the submitted proposal.
    function submitProposal(
        ExternalCall[] calldata calls,
        string calldata metadata
    ) external returns (uint256 proposalId) {
        _checkCallerIsGovernance();
        proposalId = TIMELOCK.submit(TIMELOCK.getAdminExecutor(), calls);
        emit ProposalSubmitted(msg.sender, proposalId, metadata);
    }

    /// @notice Schedules a submitted proposal.
    /// @param proposalId The id of the proposal to be scheduled.
    function scheduleProposal(uint256 proposalId) external {
        TIMELOCK.schedule(proposalId);
    }

    /// @notice Checks if a proposal can be scheduled.
    /// @param proposalId The id of the proposal to check.
    /// @return A boolean indicating whether the proposal can be scheduled.
    function canScheduleProposal(uint256 proposalId) external view returns (bool) {
        return TIMELOCK.canSchedule(proposalId);
    }

    /// @notice Cancels all pending proposals that have not been executed.
    /// @return A boolean indicating whether the operation was successful.
    function cancelAllPendingProposals() external returns (bool) {
        _checkCallerIsGovernance();
        TIMELOCK.cancelAllNonExecutedProposals();
        return true;
    }

    // ---
    // Internal Methods
    // ---

    /// @notice Checks if the msg.sender is the governance address.
    function _checkCallerIsGovernance() internal view {
        if (msg.sender != GOVERNANCE) {
            revert CallerIsNotGovernance(msg.sender);
        }
    }
}

// SPDX-FileCopyrightText: 2024 Lido <[email protected]>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {Duration} from "../types/Duration.sol";
import {Timestamp} from "../types/Timestamp.sol";

import {ExternalCall} from "../libraries/ExternalCalls.sol";
import {Status as ProposalStatus} from "../libraries/ExecutableProposals.sol";

interface ITimelock {
    struct ProposalDetails {
        uint256 id;
        address executor;
        Timestamp submittedAt;
        Timestamp scheduledAt;
        ProposalStatus status;
    }

    function submit(address executor, ExternalCall[] calldata calls) external returns (uint256 newProposalId);
    function schedule(uint256 proposalId) external;
    function execute(uint256 proposalId) external;
    function cancelAllNonExecutedProposals() external;

    function canSchedule(uint256 proposalId) external view returns (bool);
    function canExecute(uint256 proposalId) external view returns (bool);

    function getAdminExecutor() external view returns (address);
    function setAdminExecutor(address newAdminExecutor) external;
    function getGovernance() external view returns (address);
    function setGovernance(address newGovernance) external;

    function getProposal(uint256 proposalId)
        external
        view
        returns (ProposalDetails memory proposalDetails, ExternalCall[] memory calls);
    function getProposalDetails(uint256 proposalId) external view returns (ProposalDetails memory proposalDetails);
    function getProposalCalls(uint256 proposalId) external view returns (ExternalCall[] memory calls);
    function getProposalsCount() external view returns (uint256 count);

    function getAfterSubmitDelay() external view returns (Duration);
    function getAfterScheduleDelay() external view returns (Duration);
    function setAfterSubmitDelay(Duration newAfterSubmitDelay) external;
    function setAfterScheduleDelay(Duration newAfterScheduleDelay) external;
    function transferExecutorOwnership(address executor, address owner) external;
}

// SPDX-FileCopyrightText: 2024 Lido <[email protected]>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {ITimelock} from "./ITimelock.sol";

import {ExternalCall} from "../libraries/ExternalCalls.sol";

interface IGovernance {
    event ProposalSubmitted(address indexed proposerAccount, uint256 indexed proposalId, string metadata);

    function TIMELOCK() external view returns (ITimelock);
    function submitProposal(
        ExternalCall[] calldata calls,
        string calldata metadata
    ) external returns (uint256 proposalId);
    function scheduleProposal(uint256 proposalId) external;
    function cancelAllPendingProposals() external returns (bool);

    function canScheduleProposal(uint256 proposalId) external view returns (bool);
}

// SPDX-FileCopyrightText: 2024 Lido <[email protected]>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {IExternalExecutor} from "../interfaces/IExternalExecutor.sol";

/// @notice Represents an external call to a specific address with an optional ETH transfer.
/// @param target The address to call.
/// @param value The amount of ETH (in wei) to transfer with the call, capped at approximately 7.9 billion ETH.
/// @param payload The calldata payload sent to the target address.
struct ExternalCall {
    address target;
    uint96 value;
    bytes payload;
}

/// @title External Calls Library
/// @notice Provides functionality for executing multiple external calls through an `IExternalExecutor` contract.
library ExternalCalls {
    /// @notice Executes a series of external calls using the provided executor, which implements the
    ///     `IExternalExecutor` interface.
    /// @param calls An array of `ExternalCall` structs, each specifying a call to be executed.
    /// @param executor The contract responsible for executing each call, conforming to the `IExternalExecutor` interface.
    function execute(IExternalExecutor executor, ExternalCall[] memory calls) internal {
        uint256 callsCount = calls.length;
        for (uint256 i = 0; i < callsCount; ++i) {
            executor.execute(calls[i].target, calls[i].value, calls[i].payload);
        }
    }
}

// SPDX-FileCopyrightText: 2024 Lido <[email protected]>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {Timestamp, Timestamps} from "./Timestamp.sol";

// ---
// Type Definition
// ---

type Duration is uint32;

// ---
// Assign Global Operations
// ---

using {lt as <, lte as <=, eq as ==, neq as !=, gte as >=, gt as >} for Duration global;
using {addTo, plusSeconds, minusSeconds, multipliedBy, dividedBy, toSeconds} for Duration global;
using {plus as +, minus as -} for Duration global;

// ---
// Errors
// ---

error DivisionByZero();
error DurationOverflow();
error DurationUnderflow();

// ---
// Constants
// ---

/// @dev The maximum possible duration is approximately 136 years (assuming 365 days per year).
uint32 constant MAX_DURATION_VALUE = type(uint32).max;

// ---
// Comparison Operations
// ---

function lt(Duration d1, Duration d2) pure returns (bool) {
    return Duration.unwrap(d1) < Duration.unwrap(d2);
}

function lte(Duration d1, Duration d2) pure returns (bool) {
    return Duration.unwrap(d1) <= Duration.unwrap(d2);
}

function eq(Duration d1, Duration d2) pure returns (bool) {
    return Duration.unwrap(d1) == Duration.unwrap(d2);
}

function neq(Duration d1, Duration d2) pure returns (bool) {
    return Duration.unwrap(d1) != Duration.unwrap(d2);
}

function gte(Duration d1, Duration d2) pure returns (bool) {
    return Duration.unwrap(d1) >= Duration.unwrap(d2);
}

function gt(Duration d1, Duration d2) pure returns (bool) {
    return Duration.unwrap(d1) > Duration.unwrap(d2);
}

// ---
// Conversion Operations
// ---

function toSeconds(Duration d) pure returns (uint256) {
    return Duration.unwrap(d);
}

// ---
// Arithmetic Operations
// ---

function plus(Duration d1, Duration d2) pure returns (Duration) {
    unchecked {
        /// @dev Both `d1.toSeconds()` and `d2.toSeconds()` are <= type(uint32).max. Therefore, their
        ///      sum is <= type(uint256).max.
        return Durations.from(d1.toSeconds() + d2.toSeconds());
    }
}

function minus(Duration d1, Duration d2) pure returns (Duration) {
    uint256 d1Seconds = d1.toSeconds();
    uint256 d2Seconds = d2.toSeconds();

    if (d1Seconds < d2Seconds) {
        revert DurationUnderflow();
    }

    unchecked {
        /// @dev Subtraction is safe because `d1Seconds` >= `d2Seconds`.
        ///      Both `d1Seconds` and `d2Seconds` <= `type(uint32).max`, so the difference fits within `uint32`.
        return Duration.wrap(uint32(d1Seconds - d2Seconds));
    }
}

// ---
// Custom Operations
// ---

function plusSeconds(Duration d, uint256 secondsToAdd) pure returns (Duration) {
    return Durations.from(d.toSeconds() + secondsToAdd);
}

function minusSeconds(Duration d, uint256 secondsToSubtract) pure returns (Duration) {
    uint256 durationSeconds = d.toSeconds();

    if (durationSeconds < secondsToSubtract) {
        revert DurationUnderflow();
    }

    unchecked {
        /// @dev Subtraction is safe because `durationSeconds` >= `secondsToSubtract`.
        ///      Both `durationSeconds` and `secondsToSubtract` <= `type(uint32).max`,
        ///      so the difference fits within `uint32`.
        return Duration.wrap(uint32(durationSeconds - secondsToSubtract));
    }
}

function dividedBy(Duration d, uint256 divisor) pure returns (Duration) {
    if (divisor == 0) {
        revert DivisionByZero();
    }
    return Duration.wrap(uint32(d.toSeconds() / divisor));
}

function multipliedBy(Duration d, uint256 multiplicand) pure returns (Duration) {
    return Durations.from(multiplicand * d.toSeconds());
}

function addTo(Duration d, Timestamp t) pure returns (Timestamp) {
    unchecked {
        /// @dev Both `t.toSeconds()` <= `type(uint40).max` and `d.toSeconds()` <= `type(uint32).max`, so their
        ///      sum fits within `uint256`.
        return Timestamps.from(t.toSeconds() + d.toSeconds());
    }
}

// ---
// Namespaced Helper Methods
// ---

library Durations {
    Duration internal constant ZERO = Duration.wrap(0);

    function from(uint256 durationInSeconds) internal pure returns (Duration res) {
        if (durationInSeconds > MAX_DURATION_VALUE) {
            revert DurationOverflow();
        }
        /// @dev Casting `durationInSeconds` to `uint32` is safe as the check ensures it is less than or equal
        ///     to `MAX_DURATION_VALUE`, which fits within the `uint32`.
        res = Duration.wrap(uint32(durationInSeconds));
    }
}

// SPDX-FileCopyrightText: 2024 Lido <[email protected]>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

// ---
// Type Definition
// ---

type Timestamp is uint40;

// ---
// Assign Global Operations
// ---

using {lt as <, lte as <=, eq as ==, neq as !=, gte as >=, gt as >} for Timestamp global;
using {isZero, isNotZero, toSeconds} for Timestamp global;

// ---
// Errors
// ---

error TimestampOverflow();

// ---
// Constants
// ---

/// @dev The maximum value for a `Timestamp`, corresponding to approximately the year 36812.
uint40 constant MAX_TIMESTAMP_VALUE = type(uint40).max;

// ---
// Comparison Operations
// ---

function lt(Timestamp t1, Timestamp t2) pure returns (bool) {
    return Timestamp.unwrap(t1) < Timestamp.unwrap(t2);
}

function lte(Timestamp t1, Timestamp t2) pure returns (bool) {
    return Timestamp.unwrap(t1) <= Timestamp.unwrap(t2);
}

function eq(Timestamp t1, Timestamp t2) pure returns (bool) {
    return Timestamp.unwrap(t1) == Timestamp.unwrap(t2);
}

function neq(Timestamp t1, Timestamp t2) pure returns (bool) {
    return Timestamp.unwrap(t1) != Timestamp.unwrap(t2);
}

function gte(Timestamp t1, Timestamp t2) pure returns (bool) {
    return Timestamp.unwrap(t1) >= Timestamp.unwrap(t2);
}

function gt(Timestamp t1, Timestamp t2) pure returns (bool) {
    return Timestamp.unwrap(t1) > Timestamp.unwrap(t2);
}

// ---
// Conversion Operations
// ---

function toSeconds(Timestamp t) pure returns (uint256) {
    return Timestamp.unwrap(t);
}

// ---
// Custom Operations
// ---

function isZero(Timestamp t) pure returns (bool) {
    return Timestamp.unwrap(t) == 0;
}

function isNotZero(Timestamp t) pure returns (bool) {
    return Timestamp.unwrap(t) > 0;
}

// ---
// Namespaced Helper Methods
// ---

library Timestamps {
    Timestamp internal constant ZERO = Timestamp.wrap(0);

    function from(uint256 timestampInSeconds) internal pure returns (Timestamp res) {
        if (timestampInSeconds > MAX_TIMESTAMP_VALUE) {
            revert TimestampOverflow();
        }

        /// @dev Casting `timestampInSeconds` to `uint40` is safe as the check ensures it is less than or equal
        ///     to `MAX_TIMESTAMP_VALUE`, which fits within the `uint40`.
        return Timestamp.wrap(uint40(timestampInSeconds));
    }

    function now() internal view returns (Timestamp res) {
        /// @dev Skipping the check that `block.timestamp` <= `MAX_TIMESTAMP_VALUE` for gas efficiency.
        ///      Overflow is possible only after approximately 34,000 years from the Unix epoch.
        res = Timestamp.wrap(uint40(block.timestamp));
    }

    function max(Timestamp t1, Timestamp t2) internal pure returns (Timestamp) {
        return t1 > t2 ? t1 : t2;
    }
}

// SPDX-FileCopyrightText: 2024 Lido <[email protected]>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {Duration} from "../types/Duration.sol";
import {Timestamp, Timestamps} from "../types/Timestamp.sol";

import {ITimelock} from "../interfaces/ITimelock.sol";

import {ExternalCall, ExternalCalls, IExternalExecutor} from "./ExternalCalls.sol";

/// @notice Describes the lifecycle state of a proposal, defining its current status.
/// @param NotExist Proposal has not been submitted yet.
/// @param Submitted Proposal has been successfully submitted but not scheduled yet. This state is
///     only reachable from NotExist.
/// @param Scheduled Proposal has been successfully scheduled after submission. This state is only
///     reachable from Submitted.
/// @param Executed Proposal has been successfully executed after being scheduled. This state is
///     only reachable from Scheduled and is the final state of the proposal.
/// @param Cancelled Proposal was cancelled before execution. Cancelled proposals cannot be scheduled
///     or executed. This state is only reachable from Submitted or Scheduled and is the final state
///     of the proposal.
///     @dev A proposal is considered cancelled if it was not executed and its id is less than
///         the id of the last submitted proposal at the time the `cancelAll()` method was called.
///         To check if a proposal is in the `Cancelled` state, use the `_isProposalCancelled()`
///         view function.
enum Status {
    NotExist,
    Submitted,
    Scheduled,
    Executed,
    Cancelled
}

/// @title Executable Proposals Library
/// @notice Manages a collection of proposals with associated external calls stored as Proposal struct.
///     Proposals are uniquely identified by sequential ids, starting from one.
library ExecutableProposals {
    // ---
    // Data Types
    // ---

    /// @notice Efficiently stores proposal data within a single EVM slot.
    /// @param status The current status of the proposal. See `Status` for details.
    /// @param executor The address of the associated executor used for executing the proposal's calls.
    /// @param submittedAt The timestamp when the proposal was submitted.
    /// @param scheduledAt The timestamp when the proposal was scheduled for execution.
    ///     Equals zero if the proposal hasn't been scheduled yet.
    struct ProposalData {
        /// @dev slot 0: [0..7]
        Status status;
        /// @dev slot 0: [8..167]
        address executor;
        /// @dev slot 0: [168..207]
        Timestamp submittedAt;
        /// @dev slot 0: [208..247]
        Timestamp scheduledAt;
    }

    /// @notice A struct representing a proposal data with associated external calls.
    /// @param data Proposal data packed into a struct for efficient loading into memory.
    /// @param calls List of external calls associated with the proposal
    struct Proposal {
        ProposalData data;
        ExternalCall[] calls;
    }

    /// @notice The context for the library, storing relevant proposals data.
    /// @param proposalsCount The total number of proposals submitted so far.
    /// @param lastCancelledProposalId The id of the most recently cancelled proposal.
    /// @param proposals A mapping of proposal ids to their corresponding `Proposal` data.
    struct Context {
        uint64 proposalsCount;
        uint64 lastCancelledProposalId;
        mapping(uint256 proposalId => Proposal) proposals;
    }

    // ---
    // Errors
    // ---

    error EmptyCalls();
    error UnexpectedProposalStatus(uint256 proposalId, Status status);
    error AfterSubmitDelayNotPassed(uint256 proposalId);
    error AfterScheduleDelayNotPassed(uint256 proposalId);
    error MinExecutionDelayNotPassed(uint256 proposalId);

    // ---
    // Events
    // ---

    event ProposalSubmitted(uint256 indexed id, address indexed executor, ExternalCall[] calls);
    event ProposalScheduled(uint256 indexed id);
    event ProposalExecuted(uint256 indexed id);
    event ProposalsCancelledTill(uint256 proposalId);

    // ---
    // Proposal lifecycle
    // ---

    /// @notice Submits a new proposal with the specified executor and external calls.
    /// @param self The context of the Executable Proposal library.
    /// @param executor The address authorized to execute the proposal.
    /// @param calls The list of external calls to include in the proposal.
    /// @return newProposalId The id of the newly submitted proposal.
    function submit(
        Context storage self,
        address executor,
        ExternalCall[] memory calls
    ) internal returns (uint256 newProposalId) {
        if (calls.length == 0) {
            revert EmptyCalls();
        }

        /// @dev: proposal ids are one-based. The first item has id = 1
        newProposalId = ++self.proposalsCount;
        Proposal storage newProposal = self.proposals[newProposalId];

        newProposal.data.executor = executor;
        newProposal.data.status = Status.Submitted;
        newProposal.data.submittedAt = Timestamps.now();

        uint256 callsCount = calls.length;
        for (uint256 i = 0; i < callsCount; ++i) {
            newProposal.calls.push(calls[i]);
        }

        emit ProposalSubmitted(newProposalId, executor, calls);
    }

    /// @notice Marks a previously submitted proposal as scheduled for execution if the required delay period
    ///     has passed since submission and the proposal was not cancelled.
    /// @param self The context of the Executable Proposal library.
    /// @param proposalId The id of the proposal to schedule.
    /// @param afterSubmitDelay The required delay duration after submission before the proposal can be scheduled.
    ///
    function schedule(Context storage self, uint256 proposalId, Duration afterSubmitDelay) internal {
        ProposalData memory proposalData = self.proposals[proposalId].data;

        _checkProposalNotCancelled(self, proposalId, proposalData);

        if (proposalData.status != Status.Submitted) {
            revert UnexpectedProposalStatus(proposalId, proposalData.status);
        }

        if (afterSubmitDelay.addTo(proposalData.submittedAt) > Timestamps.now()) {
            revert AfterSubmitDelayNotPassed(proposalId);
        }

        proposalData.status = Status.Scheduled;
        proposalData.scheduledAt = Timestamps.now();
        self.proposals[proposalId].data = proposalData;

        emit ProposalScheduled(proposalId);
    }

    /// @notice Marks a previously scheduled proposal as executed and runs the associated external calls if the
    ///     required delay period has passed since scheduling and the proposal has not been cancelled.
    /// @param self The context of the Executable Proposal library.
    /// @param proposalId The id of the proposal to execute.
    /// @param afterScheduleDelay The minimum delay required after scheduling before execution is allowed.
    /// @param minExecutionDelay The minimum time that must elapse after submission before execution is allowed.
    function execute(
        Context storage self,
        uint256 proposalId,
        Duration afterScheduleDelay,
        Duration minExecutionDelay
    ) internal {
        Proposal memory proposal = self.proposals[proposalId];

        _checkProposalNotCancelled(self, proposalId, proposal.data);

        if (proposal.data.status != Status.Scheduled) {
            revert UnexpectedProposalStatus(proposalId, proposal.data.status);
        }

        if (afterScheduleDelay.addTo(proposal.data.scheduledAt) > Timestamps.now()) {
            revert AfterScheduleDelayNotPassed(proposalId);
        }

        if (minExecutionDelay.addTo(proposal.data.submittedAt) > Timestamps.now()) {
            revert MinExecutionDelayNotPassed(proposalId);
        }

        self.proposals[proposalId].data.status = Status.Executed;

        ExternalCalls.execute(IExternalExecutor(proposal.data.executor), proposal.calls);
        emit ProposalExecuted(proposalId);
    }

    /// @notice Marks all non-executed proposals up to the most recently submitted as cancelled, preventing their execution.
    /// @param self The context of the Executable Proposal library.
    function cancelAll(Context storage self) internal {
        uint64 lastCancelledProposalId = self.proposalsCount;
        self.lastCancelledProposalId = lastCancelledProposalId;
        emit ProposalsCancelledTill(lastCancelledProposalId);
    }

    // ---
    // Getters
    // ---

    /// @notice Determines whether a proposal is eligible to be scheduled based on its status and required delay.
    /// @param self The context of the Executable Proposal library.
    /// @param proposalId The id of the proposal to check for scheduling eligibility.
    /// @param afterSubmitDelay The minimum delay required after submission before the proposal can be scheduled.
    /// @return bool `true` if the proposal is eligible for scheduling, otherwise `false`.
    function canSchedule(
        Context storage self,
        uint256 proposalId,
        Duration afterSubmitDelay
    ) internal view returns (bool) {
        ProposalData memory proposalData = self.proposals[proposalId].data;
        return proposalId > self.lastCancelledProposalId && proposalData.status == Status.Submitted
            && Timestamps.now() >= afterSubmitDelay.addTo(proposalData.submittedAt);
    }

    /// @notice Determines whether a proposal is eligible for execution based on its status and delays requirements.
    /// @param self The context of the Executable Proposal library.
    /// @param proposalId The id of the proposal to check for execution eligibility.
    /// @param afterScheduleDelay The required delay duration after scheduling before the proposal can be executed.
    /// @param minExecutionDelay The required minimum delay after submission before execution is allowed.
    /// @return bool `true` if the proposal is eligible for execution, otherwise `false`.
    function canExecute(
        Context storage self,
        uint256 proposalId,
        Duration afterScheduleDelay,
        Duration minExecutionDelay
    ) internal view returns (bool) {
        Timestamp currentTime = Timestamps.now();
        ProposalData memory proposalData = self.proposals[proposalId].data;

        return proposalId > self.lastCancelledProposalId && proposalData.status == Status.Scheduled
            && currentTime >= afterScheduleDelay.addTo(proposalData.scheduledAt)
            && currentTime >= minExecutionDelay.addTo(proposalData.submittedAt);
    }

    /// @notice Returns the total count of submitted proposals.
    /// @param self The context of the Executable Proposal library.
    /// @return uint256 The number of submitted proposal
    function getProposalsCount(Context storage self) internal view returns (uint256) {
        return self.proposalsCount;
    }

    /// @notice Retrieves detailed information about a specific previously submitted proposal.
    /// @param self The context of the Executable Proposal library.
    /// @param proposalId The id of the proposal to retrieve details for.
    /// @return proposalDetails A struct containing the proposal’s id, executor, submission timestamp,
    ///      scheduling timestamp and status, if applicable
    function getProposalDetails(
        Context storage self,
        uint256 proposalId
    ) internal view returns (ITimelock.ProposalDetails memory proposalDetails) {
        ProposalData memory proposalData = self.proposals[proposalId].data;
        _checkProposalExists(proposalId, proposalData);

        proposalDetails.id = proposalId;
        proposalDetails.status =
            _isProposalCancelled(self, proposalId, proposalData) ? Status.Cancelled : proposalData.status;
        proposalDetails.executor = address(proposalData.executor);
        proposalDetails.submittedAt = proposalData.submittedAt;
        proposalDetails.scheduledAt = proposalData.scheduledAt;
    }

    /// @notice Retrieves the list of external calls associated with a specific previously submitted proposal.
    /// @param self The storage context for managing proposals within the Executable Proposal library.
    /// @param proposalId The id of the proposal to retrieve calls for.
    /// @return calls An array containing all external calls associated with the specified proposal
    function getProposalCalls(
        Context storage self,
        uint256 proposalId
    ) internal view returns (ExternalCall[] memory calls) {
        Proposal memory proposal = self.proposals[proposalId];
        _checkProposalExists(proposalId, proposal.data);
        calls = proposal.calls;
    }

    // ---
    // Private methods
    // ---

    function _checkProposalExists(uint256 proposalId, ProposalData memory proposalData) private pure {
        if (proposalData.status == Status.NotExist) {
            revert UnexpectedProposalStatus(proposalId, Status.NotExist);
        }
    }

    function _checkProposalNotCancelled(
        Context storage self,
        uint256 proposalId,
        ProposalData memory proposalData
    ) private view {
        if (_isProposalCancelled(self, proposalId, proposalData)) {
            revert UnexpectedProposalStatus(proposalId, Status.Cancelled);
        }
    }

    function _isProposalCancelled(
        Context storage self,
        uint256 proposalId,
        ProposalData memory proposalData
    ) private view returns (bool) {
        return proposalId <= self.lastCancelledProposalId && proposalData.status != Status.Executed;
    }
}

// SPDX-FileCopyrightText: 2024 Lido <[email protected]>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

interface IExternalExecutor {
    function execute(address target, uint256 value, bytes calldata payload) external payable;
}

Settings
{
  "remappings": [
    "@openzeppelin/=lib/openzeppelin-contracts/",
    "ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/",
    "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
    "eth-gas-reporter/=node_modules/eth-gas-reporter/",
    "forge-std/=lib/forge-std/src/",
    "hardhat/=node_modules/hardhat/",
    "kontrol-cheatcodes/=lib/kontrol-cheatcodes/src/",
    "openzeppelin-contracts/=lib/openzeppelin-contracts/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "cancun",
  "viaIR": false,
  "libraries": {}
}

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"governance","type":"address"},{"internalType":"contract ITimelock","name":"timelock","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"caller","type":"address"}],"name":"CallerIsNotGovernance","type":"error"},{"inputs":[{"internalType":"address","name":"governance","type":"address"}],"name":"InvalidGovernance","type":"error"},{"inputs":[{"internalType":"contract ITimelock","name":"timelock","type":"address"}],"name":"InvalidTimelock","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"proposerAccount","type":"address"},{"indexed":true,"internalType":"uint256","name":"proposalId","type":"uint256"},{"indexed":false,"internalType":"string","name":"metadata","type":"string"}],"name":"ProposalSubmitted","type":"event"},{"inputs":[],"name":"GOVERNANCE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TIMELOCK","outputs":[{"internalType":"contract ITimelock","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"proposalId","type":"uint256"}],"name":"canScheduleProposal","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cancelAllPendingProposals","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"proposalId","type":"uint256"}],"name":"scheduleProposal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint96","name":"value","type":"uint96"},{"internalType":"bytes","name":"payload","type":"bytes"}],"internalType":"struct ExternalCall[]","name":"calls","type":"tuple[]"},{"internalType":"string","name":"metadata","type":"string"}],"name":"submitProposal","outputs":[{"internalType":"uint256","name":"proposalId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]

60c060405234801561000f575f80fd5b5060405161089e38038061089e83398101604081905261002e916100c5565b6001600160a01b03821661006557604051633a56e4b960e01b81526001600160a01b03831660048201526024015b60405180910390fd5b6001600160a01b038116610097576040516310f6994d60e11b81526001600160a01b038216600482015260240161005c565b6001600160a01b039182166080521660a0526100fd565b6001600160a01b03811681146100c2575f80fd5b50565b5f80604083850312156100d6575f80fd5b82516100e1816100ae565b60208401519092506100f2816100ae565b809150509250929050565b60805160a0516107586101465f395f818160e60152818161013b0152818161016a015281816102a10152818161032a01526103a401525f81816069015261042601526107585ff3fe608060405234801561000f575f80fd5b5060043610610060575f3560e01c8063146278341461006457806353e51f8b146100a85780636c246870146100c95780637aadef8b146100e1578063df5aa6b114610108578063e52b17f61461011d575b5f80fd5b61008b7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b6100bb6100b63660046104b1565b610130565b60405190815260200161009f565b6100d1610296565b604051901515815260200161009f565b61008b7f000000000000000000000000000000000000000000000000000000000000000081565b61011b61011636600461054c565b610314565b005b6100d161012b36600461054c565b61038c565b5f61013961041b565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166389bc65cf7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316636608e2e26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101c4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906101e8919061057a565b87876040518463ffffffff1660e01b8152600401610208939291906105c4565b6020604051808303815f875af1158015610224573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061024891906106d1565b905080336001600160a01b03167f232ce03ddb9384ef5aeb6333cad16b1c7e68e1977e0e6e5a3666e934569a15fc85856040516102869291906106e8565b60405180910390a3949350505050565b5f61029f61041b565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663021937306040518163ffffffff1660e01b81526004015f604051808303815f87803b1580156102f7575f80fd5b505af1158015610309573d5f803e3d5ffd5b505050506001905090565b604051636fbfd40960e01b8152600481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690636fbfd409906024015f604051808303815f87803b158015610373575f80fd5b505af1158015610385573d5f803e3d5ffd5b5050505050565b604051635f1ff5af60e01b8152600481018290525f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690635f1ff5af90602401602060405180830381865afa1580156103f1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104159190610703565b92915050565b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461046a576040516382a9ceaf60e01b815233600482015260240160405180910390fd5b565b5f8083601f84011261047c575f80fd5b50813567ffffffffffffffff811115610493575f80fd5b6020830191508360208285010111156104aa575f80fd5b9250929050565b5f805f80604085870312156104c4575f80fd5b843567ffffffffffffffff8111156104da575f80fd5b8501601f810187136104ea575f80fd5b803567ffffffffffffffff811115610500575f80fd5b8760208260051b8401011115610514575f80fd5b60209182019550935085013567ffffffffffffffff811115610534575f80fd5b6105408782880161046c565b95989497509550505050565b5f6020828403121561055c575f80fd5b5035919050565b6001600160a01b0381168114610577575f80fd5b50565b5f6020828403121561058a575f80fd5b815161059581610563565b9392505050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b6001600160a01b038416815260406020820181905281018290525f6060600584901b830181019083018583605e1936839003015b878210156106c357868503605f190184528235818112610616575f80fd5b8901803561062381610563565b6001600160a01b0316865260208101356bffffffffffffffffffffffff811680821461064d575f80fd5b602088015250604081013536829003601e1901811261066a575f80fd5b0160208101903567ffffffffffffffff811115610685575f80fd5b803603821315610693575f80fd5b606060408801526106a860608801828461059c565b965050506020830192506020840193506001820191506105f8565b509298975050505050505050565b5f602082840312156106e1575f80fd5b5051919050565b602081525f6106fb60208301848661059c565b949350505050565b5f60208284031215610713575f80fd5b81518015158114610595575f80fdfea2646970667358221220de0c15afd212968dbf3ff787f12ef82c8df86dc76223c9f1244b0370a8eac98364736f6c634300081a003300000000000000000000000049b3512c44891bef83f8967d075121bd1b07a01b0000000000000000000000000a5e22782c0bd4addf10d771f0bf0406b038282d

Deployed Bytecode

0x608060405234801561000f575f80fd5b5060043610610060575f3560e01c8063146278341461006457806353e51f8b146100a85780636c246870146100c95780637aadef8b146100e1578063df5aa6b114610108578063e52b17f61461011d575b5f80fd5b61008b7f00000000000000000000000049b3512c44891bef83f8967d075121bd1b07a01b81565b6040516001600160a01b0390911681526020015b60405180910390f35b6100bb6100b63660046104b1565b610130565b60405190815260200161009f565b6100d1610296565b604051901515815260200161009f565b61008b7f0000000000000000000000000a5e22782c0bd4addf10d771f0bf0406b038282d81565b61011b61011636600461054c565b610314565b005b6100d161012b36600461054c565b61038c565b5f61013961041b565b7f0000000000000000000000000a5e22782c0bd4addf10d771f0bf0406b038282d6001600160a01b03166389bc65cf7f0000000000000000000000000a5e22782c0bd4addf10d771f0bf0406b038282d6001600160a01b0316636608e2e26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101c4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906101e8919061057a565b87876040518463ffffffff1660e01b8152600401610208939291906105c4565b6020604051808303815f875af1158015610224573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061024891906106d1565b905080336001600160a01b03167f232ce03ddb9384ef5aeb6333cad16b1c7e68e1977e0e6e5a3666e934569a15fc85856040516102869291906106e8565b60405180910390a3949350505050565b5f61029f61041b565b7f0000000000000000000000000a5e22782c0bd4addf10d771f0bf0406b038282d6001600160a01b031663021937306040518163ffffffff1660e01b81526004015f604051808303815f87803b1580156102f7575f80fd5b505af1158015610309573d5f803e3d5ffd5b505050506001905090565b604051636fbfd40960e01b8152600481018290527f0000000000000000000000000a5e22782c0bd4addf10d771f0bf0406b038282d6001600160a01b031690636fbfd409906024015f604051808303815f87803b158015610373575f80fd5b505af1158015610385573d5f803e3d5ffd5b5050505050565b604051635f1ff5af60e01b8152600481018290525f907f0000000000000000000000000a5e22782c0bd4addf10d771f0bf0406b038282d6001600160a01b031690635f1ff5af90602401602060405180830381865afa1580156103f1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104159190610703565b92915050565b336001600160a01b037f00000000000000000000000049b3512c44891bef83f8967d075121bd1b07a01b161461046a576040516382a9ceaf60e01b815233600482015260240160405180910390fd5b565b5f8083601f84011261047c575f80fd5b50813567ffffffffffffffff811115610493575f80fd5b6020830191508360208285010111156104aa575f80fd5b9250929050565b5f805f80604085870312156104c4575f80fd5b843567ffffffffffffffff8111156104da575f80fd5b8501601f810187136104ea575f80fd5b803567ffffffffffffffff811115610500575f80fd5b8760208260051b8401011115610514575f80fd5b60209182019550935085013567ffffffffffffffff811115610534575f80fd5b6105408782880161046c565b95989497509550505050565b5f6020828403121561055c575f80fd5b5035919050565b6001600160a01b0381168114610577575f80fd5b50565b5f6020828403121561058a575f80fd5b815161059581610563565b9392505050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b6001600160a01b038416815260406020820181905281018290525f6060600584901b830181019083018583605e1936839003015b878210156106c357868503605f190184528235818112610616575f80fd5b8901803561062381610563565b6001600160a01b0316865260208101356bffffffffffffffffffffffff811680821461064d575f80fd5b602088015250604081013536829003601e1901811261066a575f80fd5b0160208101903567ffffffffffffffff811115610685575f80fd5b803603821315610693575f80fd5b606060408801526106a860608801828461059c565b965050506020830192506020840193506001820191506105f8565b509298975050505050505050565b5f602082840312156106e1575f80fd5b5051919050565b602081525f6106fb60208301848661059c565b949350505050565b5f60208284031215610713575f80fd5b81518015158114610595575f80fdfea2646970667358221220de0c15afd212968dbf3ff787f12ef82c8df86dc76223c9f1244b0370a8eac98364736f6c634300081a0033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

00000000000000000000000049b3512c44891bef83f8967d075121bd1b07a01b0000000000000000000000000a5e22782c0bd4addf10d771f0bf0406b038282d

-----Decoded View---------------
Arg [0] : governance (address): 0x49B3512c44891bef83F8967d075121Bd1b07a01B
Arg [1] : timelock (address): 0x0A5E22782C0Bd4AddF10D771f0bF0406B038282d

-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 00000000000000000000000049b3512c44891bef83f8967d075121bd1b07a01b
Arg [1] : 0000000000000000000000000a5e22782c0bd4addf10d771f0bf0406b038282d


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
Loading...
Loading

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.