// 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;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {EIP712 as EIP712Base} from "solady/utils/EIP712.sol";
abstract contract EIP712 is EIP712Base {
// =============================================================
// EIP712
// =============================================================
/// @notice Helper view to read EIP-712 domain separator.
function domainSeperator() external view returns (bytes32 separator) {
return _domainSeparator();
}
/**
* @notice Helper view to hash EIP-712 typed data onchain.
*
* @param structHash EIP-712 typed data hash.
*
* @return digest EIP-712 message digest (bytes32).
*/
function hashTypedData(bytes32 structHash) external view returns (bytes32 digest) {
return _hashTypedData(structHash);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {OwnableRoles} from "solady/auth/OwnableRoles.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {IGuardians} from "../interfaces/abstract/IGuardians.sol";
/**
* @notice Guardians contract to manage guardian addresses.
*
* Guardians can be added and removed by the owner of the contract.
* The only action guardians can perform is to pause the contract.
*
* The thinking here is that, if something goes wrong,
* it is useful to have multiple addresses that can pause the contract,
* while only the owner can unpause the contract.
*/
abstract contract Guardians is IGuardians, OwnableRoles, Pausable {
// =============================================================
// CONSTANTS
// =============================================================
/// @dev use the last role possible so we don't conflict with other roles defined in the implementation contracts.
uint256 public constant GUARDIAN = 1 << 255;
// =============================================================
// PERMISSIONED FUNCTIONS
// =============================================================
/// @inheritdoc IGuardians
function addGuardian(address guardian) external onlyOwner {
_grantRoles(guardian, GUARDIAN);
}
/// @inheritdoc IGuardians
function removeGuardian(address guardian) external onlyOwner {
_removeRoles(guardian, GUARDIAN);
}
/// @inheritdoc IGuardians
function pause() external onlyRolesOrOwner(GUARDIAN) {
_pause();
}
/// @inheritdoc IGuardians
function unpause() external onlyOwner {
_unpause();
}
// =============================================================
// ROLE HELPERS
// =============================================================
/// @inheritdoc IGuardians
function isGuardian(address account) external view returns (bool) {
return hasAnyRole(account, GUARDIAN);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1155.sol)
pragma solidity ^0.8.20;
import {IERC1155} from "../token/ERC1155/IERC1155.sol";
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../token/ERC20/IERC20.sol";
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC721.sol)
pragma solidity ^0.8.20;
import {IERC721} from "../token/ERC721/IERC721.sol";
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IGuardians {
// =============================================================
// EVENTS
// =============================================================
/// @dev Emit an event when owner adds a new guardian address.
event AddGuardian(address indexed guardian);
/// @dev Emit an event when owner removes a guardian address.
event RemoveGuardian(address indexed guardian);
// =============================================================
// ERRORS
// =============================================================
/// @dev Revert if an unauthorized caller calls a protected function.
error OnlyGuardian();
// =============================================================
// PERMISSIONED FUNCTIONS
// =============================================================
/// @notice Add an address as a guardian. Only callable by owner.
function addGuardian(address guardian) external;
/// @notice Remove a guardian. Only callable by owner.
function removeGuardian(address guardian) external;
/// @notice Pause the contract. Only callable by owner or a guardian.
function pause() external;
/// @notice Unpause the contract. Only callable by owner.
function unpause() external;
// =============================================================
// ROLE HELPERS
// =============================================================
/// @notice Check if an address is a guardian.
function isGuardian(address account) external returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IIdRegistry} from "./IIdRegistry.sol";
interface IIdGateway {
// NOTE: None of these events are actually emitted by IdGateway, but because they are emitted when calling IdRegistry.register(),
// they are included here so that IdGateway's ABI includes them.
//
// =============================================================
// EVENTS
// =============================================================
/// @dev Emitted on successful registration of a new ID.
event Registered(
uint256 id, address indexed custody, string username, address indexed operator, address indexed recovery
);
// NOTE: None of these errors are actually thrown by IdGateway, but because they are thrown when calling IdRegistry.register(),
// they are included here so that IdGateway's ABI includes them.
//
// =============================================================
// ERRORS
// =============================================================
//
// These errors are thrown directly by the IdRegistry, when calling IdRegistry.register().
//
/// @dev Revert when the provided custody address has already been registered by another ID.
error CustodyAlreadyRegistered();
/// @dev Revert when the provided operator address has already been registered by another ID.
error OperatorAlreadyRegistered();
/// @dev Revert when the `custody` and `operator` addresses provided in registration are the same.
/// They must be distinct because of how we handle transfers.
error OperatorCannotBeCustody();
//
// These errors are thrown by the UsernameGateway, on username validation, when calling IdRegistry.register().
//
/// @dev Revert when the provided username has already been registered by another ID.
error UsernameAlreadyRegistered();
/// @dev Revert when the provided username is too long.
error UsernameTooLong();
/// @dev Revert when the provided username is too short.
error UsernameTooShort();
/// @dev Revert when the provided username is not url-safe. (ASCII only, no special characters, etc.)
error UsernameContainsInvalidChar();
// =============================================================
// CONSTANTS
// =============================================================
// Disable rule because we include function interfaces for constants/immutables that should stylistically be UPPERCASED.
/* solhint-disable func-name-mixedcase */
/// @notice Contract version specified in the RoyalProtocol version scheme.
function VERSION() external view returns (string memory);
/// @notice EIP712 typehash for Register signatures.
function REGISTER_TYPEHASH() external view returns (bytes32);
// =============================================================
// IMMUTABLES
// =============================================================
/// @notice The RoyalProtocol IdRegistry contract.
function ID_REGISTRY() external view returns (IIdRegistry);
/* solhint-enable func-name-mixedcase */
// =============================================================
// REGISTRATION
// =============================================================
/**
* @notice Register a new RoyalProtocol ID to the caller.
*
* Requirements:
* - The IdGateway contract is not paused.
* - The IdRegistry contract is not paused.
* - The caller must not already have a registered ID.
* - The provided `username` must be valid and unique.
* - If non-zero, the `operator` address must not already have a registered ID.
*
* @param username The username for the account, for client-side human-readable identification.
* @param operator An alias / alternate wallet that can be used as this account. (Can be address(0)).
* @param recovery The address wich can recover the account. Set to address(0) to disable recovery.
*
* @return id The registered account ID.
*/
function register(string calldata username, address operator, address recovery) external returns (uint256 id);
/**
* @notice Register a new RoyalProtocol ID to the provided `custody` address. A signed message from the `custody` address must be provided.
*
* Requirements:
* - The IdGateway contract is not paused.
* - The IdRegistry contract is not paused.
* - The `custody` address must not already have a registered ID.
* - The provided `username` must be valid and unique.
* - If non-zero, the `operator` address must not already have a registered ID.
* - The `deadline` must be in the future.
* - The EIP712 signature `sig` must be valid.
*
* @param custody The custody address for the account. Also the signer of the EIP712 `sig`.
* @param username The username for the account, for client-side human-readable identification.
* @param operator An alias / alternate wallet that can be used as this account. (Can be address(0)).
* @param recovery The address wich can recover the account. Set to address(0) to disable recovery.
* @param deadline The expiration timestamp for the signature.
* @param sig The EIP712 "Register" signature, signed by the custody address.
*
* @return id The registered account ID.
*/
function registerFor(
address custody,
string calldata username,
address operator,
address recovery,
uint256 deadline,
bytes calldata sig
) external returns (uint256 id);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IIdRegistry {
// =============================================================
// STRUCTS
// =============================================================
/**
* @dev Struct used in a getter for user data.
*
* @param id The user's ID.
* @param custody The user's custody address. Controls the ID.
* @param username The user's username.
* @param operator The user's operator address (Optional).
* Can act on behalf of the ID (but not change User data).
* @param recovery The user's recovery address (Optional).
* Can recover the ID to another custody address.
*/
struct User {
uint256 id;
address custody;
string username;
address operator; // Optional
address recovery; // Optional
}
/// @dev Struct argument for admin bulk register function,
// with operator == address(0) for each address, and user-specific recovery addresses.
struct BulkRegisterData {
uint96 id;
address custody;
string username;
address recovery;
}
/// @dev Struct argument for admin bulk register function,
/// with user-specific operator addresses and recovery addresses.
struct BulkRegisterWithOperatorData {
uint96 id;
address custody;
string username;
address operator;
address recovery;
}
/// @dev Struct argument for admin bulk register function,
/// with operator == address(0) for each address, and a default recovery address.
struct BulkRegisterWithDefaultRecoveryData {
uint96 id;
address custody;
string username;
}
/// @dev Struct argument for admin bulk register function,
/// with user-specific operator addresses, and a default recovery address.
struct BulkRegisterWithOperatorAndDefaultRecoveryData {
uint96 id;
address custody;
string username;
address operator;
}
// =============================================================
// EVENTS
// =============================================================
/// @dev Emitted on successful registration of a new ID.
event Registered(
uint256 id, address indexed custody, string username, address indexed operator, address indexed recovery
);
/// @dev Emitted on successful transfer of an ID to another address.
///
/// NOTE: We normally put `id` first in events, but this format is consistent with the ERC721 `Transfer` event,
/// which ensures various tools that know how to parse that event can also parse this one.
event Transfer(address indexed from, address indexed to, uint256 indexed id);
/// @dev Emitted on successful transfer of a username to another ID.
event UsernameTransferred(uint256 indexed fromId, uint256 indexed toId, string username);
/// @dev Emitted on successful change of username.
event UsernameChanged(uint256 indexed id, string newUsername);
/// @dev Emitted on successful change of operator address.
event OperatorAddressChanged(uint256 indexed id, address indexed newOperator);
/// @dev Emitted on successful change of recovery address.
event RecoveryAddressChanged(uint256 indexed id, address indexed newRecovery);
/// @dev Emitted on successful recovery of an ID to another address.
event Recovered(uint256 indexed id, address indexed to);
/// @dev Emitted when the Owner sets the IdGateway to a new value.
event IdGatewaySet(address oldIdGateway, address newIdGateway);
/// @dev Emitted when the Owner freezes the IdGateway dependency.
event IdGatewayFrozen(address idGateway);
/// @dev Emitted when the Owner sets the UsernameGateway to a new value.
event UsernameGatewaySet(address oldUsernameGateway, address newUsernameGateway);
/// @dev Emitted when the Owner freezes the UsernameGateway dependency.
event UsernameGatewayFrozen(address usernameGateway);
/// @dev Emitted when the Owner sets the DelegateRegistry to a new value.
event DelegateRegistrySet(address oldDelegateRegistry, address newDelegateRegistry);
/// @dev Emitted when the Owner freezes the DelegateRegistry dependency.
event DelegateRegistryFrozen(address delegateRegistry);
/// @dev Emitted when the Migrator deletes an ID as part of the migration process.
event AdminReset(uint256 indexed id);
/// @dev Emitted when the Migrator sets the IdCounter to a new value as part of the migration process.
event IdCounterSet(uint256 oldIdCounter, uint256 newIdCounter);
// =============================================================
// ERRORS
// =============================================================
/// @dev Revert when a non-IdGateway address attempts to call a gated function.
error OnlyIdGateway();
/// @dev Revert when a non-UsernameGateway address attempts to call a gated function.
error OnlyUsernameGateway();
/// @dev Revert when the provided custody address has already been registered by another ID.
error CustodyAlreadyRegistered();
/// @dev Revert when the provided operator address has already been registered by another ID.
error OperatorAlreadyRegistered();
/// @dev Revert when the `custody` and `operator` addresses provided in registration are the same.
error OperatorCannotBeCustody();
/// @dev Revert when the relevant address is not the custody address of the ID.
error OnlyCustody();
/// @dev Revert when the relevant address is not the recovery address of the ID.
error OnlyRecovery();
/// @dev Revert when the DelegateRegistry or IdGateway dependency is permanently frozen and cannot be updated.
error Frozen();
/// @dev Revert when the relevant username/ID does not exist.
error HasNoId();
//
// NOTE: None of the following errors are thrown by IdRegistry,
// but they are thrown by usernameGateway.checkUsername(),
// so are included here so that IdRegistry's ABI includes them.
//
// Needed because the bulkRegisterX migration functions live on the IdRegistry contract,
// but they call out to UsernameGateway to validate usernames when validating the registration.
//
/// @dev Revert when the provided username has already been registered by another ID.
error UsernameAlreadyRegistered();
/// @dev Revert when the username is over 16 bytes.
error UsernameTooLong();
/// @dev Revert when the provided username is too short.
error UsernameTooShort();
/// @dev Revert when the username contains invalid characters.
error UsernameContainsInvalidChar();
// =============================================================
// CONSTANTS
// =============================================================
// We write function interfaces for constants that should stylistically be UPPERCASED.
/* solhint-disable func-name-mixedcase */
/// @notice "Name" of the contract. Defined for compatibility with tools like Etherscan that detect ID transfers as token transfers. Intentionally lowercased.
function name() external view returns (string memory);
/// @notice Contract version specified in the RoyalProtocol version scheme.
function VERSION() external view returns (string memory);
/// @notice The EIP712 typehash for Transfer signatures.
function TRANSFER_TYPEHASH() external view returns (bytes32);
/// @notice The EIP712 typehash for TransferAndChangeRecovery signatures.
function TRANSFER_AND_CHANGE_OPERATOR_AND_RECOVERY_TYPEHASH() external view returns (bytes32);
/// @notice The EIP712 typehash for ChangeOperator signatures.
function CHANGE_OPERATOR_TYPEHASH() external view returns (bytes32);
/// @notice The EIP712 typehash for ChangeRecovery signatures.
function CHANGE_RECOVERY_TYPEHASH() external view returns (bytes32);
/* solhint-enable func-name-mixedcase */
// =============================================================
// STORAGE
// =============================================================
/**
* @notice The address of the IdGateway contract - the only address that can register new IDs.
*
* The IdGateway wraps registration logic and provides a swappable abstraction layer around it.
* This may be used in the future for more advanced registration logic.
*/
function idGateway() external view returns (address);
/// @notice Whether the IdGateway dependency is permanently frozen.
function idGatewayFrozen() external view returns (bool);
/**
* @notice The address of the UsernameGateway contract - the only address that can change or transfer usernames.
*
* The UsernameGateway wraps username change logic and provides a swappable abstraction layer around it.
* This may be used in the future for stricter/looser username validation.
*/
function usernameGateway() external view returns (address);
/// @notice Whether the UsernameGateway dependency is permanently frozen.
function usernameGatewayFrozen() external view returns (bool);
/**
* @notice The address of the DelegateRegistry contract. (updatable).
*
* We use this contract for the `canAct() logic to check delegations against the custody and operator.
* It MUST implement the delegate.xyz v2 interface, (and by default, is the delegate.xyz v2 contract).
*
* It is updatable so that we can extend / swap the functionality of `canAct()` in the future if necessary.
*/
function delegateRegistry() external view returns (address);
/// @notice Whether the DelegateRegistry dependency is permanently frozen.
function delegateRegistryFrozen() external view returns (bool);
/// @notice The last RoyalProtocol ID that was issued.
function idCounter() external view returns (uint256);
/// @notice Maps each address (custody/operator) to its associated ID.
function idOf(address wallet) external view returns (uint256);
/// @notice Maps each ID to its associated custody address.
function custodyOf(uint256 id) external view returns (address);
/// @notice Maps each ID to its associated username.
function usernameOf(uint256 id) external view returns (string memory);
/// @notice Maps each (lowercased) username hash to its associated ID.
function idOfUsernameHash(bytes32 usernameHash) external view returns (uint256);
/// @notice Maps each ID to its associated operator address.
function operatorOf(uint256 id) external view returns (address);
/// @notice Maps each ID to its associated recovery address.
function recoveryOf(uint256 id) external view returns (address);
// =============================================================
// REGISTRATION
// =============================================================
/**
* @notice Register a new RoyalProtocol ID to the custody address. Only callable by the IdGateway.
*
* @param custody The custody address for the ID. Controls the ID.
* @param username The username for the ID.
* @param operator The operator address for the ID. Can potentially act on behalf of the ID (but not in the IdRegistry contract itself).
* @param recovery The recovery address for the ID. Can recover the ID to another custody address.
*/
function register(address custody, string calldata username, address operator, address recovery)
external
returns (uint256 id);
// =============================================================
// TRANSFERS
// =============================================================
/// @notice Transfer the caller's ID to another address.
///
/// NOTE: This leaves the `recovery` and `operator` addresses unchanged.
function transfer(address to, uint256 deadline, bytes calldata sig) external;
/// @notice Transfer the provided ID to another address.
///
/// NOTE: This leaves the `recovery` and `operator` addresses unchanged.
function transferFor(
uint256 id,
address to,
uint256 fromDeadline,
bytes calldata fromSig,
uint256 toDeadline,
bytes calldata toSig
) external;
/// @notice Transfer the caller's ID to another address and change the recovery and operator addresses.
function transferAndChangeOperatorAndRecovery(
address to,
address newOperator,
address newRecovery,
uint256 deadline,
bytes calldata sig
) external;
/// @notice Transfer the provided ID to another address and change the recovery and operator addresses.
function transferAndChangeOperatorAndRecoveryFor(
uint256 id,
address to,
address newOperator,
address newRecovery,
uint256 fromDeadline,
bytes calldata fromSig,
uint256 toDeadline,
bytes calldata toSig
) external;
// =============================================================
// TRANSFER USERNAME
// =============================================================
/**
* @notice Transfer the username of the caller's ID to another ID. Only callable by the UsernameGateway.
* Assumes username validation has happened in the UsernameGateway.
*
* @param fromId The ID to transfer the username from.
* @param toId The ID to transfer the username to.
* @param newFromUsername The new username for the `from` ID.
*/
function unsafeTransferUsername(uint256 fromId, uint256 toId, string calldata newFromUsername) external;
// =============================================================
// CHANGE USERNAME
// =============================================================
/**
* @notice Change the username for the provided ID. Only callable by the UsernameGateway.
* Assumes username validation has happened in the UsernameGateway.
*
* @param id The ID to change the username for.
* @param newUsername The new username for the provided `id`.
*/
function unsafeChangeUsername(uint256 id, string calldata newUsername) external;
// =============================================================
// CHANGE OPERATOR
// =============================================================
/// @notice Change the operator address for the caller's ID.
function changeOperator(address newOperator) external;
/// @notice Change the operator address for the provided ID.
function changeOperatorFor(uint256 id, address newOperator, uint256 deadline, bytes calldata sig) external;
// =============================================================
// RECOVERY LOGIC
// =============================================================
/// @notice Change the recovery address for the caller's ID.
function changeRecovery(address newRecovery) external;
/// @notice Change the recovery address for the provided ID.
function changeRecoveryFor(uint256 id, address newRecovery, uint256 deadline, bytes calldata sig) external;
/// @notice Recover the ID of the `from` address ` Called by the recovery address for that ID.
function recover(uint256 id, address to, uint256 deadline, bytes calldata sig) external;
/**
* @notice Recover the ID of the `from` address on behalf of the ID's recovery address.
*
* @param id The account ID to recover.
* @param to The new custody address to recover the ID to.
* @param recoveryDeadline The deadline for the signature from the recovery address.
* @param recoverySig The signature from the recovery address.
* @param toDeadline The deadline for the signature from the new custody address.
* @param toSig The signature from the new custody address.
*/
function recoverFor(
uint256 id,
address to,
uint256 recoveryDeadline,
bytes calldata recoverySig,
uint256 toDeadline,
bytes calldata toSig
) external;
// =============================================================
// PERMISSIONED ACTIONS
// =============================================================
/// @notice Set the IdGateway contract address.
function setIdGateway(address idGateway_) external;
/// @notice Freeze the IdGateway dependency.
function freezeIdGateway() external;
/// @notice Set the UsernameGateway contract address.
function setUsernameGateway(address usernameGateway_) external;
/// @notice Freeze the UsernameGateway dependency.
function freezeUsernameGateway() external;
/// @notice Set the DelegateRegistry contract address.
function setDelegateRegistry(address delegateRegistry_) external;
/// @notice Freeze the DelegateRegistry dependency.
function freezeDelegateRegistry() external;
// =============================================================
// MIGRATION
// =============================================================
/// @notice Register a bunch of IDs as part of a migration.
function bulkRegisterIds(BulkRegisterData[] calldata data) external;
/// @notice Register a bunch of IDs with operators as part of a migration.
function bulkRegisterIdsWithOperator(BulkRegisterWithOperatorData[] calldata data) external;
/// @notice Register a bunch of IDs with a default recovery address as part of a migration.
function bulkRegisterIdsWithDefaultRecovery(BulkRegisterWithDefaultRecoveryData[] calldata data, address recovery)
external;
/// @notice Register a bunch of IDs with operators and a default recovery address as part of a migration.
function bulkRegisterIdsWithOperatorAndDefaultRecovery(
BulkRegisterWithOperatorAndDefaultRecoveryData[] calldata data,
address recovery
) external;
/// @notice Delete the accounts of a bunch of user IDs. (Only callable by the Owner).
function bulkResetIds(uint32[] calldata ids) external;
/**
* @notice Set the idCounter to a new value.
*
* Used in migrations when a bunch of IDs have been reset,
* or to group IDs together for a specific purpose.
*/
function setIdCounter(uint256 counter) external;
// =============================================================
// VIEW FUNCTIONS
// =============================================================
/// @notice Get the user data for the provided ID.
function getUserById(uint256 id) external view returns (User memory);
/// @notice Gets the ID for a given username.
function getIdByUsername(string calldata username) external view returns (uint256 id);
// =============================================================
// CAN ACT
// =============================================================
/**
* @notice Check if an address can take a given action on behalf of an ID.
*
* NOTE: Because the logic here is based on the delegateRegistry, we can swap out
* the delegateRegistry from `delegate.xyz` to our own implementation in the future,
* if we ever want to update/upgrade the logic for `canAct()`.
*
* @param id The RoyalProtocol ID to check.
* @param actor The address attempting to take the action.
* @param contractAddr The address of the contract the action is being taken on.
* @param rights The rights being requested. (Optional).
*/
function canAct(uint256 id, address actor, address contractAddr, bytes32 rights) external view returns (bool);
// =============================================================
// SIGNATURE HELPERS - VIEW FNS
// =============================================================
/// @notice Verifies a signature for a given ID is from the `custody` address.
function verifyCustodySignature(uint256 id, bytes32 digest, bytes calldata sig) external returns (bool isValid);
/// @notice Verifies a signature for a given ID is from the `custody` or `operator` address.
function verifyIdSignature(uint256 id, bytes32 digest, bytes calldata sig) external returns (bool isValid);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface INonces {
// =============================================================
// NONCES
// =============================================================
/**
* @notice Increase caller's nonce, invalidating previous signatures.
*
* @return uint256 The caller's new nonce.
*/
function useNonce() external returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ISignatures {
// =============================================================
// ERRORS
// =============================================================
/// @dev Revert when the signature provided is invalid.
error InvalidSignature();
/// @dev Revert when the block.timestamp is ahead of the signature deadline.
error SignatureExpired();
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IWithdrawable {
// =============================================================
// EVENTS
// =============================================================
/// @dev Emitted when ETH is withdrawn from the contract.
event Withdrawn(address receiver, uint256 amount);
/// @dev Emitted when an ERC20 token is withdrawn from the contract.
event WithdrawnERC20(address receiver, address token, uint256 amount);
/// @dev Emitted when an ERC721 token is withdrawn from the contract.
event WithdrawnERC721(address receiver, address token, uint256 tokenId);
/// @dev Emitted when an ERC1155 token is withdrawn from the contract.
event WithdrawnERC1155(address receiver, address token, uint256 tokenId, uint256 amount);
// =============================================================
// ERRORS
// =============================================================
/// @dev Error emitted when the provided address is the zero address.
error AddressZero();
/// @dev Error emitted when the provided amount to withdraw is less than the contract's balance.
error InsufficientBalance();
/// @dev Error emitted when the withdrawal fails.
error WithdrawalFailed();
// =============================================================
// WITHDRAW ETH
// =============================================================
/**
* @notice Withdraws ETH from the contract.
*
* @param receiver The address to receive the ETH.
*
* @dev Only the contract owner can call this function.
*/
function withdraw(address receiver) external;
// =============================================================
// WITHDRAW ERC20
// =============================================================
/**
* @notice Withdraws `amount` of an ERC20 token from the contract.
*
* @param receiver The address to receive the ERC20 tokens.
* @param token The ERC20 token address.
* @param amount The amount of ERC20 tokens to withdraw.
*
* @dev Only the contract owner can call this function.
*/
function withdrawERC20(address receiver, address token, uint256 amount) external;
/**
* @notice Withdraws all of a given ERC20 token from the contract.
*
* @param receiver The address to receive the ERC20 tokens.
* @param token The ERC20 token address.
*
* @dev Only the contract owner can call this function.
*/
function withdrawAllERC20(address receiver, address token) external;
// =============================================================
// WITHDRAW ERC721
// =============================================================
/**
* @notice Withdraws an ERC721 token from the contract.
*
* @param receiver The address to receive the ERC721 token.
* @param token The ERC721 token address.
* @param tokenId The ERC721 token ID.
*
* @dev Only the contract owner can call this function.
*/
function withdrawERC721(address receiver, address token, uint256 tokenId) external;
// =============================================================
// WITHDRAW ERC1155
// =============================================================
/**
* @notice Withdraws an ERC1155 token from the contract.
*
* @param receiver The address to receive the ERC1155 token.
* @param token The ERC1155 token address.
* @param tokenId The ERC1155 token ID.
* @param amount The amount of ERC1155 tokens to withdraw.
* @param data Additional data with no specified format.
*
* @dev Only the contract owner can call this function.
*/
function withdrawERC1155(address receiver, address token, uint256 tokenId, uint256 amount, bytes calldata data)
external;
/**
* @notice Withdraws all of a given ERC1155 token from the contract.
*
* @param receiver The address to receive the ERC1155 tokens.
* @param token The ERC1155 token address.
* @param tokenId The ERC1155 token ID.
* @param data Additional data with no specified format.
*
* @dev Only the contract owner can call this function.
*/
function withdrawAllERC1155(address receiver, address token, uint256 tokenId, bytes calldata data) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IIdGateway} from "./interfaces/IIdGateway.sol";
import {IIdRegistry} from "./interfaces/IIdRegistry.sol";
import {Withdrawable} from "./abstract/Withdrawable.sol";
import {Signatures} from "./abstract/Signatures.sol";
import {EIP712} from "./abstract/EIP712.sol";
import {Nonces} from "./abstract/Nonces.sol";
/**
* @title RoyalProtocol IdGateway
*
* @notice An abstraction layer around registration for the IdRegistry.
* Having this abstraction layer allows for switching registration logic in the future if needed.
*/
contract IdGateway is IIdGateway, Withdrawable, Signatures, EIP712, Nonces {
// =============================================================
// CONSTANTS
// =============================================================
/* solhint-disable gas-small-strings */
/// @inheritdoc IIdGateway
string public constant VERSION = "2024-07-19";
/// @inheritdoc IIdGateway
bytes32 public constant REGISTER_TYPEHASH = keccak256(
"Register(address custody,string username,address operator,address recovery,uint256 nonce,uint256 deadline)"
);
/* solhint-enable gas-small-strings */
// =============================================================
// IMMUTABLES
// =============================================================
/// @inheritdoc IIdGateway
IIdRegistry public immutable ID_REGISTRY;
// =============================================================
// CONSTRUCTOR
// =============================================================
/**
* @notice Configure IdRegistry and ownership of the contract.
*
* @param idRegistry_ The RoyalProtocol IdRegistry contract address.
* @param initialOwner_ The initial owner of the contract.
*/
constructor(IIdRegistry idRegistry_, address initialOwner_) {
ID_REGISTRY = idRegistry_;
_initializeOwner(initialOwner_);
}
// =============================================================
// EIP712
// =============================================================
/// @dev Configure the EIP712 name and version for the domain separator.
function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) {
name = "RoyalProtocol_IdGateway";
version = "1";
}
// =============================================================
// REGISTRATION
// =============================================================
/// @inheritdoc IIdGateway
function register(string calldata username, address operator, address recovery)
external
override
whenNotPaused
returns (uint256 id)
{
id = ID_REGISTRY.register(msg.sender, username, operator, recovery);
}
/// @inheritdoc IIdGateway
function registerFor(
address custody,
string calldata username,
address operator,
address recovery,
uint256 deadline,
bytes calldata sig
) external override whenNotPaused returns (uint256 id) {
// Reverts if the signature is invalid
_verifyRegisterSig({
custody: custody,
username: username,
operator: operator,
recovery: recovery,
deadline: deadline,
sig: sig
});
id = ID_REGISTRY.register(custody, username, operator, recovery);
}
// =============================================================
// SIGNATURE HELPERS
// =============================================================
/// @dev Verify the EIP712 signature for a registerFor transaction.
function _verifyRegisterSig(
address custody,
string calldata username,
address operator,
address recovery,
uint256 deadline,
bytes calldata sig
) internal {
bytes32 digest = _hashTypedData(
keccak256(
abi.encode(
REGISTER_TYPEHASH,
custody,
keccak256(bytes(username)),
operator,
recovery,
_useNonce(custody),
deadline
)
)
);
_verifySig(digest, custody, deadline, sig);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Nonces.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides tracking nonces for addresses. Nonces will only increment.
*/
abstract contract Nonces {
/**
* @dev The nonce used for an `account` is not the expected current nonce.
*/
error InvalidAccountNonce(address account, uint256 currentNonce);
mapping(address account => uint256) private _nonces;
/**
* @dev Returns the next unused nonce for an address.
*/
function nonces(address owner) public view virtual returns (uint256) {
return _nonces[owner];
}
/**
* @dev Consumes a nonce.
*
* Returns the current value and increments nonce.
*/
function _useNonce(address owner) internal virtual returns (uint256) {
// For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be
// decremented or reset. This guarantees that the nonce never overflows.
unchecked {
// It is important to do x++ and not ++x here.
return _nonces[owner]++;
}
}
/**
* @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`.
*/
function _useCheckedNonce(address owner, uint256 nonce) internal virtual {
uint256 current = _useNonce(owner);
if (nonce != current) {
revert InvalidAccountNonce(owner, current);
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple single owner authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
///
/// @dev Note:
/// This implementation does NOT auto-initialize the owner to `msg.sender`.
/// You MUST call the `_initializeOwner` in the constructor / initializer.
///
/// While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract Ownable {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The caller is not authorized to call the function.
error Unauthorized();
/// @dev The `newOwner` cannot be the zero address.
error NewOwnerIsZeroAddress();
/// @dev The `pendingOwner` does not have a valid handover request.
error NoHandoverRequest();
/// @dev Cannot double-initialize.
error AlreadyInitialized();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ownership is transferred from `oldOwner` to `newOwner`.
/// This event is intentionally kept the same as OpenZeppelin's Ownable to be
/// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),
/// despite it not being as lightweight as a single argument event.
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
/// @dev An ownership handover to `pendingOwner` has been requested.
event OwnershipHandoverRequested(address indexed pendingOwner);
/// @dev The ownership handover to `pendingOwner` has been canceled.
event OwnershipHandoverCanceled(address indexed pendingOwner);
/// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.
uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;
/// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;
/// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The owner slot is given by:
/// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`.
/// It is intentionally chosen to be a high value
/// to avoid collision with lower slots.
/// The choice of manual storage layout is to enable compatibility
/// with both regular and upgradeable contracts.
bytes32 internal constant _OWNER_SLOT =
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927;
/// The ownership handover slot of `newOwner` is given by:
/// ```
/// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))
/// let handoverSlot := keccak256(0x00, 0x20)
/// ```
/// It stores the expiry timestamp of the two-step ownership handover.
uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Override to return true to make `_initializeOwner` prevent double-initialization.
function _guardInitializeOwner() internal pure virtual returns (bool guard) {}
/// @dev Initializes the owner directly without authorization guard.
/// This function must be called upon initialization,
/// regardless of whether the contract is upgradeable or not.
/// This is to enable generalization to both regular and upgradeable contracts,
/// and to save gas in case the initial owner is not the caller.
/// For performance reasons, this function will not check if there
/// is an existing owner.
function _initializeOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
if sload(ownerSlot) {
mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`.
revert(0x1c, 0x04)
}
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
} else {
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(_OWNER_SLOT, newOwner)
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
}
}
/// @dev Sets the owner directly without authorization guard.
function _setOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
}
} else {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, newOwner)
}
}
}
/// @dev Throws if the sender is not the owner.
function _checkOwner() internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// If the caller is not the stored owner, revert.
if iszero(eq(caller(), sload(_OWNER_SLOT))) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Returns how long a two-step ownership handover is valid for in seconds.
/// Override to return a different value if needed.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
return 48 * 3600;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC UPDATE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Allows the owner to transfer the ownership to `newOwner`.
function transferOwnership(address newOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
if iszero(shl(96, newOwner)) {
mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.
revert(0x1c, 0x04)
}
}
_setOwner(newOwner);
}
/// @dev Allows the owner to renounce their ownership.
function renounceOwnership() public payable virtual onlyOwner {
_setOwner(address(0));
}
/// @dev Request a two-step ownership handover to the caller.
/// The request will automatically expire in 48 hours (172800 seconds) by default.
function requestOwnershipHandover() public payable virtual {
unchecked {
uint256 expires = block.timestamp + _ownershipHandoverValidFor();
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to `expires`.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), expires)
// Emit the {OwnershipHandoverRequested} event.
log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
}
}
}
/// @dev Cancels the two-step ownership handover to the caller, if any.
function cancelOwnershipHandover() public payable virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), 0)
// Emit the {OwnershipHandoverCanceled} event.
log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
}
}
/// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
/// Reverts if there is no existing ownership handover requested by `pendingOwner`.
function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
let handoverSlot := keccak256(0x0c, 0x20)
// If the handover does not exist, or has expired.
if gt(timestamp(), sload(handoverSlot)) {
mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.
revert(0x1c, 0x04)
}
// Set the handover slot to 0.
sstore(handoverSlot, 0)
}
_setOwner(pendingOwner);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC READ FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the owner of the contract.
function owner() public view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
result := sload(_OWNER_SLOT)
}
}
/// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
function ownershipHandoverExpiresAt(address pendingOwner)
public
view
virtual
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
// Compute the handover slot.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
// Load the handover slot.
result := sload(keccak256(0x0c, 0x20))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MODIFIERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Marks a function as only callable by the owner.
modifier onlyOwner() virtual {
_checkOwner();
_;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {Ownable} from "./Ownable.sol";
/// @notice Simple single owner and multiroles authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/OwnableRoles.sol)
///
/// @dev Note:
/// This implementation does NOT auto-initialize the owner to `msg.sender`.
/// You MUST call the `_initializeOwner` in the constructor / initializer.
///
/// While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract OwnableRoles is Ownable {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The `user`'s roles is updated to `roles`.
/// Each bit of `roles` represents whether the role is set.
event RolesUpdated(address indexed user, uint256 indexed roles);
/// @dev `keccak256(bytes("RolesUpdated(address,uint256)"))`.
uint256 private constant _ROLES_UPDATED_EVENT_SIGNATURE =
0x715ad5ce61fc9595c7b415289d59cf203f23a94fa06f04af7e489a0a76e1fe26;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The role slot of `user` is given by:
/// ```
/// mstore(0x00, or(shl(96, user), _ROLE_SLOT_SEED))
/// let roleSlot := keccak256(0x00, 0x20)
/// ```
/// This automatically ignores the upper bits of the `user` in case
/// they are not clean, as well as keep the `keccak256` under 32-bytes.
///
/// Note: This is equivalent to `uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))`.
uint256 private constant _ROLE_SLOT_SEED = 0x8b78c6d8;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Overwrite the roles directly without authorization guard.
function _setRoles(address user, uint256 roles) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, user)
// Store the new value.
sstore(keccak256(0x0c, 0x20), roles)
// Emit the {RolesUpdated} event.
log3(0, 0, _ROLES_UPDATED_EVENT_SIGNATURE, shr(96, mload(0x0c)), roles)
}
}
/// @dev Updates the roles directly without authorization guard.
/// If `on` is true, each set bit of `roles` will be turned on,
/// otherwise, each set bit of `roles` will be turned off.
function _updateRoles(address user, uint256 roles, bool on) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, user)
let roleSlot := keccak256(0x0c, 0x20)
// Load the current value.
let current := sload(roleSlot)
// Compute the updated roles if `on` is true.
let updated := or(current, roles)
// Compute the updated roles if `on` is false.
// Use `and` to compute the intersection of `current` and `roles`,
// `xor` it with `current` to flip the bits in the intersection.
if iszero(on) { updated := xor(current, and(current, roles)) }
// Then, store the new value.
sstore(roleSlot, updated)
// Emit the {RolesUpdated} event.
log3(0, 0, _ROLES_UPDATED_EVENT_SIGNATURE, shr(96, mload(0x0c)), updated)
}
}
/// @dev Grants the roles directly without authorization guard.
/// Each bit of `roles` represents the role to turn on.
function _grantRoles(address user, uint256 roles) internal virtual {
_updateRoles(user, roles, true);
}
/// @dev Removes the roles directly without authorization guard.
/// Each bit of `roles` represents the role to turn off.
function _removeRoles(address user, uint256 roles) internal virtual {
_updateRoles(user, roles, false);
}
/// @dev Throws if the sender does not have any of the `roles`.
function _checkRoles(uint256 roles) internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, caller())
// Load the stored value, and if the `and` intersection
// of the value and `roles` is zero, revert.
if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Throws if the sender is not the owner,
/// and does not have any of the `roles`.
/// Checks for ownership first, then lazily checks for roles.
function _checkOwnerOrRoles(uint256 roles) internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// If the caller is not the stored owner.
// Note: `_ROLE_SLOT_SEED` is equal to `_OWNER_SLOT_NOT`.
if iszero(eq(caller(), sload(not(_ROLE_SLOT_SEED)))) {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, caller())
// Load the stored value, and if the `and` intersection
// of the value and `roles` is zero, revert.
if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
}
/// @dev Throws if the sender does not have any of the `roles`,
/// and is not the owner.
/// Checks for roles first, then lazily checks for ownership.
function _checkRolesOrOwner(uint256 roles) internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, caller())
// Load the stored value, and if the `and` intersection
// of the value and `roles` is zero, revert.
if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) {
// If the caller is not the stored owner.
// Note: `_ROLE_SLOT_SEED` is equal to `_OWNER_SLOT_NOT`.
if iszero(eq(caller(), sload(not(_ROLE_SLOT_SEED)))) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
}
/// @dev Convenience function to return a `roles` bitmap from an array of `ordinals`.
/// This is meant for frontends like Etherscan, and is therefore not fully optimized.
/// Not recommended to be called on-chain.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _rolesFromOrdinals(uint8[] memory ordinals) internal pure returns (uint256 roles) {
/// @solidity memory-safe-assembly
assembly {
for { let i := shl(5, mload(ordinals)) } i { i := sub(i, 0x20) } {
// We don't need to mask the values of `ordinals`, as Solidity
// cleans dirty upper bits when storing variables into memory.
roles := or(shl(mload(add(ordinals, i)), 1), roles)
}
}
}
/// @dev Convenience function to return an array of `ordinals` from the `roles` bitmap.
/// This is meant for frontends like Etherscan, and is therefore not fully optimized.
/// Not recommended to be called on-chain.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _ordinalsFromRoles(uint256 roles) internal pure returns (uint8[] memory ordinals) {
/// @solidity memory-safe-assembly
assembly {
// Grab the pointer to the free memory.
ordinals := mload(0x40)
let ptr := add(ordinals, 0x20)
let o := 0
// The absence of lookup tables, De Bruijn, etc., here is intentional for
// smaller bytecode, as this function is not meant to be called on-chain.
for { let t := roles } 1 {} {
mstore(ptr, o)
// `shr` 5 is equivalent to multiplying by 0x20.
// Push back into the ordinals array if the bit is set.
ptr := add(ptr, shl(5, and(t, 1)))
o := add(o, 1)
t := shr(o, roles)
if iszero(t) { break }
}
// Store the length of `ordinals`.
mstore(ordinals, shr(5, sub(ptr, add(ordinals, 0x20))))
// Allocate the memory.
mstore(0x40, ptr)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC UPDATE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Allows the owner to grant `user` `roles`.
/// If the `user` already has a role, then it will be an no-op for the role.
function grantRoles(address user, uint256 roles) public payable virtual onlyOwner {
_grantRoles(user, roles);
}
/// @dev Allows the owner to remove `user` `roles`.
/// If the `user` does not have a role, then it will be an no-op for the role.
function revokeRoles(address user, uint256 roles) public payable virtual onlyOwner {
_removeRoles(user, roles);
}
/// @dev Allow the caller to remove their own roles.
/// If the caller does not have a role, then it will be an no-op for the role.
function renounceRoles(uint256 roles) public payable virtual {
_removeRoles(msg.sender, roles);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC READ FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the roles of `user`.
function rolesOf(address user) public view virtual returns (uint256 roles) {
/// @solidity memory-safe-assembly
assembly {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, user)
// Load the stored value.
roles := sload(keccak256(0x0c, 0x20))
}
}
/// @dev Returns whether `user` has any of `roles`.
function hasAnyRole(address user, uint256 roles) public view virtual returns (bool) {
return rolesOf(user) & roles != 0;
}
/// @dev Returns whether `user` has all of `roles`.
function hasAllRoles(address user, uint256 roles) public view virtual returns (bool) {
return rolesOf(user) & roles == roles;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MODIFIERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Marks a function as only callable by an account with `roles`.
modifier onlyRoles(uint256 roles) virtual {
_checkRoles(roles);
_;
}
/// @dev Marks a function as only callable by the owner or by an account
/// with `roles`. Checks for ownership first, then lazily checks for roles.
modifier onlyOwnerOrRoles(uint256 roles) virtual {
_checkOwnerOrRoles(roles);
_;
}
/// @dev Marks a function as only callable by an account with `roles`
/// or the owner. Checks for roles first, then lazily checks for ownership.
modifier onlyRolesOrOwner(uint256 roles) virtual {
_checkRolesOrOwner(roles);
_;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ROLE CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// IYKYK
uint256 internal constant _ROLE_0 = 1 << 0;
uint256 internal constant _ROLE_1 = 1 << 1;
uint256 internal constant _ROLE_2 = 1 << 2;
uint256 internal constant _ROLE_3 = 1 << 3;
uint256 internal constant _ROLE_4 = 1 << 4;
uint256 internal constant _ROLE_5 = 1 << 5;
uint256 internal constant _ROLE_6 = 1 << 6;
uint256 internal constant _ROLE_7 = 1 << 7;
uint256 internal constant _ROLE_8 = 1 << 8;
uint256 internal constant _ROLE_9 = 1 << 9;
uint256 internal constant _ROLE_10 = 1 << 10;
uint256 internal constant _ROLE_11 = 1 << 11;
uint256 internal constant _ROLE_12 = 1 << 12;
uint256 internal constant _ROLE_13 = 1 << 13;
uint256 internal constant _ROLE_14 = 1 << 14;
uint256 internal constant _ROLE_15 = 1 << 15;
uint256 internal constant _ROLE_16 = 1 << 16;
uint256 internal constant _ROLE_17 = 1 << 17;
uint256 internal constant _ROLE_18 = 1 << 18;
uint256 internal constant _ROLE_19 = 1 << 19;
uint256 internal constant _ROLE_20 = 1 << 20;
uint256 internal constant _ROLE_21 = 1 << 21;
uint256 internal constant _ROLE_22 = 1 << 22;
uint256 internal constant _ROLE_23 = 1 << 23;
uint256 internal constant _ROLE_24 = 1 << 24;
uint256 internal constant _ROLE_25 = 1 << 25;
uint256 internal constant _ROLE_26 = 1 << 26;
uint256 internal constant _ROLE_27 = 1 << 27;
uint256 internal constant _ROLE_28 = 1 << 28;
uint256 internal constant _ROLE_29 = 1 << 29;
uint256 internal constant _ROLE_30 = 1 << 30;
uint256 internal constant _ROLE_31 = 1 << 31;
uint256 internal constant _ROLE_32 = 1 << 32;
uint256 internal constant _ROLE_33 = 1 << 33;
uint256 internal constant _ROLE_34 = 1 << 34;
uint256 internal constant _ROLE_35 = 1 << 35;
uint256 internal constant _ROLE_36 = 1 << 36;
uint256 internal constant _ROLE_37 = 1 << 37;
uint256 internal constant _ROLE_38 = 1 << 38;
uint256 internal constant _ROLE_39 = 1 << 39;
uint256 internal constant _ROLE_40 = 1 << 40;
uint256 internal constant _ROLE_41 = 1 << 41;
uint256 internal constant _ROLE_42 = 1 << 42;
uint256 internal constant _ROLE_43 = 1 << 43;
uint256 internal constant _ROLE_44 = 1 << 44;
uint256 internal constant _ROLE_45 = 1 << 45;
uint256 internal constant _ROLE_46 = 1 << 46;
uint256 internal constant _ROLE_47 = 1 << 47;
uint256 internal constant _ROLE_48 = 1 << 48;
uint256 internal constant _ROLE_49 = 1 << 49;
uint256 internal constant _ROLE_50 = 1 << 50;
uint256 internal constant _ROLE_51 = 1 << 51;
uint256 internal constant _ROLE_52 = 1 << 52;
uint256 internal constant _ROLE_53 = 1 << 53;
uint256 internal constant _ROLE_54 = 1 << 54;
uint256 internal constant _ROLE_55 = 1 << 55;
uint256 internal constant _ROLE_56 = 1 << 56;
uint256 internal constant _ROLE_57 = 1 << 57;
uint256 internal constant _ROLE_58 = 1 << 58;
uint256 internal constant _ROLE_59 = 1 << 59;
uint256 internal constant _ROLE_60 = 1 << 60;
uint256 internal constant _ROLE_61 = 1 << 61;
uint256 internal constant _ROLE_62 = 1 << 62;
uint256 internal constant _ROLE_63 = 1 << 63;
uint256 internal constant _ROLE_64 = 1 << 64;
uint256 internal constant _ROLE_65 = 1 << 65;
uint256 internal constant _ROLE_66 = 1 << 66;
uint256 internal constant _ROLE_67 = 1 << 67;
uint256 internal constant _ROLE_68 = 1 << 68;
uint256 internal constant _ROLE_69 = 1 << 69;
uint256 internal constant _ROLE_70 = 1 << 70;
uint256 internal constant _ROLE_71 = 1 << 71;
uint256 internal constant _ROLE_72 = 1 << 72;
uint256 internal constant _ROLE_73 = 1 << 73;
uint256 internal constant _ROLE_74 = 1 << 74;
uint256 internal constant _ROLE_75 = 1 << 75;
uint256 internal constant _ROLE_76 = 1 << 76;
uint256 internal constant _ROLE_77 = 1 << 77;
uint256 internal constant _ROLE_78 = 1 << 78;
uint256 internal constant _ROLE_79 = 1 << 79;
uint256 internal constant _ROLE_80 = 1 << 80;
uint256 internal constant _ROLE_81 = 1 << 81;
uint256 internal constant _ROLE_82 = 1 << 82;
uint256 internal constant _ROLE_83 = 1 << 83;
uint256 internal constant _ROLE_84 = 1 << 84;
uint256 internal constant _ROLE_85 = 1 << 85;
uint256 internal constant _ROLE_86 = 1 << 86;
uint256 internal constant _ROLE_87 = 1 << 87;
uint256 internal constant _ROLE_88 = 1 << 88;
uint256 internal constant _ROLE_89 = 1 << 89;
uint256 internal constant _ROLE_90 = 1 << 90;
uint256 internal constant _ROLE_91 = 1 << 91;
uint256 internal constant _ROLE_92 = 1 << 92;
uint256 internal constant _ROLE_93 = 1 << 93;
uint256 internal constant _ROLE_94 = 1 << 94;
uint256 internal constant _ROLE_95 = 1 << 95;
uint256 internal constant _ROLE_96 = 1 << 96;
uint256 internal constant _ROLE_97 = 1 << 97;
uint256 internal constant _ROLE_98 = 1 << 98;
uint256 internal constant _ROLE_99 = 1 << 99;
uint256 internal constant _ROLE_100 = 1 << 100;
uint256 internal constant _ROLE_101 = 1 << 101;
uint256 internal constant _ROLE_102 = 1 << 102;
uint256 internal constant _ROLE_103 = 1 << 103;
uint256 internal constant _ROLE_104 = 1 << 104;
uint256 internal constant _ROLE_105 = 1 << 105;
uint256 internal constant _ROLE_106 = 1 << 106;
uint256 internal constant _ROLE_107 = 1 << 107;
uint256 internal constant _ROLE_108 = 1 << 108;
uint256 internal constant _ROLE_109 = 1 << 109;
uint256 internal constant _ROLE_110 = 1 << 110;
uint256 internal constant _ROLE_111 = 1 << 111;
uint256 internal constant _ROLE_112 = 1 << 112;
uint256 internal constant _ROLE_113 = 1 << 113;
uint256 internal constant _ROLE_114 = 1 << 114;
uint256 internal constant _ROLE_115 = 1 << 115;
uint256 internal constant _ROLE_116 = 1 << 116;
uint256 internal constant _ROLE_117 = 1 << 117;
uint256 internal constant _ROLE_118 = 1 << 118;
uint256 internal constant _ROLE_119 = 1 << 119;
uint256 internal constant _ROLE_120 = 1 << 120;
uint256 internal constant _ROLE_121 = 1 << 121;
uint256 internal constant _ROLE_122 = 1 << 122;
uint256 internal constant _ROLE_123 = 1 << 123;
uint256 internal constant _ROLE_124 = 1 << 124;
uint256 internal constant _ROLE_125 = 1 << 125;
uint256 internal constant _ROLE_126 = 1 << 126;
uint256 internal constant _ROLE_127 = 1 << 127;
uint256 internal constant _ROLE_128 = 1 << 128;
uint256 internal constant _ROLE_129 = 1 << 129;
uint256 internal constant _ROLE_130 = 1 << 130;
uint256 internal constant _ROLE_131 = 1 << 131;
uint256 internal constant _ROLE_132 = 1 << 132;
uint256 internal constant _ROLE_133 = 1 << 133;
uint256 internal constant _ROLE_134 = 1 << 134;
uint256 internal constant _ROLE_135 = 1 << 135;
uint256 internal constant _ROLE_136 = 1 << 136;
uint256 internal constant _ROLE_137 = 1 << 137;
uint256 internal constant _ROLE_138 = 1 << 138;
uint256 internal constant _ROLE_139 = 1 << 139;
uint256 internal constant _ROLE_140 = 1 << 140;
uint256 internal constant _ROLE_141 = 1 << 141;
uint256 internal constant _ROLE_142 = 1 << 142;
uint256 internal constant _ROLE_143 = 1 << 143;
uint256 internal constant _ROLE_144 = 1 << 144;
uint256 internal constant _ROLE_145 = 1 << 145;
uint256 internal constant _ROLE_146 = 1 << 146;
uint256 internal constant _ROLE_147 = 1 << 147;
uint256 internal constant _ROLE_148 = 1 << 148;
uint256 internal constant _ROLE_149 = 1 << 149;
uint256 internal constant _ROLE_150 = 1 << 150;
uint256 internal constant _ROLE_151 = 1 << 151;
uint256 internal constant _ROLE_152 = 1 << 152;
uint256 internal constant _ROLE_153 = 1 << 153;
uint256 internal constant _ROLE_154 = 1 << 154;
uint256 internal constant _ROLE_155 = 1 << 155;
uint256 internal constant _ROLE_156 = 1 << 156;
uint256 internal constant _ROLE_157 = 1 << 157;
uint256 internal constant _ROLE_158 = 1 << 158;
uint256 internal constant _ROLE_159 = 1 << 159;
uint256 internal constant _ROLE_160 = 1 << 160;
uint256 internal constant _ROLE_161 = 1 << 161;
uint256 internal constant _ROLE_162 = 1 << 162;
uint256 internal constant _ROLE_163 = 1 << 163;
uint256 internal constant _ROLE_164 = 1 << 164;
uint256 internal constant _ROLE_165 = 1 << 165;
uint256 internal constant _ROLE_166 = 1 << 166;
uint256 internal constant _ROLE_167 = 1 << 167;
uint256 internal constant _ROLE_168 = 1 << 168;
uint256 internal constant _ROLE_169 = 1 << 169;
uint256 internal constant _ROLE_170 = 1 << 170;
uint256 internal constant _ROLE_171 = 1 << 171;
uint256 internal constant _ROLE_172 = 1 << 172;
uint256 internal constant _ROLE_173 = 1 << 173;
uint256 internal constant _ROLE_174 = 1 << 174;
uint256 internal constant _ROLE_175 = 1 << 175;
uint256 internal constant _ROLE_176 = 1 << 176;
uint256 internal constant _ROLE_177 = 1 << 177;
uint256 internal constant _ROLE_178 = 1 << 178;
uint256 internal constant _ROLE_179 = 1 << 179;
uint256 internal constant _ROLE_180 = 1 << 180;
uint256 internal constant _ROLE_181 = 1 << 181;
uint256 internal constant _ROLE_182 = 1 << 182;
uint256 internal constant _ROLE_183 = 1 << 183;
uint256 internal constant _ROLE_184 = 1 << 184;
uint256 internal constant _ROLE_185 = 1 << 185;
uint256 internal constant _ROLE_186 = 1 << 186;
uint256 internal constant _ROLE_187 = 1 << 187;
uint256 internal constant _ROLE_188 = 1 << 188;
uint256 internal constant _ROLE_189 = 1 << 189;
uint256 internal constant _ROLE_190 = 1 << 190;
uint256 internal constant _ROLE_191 = 1 << 191;
uint256 internal constant _ROLE_192 = 1 << 192;
uint256 internal constant _ROLE_193 = 1 << 193;
uint256 internal constant _ROLE_194 = 1 << 194;
uint256 internal constant _ROLE_195 = 1 << 195;
uint256 internal constant _ROLE_196 = 1 << 196;
uint256 internal constant _ROLE_197 = 1 << 197;
uint256 internal constant _ROLE_198 = 1 << 198;
uint256 internal constant _ROLE_199 = 1 << 199;
uint256 internal constant _ROLE_200 = 1 << 200;
uint256 internal constant _ROLE_201 = 1 << 201;
uint256 internal constant _ROLE_202 = 1 << 202;
uint256 internal constant _ROLE_203 = 1 << 203;
uint256 internal constant _ROLE_204 = 1 << 204;
uint256 internal constant _ROLE_205 = 1 << 205;
uint256 internal constant _ROLE_206 = 1 << 206;
uint256 internal constant _ROLE_207 = 1 << 207;
uint256 internal constant _ROLE_208 = 1 << 208;
uint256 internal constant _ROLE_209 = 1 << 209;
uint256 internal constant _ROLE_210 = 1 << 210;
uint256 internal constant _ROLE_211 = 1 << 211;
uint256 internal constant _ROLE_212 = 1 << 212;
uint256 internal constant _ROLE_213 = 1 << 213;
uint256 internal constant _ROLE_214 = 1 << 214;
uint256 internal constant _ROLE_215 = 1 << 215;
uint256 internal constant _ROLE_216 = 1 << 216;
uint256 internal constant _ROLE_217 = 1 << 217;
uint256 internal constant _ROLE_218 = 1 << 218;
uint256 internal constant _ROLE_219 = 1 << 219;
uint256 internal constant _ROLE_220 = 1 << 220;
uint256 internal constant _ROLE_221 = 1 << 221;
uint256 internal constant _ROLE_222 = 1 << 222;
uint256 internal constant _ROLE_223 = 1 << 223;
uint256 internal constant _ROLE_224 = 1 << 224;
uint256 internal constant _ROLE_225 = 1 << 225;
uint256 internal constant _ROLE_226 = 1 << 226;
uint256 internal constant _ROLE_227 = 1 << 227;
uint256 internal constant _ROLE_228 = 1 << 228;
uint256 internal constant _ROLE_229 = 1 << 229;
uint256 internal constant _ROLE_230 = 1 << 230;
uint256 internal constant _ROLE_231 = 1 << 231;
uint256 internal constant _ROLE_232 = 1 << 232;
uint256 internal constant _ROLE_233 = 1 << 233;
uint256 internal constant _ROLE_234 = 1 << 234;
uint256 internal constant _ROLE_235 = 1 << 235;
uint256 internal constant _ROLE_236 = 1 << 236;
uint256 internal constant _ROLE_237 = 1 << 237;
uint256 internal constant _ROLE_238 = 1 << 238;
uint256 internal constant _ROLE_239 = 1 << 239;
uint256 internal constant _ROLE_240 = 1 << 240;
uint256 internal constant _ROLE_241 = 1 << 241;
uint256 internal constant _ROLE_242 = 1 << 242;
uint256 internal constant _ROLE_243 = 1 << 243;
uint256 internal constant _ROLE_244 = 1 << 244;
uint256 internal constant _ROLE_245 = 1 << 245;
uint256 internal constant _ROLE_246 = 1 << 246;
uint256 internal constant _ROLE_247 = 1 << 247;
uint256 internal constant _ROLE_248 = 1 << 248;
uint256 internal constant _ROLE_249 = 1 << 249;
uint256 internal constant _ROLE_250 = 1 << 250;
uint256 internal constant _ROLE_251 = 1 << 251;
uint256 internal constant _ROLE_252 = 1 << 252;
uint256 internal constant _ROLE_253 = 1 << 253;
uint256 internal constant _ROLE_254 = 1 << 254;
uint256 internal constant _ROLE_255 = 1 << 255;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
bool private _paused;
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Initializes the contract in unpaused state.
*/
constructor() {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Signature verification helper that supports both ECDSA signatures from EOAs
/// and ERC1271 signatures from smart contract wallets like Argent and Gnosis safe.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SignatureCheckerLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol)
///
/// @dev Note:
/// - The signature checking functions use the ecrecover precompile (0x1).
/// - The `bytes memory signature` variants use the identity precompile (0x4)
/// to copy memory internally.
/// - Unlike ECDSA signatures, contract signatures are revocable.
/// - As of Solady version 0.0.134, all `bytes signature` variants accept both
/// regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures.
/// See: https://eips.ethereum.org/EIPS/eip-2098
/// This is for calldata efficiency on smart accounts prevalent on L2s.
///
/// WARNING! Do NOT use signatures as unique identifiers:
/// - Use a nonce in the digest to prevent replay attacks on the same contract.
/// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts.
/// EIP-712 also enables readable signing of typed data for better user safety.
/// This implementation does NOT check if a signature is non-malleable.
library SignatureCheckerLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SIGNATURE CHECKING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns whether `signature` is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x40, mload(add(signature, 0x20))) // `r`.
if eq(mload(signature), 64) {
let vs := mload(add(signature, 0x40))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
if eq(mload(signature), 65) {
mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`.
mstore(0x60, mload(add(signature, 0x40))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
// Copy the `signature` over.
let n := add(0x20, mload(signature))
pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n))
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(returndatasize(), 0x44), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
break
}
}
}
/// @dev Returns whether `signature` is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNowCalldata(address signer, bytes32 hash, bytes calldata signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
if eq(signature.length, 64) {
let vs := calldataload(add(signature.offset, 0x20))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, calldataload(signature.offset)) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
if eq(signature.length, 65) {
mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`.
calldatacopy(0x40, signature.offset, 0x40) // `r`, `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), signature.length)
// Copy the `signature` over.
calldatacopy(add(m, 0x64), signature.offset, signature.length)
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(signature.length, 0x64), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
break
}
}
}
/// @dev Returns whether the signature (`r`, `vs`) is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, r) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), mload(0x60)) // `s`.
mstore8(add(m, 0xa4), mload(0x20)) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
}
/// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x20, and(v, 0xff)) // `v`.
mstore(0x40, r) // `r`.
mstore(0x60, s) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), s) // `s`.
mstore8(add(m, 0xa4), v) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC1271 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// Note: These ERC1271 operations do NOT have an ECDSA fallback.
// These functions are intended to be used with the regular `isValidSignatureNow` functions
// or other signature verification functions (e.g. P256).
/// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract.
function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes memory signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
// Copy the `signature` over.
let n := add(0x20, mload(signature))
pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n))
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(returndatasize(), 0x44), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract.
function isValidERC1271SignatureNowCalldata(
address signer,
bytes32 hash,
bytes calldata signature
) internal view returns (bool isValid) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), signature.length)
// Copy the `signature` over.
calldatacopy(add(m, 0x64), signature.offset, signature.length)
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(signature.length, 0x64), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/// @dev Returns whether the signature (`r`, `vs`) is valid for `hash`
/// for an ERC1271 `signer` contract.
function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), shr(1, shl(1, vs))) // `s`.
mstore8(add(m, 0xa4), add(shr(255, vs), 27)) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `hash`
/// for an ERC1271 `signer` contract.
function isValidERC1271SignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), s) // `s`.
mstore8(add(m, 0xa4), v) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC6492 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// Note: These ERC6492 operations do NOT have an ECDSA fallback.
// These functions are intended to be used with the regular `isValidSignatureNow` functions
// or other signature verification functions (e.g. P256).
// The calldata variants are excluded for brevity.
/// @dev Returns whether `signature` is valid for `hash`.
/// If the signature is postfixed with the ERC6492 magic number, it will attempt to
/// deploy / prepare the `signer` smart account before doing a regular ERC1271 check.
/// Note: This function is NOT reentrancy safe.
function isValidERC6492SignatureNowAllowSideEffects(
address signer,
bytes32 hash,
bytes memory signature
) internal returns (bool isValid) {
/// @solidity memory-safe-assembly
assembly {
function callIsValidSignature(signer_, hash_, signature_) -> _isValid {
let m_ := mload(0x40)
let f_ := shl(224, 0x1626ba7e)
mstore(m_, f_) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m_, 0x04), hash_)
let d_ := add(m_, 0x24)
mstore(d_, 0x40) // The offset of the `signature` in the calldata.
let n_ := add(0x20, mload(signature_))
pop(staticcall(gas(), 4, signature_, n_, add(m_, 0x44), n_))
_isValid :=
and(
eq(mload(d_), f_),
staticcall(gas(), signer_, m_, add(returndatasize(), 0x44), d_, 0x20)
)
}
for { let n := mload(signature) } 1 {} {
if iszero(eq(mload(add(signature, n)), mul(0x6492, div(not(isValid), 0xffff)))) {
isValid := callIsValidSignature(signer, hash, signature)
break
}
let o := add(signature, 0x20) // Signature bytes.
let d := add(o, mload(add(o, 0x20))) // Factory calldata.
if iszero(extcodesize(signer)) {
if iszero(call(gas(), mload(o), 0, add(d, 0x20), mload(d), codesize(), 0x00)) {
break
}
}
let s := add(o, mload(add(o, 0x40))) // Inner signature.
isValid := callIsValidSignature(signer, hash, s)
if iszero(isValid) {
if call(gas(), mload(o), 0, add(d, 0x20), mload(d), codesize(), 0x00) {
isValid := callIsValidSignature(signer, hash, s)
}
}
break
}
}
}
/// @dev Returns whether `signature` is valid for `hash`.
/// If the signature is postfixed with the ERC6492 magic number, it will attempt
/// to use a reverting verifier to deploy / prepare the `signer` smart account
/// and do a `isValidSignature` check via the reverting verifier.
/// Note: This function is reentrancy safe.
/// The reverting verifier must be deployed.
/// Otherwise, the function will return false if `signer` is not yet deployed / prepared.
/// See: https://gist.github.com/Vectorized/846a474c855eee9e441506676800a9ad
function isValidERC6492SignatureNow(address signer, bytes32 hash, bytes memory signature)
internal
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
function callIsValidSignature(signer_, hash_, signature_) -> _isValid {
let m_ := mload(0x40)
let f_ := shl(224, 0x1626ba7e)
mstore(m_, f_) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m_, 0x04), hash_)
let d_ := add(m_, 0x24)
mstore(d_, 0x40) // The offset of the `signature` in the calldata.
let n_ := add(0x20, mload(signature_))
pop(staticcall(gas(), 4, signature_, n_, add(m_, 0x44), n_))
_isValid :=
and(
eq(mload(d_), f_),
staticcall(gas(), signer_, m_, add(returndatasize(), 0x44), d_, 0x20)
)
}
for { let n := mload(signature) } 1 {} {
if iszero(eq(mload(add(signature, n)), mul(0x6492, div(not(isValid), 0xffff)))) {
isValid := callIsValidSignature(signer, hash, signature)
break
}
if extcodesize(signer) {
let o := add(signature, 0x20) // Signature bytes.
isValid := callIsValidSignature(signer, hash, add(o, mload(add(o, 0x40))))
if isValid { break }
}
let m := mload(0x40)
mstore(m, signer)
mstore(add(m, 0x20), hash)
let willBeZeroIfRevertingVerifierExists :=
call(
gas(), // Remaining gas.
0x00007bd799e4A591FeA53f8A8a3E9f931626Ba7e, // Reverting verifier.
0, // Send zero ETH.
m, // Start of memory.
add(returndatasize(), 0x40), // Length of calldata in memory.
staticcall(gas(), 4, add(signature, 0x20), n, add(m, 0x40), n), // 1.
0x00 // Length of returndata to write.
)
isValid := gt(returndatasize(), willBeZeroIfRevertingVerifierExists)
break
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HASHING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an Ethereum Signed Message, created from a `hash`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
/// JSON-RPC method as part of EIP-191.
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, hash) // Store into scratch space for keccak256.
mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes.
result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`.
}
}
/// @dev Returns an Ethereum Signed Message, created from `s`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
/// JSON-RPC method as part of EIP-191.
/// Note: Supports lengths of `s` up to 999999 bytes.
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let sLength := mload(s)
let o := 0x20
mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded.
mstore(0x00, 0x00)
// Convert the `s.length` to ASCII decimal representation: `base10(s.length)`.
for { let temp := sLength } 1 {} {
o := sub(o, 1)
mstore8(o, add(48, mod(temp, 10)))
temp := div(temp, 10)
if iszero(temp) { break }
}
let n := sub(0x3a, o) // Header length: `26 + 32 - o`.
// Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20))
mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header.
result := keccak256(add(s, sub(0x20, n)), add(n, sLength))
mstore(s, sLength) // Restore the length.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EMPTY CALLDATA HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an empty calldata bytes.
function emptySignature() internal pure returns (bytes calldata signature) {
/// @solidity memory-safe-assembly
assembly {
signature.length := 0
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {SignatureCheckerLib} from "solady/utils/SignatureCheckerLib.sol";
import {ISignatures} from "../interfaces/abstract/ISignatures.sol";
/**
* @dev Helper contract to handle signatures.
*
* Adds logic around expiring signatures based on the associated deadline.
*/
abstract contract Signatures is ISignatures {
// =============================================================
// SIGNATURE HELPERS
// =============================================================
/**
* @notice Verify a signature against a digest.
*
* Requirements:
* - The signature must not be expired (block.timestamp <= deadline).
* - The signature must be valid.
*
* Throws errors on invalid signatures, rather than returning a boolean.
*
* @param digest The digest to verify.
* @param signer The expected signer of the digest.
* @param deadline The deadline for the signature to be valid.
* @param sig The signature to verify.
*/
function _verifySig(bytes32 digest, address signer, uint256 deadline, bytes calldata sig) internal view {
if (block.timestamp > deadline) revert SignatureExpired();
if (!SignatureCheckerLib.isValidSignatureNowCalldata(signer, digest, sig)) {
revert InvalidSignature();
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IWithdrawable} from "../interfaces/abstract/IWithdrawable.sol";
import {Guardians} from "../abstract/Guardians.sol";
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/interfaces/IERC721.sol";
import {IERC1155} from "@openzeppelin/contracts/interfaces/IERC1155.sol";
/// @notice Abstract contract that provides the ability to withdraw ETH, ERC20, ERC721, and ERC1155 tokens.
///
/// @dev - The implementing contract will need to call _initializeOwner() in either the constructor or an initializer,
/// since all the withdrawal functions are restricted to the owner.
abstract contract Withdrawable is IWithdrawable, Guardians {
// =============================================================
// WITHDRAW ETH
// =============================================================
/// @inheritdoc IWithdrawable
function withdraw(address receiver) external onlyOwner {
if (receiver == address(0)) revert AddressZero();
emit Withdrawn(receiver, address(this).balance);
(bool sent,) = payable(receiver).call{value: address(this).balance}("");
if (!sent) revert WithdrawalFailed();
}
// =============================================================
// WITHDRAW ERC20
// =============================================================
/// @inheritdoc IWithdrawable
function withdrawERC20(address receiver, address token, uint256 amount) external onlyOwner {
_withdrawERC20(receiver, token, amount);
}
/// @inheritdoc IWithdrawable
function withdrawAllERC20(address receiver, address token) external onlyOwner {
uint256 totalBalance = IERC20(token).balanceOf(address(this));
_withdrawERC20(receiver, token, totalBalance);
}
/// @dev Withdraws `amount` of an ERC20 token from the contract.
function _withdrawERC20(address receiver, address token, uint256 amount) internal {
if (receiver == address(0)) revert AddressZero();
if (token == address(0)) revert AddressZero();
IERC20 erc20 = IERC20(token);
if (erc20.balanceOf(address(this)) < amount) revert InsufficientBalance();
emit WithdrawnERC20(receiver, token, amount);
bool success = erc20.transfer(receiver, amount);
if (!success) revert WithdrawalFailed();
}
// =============================================================
// WITHDRAW ERC721
// =============================================================
/// @inheritdoc IWithdrawable
function withdrawERC721(address receiver, address token, uint256 tokenId) external onlyOwner {
if (receiver == address(0)) revert AddressZero();
if (token == address(0)) revert AddressZero();
emit WithdrawnERC721(receiver, token, tokenId);
IERC721 erc721 = IERC721(token);
erc721.transferFrom(address(this), receiver, tokenId);
}
// =============================================================
// WITHDRAW ERC1155
// =============================================================
/// @inheritdoc IWithdrawable
function withdrawERC1155(address receiver, address token, uint256 tokenId, uint256 amount, bytes calldata data)
external
onlyOwner
{
_withdrawERC1155({receiver: receiver, token: token, tokenId: tokenId, amount: amount, data: data});
}
/// @inheritdoc IWithdrawable
function withdrawAllERC1155(address receiver, address token, uint256 tokenId, bytes calldata data)
external
onlyOwner
{
uint256 totalBalance = IERC1155(token).balanceOf(address(this), tokenId);
_withdrawERC1155({receiver: receiver, token: token, tokenId: tokenId, amount: totalBalance, data: data});
}
/// @dev Withdraws `amount` of an ERC1155 token from the contract.
function _withdrawERC1155(address receiver, address token, uint256 tokenId, uint256 amount, bytes calldata data)
internal
{
if (receiver == address(0)) revert AddressZero();
if (token == address(0)) revert AddressZero();
IERC1155 erc1155 = IERC1155(token);
if (erc1155.balanceOf(address(this), tokenId) < amount) revert InsufficientBalance();
emit WithdrawnERC1155(receiver, token, tokenId, amount);
erc1155.safeTransferFrom({from: address(this), to: receiver, id: tokenId, value: amount, data: data});
}
}
{
"compilationTarget": {
"src/core/IdGateway.sol": "IdGateway"
},
"evmVersion": "cancun",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
":delegate-registry/=lib/delegate-registry/src/",
":ds-test/=lib/delegate-registry/lib/forge-std/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
":forge-std/=lib/forge-std/src/",
":murky/=lib/delegate-registry/lib/murky/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":openzeppelin/=lib/delegate-registry/lib/openzeppelin-contracts/contracts/",
":solady/=lib/solady/src/"
],
"viaIR": true
}
[{"inputs":[{"internalType":"contract IIdRegistry","name":"idRegistry_","type":"address"},{"internalType":"address","name":"initialOwner_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AddressZero","type":"error"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"CustodyAlreadyRegistered","type":"error"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[],"name":"InsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"currentNonce","type":"uint256"}],"name":"InvalidAccountNonce","type":"error"},{"inputs":[],"name":"InvalidSignature","type":"error"},{"inputs":[],"name":"NewOwnerIsZeroAddress","type":"error"},{"inputs":[],"name":"NoHandoverRequest","type":"error"},{"inputs":[],"name":"OnlyGuardian","type":"error"},{"inputs":[],"name":"OperatorAlreadyRegistered","type":"error"},{"inputs":[],"name":"OperatorCannotBeCustody","type":"error"},{"inputs":[],"name":"SignatureExpired","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"UsernameAlreadyRegistered","type":"error"},{"inputs":[],"name":"UsernameContainsInvalidChar","type":"error"},{"inputs":[],"name":"UsernameTooLong","type":"error"},{"inputs":[],"name":"UsernameTooShort","type":"error"},{"inputs":[],"name":"WithdrawalFailed","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"guardian","type":"address"}],"name":"AddGuardian","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"custody","type":"address"},{"indexed":false,"internalType":"string","name":"username","type":"string"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"recovery","type":"address"}],"name":"Registered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"guardian","type":"address"}],"name":"RemoveGuardian","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"roles","type":"uint256"}],"name":"RolesUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"WithdrawnERC1155","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"WithdrawnERC20","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"WithdrawnERC721","type":"event"},{"inputs":[],"name":"GUARDIAN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ID_REGISTRY","outputs":[{"internalType":"contract IIdRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REGISTER_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"guardian","type":"address"}],"name":"addGuardian","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"cancelOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"completeOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"domainSeperator","outputs":[{"internalType":"bytes32","name":"separator","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"roles","type":"uint256"}],"name":"grantRoles","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"roles","type":"uint256"}],"name":"hasAllRoles","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"roles","type":"uint256"}],"name":"hasAnyRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"structHash","type":"bytes32"}],"name":"hashTypedData","outputs":[{"internalType":"bytes32","name":"digest","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"isGuardian","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"result","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"ownershipHandoverExpiresAt","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"username","type":"string"},{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"recovery","type":"address"}],"name":"register","outputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"custody","type":"address"},{"internalType":"string","name":"username","type":"string"},{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"recovery","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"registerFor","outputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"guardian","type":"address"}],"name":"removeGuardian","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"roles","type":"uint256"}],"name":"renounceRoles","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"requestOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"roles","type":"uint256"}],"name":"revokeRoles","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"rolesOf","outputs":[{"internalType":"uint256","name":"roles","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"useNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"withdrawAllERC1155","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"token","type":"address"}],"name":"withdrawAllERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"withdrawERC1155","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"withdrawERC721","outputs":[],"stateMutability":"nonpayable","type":"function"}]