Hoodi Testnet

Contract

0x05172CbCDb7307228F781436b327679e4DAE166B

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:
ResealManager

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 {Address} from "@openzeppelin/contracts/utils/Address.sol";

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

/// @title Reseal Manager
/// @notice Allows to extend pause of temporarily paused contracts to permanent pause or resume it.
contract ResealManager is IResealManager {
    // ---
    // Errors
    // ---

    error SealableWrongPauseState();
    error CallerIsNotGovernance(address caller);

    // ---
    // Immutables & Constants
    // ---

    uint256 public constant PAUSE_INFINITELY = type(uint256).max;
    ITimelock public immutable EMERGENCY_PROTECTED_TIMELOCK;

    // ---
    // Constructor
    // ---

    /// @notice Initializes the ResealManager contract.
    /// @param emergencyProtectedTimelock The address of the Timelock contract.
    constructor(ITimelock emergencyProtectedTimelock) {
        EMERGENCY_PROTECTED_TIMELOCK = emergencyProtectedTimelock;
    }

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

    /// @notice Extends the pause of the specified sealable contract.
    /// @dev Works only if conditions are met:
    /// - ResealManager has PAUSE_ROLE and RESUME_ROLE for target contract;
    /// - Contract are paused until timestamp after current timestamp and not for infinite time;
    /// - Function is called by the governance contract.
    /// @param sealable The address of the sealable contract.
    function reseal(address sealable) external {
        _checkCallerIsGovernance();

        uint256 sealableResumeSinceTimestamp = ISealable(sealable).getResumeSinceTimestamp();
        if (block.timestamp >= sealableResumeSinceTimestamp || sealableResumeSinceTimestamp == PAUSE_INFINITELY) {
            revert SealableWrongPauseState();
        }
        Address.functionCall(sealable, abi.encodeWithSelector(ISealable.resume.selector));
        Address.functionCall(sealable, abi.encodeWithSelector(ISealable.pauseFor.selector, PAUSE_INFINITELY));
    }

    /// @notice Resumes the specified sealable contract if it is paused.
    /// @dev Works only if conditions are met:
    /// - ResealManager has RESUME_ROLE for target contract;
    /// - Contract are paused until timestamp after current timestamp;
    /// - Function is called by the governance contract.
    /// @param sealable The address of the sealable contract.
    function resume(address sealable) external {
        _checkCallerIsGovernance();

        uint256 sealableResumeSinceTimestamp = ISealable(sealable).getResumeSinceTimestamp();
        if (block.timestamp >= sealableResumeSinceTimestamp) {
            revert SealableWrongPauseState();
        }
        Address.functionCall(sealable, abi.encodeWithSelector(ISealable.resume.selector));
    }

    // ---
    // Internal methods
    // ---

    /// @notice Ensures that the function can only be called by the governance address.
    function _checkCallerIsGovernance() internal view {
        address governance = EMERGENCY_PROTECTED_TIMELOCK.getGovernance();
        if (msg.sender != governance) {
            revert CallerIsNotGovernance(msg.sender);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedInnerCall();

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert AddressInsufficientBalance(address(this));
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert FailedInnerCall();
        }
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
     * unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {FailedInnerCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert FailedInnerCall();
        }
    }
}

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

interface ISealable {
    function resume() external;
    function pauseFor(uint256 duration) external;
    function getResumeSinceTimestamp() external view returns (uint256);
}

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

interface IResealManager {
    function resume(address sealable) external;
    function reseal(address sealable) external;
}

// 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 {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 {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":"contract ITimelock","name":"emergencyProtectedTimelock","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"caller","type":"address"}],"name":"CallerIsNotGovernance","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"SealableWrongPauseState","type":"error"},{"inputs":[],"name":"EMERGENCY_PROTECTED_TIMELOCK","outputs":[{"internalType":"contract ITimelock","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_INFINITELY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"sealable","type":"address"}],"name":"reseal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sealable","type":"address"}],"name":"resume","outputs":[],"stateMutability":"nonpayable","type":"function"}]

60a0604052348015600e575f80fd5b506040516105c03803806105c0833981016040819052602b91603b565b6001600160a01b03166080526066565b5f60208284031215604a575f80fd5b81516001600160a01b0381168114605f575f80fd5b9392505050565b60805161053c6100845f395f8181609601526102aa015261053c5ff3fe608060405234801561000f575f80fd5b506004361061004a575f3560e01c80631e4894b91461004e578063793c194614610063578063a302ee3814610076578063aa1e0dd614610091575b5f80fd5b61006161005c3660046104a3565b6100d0565b005b6100616100713660046104a3565b6101e8565b61007e5f1981565b6040519081526020015b60405180910390f35b6100b87f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610088565b6100d86102a7565b5f816001600160a01b031663589ff76c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610115573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061013991906104be565b9050804210158061014a57505f1981145b1561016857604051636aab9d5360e11b815260040160405180910390fd5b6040805160048152602481019091526020810180516001600160e01b0316630237bed160e11b17905261019c90839061035d565b50604080515f196024808301919091528251808303909101815260449091019091526020810180516001600160e01b031663f3f449c760e01b1790526101e390839061035d565b505050565b6101f06102a7565b5f816001600160a01b031663589ff76c6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561022d573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061025191906104be565b905080421061027357604051636aab9d5360e11b815260040160405180910390fd5b6040805160048152602481019091526020810180516001600160e01b0316630237bed160e11b1790526101e390839061035d565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663289b3c0d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610304573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061032891906104d5565b9050336001600160a01b0382161461035a576040516382a9ceaf60e01b81523360048201526024015b60405180910390fd5b50565b606061036a83835f610371565b9392505050565b6060814710156103965760405163cd78605960e01b8152306004820152602401610351565b5f80856001600160a01b031684866040516103b191906104f0565b5f6040518083038185875af1925050503d805f81146103eb576040519150601f19603f3d011682016040523d82523d5f602084013e6103f0565b606091505b509150915061040086838361040a565b9695505050505050565b60608261041f5761041a82610466565b61036a565b815115801561043657506001600160a01b0384163b155b1561045f57604051639996b31560e01b81526001600160a01b0385166004820152602401610351565b508061036a565b8051156104765780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b6001600160a01b038116811461035a575f80fd5b5f602082840312156104b3575f80fd5b813561036a8161048f565b5f602082840312156104ce575f80fd5b5051919050565b5f602082840312156104e5575f80fd5b815161036a8161048f565b5f82518060208501845e5f92019182525091905056fea26469706673582212209fd22d65a62658d0083be4fbefd7fdc297a1a79df0471c7be97ec000b136751864736f6c634300081a00330000000000000000000000000a5e22782c0bd4addf10d771f0bf0406b038282d

Deployed Bytecode

0x608060405234801561000f575f80fd5b506004361061004a575f3560e01c80631e4894b91461004e578063793c194614610063578063a302ee3814610076578063aa1e0dd614610091575b5f80fd5b61006161005c3660046104a3565b6100d0565b005b6100616100713660046104a3565b6101e8565b61007e5f1981565b6040519081526020015b60405180910390f35b6100b87f0000000000000000000000000a5e22782c0bd4addf10d771f0bf0406b038282d81565b6040516001600160a01b039091168152602001610088565b6100d86102a7565b5f816001600160a01b031663589ff76c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610115573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061013991906104be565b9050804210158061014a57505f1981145b1561016857604051636aab9d5360e11b815260040160405180910390fd5b6040805160048152602481019091526020810180516001600160e01b0316630237bed160e11b17905261019c90839061035d565b50604080515f196024808301919091528251808303909101815260449091019091526020810180516001600160e01b031663f3f449c760e01b1790526101e390839061035d565b505050565b6101f06102a7565b5f816001600160a01b031663589ff76c6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561022d573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061025191906104be565b905080421061027357604051636aab9d5360e11b815260040160405180910390fd5b6040805160048152602481019091526020810180516001600160e01b0316630237bed160e11b1790526101e390839061035d565b5f7f0000000000000000000000000a5e22782c0bd4addf10d771f0bf0406b038282d6001600160a01b031663289b3c0d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610304573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061032891906104d5565b9050336001600160a01b0382161461035a576040516382a9ceaf60e01b81523360048201526024015b60405180910390fd5b50565b606061036a83835f610371565b9392505050565b6060814710156103965760405163cd78605960e01b8152306004820152602401610351565b5f80856001600160a01b031684866040516103b191906104f0565b5f6040518083038185875af1925050503d805f81146103eb576040519150601f19603f3d011682016040523d82523d5f602084013e6103f0565b606091505b509150915061040086838361040a565b9695505050505050565b60608261041f5761041a82610466565b61036a565b815115801561043657506001600160a01b0384163b155b1561045f57604051639996b31560e01b81526001600160a01b0385166004820152602401610351565b508061036a565b8051156104765780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b6001600160a01b038116811461035a575f80fd5b5f602082840312156104b3575f80fd5b813561036a8161048f565b5f602082840312156104ce575f80fd5b5051919050565b5f602082840312156104e5575f80fd5b815161036a8161048f565b5f82518060208501845e5f92019182525091905056fea26469706673582212209fd22d65a62658d0083be4fbefd7fdc297a1a79df0471c7be97ec000b136751864736f6c634300081a0033

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

0000000000000000000000000a5e22782c0bd4addf10d771f0bf0406b038282d

-----Decoded View---------------
Arg [0] : emergencyProtectedTimelock (address): 0x0A5E22782C0Bd4AddF10D771f0bF0406B038282d

-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 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.