ETH Price: $2,011.66 (+4.22%)
Gas: 0.15 Gwei

Contract Diff Checker

Contract Name:
Router

Contract Source Code:

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;

import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {Ownable2Step, Ownable} from "@openzeppelin/contracts/access/Ownable2Step.sol";

import {IRouter} from "src/interfaces/IRouter.sol";
import {IRouterModule} from "src/interfaces/IRouterModule.sol";

/// @title Router.
/// @author Stake DAO
/// @custom:github @stake-dao
/// @custom:contact contact@stakedao.org

/// @notice Router serves as the single entry point for all Staking V2 operations.
///         It allows for the execution of arbitrary delegate calls to registered modules,
///         enabling modular functionality extension while maintaining a unified interface.
contract Router is IRouter, Ownable2Step {
    ///////////////////////////////////////////////////////////////
    // --- CONSTANTS
    ///////////////////////////////////////////////////////////////

    /// @notice The storage buffer for the modules
    /// @dev Instead of using a traditional mapping that hashes the key to calculate the slot,
    ///      we directly use the unique identifier of each module as the storage slot.
    ///
    ///      The storage buffer exists to avoid future collisions. Note the owner of this
    ///      contract takes one slot in storage (slot 0).
    ///
    ///      The value of the buffer is equal to the keccak256 hash of the constant
    ///      string "STAKEDAO.STAKING.V2.ROUTER.V1", meaning the modules will be
    ///      stored starting at slot `0x5fb198ff3ff065a7e746cc70c28b38b1f3eeaf1a559ede71c28b60a0759b061b`.
    ///      This is a gas cost optimization made possible due to the simplicity of the storage layout.
    bytes32 internal constant $buffer = keccak256("STAKEDAO.STAKING.V2.ROUTER.V1");
    string public constant version = "1.0.0";

    ///////////////////////////////////////////////////////////////
    // --- EVENTS - ERRORS
    ///////////////////////////////////////////////////////////////

    // @notice Thrown when trying to set a module with an empty name
    error EmptyModuleName();

    /// @notice Emitted when a module is set
    /// @param identifier The unique identifier of the module (indexed value)
    /// @param module The address of the module
    /// @param name The name of the module
    event ModuleSet(uint8 indexed identifier, address module, string name);

    // @notice Thrown when a module is already set
    // @dev Only thrown when setting a module in safe mode

    error IdentifierAlreadyUsed(uint8 identifier);

    // @notice Thrown when trying to call a module that is not set
    error ModuleNotSet(uint8 identifier);

    constructor() Ownable(msg.sender) {}

    ///////////////////////////////////////////////////////////////
    // --- MODULES MANAGEMENT
    ///////////////////////////////////////////////////////////////

    /**
     * @notice Gets the storage buffer used to store the modules.
     *         The buffer acts as an offset for the storage of modules.
     * @return buffer The storage buffer
     */
    function getStorageBuffer() public pure returns (bytes32 buffer) {
        buffer = $buffer;
    }

    /// @notice Sets a module
    /// @dev The module is set at the storage slot `buffer + identifier`
    ///
    ///      While not enforced by the code, developers are expected to use
    ///      incremental identifiers when setting modules.
    ///      This allows modules to be enumerated using the `enumerateModules` helper.
    ///      Note that this is just a convention, and modules should be indexed off-chain for
    ///      efficiency and correctness.
    /// @param identifier The unique identifier of the module
    /// @param module The address of the module
    /// @custom:throws OwnableUnauthorizedAccount if the caller is not the owner
    function setModule(uint8 identifier, address module) public onlyOwner {
        string memory moduleName = IRouterModule(module).name();
        require(bytes(moduleName).length != 0, EmptyModuleName());

        bytes32 buffer = getStorageBuffer();
        assembly ("memory-safe") {
            sstore(add(buffer, identifier), module)
        }

        emit ModuleSet(identifier, module, IRouterModule(module).name());
    }

    /// @notice Sets a module in safe mode
    /// @dev The module can be set to address(0) to erase it
    /// @param identifier The unique identifier of the module
    /// @param module The address of the module
    /// @custom:throws OwnableUnauthorizedAccount if the caller is not the owner
    /// @custom:throws IdentifierAlreadyUsed if the identifier is already set
    function safeSetModule(uint8 identifier, address module) external {
        require(getModule(identifier) == address(0), IdentifierAlreadyUsed(identifier));

        setModule(identifier, module);
    }

    /// @notice Gets the module at the given identifier
    /// @param identifier The unique identifier of the module
    /// @return module The address of the module. Returns address(0) if the module is not set
    function getModule(uint8 identifier) public view returns (address module) {
        bytes32 buffer = getStorageBuffer();
        assembly ("memory-safe") {
            module := sload(add(buffer, identifier))
        }
    }

    /// @notice Gets the name of the module at the given identifier
    /// @param identifier The unique identifier of the module
    /// @return name The name of the module. Returns an empty string if the module is not set
    function getModuleName(uint8 identifier) public view returns (string memory name) {
        address module = getModule(identifier);
        if (module != address(0)) name = IRouterModule(module).name();
    }

    /// @notice Convenient function to enumerate the incrementally stored modules
    /// @dev Never call this function on-chain. It is only meant to be used off-chain for informational purposes.
    ///      This function should not replace off-chain indexing of the modules.
    ///
    ///      This function stops iterating when it encounters address(0). This means that
    ///      if the modules are not stored contiguously, this function will return only a subset of the modules.
    /// @return modules The concatenated addresses of the modules in a bytes array.
    ///                 The length of the returned bytes array is `20 * n`, where `n` is the number of modules.
    ///                 Returns an empty bytes array if the first slot is not set.
    function enumerateModules() external view returns (bytes memory modules) {
        for (uint8 i; i < type(uint8).max; i++) {
            address module = getModule(i);

            if (module == address(0)) break;

            modules = abi.encodePacked(modules, module);
        }
    }

    ///////////////////////////////////////////////////////////////
    // --- EXECUTION
    ///////////////////////////////////////////////////////////////

    /// @notice Executes a batch of delegate calls to registered modules.
    /// @dev Each element in the `calls` array must be encoded as:
    ///      - 1 byte: the module identifier (`uint8`), corresponding to a registered module.
    ///      - N bytes: Optional ABI-encoded call data using `abi.encodeWithSelector(...)`, where:
    ///         - The first 4 bytes represent the function selector.
    ///         - The remaining bytes (a multiple of 32) represent the function arguments.
    ///
    ///      Example: `bytes.concat(bytes1(identifier), abi.encodeWithSelector(...))`
    ///
    ///      All calls are performed using `delegatecall`, so state changes affect this contract.
    /// @param calls An array of encoded calls. Each call must start with a 1-byte module identifier
    ///              followed by the ABI-encoded function call data.
    /// @return returnData An array containing the returned data for each call, in order.
    /// @custom:throws OwnableUnauthorizedAccount if the caller is not the owner
    /// @custom:throws ModuleNotSet if the module for a given identifier is not set
    /// @custom:throws _ if a `calls[i]` element is empty
    function execute(bytes[] calldata calls) external payable returns (bytes[] memory) {
        bytes[] memory returnData = new bytes[](calls.length);

        for (uint256 i; i < calls.length; i++) {
            address module = getModule(uint8(calls[i][0]));
            require(module != address(0), ModuleNotSet(uint8(calls[i][0])));

            // `calls[i][1:]` is the optional calldata, including the function selector, w/o the module identifier
            returnData[i] = Address.functionDelegateCall(module, calls[i][1:]);
        }

        return returnData;
    }
}

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

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

pragma solidity ^0.8.20;

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

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @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 Errors.InsufficientBalance(address(this).balance, amount);
        }

        (bool success, bytes memory returndata) = recipient.call{value: amount}("");
        if (!success) {
            _revert(returndata);
        }
    }

    /**
     * @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
     * {Errors.FailedCall} 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 Errors.InsufficientBalance(address(this).balance, value);
        }
        (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 {Errors.FailedCall}) 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 {Errors.FailedCall} 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 {Errors.FailedCall}.
     */
    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
            assembly ("memory-safe") {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert Errors.FailedCall();
        }
    }
}

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * This extension of the {Ownable} contract includes a two-step mechanism to transfer
 * ownership, where the new owner must call {acceptOwnership} in order to replace the
 * old one. This can help prevent common mistakes, such as transfers of ownership to
 * incorrect accounts, or to contracts that are unable to interact with the
 * permission system.
 *
 * The initial owner is specified at deployment time in the constructor for `Ownable`. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2Step is Ownable {
    address private _pendingOwner;

    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Returns the address of the pending owner.
     */
    function pendingOwner() public view virtual returns (address) {
        return _pendingOwner;
    }

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     *
     * Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        _pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        delete _pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        if (pendingOwner() != sender) {
            revert OwnableUnauthorizedAccount(sender);
        }
        _transferOwnership(sender);
    }
}

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;

interface IRouter {
    function execute(bytes[] calldata data) external payable returns (bytes[] memory returnData);
    function setModule(uint8 identifier, address module) external;
    function safeSetModule(uint8 identifier, address module) external;
    function getModule(uint8 identifier) external view returns (address module);
    function getModuleName(uint8 identifier) external view returns (string memory name);
    function version() external view returns (string memory version);
}

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;

interface IRouterModule {
    function name() external view returns (string memory name);
    function version() external view returns (string memory version);
}

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

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

pragma solidity ^0.8.20;

/**
 * @dev Collection of common custom errors used in multiple contracts
 *
 * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
 * It is recommended to avoid relying on the error API for critical functionality.
 *
 * _Available since v5.1._
 */
library Errors {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error InsufficientBalance(uint256 balance, uint256 needed);

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

    /**
     * @dev The deployment failed.
     */
    error FailedDeployment();

    /**
     * @dev A necessary precompile is missing.
     */
    error MissingPrecompile(address);
}

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

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

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

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

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):