S Price: $0.523093 (-12.37%)

Contract Diff Checker

Contract Name:
SwitchboardRouter

Contract Source Code:

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

interface IEIP712 {
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

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

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

/// @title SignatureTransfer
/// @notice Handles ERC20 token transfers through signature based actions
/// @dev Requires user's token approval on the Permit2 contract
interface ISignatureTransfer is IEIP712 {
    /// @notice Thrown when the requested amount for a transfer is larger than the permissioned amount
    /// @param maxAmount The maximum amount a spender can request to transfer
    error InvalidAmount(uint256 maxAmount);

    /// @notice Thrown when the number of tokens permissioned to a spender does not match the number of tokens being transferred
    /// @dev If the spender does not need to transfer the number of tokens permitted, the spender can request amount 0 to be transferred
    error LengthMismatch();

    /// @notice Emits an event when the owner successfully invalidates an unordered nonce.
    event UnorderedNonceInvalidation(address indexed owner, uint256 word, uint256 mask);

    /// @notice The token and amount details for a transfer signed in the permit transfer signature
    struct TokenPermissions {
        // ERC20 token address
        address token;
        // the maximum amount that can be spent
        uint256 amount;
    }

    /// @notice The signed permit message for a single token transfer
    struct PermitTransferFrom {
        TokenPermissions permitted;
        // a unique value for every token owner's signature to prevent signature replays
        uint256 nonce;
        // deadline on the permit signature
        uint256 deadline;
    }

    /// @notice Specifies the recipient address and amount for batched transfers.
    /// @dev Recipients and amounts correspond to the index of the signed token permissions array.
    /// @dev Reverts if the requested amount is greater than the permitted signed amount.
    struct SignatureTransferDetails {
        // recipient address
        address to;
        // spender requested amount
        uint256 requestedAmount;
    }

    /// @notice Used to reconstruct the signed permit message for multiple token transfers
    /// @dev Do not need to pass in spender address as it is required that it is msg.sender
    /// @dev Note that a user still signs over a spender address
    struct PermitBatchTransferFrom {
        // the tokens and corresponding amounts permitted for a transfer
        TokenPermissions[] permitted;
        // a unique value for every token owner's signature to prevent signature replays
        uint256 nonce;
        // deadline on the permit signature
        uint256 deadline;
    }

    /// @notice A map from token owner address and a caller specified word index to a bitmap. Used to set bits in the bitmap to prevent against signature replay protection
    /// @dev Uses unordered nonces so that permit messages do not need to be spent in a certain order
    /// @dev The mapping is indexed first by the token owner, then by an index specified in the nonce
    /// @dev It returns a uint256 bitmap
    /// @dev The index, or wordPosition is capped at type(uint248).max
    function nonceBitmap(address, uint256) external view returns (uint256);

    /// @notice Transfers a token using a signed permit message
    /// @dev Reverts if the requested amount is greater than the permitted signed amount
    /// @param permit The permit data signed over by the owner
    /// @param owner The owner of the tokens to transfer
    /// @param transferDetails The spender's requested transfer details for the permitted token
    /// @param signature The signature to verify
    function permitTransferFrom(
        PermitTransferFrom memory permit,
        SignatureTransferDetails calldata transferDetails,
        address owner,
        bytes calldata signature
    ) external;

    /// @notice Transfers a token using a signed permit message
    /// @notice Includes extra data provided by the caller to verify signature over
    /// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition
    /// @dev Reverts if the requested amount is greater than the permitted signed amount
    /// @param permit The permit data signed over by the owner
    /// @param owner The owner of the tokens to transfer
    /// @param transferDetails The spender's requested transfer details for the permitted token
    /// @param witness Extra data to include when checking the user signature
    /// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash
    /// @param signature The signature to verify
    function permitWitnessTransferFrom(
        PermitTransferFrom memory permit,
        SignatureTransferDetails calldata transferDetails,
        address owner,
        bytes32 witness,
        string calldata witnessTypeString,
        bytes calldata signature
    ) external;

    /// @notice Transfers multiple tokens using a signed permit message
    /// @param permit The permit data signed over by the owner
    /// @param owner The owner of the tokens to transfer
    /// @param transferDetails Specifies the recipient and requested amount for the token transfer
    /// @param signature The signature to verify
    function permitTransferFrom(
        PermitBatchTransferFrom memory permit,
        SignatureTransferDetails[] calldata transferDetails,
        address owner,
        bytes calldata signature
    ) external;

    /// @notice Transfers multiple tokens using a signed permit message
    /// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition
    /// @notice Includes extra data provided by the caller to verify signature over
    /// @param permit The permit data signed over by the owner
    /// @param owner The owner of the tokens to transfer
    /// @param transferDetails Specifies the recipient and requested amount for the token transfer
    /// @param witness Extra data to include when checking the user signature
    /// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash
    /// @param signature The signature to verify
    function permitWitnessTransferFrom(
        PermitBatchTransferFrom memory permit,
        SignatureTransferDetails[] calldata transferDetails,
        address owner,
        bytes32 witness,
        string calldata witnessTypeString,
        bytes calldata signature
    ) external;

    /// @notice Invalidates the bits specified in mask for the bitmap at the word position
    /// @dev The wordPos is maxed at type(uint248).max
    /// @param wordPos A number to index the nonceBitmap at
    /// @param mask A bitmap masked against msg.sender's current bitmap at the word position
    function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external;
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

/// @dev used to rescue funds
bytes32 constant RESCUE_ROLE = keccak256("RESCUE_ROLE");

/// @dev used to update configs on protocol contracts
bytes32 constant CONFIG_ROLE = keccak256("CONFIG_ROLE");

/// @dev used to update Socket DL configs
bytes32 constant SOCKET_CONFIG_ROLE = keccak256("SOCKET_CONFIG_ROLE");

/// @dev used to allow cancellation of requests
bytes32 constant CANCEL_ROLE = keccak256("CANCEL_ROLE");

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

/*//////////////////////////////////////////////////////////////
                         BUNGEE ERRORS
//////////////////////////////////////////////////////////////*/

error MofaSignatureInvalid();
error InsufficientNativeAmount();
error UnsupportedRequest();
error RouterNotRegistered();
error CallerNotBungeeGateway();
error CallerNotEntrypoint();
error SwapOutputInsufficient();
error MinOutputNotMet();
error InvalidRequest();
error FulfilmentDeadlineNotMet();
error CallerNotDelegate();
error InvalidMsg();
error RequestProcessed();
error RequestNotProcessed();
error InvalidSwitchboard();
error PromisedAmountNotMet();
error MsgReceiveFailed();
error RouterAlreadyRegistered();
error InvalidFulfil();
error NotImplemented();
error OnlyOwner();
error OnlyNominee();
error InvalidReceiver();
error ImplAlreadyRegistered();
error InvalidAddress();

/*//////////////////////////////////////////////////////////////
                       SWITCHBOARD ERRORS
//////////////////////////////////////////////////////////////*/

error NotSiblingBungeeGateway();
error NotSocket();
error NotSwitchboardRouter();
error NotSwitchboardPlug();
error SwitchboardPlugZero();
error ConnectionAlreadyInitialised();
error IncorrectSwitchboard();

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol";

import {IEntrypoint} from "../interfaces/IEntrypoint.sol";
import {AccessControl} from "../utils/AccessControl.sol";
import {ISwapExecutor} from "../interfaces/ISwapExecutor.sol";
import {ICalldataExecutor} from "../interfaces/ICalldataExecutor.sol";
import {ISwitchboardRouter} from "../interfaces/ISwitchboardRouter.sol";
import {IFeeCollector} from "../interfaces/IFeeCollector.sol";

/**
 * @notice WithdrawnRequest struct
 * @dev This struct is used to store info about withdrawn requests on the origin chain
 */
struct WithdrawnRequest {
    address token;
    uint256 amount;
    address receiver;
}

abstract contract BungeeGatewayStorage is AccessControl {
    /// @dev address used to identify native token
    address public constant NATIVE_TOKEN_ADDRESS = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);

    /// @notice address of the permit 2 contract
    ISignatureTransfer public immutable PERMIT2;

    /// @notice address of the Entrypoint
    /// @dev Entrypoint for Bungee, all extraction requests are sent to this contract.
    IEntrypoint public ENTRYPOINT;

    /// @notice address of the SwitchboardRouter
    /// @dev BungeeGateway uses this contract to handle cross-chain messages via Socket
    ISwitchboardRouter public SWITCHBOARD_ROUTER;

    /// @notice address of the SwapExecutor
    /// @dev BungeeGateway delegates swap executions to this contract.
    ISwapExecutor public SWAP_EXECUTOR;

    /// @notice address of the CalldataExecutor
    /// @dev BungeeGateway delegates calldata execution at destination chain to this contract.
    ICalldataExecutor public CALLDATA_EXECUTOR;

    /// @notice address of the FeeCollector
    /// @dev BungeeGateway collects affiliate fees from the users and transfers them to this contract.
    IFeeCollector public FEE_COLLECTOR;

    /// @notice this mapping holds all implementation contract addresses against their implId
    mapping(uint8 implId => address impl) internal _impls;

    /// @notice this mapping tracks whether an implId has been used & removed
    /// @dev used to prevent reusing an implId
    mapping(uint8 implId => bool removed) internal _removedImpls;

    /// @notice this mapping holds all the receiver contracts, these contracts will receive funds on the destination chain.
    /// @dev bridged funds would reach receiver contracts first and then transmitter uses these funds to fulfil order.
    mapping(address router => mapping(uint256 toChainId => address whitelistedReceiver)) internal whitelistedReceivers;

    /// @notice this mapping holds all the addresses that are routers.
    /// @dev bungee sends funds from the users to these routers.
    /// @dev bungee calls these when fulfilment happens on the destination.
    mapping(address routers => bool supported) internal bungeeRouters;

    /// @notice this mapping stores orders that have been withdrawn on the originChain
    /// @dev Requests are deleted from the extractedRequests mapping when withdrawn on the origin chain
    /// @dev Can be used by external contracts to track & use withdrawal info about requests
    mapping(bytes32 requestHash => WithdrawnRequest request) internal _withdrawnRequests;

    /// @notice this mapping stores the settlement amounts collected for the beneficiaries
    /// @dev not all routers would have settlement, so these amounts may not be cleared for some routers
    mapping(address beneficiary => mapping(address router => mapping(address token => uint256 amount)))
        public beneficiarySettlements;

    /**
     * @notice Constructor.
     * @dev Defines all immutable variables & owner
     * @param _owner owner of the contract.
     * @param _permit2 address of the permit 2 contract.
     */
    constructor(address _owner, address _permit2) AccessControl(_owner) {
        PERMIT2 = ISignatureTransfer(_permit2);
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import {AccessControl} from "../utils/AccessControl.sol";
import {CONFIG_ROLE, SOCKET_CONFIG_ROLE} from "../common/AccessRoles.sol";
import {
    CallerNotBungeeGateway,
    NotSiblingBungeeGateway,
    NotSwitchboardPlug,
    SwitchboardPlugZero,
    IncorrectSwitchboard,
    InvalidMsg
} from "../common/Errors.sol";
import {ISwitchboardPlug} from "../interfaces/ISwitchboardPlug.sol";
import {IBungeeGateway} from "../interfaces/IBungeeGateway.sol";

/// @title SwitchboardRouter
/// @notice Routes messages between Bungee Gateway and different SwitchboardPlugs
/// @dev Enables Bungee Protocol users to use multiple different Switchboards on Socket simultaneously
/// @dev Enables to send messages via different Switchboards without being tied to a single Switchboard & Plug
contract SwitchboardRouter is AccessControl {
    /*//////////////////////////////////////////////////////////////////////////
                                PUBLIC STORAGE
    //////////////////////////////////////////////////////////////////////////*/

    /// @notice Bungee Gateway contract address
    address public BUNGEE_GATEWAY;

    /// @notice An ever-increasing switchboardId count
    /// @dev prevents switchboardId reuse
    uint32 public switchboardIdCount = 1;

    /// @notice maps switchboardId to switchboardPlug
    mapping(uint32 switchboardId => ISwitchboardPlug switchboardPlug) public switchboardPlugs;

    /// @notice Tracks BungeeGateway contracts on sibling chains
    mapping(uint32 chainId => address bungeeGateway) public bungeeGateways;

    constructor(address _owner, address _bungeeGateway) AccessControl(_owner) {
        _grantRole(CONFIG_ROLE, _owner);
        _grantRole(SOCKET_CONFIG_ROLE, _owner);

        BUNGEE_GATEWAY = _bungeeGateway;
    }

    /*//////////////////////////////////////////////////////////////////////////
                                EXTERNAL FUNCTIONS
    //////////////////////////////////////////////////////////////////////////*/

    /// @notice Send Settlement Message via outbound message to Socket DL
    /// @param chainSlug chain slug used in Socket where the requests are to be settled on
    /// @param switchboardId identifier for settlement message security
    /// @param msgId identifier for the message type. Used as implementation id from Bungee Gateway
    /// @param destGasLimit gasLimit required to execute the settlement message on the destination chain
    /// @param payload settlement message data
    function sendOutboundMsg(
        uint32 chainSlug,
        uint32 switchboardId,
        uint8 msgId,
        uint256 destGasLimit,
        bytes calldata payload
    ) external payable {
        // checks if the caller is Bungee Gateway
        (ISwitchboardPlug switchboardPlug, address siblingBungeeGateway) = _validateOutboundMsg(
            chainSlug,
            switchboardId
        );

        // encodes msg.sender (Bungee Gateway) along with the payload
        // calls outbound with the payload
        switchboardPlug.outbound{value: msg.value}(
            chainSlug,
            destGasLimit,
            abi.encodePacked(msgId, siblingBungeeGateway, msg.sender, payload)
        );
    }

    /// @notice Accept inbound message from another chain via Switchboard
    /// @dev Can only be called by the configured SwitchboardPlug
    /// @param switchboardId id of the switchboardPlug
    /// @param siblingChainId id of the sibling chain where the message is coming from
    /// @param payload msg payload sent.
    function receiveAndDeliverMsg(uint32 switchboardId, uint32 siblingChainId, bytes calldata payload) external {
        // checks if the caller is switchboardPlug
        if (msg.sender != address(switchboardPlugs[switchboardId])) revert NotSwitchboardPlug();

        // decodes the receiving bungee Gateway address from the incoming payload
        uint8 msgId = uint8(bytes1(payload));
        address msgReceiver = address(bytes20(payload[1:]));
        address msgSender = address(bytes20(payload[21:]));

        // @audit msgReceiver need to part of the message actually. receiver can always be the configured bungee gateway
        // only problematic case would be when there is message delivered to the wrong bungee gateway,
        // when there is an out of date configuration.
        // Is it safe to remove it?
        if (msgReceiver != BUNGEE_GATEWAY || msgSender != bungeeGateways[siblingChainId]) revert InvalidMsg();

        // sends inbound message to Bungee
        IBungeeGateway(msgReceiver).inboundMsgFromSwitchboard(
            msgId,
            switchboardId,
            abi.encodePacked(switchboardId, payload[41:])
        );
    }

    /*//////////////////////////////////////////////////////////////////////////
                                ADMIN FUNCTIONS
    //////////////////////////////////////////////////////////////////////////*/

    /// @notice adds new sibling bungee gateway contract
    ///  Can only be called by CONFIG_ROLE.
    /// @param _bungeeGateway address of the bungee gateway on sibling chain.
    /// @param _chainId id of the sibling chain.

    function addBungeeGateway(address _bungeeGateway, uint32 _chainId) external onlyRole(CONFIG_ROLE) {
        bungeeGateways[_chainId] = _bungeeGateway;
    }

    /// @notice sets the bungee gateway contract.
    ///  Can only be called by CONFIG_ROLE.
    /// @param _bungeeGateway address of the new bungee gateway.
    function setBungeeGateway(address _bungeeGateway) external onlyRole(CONFIG_ROLE) {
        BUNGEE_GATEWAY = _bungeeGateway;
    }

    /// @notice adds switchboardPlug to switchboardPlugs mapping
    /// @dev Can only be called by SOCKET_CONFIG_ROLE
    /// @param switchboardPlug address of the switchboardPlug mapped to the id
    function addSwitchboardPlug(address switchboardPlug) external onlyRole(SOCKET_CONFIG_ROLE) {
        // maps switchboardId to SwitchboardPlug
        switchboardPlugs[switchboardIdCount] = ISwitchboardPlug(switchboardPlug);

        // checks if the SWITCHBOARD_ID() on the contract is the same as one being assigned on SwitchboardPlug
        if (switchboardIdCount != switchboardPlugs[switchboardIdCount].SWITCHBOARD_ID()) revert IncorrectSwitchboard();

        // increments switchboard count
        switchboardIdCount += 1;
    }

    /// @notice Disables SwitchboardPlug
    /// @dev Can only be called by SOCKET_CONFIG_ROLE
    /// @param switchboardId id of the plug to be disabled
    function removeSwitchboardPlug(uint32 switchboardId) external onlyRole(SOCKET_CONFIG_ROLE) {
        delete switchboardPlugs[switchboardId];
    }

    /// @notice Connects SwitchboardPlug to sibling chain
    /// @dev Can only be called by SOCKET_CONFIG_ROLE
    function connectSwitchboardPlug(
        uint32 switchboardId,
        uint32 siblingChainSlug,
        address siblingPlug
    ) external onlyRole(SOCKET_CONFIG_ROLE) {
        switchboardPlugs[switchboardId].connect(siblingChainSlug, siblingPlug);
    }

    /*//////////////////////////////////////////////////////////////////////////
                                INTERNAL FUNCTIONS
    //////////////////////////////////////////////////////////////////////////*/

    /// @notice validates the outbound message
    /// @dev checks if the caller is Bungee Gateway and if the sibling bungee gateway exists
    function _validateOutboundMsg(
        uint32 toChainId,
        uint32 switchboardId
    ) internal view returns (ISwitchboardPlug switchboardPlug, address siblingBungeeGateway) {
        // revert if caller is not bungee gateway
        if (msg.sender != BUNGEE_GATEWAY) revert CallerNotBungeeGateway();

        // revert if the bungee gateway address does not exist on the sibling chain
        siblingBungeeGateway = bungeeGateways[toChainId];
        if (bungeeGateways[toChainId] == address(0)) revert NotSiblingBungeeGateway();

        // finds the switchboardPlug to route the message through
        switchboardPlug = switchboardPlugs[switchboardId];

        // checks if switchboardPlug is not initialised for the switchboardId
        if (address(switchboardPlug) == address(0)) revert SwitchboardPlugZero();
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import {WithdrawnRequest} from "../core/BungeeGatewayStorage.sol";

interface IBungeeGateway {
    function setWhitelistedReceiver(address receiver, uint256 destinationChainId, address router) external;

    function getWhitelistedReceiver(address router, uint256 destinationChainId) external view returns (address);

    function inboundMsgFromSwitchboard(uint8 msgId, uint32 switchboardId, bytes calldata payload) external;

    function isBungeeRouter(address router) external view returns (bool);

    function withdrawnRequests(bytes32 requestHash) external view returns (WithdrawnRequest memory);

    function executeImpl(uint8 implId, bytes calldata data) external payable returns (bytes memory);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

interface ICalldataExecutor {
    function executeCalldata(address to, bytes memory encodedData, uint256 msgGasLimit) external returns (bool);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

interface IEntrypoint {
    function executeSOR(bytes calldata data, bytes calldata mofaSignature) external payable returns (bytes memory);
    function executeSR(bytes calldata data, bytes calldata mofaSignature) external payable returns (bytes memory);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

interface IFeeCollector {
    function registerFee(address feeTaker, uint256 feeAmount, address feeToken) external;
    function registerLockedFee(address feeTaker, uint256 feeAmount, address feeToken, bytes32 requestHash) external;
    function settleFee(bytes32 requestHash) external;
    function refundFee(bytes32 requestHash, address to) external;
}

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

/**
 * @title IPlug
 * @notice Interface for a plug contract that executes the message received from a source chain.
 */
interface IPlug {
    /**
     * @dev this should be only executable by socket
     * @notice executes the message received from source chain
     * @notice It is expected to have original sender checks in the destination plugs using payload
     * @param srcChainSlug_ chain slug of source
     * @param payload_ the data which is needed by plug at inbound call on remote
     */
    function inbound(uint32 srcChainSlug_, bytes calldata payload_) external payable;
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

interface ISwapExecutor {
    function executeSwap(address token, uint256 amount, address swapRouter, bytes memory swapPayload) external payable;
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

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

interface ISwitchboardPlug is IPlug {
    function SWITCHBOARD_ID() external view returns (uint32);

    function connect(uint32 siblingChainSlug, address siblingPlug) external;

    function outbound(uint32 siblingChainSlug, uint256 msgGasLimit, bytes calldata payload) external payable;
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

interface ISwitchboardRouter {
    function sendOutboundMsg(
        uint32 originChainId,
        uint32 switchboardId,
        uint8 msgId,
        uint256 destGasLimit,
        bytes calldata payload
    ) external payable;

    function receiveAndDeliverMsg(uint32 switchboardId, uint32 siblingChainId, bytes calldata payload) external;
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

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

/**
 * @title AccessControl
 * @dev This abstract contract implements access control mechanism based on roles.
 * Each role can have one or more addresses associated with it, which are granted
 * permission to execute functions with the onlyRole modifier.
 */
abstract contract AccessControl is Ownable {
    /**
     * @dev A mapping of roles to a mapping of addresses to boolean values indicating whether or not they have the role.
     */
    mapping(bytes32 => mapping(address => bool)) private _permits;

    /**
     * @dev Emitted when a role is granted to an address.
     */
    event RoleGranted(bytes32 indexed role, address indexed grantee);

    /**
     * @dev Emitted when a role is revoked from an address.
     */
    event RoleRevoked(bytes32 indexed role, address indexed revokee);

    /**
     * @dev Error message thrown when an address does not have permission to execute a function with onlyRole modifier.
     */
    error NoPermit(bytes32 role);

    /**
     * @dev Constructor that sets the owner of the contract.
     */
    constructor(address owner_) Ownable(owner_) {}

    /**
     * @dev Modifier that restricts access to addresses having roles
     * Throws an error if the caller do not have permit
     */
    modifier onlyRole(bytes32 role) {
        if (!_permits[role][msg.sender]) revert NoPermit(role);
        _;
    }

    /**
     * @dev Checks and reverts if an address do not have a specific role.
     * @param role_ The role to check.
     * @param address_ The address to check.
     */
    function _checkRole(bytes32 role_, address address_) internal virtual {
        if (!_hasRole(role_, address_)) revert NoPermit(role_);
    }

    /**
     * @dev Grants a role to a given address.
     * @param role_ The role to grant.
     * @param grantee_ The address to grant the role to.
     * Emits a RoleGranted event.
     * Can only be called by the owner of the contract.
     */
    function grantRole(bytes32 role_, address grantee_) external virtual onlyOwner {
        _grantRole(role_, grantee_);
    }

    /**
     * @dev Revokes a role from a given address.
     * @param role_ The role to revoke.
     * @param revokee_ The address to revoke the role from.
     * Emits a RoleRevoked event.
     * Can only be called by the owner of the contract.
     */
    function revokeRole(bytes32 role_, address revokee_) external virtual onlyOwner {
        _revokeRole(role_, revokee_);
    }

    /**
     * @dev Internal function to grant a role to a given address.
     * @param role_ The role to grant.
     * @param grantee_ The address to grant the role to.
     * Emits a RoleGranted event.
     */
    function _grantRole(bytes32 role_, address grantee_) internal {
        _permits[role_][grantee_] = true;
        emit RoleGranted(role_, grantee_);
    }

    /**
     * @dev Internal function to revoke a role from a given address.
     * @param role_ The role to revoke.
     * @param revokee_ The address to revoke the role from.
     * Emits a RoleRevoked event.
     */
    function _revokeRole(bytes32 role_, address revokee_) internal {
        _permits[role_][revokee_] = false;
        emit RoleRevoked(role_, revokee_);
    }

    /**
     * @dev Checks whether an address has a specific role.
     * @param role_ The role to check.
     * @param address_ The address to check.
     * @return A boolean value indicating whether or not the address has the role.
     */
    function hasRole(bytes32 role_, address address_) public view returns (bool) {
        return _hasRole(role_, address_);
    }

    function _hasRole(bytes32 role_, address address_) internal view returns (bool) {
        return _permits[role_][address_];
    }
}

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.19;

import {OnlyOwner, OnlyNominee} from "../common/Errors.sol";

// @audit Audited before by Zellic: https://github.com/SocketDotTech/audits/blob/main/Socket-DL/07-2023%20-%20Data%20Layer%20-%20Zellic.pdf
abstract contract Ownable {
    address private _owner;
    address private _nominee;

    event OwnerNominated(address indexed nominee);
    event OwnerClaimed(address indexed claimer);

    constructor(address owner_) {
        _claimOwner(owner_);
    }

    modifier onlyOwner() {
        if (msg.sender != _owner) {
            revert OnlyOwner();
        }
        _;
    }

    function owner() public view returns (address) {
        return _owner;
    }

    function nominee() public view returns (address) {
        return _nominee;
    }

    function nominateOwner(address nominee_) external {
        if (msg.sender != _owner) {
            revert OnlyOwner();
        }
        _nominee = nominee_;
        emit OwnerNominated(_nominee);
    }

    function claimOwner() external {
        if (msg.sender != _nominee) {
            revert OnlyNominee();
        }
        _claimOwner(msg.sender);
    }

    function _claimOwner(address claimer_) internal {
        _owner = claimer_;
        _nominee = address(0);
        emit OwnerClaimed(claimer_);
    }
}

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

Context size (optional):