TimelockedCall

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.19;

// src/core/BaseOwnable.sol

abstract contract BaseOwnable {
    error OwnerOnly();

    address internal _owner;

    /**
     * @notice Triggers when contract ownership changes.
     * @param previousOwner The previous owner of the contract.
     * @param newOwner The new owner of the contract.
     */
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        if (msg.sender != _owner) revert OwnerOnly();
        _;
    }

    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// src/core/BaseReentrancyGuard.sol

/**
 * @title Base reentrancy guard. This is constructor-less implementation for both proxies and standalone contracts.
 */
abstract contract BaseReentrancyGuard {
    error ReentrantCall();
    
    uint256 internal constant _REENTRANCY_NOT_ENTERED = 1;
    uint256 internal constant _REENTRANCY_ENTERED = 2;

    uint256 internal _reentrancyStatus;

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        if (_reentrancyStatus == _REENTRANCY_ENTERED) revert ReentrantCall();

        // Any calls to nonReentrant after this point will fail
        _reentrancyStatus = _REENTRANCY_ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _reentrancyStatus = _REENTRANCY_NOT_ENTERED;
    }
}

// src/core/interfaces/IAddressWhitelist.sol

/**
 * @notice Defines the interface for whitelisting addresses.
 */
interface IAddressWhitelist {
    /**
     * @notice Whitelists the address specified.
     * @param addr The address to enable
     */
    function enableAddress (address addr) external;

    /**
     * @notice Whitelists the addresses specified.
     * @param arr The addresses to enable
     */
    function enableAddresses (address[] calldata arr) external;

    /**
     * @notice Disables the address specified.
     * @param addr The address to disable
     */
    function disableAddress (address addr) external;

    /**
     * @notice Disables the addresses specified.
     * @param arr The addresses to disable
     */
    function disableAddresses (address[] calldata arr) external;

    /**
     * @notice Indicates if the address is whitelisted or not.
     * @param addr The address to disable
     * @return Returns 1 if the address is whitelisted
     */
    function isWhitelistedAddress (address addr) external view returns (bool);

    /**
     * This event is triggered when a new address is whitelisted.
     * @param addr The address that was whitelisted
     */
    event OnAddressEnabled(address addr);

    /**
     * This event is triggered when an address is disabled.
     * @param addr The address that was disabled
     */
    event OnAddressDisabled(address addr);
}

// src/core/interfaces/IOwnable.sol

interface IOwnable {
    function transferOwnership(address newOwner) external;
    function owner() external view returns (address);
}

// src/core/interfaces/ITimelockedCall.sol

interface ITimelockedCall {
    function initScheduler(address addr, uint256 newTimeLockDuration) external;
    function enableScheduler(address addr) external;
    function disableScheduler(address addr) external;

    function schedule(bytes32 h, address consumerAddr) external;
    function consume(bytes32 h) external;
    function consumeOwnership(bytes32 h, address prevOwnerAddr, address newOwnerAddr) external;
}

// src/core/LightweightOwnable.sol

/**
 * @title Lightweight version of the ownership contract. This contract has a reentrancy guard.
 */
abstract contract LightweightOwnable is IOwnable, BaseReentrancyGuard, BaseOwnable {
    /**
     * @notice Transfers ownership of the contract to the account specified.
     * @param newOwner The address of the new owner.
     */
    function transferOwnership(address newOwner) external virtual nonReentrant onlyOwner {
        _transferOwnership(newOwner);
    }

    /**
     * @notice Gets the owner of the contract.
     * @return address The address who owns the contract.
     */
    function owner() external view virtual returns (address) {
        return _owner;
    }    
}

// src/core/AddressWhitelist.sol

/**
 * @title Standalone contract for whitelisting addresses.
 */
contract AddressWhitelist is IAddressWhitelist, LightweightOwnable {
    mapping (address => bool) internal _whitelistedAddresses;

    constructor(address ownerAddr) {
        require(ownerAddr != address(0), "Owner required");
        _owner = ownerAddr;
    }

    /**
     * @notice Whitelists the address specified.
     * @param addr The address to enable
     */
    function enableAddress (address addr) external override nonReentrant onlyOwner {
        require(!_whitelistedAddresses[addr], "Already enabled");
        _whitelistedAddresses[addr] = true;
        emit OnAddressEnabled(addr);
    }

    /**
     * @notice Whitelists the addresses specified.
     * @param arr The addresses to enable
     */
    function enableAddresses (address[] calldata arr) external override nonReentrant onlyOwner {
        require(arr.length > 0, "Addresses required");

        for (uint256 i; i < arr.length; i++) {
            require(arr[i] != address(0), "Invalid address");
            require(!_whitelistedAddresses[arr[i]], "Already enabled");
            _whitelistedAddresses[arr[i]] = true;
            emit OnAddressEnabled(arr[i]);
        }
    }

    /**
     * @notice Disables the address specified.
     * @param addr The address to disable
     */
    function disableAddress (address addr) external override nonReentrant onlyOwner {
        require(_whitelistedAddresses[addr], "Already disabled");
        _whitelistedAddresses[addr] = false;
        emit OnAddressDisabled(addr);
    }

    /**
     * @notice Disables the addresses specified.
     * @param arr The addresses to disable
     */
    function disableAddresses (address[] calldata arr) external override nonReentrant onlyOwner {
        for (uint256 i; i < arr.length; i++) {
            require(_whitelistedAddresses[arr[i]], "Already disabled");
            _whitelistedAddresses[arr[i]] = false;
            emit OnAddressDisabled(arr[i]);
        }
    }

    /**
     * @notice Indicates if the address is whitelisted or not.
     * @param addr The address to evaluate.
     * @return Returns true if the address is whitelisted.
     */
    function isWhitelistedAddress (address addr) external view override returns (bool) {
        return _whitelistedAddresses[addr];
    }
}

// src/core/TimelockedCall.sol

/**
 * @title Contract for managing time-locked function calls.
 */
contract TimelockedCall is ITimelockedCall, AddressWhitelist {
    struct TimelockedCallInfo {
        uint256 targetEpoch;     // The unix epoch at which the hash can be consumed
        address createdBy;       // The address of the scheduler
        address consumerAddress; // The address of the consumer
    }

    /// @notice The schedulers authorized for a given sender. (sender => scheduler => enabled/disabled)
    mapping (address => mapping(address => bool)) private whitelistedSchedulers;

    /// @notice The time-lock info of a given hash.
    mapping (bytes32 => TimelockedCallInfo) public queue;

    /// @notice The time-lock duration of every consumer address.
    mapping (address => uint256) public timeLockDuration;

    /// @notice Triggers when a hash is scheduled for the address specified.
    event HashScheduled(bytes32 h, address consumerAddress);

    /// @notice Triggers when a hash is consumed by the address specified.
    event HashConsumed(bytes32 h, address consumerAddress);

    /// @notice Triggers when a new scheduler is enabled for the consumer address specified.
    event SchedulerEnabled(address consumerAddress, address schedulerAddress);

    /// @notice Triggers when an existing scheduler is disabled for the consumer address specified.
    event SchedulerDisabled(address consumerAddress, address schedulerAddress);

    constructor(address ownerAddr) AddressWhitelist(ownerAddr) {
    }

    modifier ifSenderWhitelisted() {
        require(_whitelistedAddresses[msg.sender], "Unauthorized sender");
        _;
    }

    modifier ifTimeLockConfigured() {
        require(timeLockDuration[msg.sender] > 0, "Not configured");
        _;
    }

    /**
     * @notice Sets the initial scheduler and time-lock duration for the current message sender.
     * @param addr The address of the initial scheduler. You can add more addresses later.
     * @param newTimeLockDuration The duration of the time-lock for the current message sender.
     */
    function initScheduler(address addr, uint256 newTimeLockDuration) external override nonReentrant ifSenderWhitelisted {
        require(addr != address(0), "Address required");
        require(newTimeLockDuration > 0, "Duration required");
        require(timeLockDuration[msg.sender] == 0, "Already initialized");

        whitelistedSchedulers[msg.sender][addr] = true;
        timeLockDuration[msg.sender] = newTimeLockDuration;

        emit SchedulerEnabled(msg.sender, addr);
    }

    /**
     * @notice Authorizes the address specified to schedule calls. The calls will be consumed by the current message sender.
     * @param addr Specifies the address of the scheduler to authorize.
     */
    function enableScheduler(address addr) external override nonReentrant ifTimeLockConfigured {
        _enableScheduler(addr);
    }

    /**
     * @notice Revokes the address specified from scheduling calls for the current message sender.
     * @param addr Specifies the address of the scheduler to revoke.
     */
    function disableScheduler(address addr) external override nonReentrant ifTimeLockConfigured {
        _disableScheduler(addr);
    }

    /**
     * @notice Schedules a hash to be consumed by the address specified.
     * @param h Specifies the hash.
     * @param consumerAddr Specifies the address of the consumer.
     */
    function schedule(bytes32 h, address consumerAddr) external override nonReentrant {
        require(h != bytes32(0), "Hash required");
        require(whitelistedSchedulers[consumerAddr][msg.sender], "Unauthorized sender");
        require(timeLockDuration[consumerAddr] > 0, "Not configured");

        bytes32 h2 = keccak256(abi.encode(h, consumerAddr));
        require(queue[h2].targetEpoch == 0, "Already enqueued");
        
        queue[h2] = TimelockedCallInfo({
            createdBy: msg.sender,
            consumerAddress: consumerAddr,
            targetEpoch: block.timestamp + timeLockDuration[consumerAddr]
        });

        emit HashScheduled(h, consumerAddr);
    }

    /**
     * @notice Consumes the hash specified.
     * @param h Specifies the hash.
     */
    function consume(bytes32 h) external override nonReentrant ifTimeLockConfigured {
        bytes32 h2 = keccak256(abi.encode(h, msg.sender));
        _consume(h2);
    }

    /**
     * @notice Consumes the hash specified. The hash represents the transferOwnership function.
     * @param h Specifies the hash.
     * @param prevOwnerAddr The current owner of the contract at hand.
     * @param newOwnerAddr The address of the new owner.
     */
    function consumeOwnership(
        bytes32 h,
        address prevOwnerAddr,
        address newOwnerAddr
    ) external override nonReentrant ifTimeLockConfigured {
        bytes32 h2 = keccak256(abi.encode(h, msg.sender));

        _disableScheduler(prevOwnerAddr);
        _consume(h2);
        _enableScheduler(newOwnerAddr);
    }

    function _consume(bytes32 h) internal {
        require(queue[h].targetEpoch > 0, "Hash not enqueued");
        require(msg.sender == queue[h].consumerAddress, "Unauthorized consumer");
        require(block.timestamp > queue[h].targetEpoch, "Timelock in place");

        delete queue[h];

        emit HashConsumed(h, msg.sender);
    }

    function _enableScheduler(address addr) internal {
        require(addr != address(0), "Address required");
        require(!whitelistedSchedulers[msg.sender][addr], "Already enabled");
        whitelistedSchedulers[msg.sender][addr] = true;
        emit SchedulerEnabled(msg.sender, addr);
    }

    function _disableScheduler(address addr) internal {
        require(addr != address(0), "Address required");
        require(whitelistedSchedulers[msg.sender][addr], "Already disabled");
        whitelistedSchedulers[msg.sender][addr] = false;
        emit SchedulerDisabled(msg.sender, addr);
    }
}

Tags:
Multisig, Multi-Signature|addr:0x653d37c4c037c19f9d34871e8e058f4d4fcea015|verified:true|block:23497058|tx:0x31d583825ed71fad140d991b5abde0dc97d1eebee475382182a7f2f42697c1e2|first_check:1759495772

Submitted on: 2025-10-03 14:49:33

Comments

Log in to comment.

No comments yet.