// 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.4;
/// @notice Contract for EIP-712 typed structured data hashing and signing.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/EIP712.sol)
/// @author Modified from Solbase (https://github.com/Sol-DAO/solbase/blob/main/src/utils/EIP712.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol)
///
/// @dev Note, this implementation:
/// - Uses `address(this)` for the `verifyingContract` field.
/// - Does NOT use the optional EIP-712 salt.
/// - Does NOT use any EIP-712 extensions.
/// This is for simplicity and to save gas.
/// If you need to customize, please fork / modify accordingly.
abstract contract EIP712 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS AND IMMUTABLES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
bytes32 internal constant _DOMAIN_TYPEHASH =
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
uint256 private immutable _cachedThis;
uint256 private immutable _cachedChainId;
bytes32 private immutable _cachedNameHash;
bytes32 private immutable _cachedVersionHash;
bytes32 private immutable _cachedDomainSeparator;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTRUCTOR */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Cache the hashes for cheaper runtime gas costs.
/// In the case of upgradeable contracts (i.e. proxies),
/// or if the chain id changes due to a hard fork,
/// the domain separator will be seamlessly calculated on-the-fly.
constructor() {
_cachedThis = uint256(uint160(address(this)));
_cachedChainId = block.chainid;
string memory name;
string memory version;
if (!_domainNameAndVersionMayChange()) (name, version) = _domainNameAndVersion();
bytes32 nameHash = _domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(name));
bytes32 versionHash =
_domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(version));
_cachedNameHash = nameHash;
_cachedVersionHash = versionHash;
bytes32 separator;
if (!_domainNameAndVersionMayChange()) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Load the free memory pointer.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), nameHash)
mstore(add(m, 0x40), versionHash)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
separator := keccak256(m, 0xa0)
}
}
_cachedDomainSeparator = separator;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* FUNCTIONS TO OVERRIDE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Please override this function to return the domain name and version.
/// ```
/// function _domainNameAndVersion()
/// internal
/// pure
/// virtual
/// returns (string memory name, string memory version)
/// {
/// name = "Solady";
/// version = "1";
/// }
/// ```
///
/// Note: If the returned result may change after the contract has been deployed,
/// you must override `_domainNameAndVersionMayChange()` to return true.
function _domainNameAndVersion()
internal
view
virtual
returns (string memory name, string memory version);
/// @dev Returns if `_domainNameAndVersion()` may change
/// after the contract has been deployed (i.e. after the constructor).
/// Default: false.
function _domainNameAndVersionMayChange() internal pure virtual returns (bool result) {}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HASHING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the EIP-712 domain separator.
function _domainSeparator() internal view virtual returns (bytes32 separator) {
if (_domainNameAndVersionMayChange()) {
separator = _buildDomainSeparator();
} else {
separator = _cachedDomainSeparator;
if (_cachedDomainSeparatorInvalidated()) separator = _buildDomainSeparator();
}
}
/// @dev Returns the hash of the fully encoded EIP-712 message for this domain,
/// given `structHash`, as defined in
/// https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct.
///
/// The hash can be used together with {ECDSA-recover} to obtain the signer of a message:
/// ```
/// bytes32 digest = _hashTypedData(keccak256(abi.encode(
/// keccak256("Mail(address to,string contents)"),
/// mailTo,
/// keccak256(bytes(mailContents))
/// )));
/// address signer = ECDSA.recover(digest, signature);
/// ```
function _hashTypedData(bytes32 structHash) internal view virtual returns (bytes32 digest) {
// We will use `digest` to store the domain separator to save a bit of gas.
if (_domainNameAndVersionMayChange()) {
digest = _buildDomainSeparator();
} else {
digest = _cachedDomainSeparator;
if (_cachedDomainSeparatorInvalidated()) digest = _buildDomainSeparator();
}
/// @solidity memory-safe-assembly
assembly {
// Compute the digest.
mstore(0x00, 0x1901000000000000) // Store "\x19\x01".
mstore(0x1a, digest) // Store the domain separator.
mstore(0x3a, structHash) // Store the struct hash.
digest := keccak256(0x18, 0x42)
// Restore the part of the free memory slot that was overwritten.
mstore(0x3a, 0)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EIP-5267 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev See: https://eips.ethereum.org/EIPS/eip-5267
function eip712Domain()
public
view
virtual
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
fields = hex"0f"; // `0b01111`.
(name, version) = _domainNameAndVersion();
chainId = block.chainid;
verifyingContract = address(this);
salt = salt; // `bytes32(0)`.
extensions = extensions; // `new uint256[](0)`.
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PRIVATE HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the EIP-712 domain separator.
function _buildDomainSeparator() private view returns (bytes32 separator) {
// We will use `separator` to store the name hash to save a bit of gas.
bytes32 versionHash;
if (_domainNameAndVersionMayChange()) {
(string memory name, string memory version) = _domainNameAndVersion();
separator = keccak256(bytes(name));
versionHash = keccak256(bytes(version));
} else {
separator = _cachedNameHash;
versionHash = _cachedVersionHash;
}
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Load the free memory pointer.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), separator) // Name hash.
mstore(add(m, 0x40), versionHash)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
separator := keccak256(m, 0xa0)
}
}
/// @dev Returns if the cached domain separator has been invalidated.
function _cachedDomainSeparatorInvalidated() private view returns (bool result) {
uint256 cachedChainId = _cachedChainId;
uint256 cachedThis = _cachedThis;
/// @solidity memory-safe-assembly
assembly {
result := iszero(and(eq(chainid(), cachedChainId), eq(address(), cachedThis)))
}
}
}
// 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);
}
}
/* solhint-disable */
// This file is the interface for delegate.xyz
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.13;
/**
* @title IDelegateRegistry
* @custom:version 2.0
* @custom:author foobar (0xfoobar)
* @notice A standalone immutable registry storing delegated permissions from one address to another
*/
interface IDelegateRegistry {
/// @notice Delegation type, NONE is used when a delegation does not exist or is revoked
enum DelegationType {
NONE,
ALL,
CONTRACT,
ERC721,
ERC20,
ERC1155
}
/// @notice Struct for returning delegations
struct Delegation {
DelegationType type_;
address to;
address from;
bytes32 rights;
address contract_;
uint256 tokenId;
uint256 amount;
}
/// @notice Emitted when an address delegates or revokes rights for their entire wallet
event DelegateAll(address indexed from, address indexed to, bytes32 rights, bool enable);
/// @notice Emitted when an address delegates or revokes rights for a contract address
event DelegateContract(
address indexed from, address indexed to, address indexed contract_, bytes32 rights, bool enable
);
/// @notice Emitted when an address delegates or revokes rights for an ERC721 tokenId
event DelegateERC721(
address indexed from,
address indexed to,
address indexed contract_,
uint256 tokenId,
bytes32 rights,
bool enable
);
/// @notice Emitted when an address delegates or revokes rights for an amount of ERC20 tokens
event DelegateERC20(
address indexed from, address indexed to, address indexed contract_, bytes32 rights, uint256 amount
);
/// @notice Emitted when an address delegates or revokes rights for an amount of an ERC1155 tokenId
event DelegateERC1155(
address indexed from,
address indexed to,
address indexed contract_,
uint256 tokenId,
bytes32 rights,
uint256 amount
);
/// @notice Thrown if multicall calldata is malformed
error MulticallFailed();
/**
* ----------- WRITE -----------
*/
/**
* @notice Call multiple functions in the current contract and return the data from all of them if they all succeed
* @param data The encoded function data for each of the calls to make to this contract
* @return results The results from each of the calls passed in via data
*/
function multicall(bytes[] calldata data) external payable returns (bytes[] memory results);
/**
* @notice Allow the delegate to act on behalf of `msg.sender` for all contracts
* @param to The address to act as delegate
* @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
* @param enable Whether to enable or disable this delegation, true delegates and false revokes
* @return delegationHash The unique identifier of the delegation
*/
function delegateAll(address to, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash);
/**
* @notice Allow the delegate to act on behalf of `msg.sender` for a specific contract
* @param to The address to act as delegate
* @param contract_ The contract whose rights are being delegated
* @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
* @param enable Whether to enable or disable this delegation, true delegates and false revokes
* @return delegationHash The unique identifier of the delegation
*/
function delegateContract(address to, address contract_, bytes32 rights, bool enable)
external
payable
returns (bytes32 delegationHash);
/**
* @notice Allow the delegate to act on behalf of `msg.sender` for a specific ERC721 token
* @param to The address to act as delegate
* @param contract_ The contract whose rights are being delegated
* @param tokenId The token id to delegate
* @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
* @param enable Whether to enable or disable this delegation, true delegates and false revokes
* @return delegationHash The unique identifier of the delegation
*/
function delegateERC721(address to, address contract_, uint256 tokenId, bytes32 rights, bool enable)
external
payable
returns (bytes32 delegationHash);
/**
* @notice Allow the delegate to act on behalf of `msg.sender` for a specific amount of ERC20 tokens
* @dev The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound)
* @param to The address to act as delegate
* @param contract_ The address for the fungible token contract
* @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
* @param amount The amount to delegate, > 0 delegates and 0 revokes
* @return delegationHash The unique identifier of the delegation
*/
function delegateERC20(address to, address contract_, bytes32 rights, uint256 amount)
external
payable
returns (bytes32 delegationHash);
/**
* @notice Allow the delegate to act on behalf of `msg.sender` for a specific amount of ERC1155 tokens
* @dev The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound)
* @param to The address to act as delegate
* @param contract_ The address of the contract that holds the token
* @param tokenId The token id to delegate
* @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
* @param amount The amount of that token id to delegate, > 0 delegates and 0 revokes
* @return delegationHash The unique identifier of the delegation
*/
function delegateERC1155(address to, address contract_, uint256 tokenId, bytes32 rights, uint256 amount)
external
payable
returns (bytes32 delegationHash);
/**
* ----------- CHECKS -----------
*/
/**
* @notice Check if `to` is a delegate of `from` for the entire wallet
* @param to The potential delegate address
* @param from The potential address who delegated rights
* @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
* @return valid Whether delegate is granted to act on the from's behalf
*/
function checkDelegateForAll(address to, address from, bytes32 rights) external view returns (bool);
/**
* @notice Check if `to` is a delegate of `from` for the specified `contract_` or the entire wallet
* @param to The delegated address to check
* @param contract_ The specific contract address being checked
* @param from The cold wallet who issued the delegation
* @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
* @return valid Whether delegate is granted to act on from's behalf for entire wallet or that specific contract
*/
function checkDelegateForContract(address to, address from, address contract_, bytes32 rights)
external
view
returns (bool);
/**
* @notice Check if `to` is a delegate of `from` for the specific `contract` and `tokenId`, the entire `contract_`, or the entire wallet
* @param to The delegated address to check
* @param contract_ The specific contract address being checked
* @param tokenId The token id for the token to delegating
* @param from The wallet that issued the delegation
* @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
* @return valid Whether delegate is granted to act on from's behalf for entire wallet, that contract, or that specific tokenId
*/
function checkDelegateForERC721(address to, address from, address contract_, uint256 tokenId, bytes32 rights)
external
view
returns (bool);
/**
* @notice Returns the amount of ERC20 tokens the delegate is granted rights to act on the behalf of
* @param to The delegated address to check
* @param contract_ The address of the token contract
* @param from The cold wallet who issued the delegation
* @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
* @return balance The delegated balance, which will be 0 if the delegation does not exist
*/
function checkDelegateForERC20(address to, address from, address contract_, bytes32 rights)
external
view
returns (uint256);
/**
* @notice Returns the amount of a ERC1155 tokens the delegate is granted rights to act on the behalf of
* @param to The delegated address to check
* @param contract_ The address of the token contract
* @param tokenId The token id to check the delegated amount of
* @param from The cold wallet who issued the delegation
* @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
* @return balance The delegated balance, which will be 0 if the delegation does not exist
*/
function checkDelegateForERC1155(address to, address from, address contract_, uint256 tokenId, bytes32 rights)
external
view
returns (uint256);
/**
* ----------- ENUMERATIONS -----------
*/
/**
* @notice Returns all enabled delegations a given delegate has received
* @param to The address to retrieve delegations for
* @return delegations Array of Delegation structs
*/
function getIncomingDelegations(address to) external view returns (Delegation[] memory delegations);
/**
* @notice Returns all enabled delegations an address has given out
* @param from The address to retrieve delegations for
* @return delegations Array of Delegation structs
*/
function getOutgoingDelegations(address from) external view returns (Delegation[] memory delegations);
/**
* @notice Returns all hashes associated with enabled delegations an address has received
* @param to The address to retrieve incoming delegation hashes for
* @return delegationHashes Array of delegation hashes
*/
function getIncomingDelegationHashes(address to) external view returns (bytes32[] memory delegationHashes);
/**
* @notice Returns all hashes associated with enabled delegations an address has given out
* @param from The address to retrieve outgoing delegation hashes for
* @return delegationHashes Array of delegation hashes
*/
function getOutgoingDelegationHashes(address from) external view returns (bytes32[] memory delegationHashes);
/**
* @notice Returns the delegations for a given array of delegation hashes
* @param delegationHashes is an array of hashes that correspond to delegations
* @return delegations Array of Delegation structs, return empty structs for nonexistent or revoked delegations
*/
function getDelegationsFromHashes(bytes32[] calldata delegationHashes)
external
view
returns (Delegation[] memory delegations);
/**
* ----------- STORAGE ACCESS -----------
*/
/**
* @notice Allows external contracts to read arbitrary storage slots
*/
function readSlot(bytes32 location) external view returns (bytes32);
/**
* @notice Allows external contracts to read an arbitrary array of storage slots
*/
function readSlots(bytes32[] calldata locations) external view returns (bytes32[] memory);
}
/* solhint-enable */
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (token/ERC1155/IERC1155.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC1155 compliant contract, as defined in the
* https://eips.ethereum.org/EIPS/eip-1155[EIP].
*/
interface IERC1155 is IERC165 {
/**
* @dev Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`.
*/
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
/**
* @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
* transfers.
*/
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] values
);
/**
* @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
* `approved`.
*/
event ApprovalForAll(address indexed account, address indexed operator, bool approved);
/**
* @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
*
* If an {URI} event was emitted for `id`, the standard
* https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
* returned by {IERC1155MetadataURI-uri}.
*/
event URI(string value, uint256 indexed id);
/**
* @dev Returns the value of tokens of token type `id` owned by `account`.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function balanceOf(address account, uint256 id) external view returns (uint256);
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
*
* Requirements:
*
* - `accounts` and `ids` must have the same length.
*/
function balanceOfBatch(
address[] calldata accounts,
uint256[] calldata ids
) external view returns (uint256[] memory);
/**
* @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
*
* Emits an {ApprovalForAll} event.
*
* Requirements:
*
* - `operator` cannot be the caller.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
*
* See {setApprovalForAll}.
*/
function isApprovedForAll(address account, address operator) external view returns (bool);
/**
* @dev Transfers a `value` amount of tokens of type `id` from `from` to `to`.
*
* WARNING: This function can potentially allow a reentrancy attack when transferring tokens
* to an untrusted contract, when invoking {onERC1155Received} on the receiver.
* Ensure to follow the checks-effects-interactions pattern and consider employing
* reentrancy guards when interacting with untrusted contracts.
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
* - `from` must have a balance of tokens of type `id` of at least `value` amount.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
* acceptance magic value.
*/
function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external;
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
*
* WARNING: This function can potentially allow a reentrancy attack when transferring tokens
* to an untrusted contract, when invoking {onERC1155BatchReceived} on the receiver.
* Ensure to follow the checks-effects-interactions pattern and consider employing
* reentrancy guards when interacting with untrusted contracts.
*
* Emits either a {TransferSingle} or a {TransferBatch} event, depending on the length of the array arguments.
*
* Requirements:
*
* - `ids` and `values` must have the same length.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
* acceptance magic value.
*/
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
) external;
}
// 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) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
* {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the address zero.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
// 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;
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 {
address custody;
string username;
address recovery;
}
/// @dev Struct argument for admin bulk register function,
/// with user-specific operator addresses and recovery addresses.
struct BulkRegisterWithOperatorData {
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 {
address custody;
string username;
}
/// @dev Struct argument for admin bulk register function,
/// with user-specific operator addresses, and a default recovery address.
struct BulkRegisterWithOperatorAndDefaultRecoveryData {
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 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 Set the idCounter to a new value.
*
* Used in migrations to allocate specific ID ranges.
*/
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;
/* solhint-disable ordering */
interface IMigration {
// =============================================================
// EVENTS
// =============================================================
/**
* @dev Emit an event when the admin calls migrate().
*
* @param migratedAt The timestamp at which the migration occurred.
*/
event Migrated(uint256 indexed migratedAt);
/**
* @notice Emit an event when the owner changes the migrator address.
*
* @param oldMigrator The address of the previous migrator.
* @param newMigrator The address of the new migrator.
*/
event SetMigrator(address oldMigrator, address newMigrator);
// =============================================================
// ERRORS
// =============================================================
/// @dev Revert if the caller is not the migrator.
error OnlyMigrator();
/// @dev Revert if the migrator calls a migration function after the grace period.
error PermissionRevoked();
/// @dev Revert if the migrator calls migrate more than once.
error AlreadyMigrated();
// =============================================================
// IMMUTABLES
// =============================================================
// Disable function name mixedcase because immutables should be uppercase.
/* solhint-disable func-name-mixedcase */
/**
* @notice Period in seconds after migration during which admin can continue to call protected
* migration functions. Admins can make corrections to the migrated data during the
* grace period if necessary, but cannot make changes after it expires.
*/
function GRACE_PERIOD() external view returns (uint24);
/* solhint-enable func-name-mixedcase */
// =============================================================
// STORAGE
// =============================================================
/**
* @notice Migration admin address.
*/
function migrator() external view returns (address);
/**
* @notice Timestamp at which the data was migrated.
*/
function migratedAt() external view returns (uint40);
// =============================================================
// PERMISSIONED ACTIONS
// =============================================================
/**
* @notice Set the time of the migration and emit an event.
*
* Only callable by the migrator.
*/
function migrate() external;
/**
* @notice Set the migrator address. Only callable by owner.
*
* @param migrator_ Migrator address.
*/
function setMigrator(address migrator_) external;
// =============================================================
// VIEWS
// =============================================================
/**
* @notice Check if the contract has been migrated.
*
* @return true if the contract has been migrated, false otherwise.
*/
function isMigrated() external view returns (bool);
}
/* solhint-enable ordering */
// 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;
import {IIdRegistry} from "./IIdRegistry.sol";
interface IUsernameGateway {
// NOTE: None of these events are actually emitted by IdGateway, but because they are emitted when calling
// either IdRegistry.unsafeChangeUsername() or IdRegistry.unsafeTransferUsername(),
// they are included here so that UsernameGateway's ABI includes them.
//
// =============================================================
// EVENTS
// =============================================================
/// @dev Emitted on successful change of username.
event UsernameChanged(uint256 indexed id, string newUsername);
/// @dev Emitted on successful transfer of a username to another ID.
event UsernameTransferred(uint256 indexed fromId, uint256 indexed toId, string username);
// =============================================================
// ERRORS
// =============================================================
/// @dev Revert when the relevant address is not the IdRegistry contract.
error OnlyIdRegistry();
/// @dev Revert when msg.sender is not the custody address of its associated ID.
error OnlyCustody();
/// @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 The EIP712 typehash for TransferUsername signatures.
function TRANSFER_USERNAME_TYPEHASH() external view returns (bytes32);
/// @notice The EIP712 typehash for ChangeUsername signatures.
function CHANGE_USERNAME_TYPEHASH() external view returns (bytes32);
// =============================================================
// IMMUTABLES
// =============================================================
/// @notice The RoyalProtocol IdRegistry contract.
function ID_REGISTRY() external view returns (IIdRegistry);
/* solhint-enable func-name-mixedcase */
// =============================================================
// TRANSFER USERNAME
// =============================================================
/**
* @notice Transfer the username of the caller's ID to another ID.
*
* @param toId The ID to transfer the username to.
* @param newFromUsername The new username for the caller's ID.
* @param toDeadline The deadline for the signature from the `to` ID.
* @param toSig The signature from the `to` ID.
*/
function transferUsername(uint256 toId, string calldata newFromUsername, uint256 toDeadline, bytes calldata toSig)
external;
/**
* @notice Transfer the username of the `from` ID to the `to` ID.
*
* @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.
* @param fromDeadline The deadline for the signature from the `from` ID.
* @param fromSig The signature from the `from` ID.
* @param toDeadline The deadline for the signature from the `to` ID.
* @param toSig The signature from the `to` ID.
*/
function transferUsernameFor(
uint256 fromId,
uint256 toId,
string calldata newFromUsername,
uint256 fromDeadline,
bytes calldata fromSig,
uint256 toDeadline,
bytes calldata toSig
) external;
// =============================================================
// CHANGE USERNAME
// =============================================================
/// @notice Change the username for the caller's ID.
function changeUsername(string calldata newUsername) external;
/// @notice Change the username for the provided ID.
function changeUsernameFor(uint256 id, string calldata newUsername, uint256 deadline, bytes calldata sig)
external;
// =============================================================
// PERMISSIONED ACTIONS
// =============================================================
/// @notice Forces a username transfer from one ID to another. (Only callable by the Owner).
function forceTransferUsername(uint256 fromId, uint256 toId, string calldata newFromUsername) external;
/// @notice Forces a username change for the provided ID. (Only callable by the Owner).
function forceChangeUsername(uint256 id, string calldata newUsername) external;
// =============================================================
// USERNAME VALIDATION
// =============================================================
/**
* @notice Check if a username is valid.
* Intended to be used by DApps to check if a username is valid before attempting to register it.
* Also used by the IdRegistry when registering a new ID.
*
* @return True if the username is valid, reverts otherwise.
*
* - Must be unique.
* - Must be <= 16 bytes (ASCII characters) in length.
* - All characters must be alphanumeric, "_" underscores, or "-" hyphens.
*/
function checkUsername(string calldata username) external view returns (bool);
}
// 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 {IIdRegistry} from "./interfaces/IIdRegistry.sol";
import {IDelegateRegistry} from "./interfaces/IDelegateRegistry.sol";
import {IUsernameGateway} from "./interfaces/IUsernameGateway.sol";
import {EIP712} from "./abstract/EIP712.sol";
import {Nonces} from "./abstract/Nonces.sol";
import {Migration} from "./abstract/Migration.sol";
import {Signatures} from "./abstract/Signatures.sol";
import {SignatureCheckerLib} from "solady/utils/SignatureCheckerLib.sol";
import {LibString} from "solady/utils/LibString.sol";
/**
* @title RoyalProtocol IdRegistry
*
* @notice The core User registry for the RoyalProtocol. Identities need to be registered here to act on the protocol.
*
* Holds all the necessary information for a user, including custody, username, operator, and recovery addresses.
* Also includes the logic for transferring custody, recovering custody, and changing operator or recovery addresses.
*/
contract IdRegistry is IIdRegistry, EIP712, Nonces, Migration, Signatures {
// =============================================================
// CONSTANTS
// =============================================================
/* solhint-disable gas-small-strings */
// `name` is intentionally lowercased for compatibility with tools like Etherscan,
// that detect account ID transfers as token transfers, due to us using the `Transfer` event.
/* solhint-disable const-name-snakecase */
/// @inheritdoc IIdRegistry
string public constant name = "RoyalProtocol ID";
/* solhint-enable const-name-snakecase */
/// @inheritdoc IIdRegistry
string public constant VERSION = "2024-07-29";
/// @inheritdoc IIdRegistry
bytes32 public constant TRANSFER_TYPEHASH =
keccak256("Transfer(uint256 id,address to,uint256 nonce,uint256 deadline)");
/// @inheritdoc IIdRegistry
bytes32 public constant TRANSFER_AND_CHANGE_OPERATOR_AND_RECOVERY_TYPEHASH = keccak256(
"TransferAndChangeOperatorAndRecovery(uint256 id,address to,address newOperator,address newRecovery,uint256 nonce,uint256 deadline)"
);
/// @inheritdoc IIdRegistry
bytes32 public constant CHANGE_OPERATOR_TYPEHASH =
keccak256("ChangeOperator(uint256 id,address newOperator,uint256 nonce,uint256 deadline)");
/// @inheritdoc IIdRegistry
bytes32 public constant CHANGE_RECOVERY_TYPEHASH =
keccak256("ChangeRecovery(uint256 id,address newRecovery,uint256 nonce,uint256 deadline)");
/* solhint-enable gas-small-strings */
// =============================================================
// STORAGE
// =============================================================
/// @inheritdoc IIdRegistry
address public idGateway;
/// @inheritdoc IIdRegistry
bool public idGatewayFrozen;
/// @inheritdoc IIdRegistry
address public usernameGateway;
/// @inheritdoc IIdRegistry
bool public usernameGatewayFrozen;
/// @inheritdoc IIdRegistry
///
/// @dev Default to canonical address for the v2 delegate.xyz DelegateRegistry contract.
address public delegateRegistry = 0x00000000000000447e69651d841bD8D104Bed493;
/// @inheritdoc IIdRegistry
bool public delegateRegistryFrozen;
/// @inheritdoc IIdRegistry
uint256 public idCounter = 0;
/// @inheritdoc IIdRegistry
mapping(address wallet => uint256 id) public idOf;
/// @inheritdoc IIdRegistry
mapping(uint256 id => address custody) public custodyOf;
/// @inheritdoc IIdRegistry
mapping(uint256 id => string username) public usernameOf;
/// @inheritdoc IIdRegistry
mapping(bytes32 usernameHash => uint256 id) public idOfUsernameHash;
/// @inheritdoc IIdRegistry
mapping(uint256 id => address operator) public operatorOf;
/// @inheritdoc IIdRegistry
mapping(uint256 id => address recovery) public recoveryOf;
// =============================================================
// MODIFIERS
// =============================================================
/// @dev Ensures that only the IdGateway contract can call the function. (for initial registration).
modifier onlyIdGateway() {
if (msg.sender != idGateway) revert OnlyIdGateway();
_;
}
/// @dev Ensures that only the UsernameGateway contract can call the function. (for username operations).
modifier onlyUsernameGateway() {
if (msg.sender != usernameGateway) revert OnlyUsernameGateway();
_;
}
// =============================================================
// CONSTRUCTOR
// =============================================================
/**
* @notice Sets up roles on the contract and pauses it so migrations can take place (if any).
* Pausing takes place in Migration() constructor.
*
* @param migrator_ Migrator address.
* @param initialOwner_ Initial owner address.
*
*/
constructor(address migrator_, address initialOwner_) Migration(24 hours, migrator_, 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_IdRegistry";
version = "1";
}
// =============================================================
// REGISTRATION
// =============================================================
/// @inheritdoc IIdRegistry
///
/// @dev Includes `whenNotPaused` because the IdRegistry is paused on deploy, and we may want to run a migration before unpausing.
/// Technically you could handle this by just pausing the IdGateway, but this is more explicit / less error-prone.
function register(address custody, string calldata username, address operator, address recovery)
external
override
onlyIdGateway
whenNotPaused
returns (uint256 id)
{
_validateRegister(custody, username, operator);
id = _unsafeRegister({custody: custody, username: username, operator: operator, recovery: recovery});
}
/**
* @dev Validate the registration of a new ID.
*
* - The custody address must not already have a registered ID.
* - The username is valid according to the UsernameGateway.
* - The operator address must not already have a registered ID.
* - The operator address must not be the same as the custody address.
*/
function _validateRegister(address custody, string calldata username, address operator) internal view {
if (idOf[custody] != 0) revert CustodyAlreadyRegistered();
// We do validate registers inside a loop in bulk migrations,
// but we control the UsernameGateway, so no risk to call out to an external contract.
// Also, we don't need to check a return value because checkUsername reverts on failure.
//
// Can also ignore the return value because the UsernameGateway reverts on any errors, which is all we care about.
//
// slither-disable-start unused-return
// slither-disable-next-line calls-loop
IUsernameGateway(usernameGateway).checkUsername(username);
// slither-disable-end unused-return
// The operator cannot be the same as the custody address,
// due to the logic in handling transfers and changeOperator().
if (idOf[operator] != 0) revert OperatorAlreadyRegistered();
if (operator == custody) revert OperatorCannotBeCustody();
}
/**
* @dev Registers a new ID.
*
* Sets up the custody address and username of a new ID.
* If the operator and/or recovery addresses are provided, they are also set up.
*
* Emits a Registered event.
*/
function _unsafeRegister(address custody, string calldata username, address operator, address recovery)
internal
returns (uint256 id)
{
// Increment before assignment - so no-one gets an ID of `0`.
// Thus `0` can be reserved for a falsy value in various checks.
unchecked {
id = ++idCounter;
}
// Set up custody
idOf[custody] = id;
custodyOf[id] = custody;
// Set up username
usernameOf[id] = username;
string memory lowercaseUsername = LibString.lower(username);
idOfUsernameHash[keccak256(bytes(lowercaseUsername))] = id;
// Only set operator if it's not the zero address, because we dont want idOf[address(0)] to be set.
if (operator != address(0)) {
idOf[operator] = id;
operatorOf[id] = operator;
}
// Set up recovery
recoveryOf[id] = recovery;
emit Registered({id: id, custody: custody, username: username, operator: operator, recovery: recovery});
}
// =============================================================
// TRANSFERS
// =============================================================
/// @inheritdoc IIdRegistry
function transfer(address to, uint256 deadline, bytes calldata sig) external override {
uint256 id = idOf[msg.sender];
if (custodyOf[id] != msg.sender) revert OnlyCustody();
_validateTransfer(to);
_verifyTransferSig({id: id, to: to, deadline: deadline, signer: to, sig: sig});
_unsafeTransfer(id, to);
}
/// @inheritdoc IIdRegistry
function transferFor(
uint256 id,
address to,
uint256 fromDeadline,
bytes calldata fromSig,
uint256 toDeadline,
bytes calldata toSig
) external override {
_validateTransfer(to);
address from = custodyOf[id];
_verifyTransferSig({id: id, to: to, deadline: fromDeadline, signer: from, sig: fromSig});
_verifyTransferSig({id: id, to: to, deadline: toDeadline, signer: to, sig: toSig});
_unsafeTransfer(id, to);
}
/// @inheritdoc IIdRegistry
function transferAndChangeOperatorAndRecovery(
address to,
address newOperator,
address newRecovery,
uint256 deadline,
bytes calldata sig
) external override {
uint256 id = idOf[msg.sender];
if (custodyOf[id] != msg.sender) revert OnlyCustody();
_validateTransfer(to);
_validateChangeOperator(newOperator);
_verifyTransferAndChangeRecoverySig({
id: id,
to: to,
newOperator: newOperator,
newRecovery: newRecovery,
deadline: deadline,
signer: to,
sig: sig
});
_unsafeTransfer(id, to);
_unsafeChangeOperator(id, newOperator);
_unsafeChangeRecovery(id, newRecovery);
}
/// @inheritdoc IIdRegistry
function transferAndChangeOperatorAndRecoveryFor(
uint256 id,
address to,
address newOperator,
address newRecovery,
uint256 fromDeadline,
bytes calldata fromSig,
uint256 toDeadline,
bytes calldata toSig
) external override {
_validateTransfer(to);
_validateChangeOperator(newOperator);
address from = custodyOf[id];
_verifyTransferAndChangeRecoverySig({
id: id,
to: to,
newOperator: newOperator,
newRecovery: newRecovery,
deadline: fromDeadline,
signer: from,
sig: fromSig
});
_verifyTransferAndChangeRecoverySig({
id: id,
to: to,
newOperator: newOperator,
newRecovery: newRecovery,
deadline: toDeadline,
signer: to,
sig: toSig
});
_unsafeTransfer(id, to);
_unsafeChangeOperator(id, newOperator);
_unsafeChangeRecovery(id, newRecovery);
}
/// @dev Validate the transfer of an ID from one address to another.
function _validateTransfer(address to) internal view {
// The recipient must not already have an ID.
if (idOf[to] != 0) revert CustodyAlreadyRegistered();
}
/// @dev Transfer an ID from one address to another.
function _unsafeTransfer(uint256 id, address to) internal whenNotPaused {
// Delete old custody lookup.
address from = custodyOf[id];
idOf[from] = 0;
// Set new custody address.
custodyOf[id] = to;
// This can't actually happen, because of the signature checks.
if (to != address(0)) {
idOf[to] = id;
}
emit Transfer(from, to, id);
}
// =============================================================
// TRANSFER USERNAME
// =============================================================
/// @inheritdoc IIdRegistry
function unsafeTransferUsername(uint256 fromId, uint256 toId, string calldata newFromUsername)
external
override
onlyUsernameGateway
{
_unsafeTransferUsername(fromId, toId, newFromUsername);
}
/// @dev Transfer the current username of `fromId` to `toId`, and set `fromId`'s username to `newFromUsername`.
///
/// NOTE: Intentionally omits `whenNotPaused`, because that is handled by the UsernameGateway.
function _unsafeTransferUsername(uint256 fromId, uint256 toId, string calldata newFromUsername) internal {
string memory transferredUsername = usernameOf[fromId];
_unsafeChangeUsername(fromId, newFromUsername);
_unsafeChangeUsername(toId, transferredUsername);
emit UsernameTransferred(fromId, toId, transferredUsername);
}
// =============================================================
// CHANGE USERNAME
// =============================================================
/// @inheritdoc IIdRegistry
function unsafeChangeUsername(uint256 id, string calldata newUsername) external override onlyUsernameGateway {
_unsafeChangeUsername(id, newUsername);
}
/// @dev Change the username for an ID.
///
/// NOTE: Intentionally omits `whenNotPaused`, because that is handled by the UsernameGateway.
function _unsafeChangeUsername(uint256 id, string memory newUsername) internal {
// Delete old username lookup hash
idOfUsernameHash[keccak256(bytes(LibString.lower(usernameOf[id])))] = 0;
// Update username
usernameOf[id] = newUsername;
idOfUsernameHash[keccak256(bytes(LibString.lower(newUsername)))] = id;
emit UsernameChanged(id, newUsername);
}
// =============================================================
// CHANGE OPERATOR
// =============================================================
/// @inheritdoc IIdRegistry
function changeOperator(address newOperator) external override {
uint256 id = idOf[msg.sender];
if (custodyOf[id] != msg.sender) revert OnlyCustody();
_validateChangeOperator(newOperator);
_unsafeChangeOperator(id, newOperator);
}
/// @inheritdoc IIdRegistry
function changeOperatorFor(uint256 id, address newOperator, uint256 deadline, bytes calldata sig)
external
override
{
_validateChangeOperator(newOperator);
_verifyChangeOperatorSig({id: id, newOperator: newOperator, deadline: deadline, sig: sig});
_unsafeChangeOperator(id, newOperator);
}
function _validateChangeOperator(address newOperator) internal view {
// The operator must not already have an ID.
if (idOf[newOperator] != 0) revert OperatorAlreadyRegistered();
}
/// @dev Change the operator address for an ID.
function _unsafeChangeOperator(uint256 id, address newOperator) internal whenNotPaused {
// Remove old operator
idOf[operatorOf[id]] = 0;
// Set new operator
operatorOf[id] = newOperator;
// Only set the idOf for the operator if its not address(0)
if (newOperator != address(0)) {
idOf[newOperator] = id;
}
emit OperatorAddressChanged(id, newOperator);
}
// =============================================================
// RECOVERY LOGIC
// =============================================================
/// @inheritdoc IIdRegistry
function changeRecovery(address newRecovery) external override {
uint256 id = idOf[msg.sender];
if (custodyOf[id] != msg.sender) revert OnlyCustody();
_unsafeChangeRecovery(id, newRecovery);
}
/// @inheritdoc IIdRegistry
function changeRecoveryFor(uint256 id, address newRecovery, uint256 deadline, bytes calldata sig)
external
override
{
_verifyChangeRecoverySig({id: id, newRecovery: newRecovery, deadline: deadline, sig: sig});
_unsafeChangeRecovery(id, newRecovery);
}
/// @dev Change the recovery address for an ID.
function _unsafeChangeRecovery(uint256 id, address newRecovery) internal whenNotPaused {
recoveryOf[id] = newRecovery;
emit RecoveryAddressChanged(id, newRecovery);
}
/// @inheritdoc IIdRegistry
function recover(uint256 id, address to, uint256 deadline, bytes calldata sig) external override {
// Revert if the caller is not the recovery address
if (recoveryOf[id] != msg.sender) revert OnlyRecovery();
_validateTransfer(to);
_verifyTransferSig({id: id, to: to, deadline: deadline, signer: to, sig: sig});
_unsafeTransfer(id, to);
emit Recovered(id, to);
}
/// @inheritdoc IIdRegistry
function recoverFor(
uint256 id,
address to,
uint256 recoveryDeadline,
bytes calldata recoverySig,
uint256 toDeadline,
bytes calldata toSig
) external override {
_validateTransfer(to);
address recovery = recoveryOf[id];
_verifyTransferSig({id: id, to: to, deadline: recoveryDeadline, signer: recovery, sig: recoverySig});
_verifyTransferSig({id: id, to: to, deadline: toDeadline, signer: to, sig: toSig});
_unsafeTransfer(id, to);
emit Recovered(id, to);
}
// =============================================================
// PERMISSIONED ACTIONS
// =============================================================
/// @inheritdoc IIdRegistry
function setIdGateway(address idGateway_) external override onlyOwner {
if (idGatewayFrozen) revert Frozen();
emit IdGatewaySet(idGateway, idGateway_);
idGateway = idGateway_;
}
/// @inheritdoc IIdRegistry
function freezeIdGateway() external override onlyOwner {
if (idGatewayFrozen) revert Frozen();
emit IdGatewayFrozen(idGateway);
idGatewayFrozen = true;
}
/// @inheritdoc IIdRegistry
function setUsernameGateway(address usernameGateway_) external override onlyOwner {
if (usernameGatewayFrozen) revert Frozen();
emit UsernameGatewaySet(usernameGateway, usernameGateway_);
usernameGateway = usernameGateway_;
}
/// @inheritdoc IIdRegistry
function freezeUsernameGateway() external override onlyOwner {
if (usernameGatewayFrozen) revert Frozen();
emit UsernameGatewayFrozen(usernameGateway);
usernameGatewayFrozen = true;
}
/// @inheritdoc IIdRegistry
function setDelegateRegistry(address delegateRegistry_) external override onlyOwner {
if (delegateRegistryFrozen) revert Frozen();
emit DelegateRegistrySet(delegateRegistry, delegateRegistry_);
delegateRegistry = delegateRegistry_;
}
/// @inheritdoc IIdRegistry
function freezeDelegateRegistry() external override onlyOwner {
if (delegateRegistryFrozen) revert Frozen();
emit DelegateRegistryFrozen(delegateRegistry);
delegateRegistryFrozen = true;
}
// =============================================================
// MIGRATION
// =============================================================
/// @inheritdoc IIdRegistry
function bulkRegisterIds(BulkRegisterData[] calldata data) external override onlyMigrator {
uint256 length = data.length;
unchecked {
for (uint256 i = 0; i < length; i++) {
BulkRegisterData calldata d = data[i];
_validateRegister(d.custody, d.username, address(0));
_unsafeRegister({custody: d.custody, username: d.username, operator: address(0), recovery: d.recovery});
}
}
}
/// @inheritdoc IIdRegistry
function bulkRegisterIdsWithOperator(BulkRegisterWithOperatorData[] calldata data) external override onlyMigrator {
uint256 length = data.length;
unchecked {
for (uint256 i = 0; i < length; i++) {
BulkRegisterWithOperatorData calldata d = data[i];
_validateRegister(d.custody, d.username, d.operator);
_unsafeRegister({custody: d.custody, username: d.username, operator: d.operator, recovery: d.recovery});
}
}
}
/// @inheritdoc IIdRegistry
function bulkRegisterIdsWithDefaultRecovery(BulkRegisterWithDefaultRecoveryData[] calldata data, address recovery)
external
override
onlyMigrator
{
uint256 length = data.length;
unchecked {
for (uint256 i = 0; i < length; i++) {
BulkRegisterWithDefaultRecoveryData calldata d = data[i];
_validateRegister(d.custody, d.username, address(0));
_unsafeRegister({custody: d.custody, username: d.username, operator: address(0), recovery: recovery});
}
}
}
/// @inheritdoc IIdRegistry
function bulkRegisterIdsWithOperatorAndDefaultRecovery(
BulkRegisterWithOperatorAndDefaultRecoveryData[] calldata data,
address recovery
) external override onlyMigrator {
uint256 length = data.length;
unchecked {
for (uint256 i = 0; i < length; i++) {
BulkRegisterWithOperatorAndDefaultRecoveryData calldata d = data[i];
_validateRegister(d.custody, d.username, d.operator);
_unsafeRegister({custody: d.custody, username: d.username, operator: d.operator, recovery: recovery});
}
}
}
/// @inheritdoc IIdRegistry
function setIdCounter(uint256 counter) external override onlyMigrator {
emit IdCounterSet(idCounter, counter);
idCounter = counter;
}
// =============================================================
// VIEW FUNCTIONS
// =============================================================
/// @inheritdoc IIdRegistry
function getUserById(uint256 id) external view override returns (User memory user) {
if (id > idCounter) revert HasNoId();
return User({
id: id,
custody: custodyOf[id],
username: usernameOf[id],
operator: operatorOf[id],
recovery: recoveryOf[id]
});
}
/// @inheritdoc IIdRegistry
function getIdByUsername(string calldata username) external view override returns (uint256 id) {
id = idOfUsernameHash[keccak256(bytes(LibString.lower(username)))];
if (id == 0) revert HasNoId();
}
// =============================================================
// CAN ACT
// =============================================================
/// @inheritdoc IIdRegistry
///
/// @dev bytes32 rights follows delegate.xyz interface. (Could be role, functionSignature, etc.)
function canAct(uint256 id, address actor, address contractAddr, bytes32 rights)
external
view
override
returns (bool)
{
// The actor must have _some_ registered ID.
// (which could be different than the ID they are acting on behalf of).
if (idOf[actor] == 0) return false;
// If the actor is the custody or operator address, they can act.
address custody = custodyOf[id];
if (actor == custody) return true;
address operator = operatorOf[id];
if (actor == operator) return true;
// If the actor is a delegate for the custody or operator, they can act.
bool custodyDelegation =
IDelegateRegistry(delegateRegistry).checkDelegateForContract(actor, custody, contractAddr, rights);
if (custodyDelegation) return true;
bool operatorDelegation =
IDelegateRegistry(delegateRegistry).checkDelegateForContract(actor, operator, contractAddr, rights);
if (operatorDelegation) return true;
// If none of the above, the actor cannot act for that ID.
return false;
}
// =============================================================
// SIGNATURE HELPERS - VIEW FNS
// =============================================================
/// @inheritdoc IIdRegistry
function verifyCustodySignature(uint256 id, bytes32 digest, bytes calldata sig)
external
view
override
returns (bool isValid)
{
address custody = custodyOf[id];
isValid = SignatureCheckerLib.isValidSignatureNowCalldata(custody, digest, sig);
}
/// @inheritdoc IIdRegistry
function verifyIdSignature(uint256 id, bytes32 digest, bytes calldata sig)
external
view
override
returns (bool isValid)
{
address custody = custodyOf[id];
address operator = operatorOf[id];
isValid = SignatureCheckerLib.isValidSignatureNowCalldata(custody, digest, sig)
|| SignatureCheckerLib.isValidSignatureNowCalldata(operator, digest, sig);
}
// =============================================================
// SIGNATURE HELPERS
// =============================================================
/// @dev Verify the EIP712 signature for a transfer(For) transaction.
function _verifyTransferSig(uint256 id, address to, uint256 deadline, address signer, bytes calldata sig)
internal
{
bytes32 digest = _hashTypedData(keccak256(abi.encode(TRANSFER_TYPEHASH, id, to, _useNonce(signer), deadline)));
_verifySig(digest, signer, deadline, sig);
}
/// @dev Verify the EIP712 signature for a transferAndChangeRecovery(For) transaction.
function _verifyTransferAndChangeRecoverySig(
uint256 id,
address to,
address newOperator,
address newRecovery,
uint256 deadline,
address signer,
bytes calldata sig
) internal {
bytes32 digest = _hashTypedData(
keccak256(
abi.encode(
TRANSFER_AND_CHANGE_OPERATOR_AND_RECOVERY_TYPEHASH,
id,
to,
newOperator,
newRecovery,
_useNonce(signer),
deadline
)
)
);
_verifySig(digest, signer, deadline, sig);
}
/// @dev Verify the EIP712 signature for a changeOperatorFor transaction.
function _verifyChangeOperatorSig(uint256 id, address newOperator, uint256 deadline, bytes calldata sig) internal {
// Needed to get nonce for the user and to verify signature.
address custody = custodyOf[id];
bytes32 digest = _hashTypedData(
keccak256(abi.encode(CHANGE_OPERATOR_TYPEHASH, id, newOperator, _useNonce(custody), deadline))
);
_verifySig(digest, custody, deadline, sig);
}
/// @dev Verify the EIP712 signature for a changeRecoveryFor transaction.
function _verifyChangeRecoverySig(uint256 id, address newRecovery, uint256 deadline, bytes calldata sig) internal {
// Needed to get nonce for the user and to verify signature.
address custody = custodyOf[id];
bytes32 digest = _hashTypedData(
keccak256(abi.encode(CHANGE_RECOVERY_TYPEHASH, id, newRecovery, _useNonce(custody), deadline))
);
_verifySig(digest, custody, deadline, sig);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Library for converting numbers into strings and other string operations.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibString.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibString.sol)
///
/// @dev Note:
/// For performance and bytecode compactness, most of the string operations are restricted to
/// byte strings (7-bit ASCII), except where otherwise specified.
/// Usage of byte string operations on charsets with runes spanning two or more bytes
/// can lead to undefined behavior.
library LibString {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The length of the output is too small to contain all the hex digits.
error HexLengthInsufficient();
/// @dev The length of the string is more than 32 bytes.
error TooBigForSmallString();
/// @dev The input string must be a 7-bit ASCII.
error StringNot7BitASCII();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The constant returned when the `search` is not found in the string.
uint256 internal constant NOT_FOUND = type(uint256).max;
/// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.
uint128 internal constant ALPHANUMERIC_7_BIT_ASCII = 0x7fffffe07fffffe03ff000000000000;
/// @dev Lookup for 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.
uint128 internal constant LETTERS_7_BIT_ASCII = 0x7fffffe07fffffe0000000000000000;
/// @dev Lookup for 'abcdefghijklmnopqrstuvwxyz'.
uint128 internal constant LOWERCASE_7_BIT_ASCII = 0x7fffffe000000000000000000000000;
/// @dev Lookup for 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
uint128 internal constant UPPERCASE_7_BIT_ASCII = 0x7fffffe0000000000000000;
/// @dev Lookup for '0123456789'.
uint128 internal constant DIGITS_7_BIT_ASCII = 0x3ff000000000000;
/// @dev Lookup for '0123456789abcdefABCDEF'.
uint128 internal constant HEXDIGITS_7_BIT_ASCII = 0x7e0000007e03ff000000000000;
/// @dev Lookup for '01234567'.
uint128 internal constant OCTDIGITS_7_BIT_ASCII = 0xff000000000000;
/// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'.
uint128 internal constant PRINTABLE_7_BIT_ASCII = 0x7fffffffffffffffffffffff00003e00;
/// @dev Lookup for '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'.
uint128 internal constant PUNCTUATION_7_BIT_ASCII = 0x78000001f8000001fc00fffe00000000;
/// @dev Lookup for ' \t\n\r\x0b\x0c'.
uint128 internal constant WHITESPACE_7_BIT_ASCII = 0x100003e00;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* DECIMAL OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the base 10 decimal representation of `value`.
function toString(uint256 value) internal pure returns (string memory str) {
/// @solidity memory-safe-assembly
assembly {
// The maximum value of a uint256 contains 78 digits (1 byte per digit), but
// we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned.
// We will need 1 word for the trailing zeros padding, 1 word for the length,
// and 3 words for a maximum of 78 digits.
str := add(mload(0x40), 0x80)
mstore(0x40, add(str, 0x20)) // Allocate the memory.
mstore(str, 0) // Zeroize the slot after the string.
let end := str // Cache the end of the memory to calculate the length later.
let w := not(0) // Tsk.
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
for { let temp := value } 1 {} {
str := add(str, w) // `sub(str, 1)`.
// Store the character to the pointer.
// The ASCII index of the '0' character is 48.
mstore8(str, add(48, mod(temp, 10)))
temp := div(temp, 10) // Keep dividing `temp` until zero.
if iszero(temp) { break }
}
let length := sub(end, str)
str := sub(str, 0x20) // Move the pointer 32 bytes back to make room for the length.
mstore(str, length) // Store the length.
}
}
/// @dev Returns the base 10 decimal representation of `value`.
function toString(int256 value) internal pure returns (string memory str) {
if (value >= 0) return toString(uint256(value));
unchecked {
str = toString(~uint256(value) + 1);
}
/// @solidity memory-safe-assembly
assembly {
// We still have some spare memory space on the left,
// as we have allocated 3 words (96 bytes) for up to 78 digits.
let length := mload(str) // Load the string length.
mstore(str, 0x2d) // Store the '-' character.
str := sub(str, 1) // Move back the string pointer by a byte.
mstore(str, add(length, 1)) // Update the string length.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HEXADECIMAL OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the hexadecimal representation of `value`,
/// left-padded to an input length of `length` bytes.
/// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte,
/// giving a total length of `length * 2 + 2` bytes.
/// Reverts if `length` is too small for the output to contain all the digits.
function toHexString(uint256 value, uint256 length) internal pure returns (string memory str) {
str = toHexStringNoPrefix(value, length);
/// @solidity memory-safe-assembly
assembly {
let strLength := add(mload(str), 2) // Compute the length.
mstore(str, 0x3078) // Store the "0x" prefix.
str := sub(str, 2) // Move the pointer.
mstore(str, strLength) // Store the length.
}
}
/// @dev Returns the hexadecimal representation of `value`,
/// left-padded to an input length of `length` bytes.
/// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte,
/// giving a total length of `length * 2` bytes.
/// Reverts if `length` is too small for the output to contain all the digits.
function toHexStringNoPrefix(uint256 value, uint256 length)
internal
pure
returns (string memory str)
{
/// @solidity memory-safe-assembly
assembly {
// We need 0x20 bytes for the trailing zeros padding, `length * 2` bytes
// for the digits, 0x02 bytes for the prefix, and 0x20 bytes for the length.
// We add 0x20 to the total and round down to a multiple of 0x20.
// (0x20 + 0x20 + 0x02 + 0x20) = 0x62.
str := add(mload(0x40), and(add(shl(1, length), 0x42), not(0x1f)))
mstore(0x40, add(str, 0x20)) // Allocate the memory.
mstore(str, 0) // Zeroize the slot after the string.
let end := str // Cache the end to calculate the length later.
// Store "0123456789abcdef" in scratch space.
mstore(0x0f, 0x30313233343536373839616263646566)
let start := sub(str, add(length, length))
let w := not(1) // Tsk.
let temp := value
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
for {} 1 {} {
str := add(str, w) // `sub(str, 2)`.
mstore8(add(str, 1), mload(and(temp, 15)))
mstore8(str, mload(and(shr(4, temp), 15)))
temp := shr(8, temp)
if iszero(xor(str, start)) { break }
}
if temp {
mstore(0x00, 0x2194895a) // `HexLengthInsufficient()`.
revert(0x1c, 0x04)
}
let strLength := sub(end, str)
str := sub(str, 0x20)
mstore(str, strLength) // Store the length.
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte.
/// As address are 20 bytes long, the output will left-padded to have
/// a length of `20 * 2 + 2` bytes.
function toHexString(uint256 value) internal pure returns (string memory str) {
str = toHexStringNoPrefix(value);
/// @solidity memory-safe-assembly
assembly {
let strLength := add(mload(str), 2) // Compute the length.
mstore(str, 0x3078) // Store the "0x" prefix.
str := sub(str, 2) // Move the pointer.
mstore(str, strLength) // Store the length.
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is prefixed with "0x".
/// The output excludes leading "0" from the `toHexString` output.
/// `0x00: "0x0", 0x01: "0x1", 0x12: "0x12", 0x123: "0x123"`.
function toMinimalHexString(uint256 value) internal pure returns (string memory str) {
str = toHexStringNoPrefix(value);
/// @solidity memory-safe-assembly
assembly {
let o := eq(byte(0, mload(add(str, 0x20))), 0x30) // Whether leading zero is present.
let strLength := add(mload(str), 2) // Compute the length.
mstore(add(str, o), 0x3078) // Store the "0x" prefix, accounting for leading zero.
str := sub(add(str, o), 2) // Move the pointer, accounting for leading zero.
mstore(str, sub(strLength, o)) // Store the length, accounting for leading zero.
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output excludes leading "0" from the `toHexStringNoPrefix` output.
/// `0x00: "0", 0x01: "1", 0x12: "12", 0x123: "123"`.
function toMinimalHexStringNoPrefix(uint256 value) internal pure returns (string memory str) {
str = toHexStringNoPrefix(value);
/// @solidity memory-safe-assembly
assembly {
let o := eq(byte(0, mload(add(str, 0x20))), 0x30) // Whether leading zero is present.
let strLength := mload(str) // Get the length.
str := add(str, o) // Move the pointer, accounting for leading zero.
mstore(str, sub(strLength, o)) // Store the length, accounting for leading zero.
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is encoded using 2 hexadecimal digits per byte.
/// As address are 20 bytes long, the output will left-padded to have
/// a length of `20 * 2` bytes.
function toHexStringNoPrefix(uint256 value) internal pure returns (string memory str) {
/// @solidity memory-safe-assembly
assembly {
// We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length,
// 0x02 bytes for the prefix, and 0x40 bytes for the digits.
// The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x40) is 0xa0.
str := add(mload(0x40), 0x80)
mstore(0x40, add(str, 0x20)) // Allocate the memory.
mstore(str, 0) // Zeroize the slot after the string.
let end := str // Cache the end to calculate the length later.
mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup.
let w := not(1) // Tsk.
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
for { let temp := value } 1 {} {
str := add(str, w) // `sub(str, 2)`.
mstore8(add(str, 1), mload(and(temp, 15)))
mstore8(str, mload(and(shr(4, temp), 15)))
temp := shr(8, temp)
if iszero(temp) { break }
}
let strLength := sub(end, str)
str := sub(str, 0x20)
mstore(str, strLength) // Store the length.
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is prefixed with "0x", encoded using 2 hexadecimal digits per byte,
/// and the alphabets are capitalized conditionally according to
/// https://eips.ethereum.org/EIPS/eip-55
function toHexStringChecksummed(address value) internal pure returns (string memory str) {
str = toHexString(value);
/// @solidity memory-safe-assembly
assembly {
let mask := shl(6, div(not(0), 255)) // `0b010000000100000000 ...`
let o := add(str, 0x22)
let hashed := and(keccak256(o, 40), mul(34, mask)) // `0b10001000 ... `
let t := shl(240, 136) // `0b10001000 << 240`
for { let i := 0 } 1 {} {
mstore(add(i, i), mul(t, byte(i, hashed)))
i := add(i, 1)
if eq(i, 20) { break }
}
mstore(o, xor(mload(o), shr(1, and(mload(0x00), and(mload(o), mask)))))
o := add(o, 0x20)
mstore(o, xor(mload(o), shr(1, and(mload(0x20), and(mload(o), mask)))))
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte.
function toHexString(address value) internal pure returns (string memory str) {
str = toHexStringNoPrefix(value);
/// @solidity memory-safe-assembly
assembly {
let strLength := add(mload(str), 2) // Compute the length.
mstore(str, 0x3078) // Store the "0x" prefix.
str := sub(str, 2) // Move the pointer.
mstore(str, strLength) // Store the length.
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is encoded using 2 hexadecimal digits per byte.
function toHexStringNoPrefix(address value) internal pure returns (string memory str) {
/// @solidity memory-safe-assembly
assembly {
str := mload(0x40)
// Allocate the memory.
// We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length,
// 0x02 bytes for the prefix, and 0x28 bytes for the digits.
// The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x28) is 0x80.
mstore(0x40, add(str, 0x80))
mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup.
str := add(str, 2)
mstore(str, 40) // Store the length.
let o := add(str, 0x20)
mstore(add(o, 40), 0) // Zeroize the slot after the string.
value := shl(96, value)
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
for { let i := 0 } 1 {} {
let p := add(o, add(i, i))
let temp := byte(i, value)
mstore8(add(p, 1), mload(and(temp, 15)))
mstore8(p, mload(shr(4, temp)))
i := add(i, 1)
if eq(i, 20) { break }
}
}
}
/// @dev Returns the hex encoded string from the raw bytes.
/// The output is encoded using 2 hexadecimal digits per byte.
function toHexString(bytes memory raw) internal pure returns (string memory str) {
str = toHexStringNoPrefix(raw);
/// @solidity memory-safe-assembly
assembly {
let strLength := add(mload(str), 2) // Compute the length.
mstore(str, 0x3078) // Store the "0x" prefix.
str := sub(str, 2) // Move the pointer.
mstore(str, strLength) // Store the length.
}
}
/// @dev Returns the hex encoded string from the raw bytes.
/// The output is encoded using 2 hexadecimal digits per byte.
function toHexStringNoPrefix(bytes memory raw) internal pure returns (string memory str) {
/// @solidity memory-safe-assembly
assembly {
let length := mload(raw)
str := add(mload(0x40), 2) // Skip 2 bytes for the optional prefix.
mstore(str, add(length, length)) // Store the length of the output.
mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup.
let o := add(str, 0x20)
let end := add(raw, length)
for {} iszero(eq(raw, end)) {} {
raw := add(raw, 1)
mstore8(add(o, 1), mload(and(mload(raw), 15)))
mstore8(o, mload(and(shr(4, mload(raw)), 15)))
o := add(o, 2)
}
mstore(o, 0) // Zeroize the slot after the string.
mstore(0x40, add(o, 0x20)) // Allocate the memory.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* RUNE STRING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the number of UTF characters in the string.
function runeCount(string memory s) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
if mload(s) {
mstore(0x00, div(not(0), 255))
mstore(0x20, 0x0202020202020202020202020202020202020202020202020303030304040506)
let o := add(s, 0x20)
let end := add(o, mload(s))
for { result := 1 } 1 { result := add(result, 1) } {
o := add(o, byte(0, mload(shr(250, mload(o)))))
if iszero(lt(o, end)) { break }
}
}
}
}
/// @dev Returns if this string is a 7-bit ASCII string.
/// (i.e. all characters codes are in [0..127])
function is7BitASCII(string memory s) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
let mask := shl(7, div(not(0), 255))
result := 1
let n := mload(s)
if n {
let o := add(s, 0x20)
let end := add(o, n)
let last := mload(end)
mstore(end, 0)
for {} 1 {} {
if and(mask, mload(o)) {
result := 0
break
}
o := add(o, 0x20)
if iszero(lt(o, end)) { break }
}
mstore(end, last)
}
}
}
/// @dev Returns if this string is a 7-bit ASCII string,
/// AND all characters are in the `allowed` lookup.
/// Note: If `s` is empty, returns true regardless of `allowed`.
function is7BitASCII(string memory s, uint128 allowed) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := 1
if mload(s) {
let allowed_ := shr(128, shl(128, allowed))
let o := add(s, 0x20)
let end := add(o, mload(s))
for {} 1 {} {
result := and(result, shr(byte(0, mload(o)), allowed_))
o := add(o, 1)
if iszero(and(result, lt(o, end))) { break }
}
}
}
}
/// @dev Converts the bytes in the 7-bit ASCII string `s` to
/// an allowed lookup for use in `is7BitASCII(s, allowed)`.
/// To save runtime gas, you can cache the result in an immutable variable.
function to7BitASCIIAllowedLookup(string memory s) internal pure returns (uint128 result) {
/// @solidity memory-safe-assembly
assembly {
if mload(s) {
let o := add(s, 0x20)
let end := add(o, mload(s))
for {} 1 {} {
result := or(result, shl(byte(0, mload(o)), 1))
o := add(o, 1)
if iszero(lt(o, end)) { break }
}
if shr(128, result) {
mstore(0x00, 0xc9807e0d) // `StringNot7BitASCII()`.
revert(0x1c, 0x04)
}
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* BYTE STRING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// For performance and bytecode compactness, byte string operations are restricted
// to 7-bit ASCII strings. All offsets are byte offsets, not UTF character offsets.
// Usage of byte string operations on charsets with runes spanning two or more bytes
// can lead to undefined behavior.
/// @dev Returns `subject` all occurrences of `search` replaced with `replacement`.
function replace(string memory subject, string memory search, string memory replacement)
internal
pure
returns (string memory result)
{
/// @solidity memory-safe-assembly
assembly {
let subjectLength := mload(subject)
let searchLength := mload(search)
let replacementLength := mload(replacement)
subject := add(subject, 0x20)
search := add(search, 0x20)
replacement := add(replacement, 0x20)
result := add(mload(0x40), 0x20)
let subjectEnd := add(subject, subjectLength)
if iszero(gt(searchLength, subjectLength)) {
let subjectSearchEnd := add(sub(subjectEnd, searchLength), 1)
let h := 0
if iszero(lt(searchLength, 0x20)) { h := keccak256(search, searchLength) }
let m := shl(3, sub(0x20, and(searchLength, 0x1f)))
let s := mload(search)
for {} 1 {} {
let t := mload(subject)
// Whether the first `searchLength % 32` bytes of
// `subject` and `search` matches.
if iszero(shr(m, xor(t, s))) {
if h {
if iszero(eq(keccak256(subject, searchLength), h)) {
mstore(result, t)
result := add(result, 1)
subject := add(subject, 1)
if iszero(lt(subject, subjectSearchEnd)) { break }
continue
}
}
// Copy the `replacement` one word at a time.
for { let o := 0 } 1 {} {
mstore(add(result, o), mload(add(replacement, o)))
o := add(o, 0x20)
if iszero(lt(o, replacementLength)) { break }
}
result := add(result, replacementLength)
subject := add(subject, searchLength)
if searchLength {
if iszero(lt(subject, subjectSearchEnd)) { break }
continue
}
}
mstore(result, t)
result := add(result, 1)
subject := add(subject, 1)
if iszero(lt(subject, subjectSearchEnd)) { break }
}
}
let resultRemainder := result
result := add(mload(0x40), 0x20)
let k := add(sub(resultRemainder, result), sub(subjectEnd, subject))
// Copy the rest of the string one word at a time.
for {} lt(subject, subjectEnd) {} {
mstore(resultRemainder, mload(subject))
resultRemainder := add(resultRemainder, 0x20)
subject := add(subject, 0x20)
}
result := sub(result, 0x20)
let last := add(add(result, 0x20), k) // Zeroize the slot after the string.
mstore(last, 0)
mstore(0x40, add(last, 0x20)) // Allocate the memory.
mstore(result, k) // Store the length.
}
}
/// @dev Returns the byte index of the first location of `search` in `subject`,
/// searching from left to right, starting from `from`.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found.
function indexOf(string memory subject, string memory search, uint256 from)
internal
pure
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
for { let subjectLength := mload(subject) } 1 {} {
if iszero(mload(search)) {
if iszero(gt(from, subjectLength)) {
result := from
break
}
result := subjectLength
break
}
let searchLength := mload(search)
let subjectStart := add(subject, 0x20)
result := not(0) // Initialize to `NOT_FOUND`.
subject := add(subjectStart, from)
let end := add(sub(add(subjectStart, subjectLength), searchLength), 1)
let m := shl(3, sub(0x20, and(searchLength, 0x1f)))
let s := mload(add(search, 0x20))
if iszero(and(lt(subject, end), lt(from, subjectLength))) { break }
if iszero(lt(searchLength, 0x20)) {
for { let h := keccak256(add(search, 0x20), searchLength) } 1 {} {
if iszero(shr(m, xor(mload(subject), s))) {
if eq(keccak256(subject, searchLength), h) {
result := sub(subject, subjectStart)
break
}
}
subject := add(subject, 1)
if iszero(lt(subject, end)) { break }
}
break
}
for {} 1 {} {
if iszero(shr(m, xor(mload(subject), s))) {
result := sub(subject, subjectStart)
break
}
subject := add(subject, 1)
if iszero(lt(subject, end)) { break }
}
break
}
}
}
/// @dev Returns the byte index of the first location of `search` in `subject`,
/// searching from left to right.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found.
function indexOf(string memory subject, string memory search)
internal
pure
returns (uint256 result)
{
result = indexOf(subject, search, 0);
}
/// @dev Returns the byte index of the first location of `search` in `subject`,
/// searching from right to left, starting from `from`.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found.
function lastIndexOf(string memory subject, string memory search, uint256 from)
internal
pure
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
for {} 1 {} {
result := not(0) // Initialize to `NOT_FOUND`.
let searchLength := mload(search)
if gt(searchLength, mload(subject)) { break }
let w := result
let fromMax := sub(mload(subject), searchLength)
if iszero(gt(fromMax, from)) { from := fromMax }
let end := add(add(subject, 0x20), w)
subject := add(add(subject, 0x20), from)
if iszero(gt(subject, end)) { break }
// As this function is not too often used,
// we shall simply use keccak256 for smaller bytecode size.
for { let h := keccak256(add(search, 0x20), searchLength) } 1 {} {
if eq(keccak256(subject, searchLength), h) {
result := sub(subject, add(end, 1))
break
}
subject := add(subject, w) // `sub(subject, 1)`.
if iszero(gt(subject, end)) { break }
}
break
}
}
}
/// @dev Returns the byte index of the first location of `search` in `subject`,
/// searching from right to left.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found.
function lastIndexOf(string memory subject, string memory search)
internal
pure
returns (uint256 result)
{
result = lastIndexOf(subject, search, uint256(int256(-1)));
}
/// @dev Returns true if `search` is found in `subject`, false otherwise.
function contains(string memory subject, string memory search) internal pure returns (bool) {
return indexOf(subject, search) != NOT_FOUND;
}
/// @dev Returns whether `subject` starts with `search`.
function startsWith(string memory subject, string memory search)
internal
pure
returns (bool result)
{
/// @solidity memory-safe-assembly
assembly {
let searchLength := mload(search)
// Just using keccak256 directly is actually cheaper.
// forgefmt: disable-next-item
result := and(
iszero(gt(searchLength, mload(subject))),
eq(
keccak256(add(subject, 0x20), searchLength),
keccak256(add(search, 0x20), searchLength)
)
)
}
}
/// @dev Returns whether `subject` ends with `search`.
function endsWith(string memory subject, string memory search)
internal
pure
returns (bool result)
{
/// @solidity memory-safe-assembly
assembly {
let searchLength := mload(search)
let subjectLength := mload(subject)
// Whether `search` is not longer than `subject`.
let withinRange := iszero(gt(searchLength, subjectLength))
// Just using keccak256 directly is actually cheaper.
// forgefmt: disable-next-item
result := and(
withinRange,
eq(
keccak256(
// `subject + 0x20 + max(subjectLength - searchLength, 0)`.
add(add(subject, 0x20), mul(withinRange, sub(subjectLength, searchLength))),
searchLength
),
keccak256(add(search, 0x20), searchLength)
)
)
}
}
/// @dev Returns `subject` repeated `times`.
function repeat(string memory subject, uint256 times)
internal
pure
returns (string memory result)
{
/// @solidity memory-safe-assembly
assembly {
let subjectLength := mload(subject)
if iszero(or(iszero(times), iszero(subjectLength))) {
subject := add(subject, 0x20)
result := mload(0x40)
let output := add(result, 0x20)
for {} 1 {} {
// Copy the `subject` one word at a time.
for { let o := 0 } 1 {} {
mstore(add(output, o), mload(add(subject, o)))
o := add(o, 0x20)
if iszero(lt(o, subjectLength)) { break }
}
output := add(output, subjectLength)
times := sub(times, 1)
if iszero(times) { break }
}
mstore(output, 0) // Zeroize the slot after the string.
let resultLength := sub(output, add(result, 0x20))
mstore(result, resultLength) // Store the length.
mstore(0x40, add(result, add(resultLength, 0x40))) // Allocate the memory.
}
}
}
/// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive).
/// `start` and `end` are byte offsets.
function slice(string memory subject, uint256 start, uint256 end)
internal
pure
returns (string memory result)
{
/// @solidity memory-safe-assembly
assembly {
let subjectLength := mload(subject)
if iszero(gt(subjectLength, end)) { end := subjectLength }
if iszero(gt(subjectLength, start)) { start := subjectLength }
if lt(start, end) {
result := mload(0x40)
let resultLength := sub(end, start)
mstore(result, resultLength)
subject := add(subject, start)
let w := not(0x1f)
// Copy the `subject` one word at a time, backwards.
for { let o := and(add(resultLength, 0x1f), w) } 1 {} {
mstore(add(result, o), mload(add(subject, o)))
o := add(o, w) // `sub(o, 0x20)`.
if iszero(o) { break }
}
// Zeroize the slot after the string.
mstore(add(add(result, 0x20), resultLength), 0)
mstore(0x40, add(result, add(resultLength, 0x40))) // Allocate the memory.
}
}
}
/// @dev Returns a copy of `subject` sliced from `start` to the end of the string.
/// `start` is a byte offset.
function slice(string memory subject, uint256 start)
internal
pure
returns (string memory result)
{
result = slice(subject, start, uint256(int256(-1)));
}
/// @dev Returns all the indices of `search` in `subject`.
/// The indices are byte offsets.
function indicesOf(string memory subject, string memory search)
internal
pure
returns (uint256[] memory result)
{
/// @solidity memory-safe-assembly
assembly {
let subjectLength := mload(subject)
let searchLength := mload(search)
if iszero(gt(searchLength, subjectLength)) {
subject := add(subject, 0x20)
search := add(search, 0x20)
result := add(mload(0x40), 0x20)
let subjectStart := subject
let subjectSearchEnd := add(sub(add(subject, subjectLength), searchLength), 1)
let h := 0
if iszero(lt(searchLength, 0x20)) { h := keccak256(search, searchLength) }
let m := shl(3, sub(0x20, and(searchLength, 0x1f)))
let s := mload(search)
for {} 1 {} {
let t := mload(subject)
// Whether the first `searchLength % 32` bytes of
// `subject` and `search` matches.
if iszero(shr(m, xor(t, s))) {
if h {
if iszero(eq(keccak256(subject, searchLength), h)) {
subject := add(subject, 1)
if iszero(lt(subject, subjectSearchEnd)) { break }
continue
}
}
// Append to `result`.
mstore(result, sub(subject, subjectStart))
result := add(result, 0x20)
// Advance `subject` by `searchLength`.
subject := add(subject, searchLength)
if searchLength {
if iszero(lt(subject, subjectSearchEnd)) { break }
continue
}
}
subject := add(subject, 1)
if iszero(lt(subject, subjectSearchEnd)) { break }
}
let resultEnd := result
// Assign `result` to the free memory pointer.
result := mload(0x40)
// Store the length of `result`.
mstore(result, shr(5, sub(resultEnd, add(result, 0x20))))
// Allocate memory for result.
// We allocate one more word, so this array can be recycled for {split}.
mstore(0x40, add(resultEnd, 0x20))
}
}
}
/// @dev Returns a arrays of strings based on the `delimiter` inside of the `subject` string.
function split(string memory subject, string memory delimiter)
internal
pure
returns (string[] memory result)
{
uint256[] memory indices = indicesOf(subject, delimiter);
/// @solidity memory-safe-assembly
assembly {
let w := not(0x1f)
let indexPtr := add(indices, 0x20)
let indicesEnd := add(indexPtr, shl(5, add(mload(indices), 1)))
mstore(add(indicesEnd, w), mload(subject))
mstore(indices, add(mload(indices), 1))
let prevIndex := 0
for {} 1 {} {
let index := mload(indexPtr)
mstore(indexPtr, 0x60)
if iszero(eq(index, prevIndex)) {
let element := mload(0x40)
let elementLength := sub(index, prevIndex)
mstore(element, elementLength)
// Copy the `subject` one word at a time, backwards.
for { let o := and(add(elementLength, 0x1f), w) } 1 {} {
mstore(add(element, o), mload(add(add(subject, prevIndex), o)))
o := add(o, w) // `sub(o, 0x20)`.
if iszero(o) { break }
}
// Zeroize the slot after the string.
mstore(add(add(element, 0x20), elementLength), 0)
// Allocate memory for the length and the bytes,
// rounded up to a multiple of 32.
mstore(0x40, add(element, and(add(elementLength, 0x3f), w)))
// Store the `element` into the array.
mstore(indexPtr, element)
}
prevIndex := add(index, mload(delimiter))
indexPtr := add(indexPtr, 0x20)
if iszero(lt(indexPtr, indicesEnd)) { break }
}
result := indices
if iszero(mload(delimiter)) {
result := add(indices, 0x20)
mstore(result, sub(mload(indices), 2))
}
}
}
/// @dev Returns a concatenated string of `a` and `b`.
/// Cheaper than `string.concat()` and does not de-align the free memory pointer.
function concat(string memory a, string memory b)
internal
pure
returns (string memory result)
{
/// @solidity memory-safe-assembly
assembly {
let w := not(0x1f)
result := mload(0x40)
let aLength := mload(a)
// Copy `a` one word at a time, backwards.
for { let o := and(add(aLength, 0x20), w) } 1 {} {
mstore(add(result, o), mload(add(a, o)))
o := add(o, w) // `sub(o, 0x20)`.
if iszero(o) { break }
}
let bLength := mload(b)
let output := add(result, aLength)
// Copy `b` one word at a time, backwards.
for { let o := and(add(bLength, 0x20), w) } 1 {} {
mstore(add(output, o), mload(add(b, o)))
o := add(o, w) // `sub(o, 0x20)`.
if iszero(o) { break }
}
let totalLength := add(aLength, bLength)
let last := add(add(result, 0x20), totalLength)
mstore(last, 0) // Zeroize the slot after the string.
mstore(result, totalLength) // Store the length.
mstore(0x40, add(last, 0x20)) // Allocate the memory.
}
}
/// @dev Returns a copy of the string in either lowercase or UPPERCASE.
/// WARNING! This function is only compatible with 7-bit ASCII strings.
function toCase(string memory subject, bool toUpper)
internal
pure
returns (string memory result)
{
/// @solidity memory-safe-assembly
assembly {
let length := mload(subject)
if length {
result := add(mload(0x40), 0x20)
subject := add(subject, 1)
let flags := shl(add(70, shl(5, toUpper)), 0x3ffffff)
let w := not(0)
for { let o := length } 1 {} {
o := add(o, w)
let b := and(0xff, mload(add(subject, o)))
mstore8(add(result, o), xor(b, and(shr(b, flags), 0x20)))
if iszero(o) { break }
}
result := mload(0x40)
mstore(result, length) // Store the length.
let last := add(add(result, 0x20), length)
mstore(last, 0) // Zeroize the slot after the string.
mstore(0x40, add(last, 0x20)) // Allocate the memory.
}
}
}
/// @dev Returns a string from a small bytes32 string.
/// `s` must be null-terminated, or behavior will be undefined.
function fromSmallString(bytes32 s) internal pure returns (string memory result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
let n := 0
for {} byte(n, s) { n := add(n, 1) } {} // Scan for '\0'.
mstore(result, n) // Store the length.
let o := add(result, 0x20)
mstore(o, s) // Store the bytes of the string.
mstore(add(o, n), 0) // Zeroize the slot after the string.
mstore(0x40, add(result, 0x40)) // Allocate the memory.
}
}
/// @dev Returns the small string, with all bytes after the first null byte zeroized.
function normalizeSmallString(bytes32 s) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
for {} byte(result, s) { result := add(result, 1) } {} // Scan for '\0'.
mstore(0x00, s)
mstore(result, 0x00)
result := mload(0x00)
}
}
/// @dev Returns the string as a normalized null-terminated small string.
function toSmallString(string memory s) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(s)
if iszero(lt(result, 33)) {
mstore(0x00, 0xec92f9a3) // `TooBigForSmallString()`.
revert(0x1c, 0x04)
}
result := shl(shl(3, sub(32, result)), mload(add(s, result)))
}
}
/// @dev Returns a lowercased copy of the string.
/// WARNING! This function is only compatible with 7-bit ASCII strings.
function lower(string memory subject) internal pure returns (string memory result) {
result = toCase(subject, false);
}
/// @dev Returns an UPPERCASED copy of the string.
/// WARNING! This function is only compatible with 7-bit ASCII strings.
function upper(string memory subject) internal pure returns (string memory result) {
result = toCase(subject, true);
}
/// @dev Escapes the string to be used within HTML tags.
function escapeHTML(string memory s) internal pure returns (string memory result) {
/// @solidity memory-safe-assembly
assembly {
let end := add(s, mload(s))
result := add(mload(0x40), 0x20)
// Store the bytes of the packed offsets and strides into the scratch space.
// `packed = (stride << 5) | offset`. Max offset is 20. Max stride is 6.
mstore(0x1f, 0x900094)
mstore(0x08, 0xc0000000a6ab)
// Store ""&'<>" into the scratch space.
mstore(0x00, shl(64, 0x2671756f743b26616d703b262333393b266c743b2667743b))
for {} iszero(eq(s, end)) {} {
s := add(s, 1)
let c := and(mload(s), 0xff)
// Not in `["\"","'","&","<",">"]`.
if iszero(and(shl(c, 1), 0x500000c400000000)) {
mstore8(result, c)
result := add(result, 1)
continue
}
let t := shr(248, mload(c))
mstore(result, mload(and(t, 0x1f)))
result := add(result, shr(5, t))
}
let last := result
mstore(last, 0) // Zeroize the slot after the string.
result := mload(0x40)
mstore(result, sub(last, add(result, 0x20))) // Store the length.
mstore(0x40, add(last, 0x20)) // Allocate the memory.
}
}
/// @dev Escapes the string to be used within double-quotes in a JSON.
/// If `addDoubleQuotes` is true, the result will be enclosed in double-quotes.
function escapeJSON(string memory s, bool addDoubleQuotes)
internal
pure
returns (string memory result)
{
/// @solidity memory-safe-assembly
assembly {
let end := add(s, mload(s))
result := add(mload(0x40), 0x20)
if addDoubleQuotes {
mstore8(result, 34)
result := add(1, result)
}
// Store "\\u0000" in scratch space.
// Store "0123456789abcdef" in scratch space.
// Also, store `{0x08:"b", 0x09:"t", 0x0a:"n", 0x0c:"f", 0x0d:"r"}`.
// into the scratch space.
mstore(0x15, 0x5c75303030303031323334353637383961626364656662746e006672)
// Bitmask for detecting `["\"","\\"]`.
let e := or(shl(0x22, 1), shl(0x5c, 1))
for {} iszero(eq(s, end)) {} {
s := add(s, 1)
let c := and(mload(s), 0xff)
if iszero(lt(c, 0x20)) {
if iszero(and(shl(c, 1), e)) {
// Not in `["\"","\\"]`.
mstore8(result, c)
result := add(result, 1)
continue
}
mstore8(result, 0x5c) // "\\".
mstore8(add(result, 1), c)
result := add(result, 2)
continue
}
if iszero(and(shl(c, 1), 0x3700)) {
// Not in `["\b","\t","\n","\f","\d"]`.
mstore8(0x1d, mload(shr(4, c))) // Hex value.
mstore8(0x1e, mload(and(c, 15))) // Hex value.
mstore(result, mload(0x19)) // "\\u00XX".
result := add(result, 6)
continue
}
mstore8(result, 0x5c) // "\\".
mstore8(add(result, 1), mload(add(c, 8)))
result := add(result, 2)
}
if addDoubleQuotes {
mstore8(result, 34)
result := add(1, result)
}
let last := result
mstore(last, 0) // Zeroize the slot after the string.
result := mload(0x40)
mstore(result, sub(last, add(result, 0x20))) // Store the length.
mstore(0x40, add(last, 0x20)) // Allocate the memory.
}
}
/// @dev Escapes the string to be used within double-quotes in a JSON.
function escapeJSON(string memory s) internal pure returns (string memory result) {
result = escapeJSON(s, false);
}
/// @dev Returns whether `a` equals `b`.
function eq(string memory a, string memory b) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := eq(keccak256(add(a, 0x20), mload(a)), keccak256(add(b, 0x20), mload(b)))
}
}
/// @dev Returns whether `a` equals `b`, where `b` is a null-terminated small string.
function eqs(string memory a, bytes32 b) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
// These should be evaluated on compile time, as far as possible.
let m := not(shl(7, div(not(iszero(b)), 255))) // `0x7f7f ...`.
let x := not(or(m, or(b, add(m, and(b, m)))))
let r := shl(7, iszero(iszero(shr(128, x))))
r := or(r, shl(6, iszero(iszero(shr(64, shr(r, x))))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
// forgefmt: disable-next-item
result := gt(eq(mload(a), add(iszero(x), xor(31, shr(3, r)))),
xor(shr(add(8, r), b), shr(add(8, r), mload(add(a, 0x20)))))
}
}
/// @dev Packs a single string with its length into a single word.
/// Returns `bytes32(0)` if the length is zero or greater than 31.
function packOne(string memory a) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
// We don't need to zero right pad the string,
// since this is our own custom non-standard packing scheme.
result :=
mul(
// Load the length and the bytes.
mload(add(a, 0x1f)),
// `length != 0 && length < 32`. Abuses underflow.
// Assumes that the length is valid and within the block gas limit.
lt(sub(mload(a), 1), 0x1f)
)
}
}
/// @dev Unpacks a string packed using {packOne}.
/// Returns the empty string if `packed` is `bytes32(0)`.
/// If `packed` is not an output of {packOne}, the output behavior is undefined.
function unpackOne(bytes32 packed) internal pure returns (string memory result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40) // Grab the free memory pointer.
mstore(0x40, add(result, 0x40)) // Allocate 2 words (1 for the length, 1 for the bytes).
mstore(result, 0) // Zeroize the length slot.
mstore(add(result, 0x1f), packed) // Store the length and bytes.
mstore(add(add(result, 0x20), mload(result)), 0) // Right pad with zeroes.
}
}
/// @dev Packs two strings with their lengths into a single word.
/// Returns `bytes32(0)` if combined length is zero or greater than 30.
function packTwo(string memory a, string memory b) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let aLength := mload(a)
// We don't need to zero right pad the strings,
// since this is our own custom non-standard packing scheme.
result :=
mul(
or( // Load the length and the bytes of `a` and `b`.
shl(shl(3, sub(0x1f, aLength)), mload(add(a, aLength))),
mload(sub(add(b, 0x1e), aLength))
),
// `totalLength != 0 && totalLength < 31`. Abuses underflow.
// Assumes that the lengths are valid and within the block gas limit.
lt(sub(add(aLength, mload(b)), 1), 0x1e)
)
}
}
/// @dev Unpacks strings packed using {packTwo}.
/// Returns the empty strings if `packed` is `bytes32(0)`.
/// If `packed` is not an output of {packTwo}, the output behavior is undefined.
function unpackTwo(bytes32 packed)
internal
pure
returns (string memory resultA, string memory resultB)
{
/// @solidity memory-safe-assembly
assembly {
resultA := mload(0x40) // Grab the free memory pointer.
resultB := add(resultA, 0x40)
// Allocate 2 words for each string (1 for the length, 1 for the byte). Total 4 words.
mstore(0x40, add(resultB, 0x40))
// Zeroize the length slots.
mstore(resultA, 0)
mstore(resultB, 0)
// Store the lengths and bytes.
mstore(add(resultA, 0x1f), packed)
mstore(add(resultB, 0x1f), mload(add(add(resultA, 0x20), mload(resultA))))
// Right pad with zeroes.
mstore(add(add(resultA, 0x20), mload(resultA)), 0)
mstore(add(add(resultB, 0x20), mload(resultB)), 0)
}
}
/// @dev Directly returns `a` without copying.
function directReturn(string memory a) internal pure {
assembly {
// Assumes that the string does not start from the scratch space.
let retStart := sub(a, 0x20)
let retUnpaddedSize := add(mload(a), 0x40)
// Right pad with zeroes. Just in case the string is produced
// by a method that doesn't zero right pad.
mstore(add(retStart, retUnpaddedSize), 0)
mstore(retStart, 0x20) // Store the return offset.
// End the transaction, returning the string.
return(retStart, and(not(0x1f), add(0x1f, retUnpaddedSize)))
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Withdrawable} from "../abstract/Withdrawable.sol";
import {IMigration} from "../interfaces/abstract/IMigration.sol";
/**
* @title Migration
*
* @notice Provides an abstraction around an initial "migration" for contracts.
* It pauses the contract as part of its constructor.
* Also note that it is built on Withdrawable, which is built on top of Guardians, which implies Ownable and Pausable.
*/
abstract contract Migration is IMigration, Withdrawable {
// =============================================================
// IMMUTABLES
// =============================================================
/// @inheritdoc IMigration
uint24 public immutable GRACE_PERIOD;
// =============================================================
// STORAGE
// =============================================================
/// @inheritdoc IMigration
address public migrator;
/// @inheritdoc IMigration
uint40 public migratedAt;
// =============================================================
// MODIFIERS
// =============================================================
/**
* @notice Allow only the migrator to call the protected function.
* Revoke permissions after the migration period.
*/
modifier onlyMigrator() {
if (msg.sender != migrator) revert OnlyMigrator();
if (isMigrated() && block.timestamp > migratedAt + GRACE_PERIOD) {
revert PermissionRevoked();
}
_requirePaused();
_;
}
// =============================================================
// CONSTRUCTOR
// =============================================================
/**
* @notice Set the grace period and migrator address.
* Pauses contract at deployment time.
*
* @param gracePeriod_ Migration grace period in seconds.
* @param migrator_ Initial migrator address (distinct from owner).
* @param initialOwner_ Initial owner address.
*/
constructor(uint24 gracePeriod_, address migrator_, address initialOwner_) {
GRACE_PERIOD = gracePeriod_;
migrator = migrator_;
_initializeOwner(initialOwner_);
emit SetMigrator(address(0), migrator_);
_pause();
}
// =============================================================
// VIEWS
// =============================================================
/// @inheritdoc IMigration
function isMigrated() public view returns (bool) {
return migratedAt != 0;
}
// =============================================================
// MIGRATION
// =============================================================
/// @inheritdoc IMigration
function migrate() external {
if (msg.sender != migrator) revert OnlyMigrator();
if (isMigrated()) revert AlreadyMigrated();
_requirePaused();
migratedAt = uint40(block.timestamp);
emit Migrated(migratedAt);
}
// =============================================================
// SET MIGRATOR
// =============================================================
/// @inheritdoc IMigration
function setMigrator(address migrator_) public onlyOwner {
if (isMigrated()) revert AlreadyMigrated();
_requirePaused();
emit SetMigrator(migrator, migrator_);
migrator = migrator_;
}
}
// 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/IdRegistry.sol": "IdRegistry"
},
"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":"address","name":"migrator_","type":"address"},{"internalType":"address","name":"initialOwner_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AddressZero","type":"error"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"AlreadyMigrated","type":"error"},{"inputs":[],"name":"CustodyAlreadyRegistered","type":"error"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[],"name":"Frozen","type":"error"},{"inputs":[],"name":"HasNoId","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":"OnlyCustody","type":"error"},{"inputs":[],"name":"OnlyGuardian","type":"error"},{"inputs":[],"name":"OnlyIdGateway","type":"error"},{"inputs":[],"name":"OnlyMigrator","type":"error"},{"inputs":[],"name":"OnlyRecovery","type":"error"},{"inputs":[],"name":"OnlyUsernameGateway","type":"error"},{"inputs":[],"name":"OperatorAlreadyRegistered","type":"error"},{"inputs":[],"name":"OperatorCannotBeCustody","type":"error"},{"inputs":[],"name":"PermissionRevoked","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":false,"internalType":"address","name":"delegateRegistry","type":"address"}],"name":"DelegateRegistryFrozen","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldDelegateRegistry","type":"address"},{"indexed":false,"internalType":"address","name":"newDelegateRegistry","type":"address"}],"name":"DelegateRegistrySet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldIdCounter","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newIdCounter","type":"uint256"}],"name":"IdCounterSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"idGateway","type":"address"}],"name":"IdGatewayFrozen","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldIdGateway","type":"address"},{"indexed":false,"internalType":"address","name":"newIdGateway","type":"address"}],"name":"IdGatewaySet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"migratedAt","type":"uint256"}],"name":"Migrated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"newOperator","type":"address"}],"name":"OperatorAddressChanged","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":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"newRecovery","type":"address"}],"name":"RecoveryAddressChanged","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":"oldMigrator","type":"address"},{"indexed":false,"internalType":"address","name":"newMigrator","type":"address"}],"name":"SetMigrator","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"string","name":"newUsername","type":"string"}],"name":"UsernameChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"usernameGateway","type":"address"}],"name":"UsernameGatewayFrozen","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldUsernameGateway","type":"address"},{"indexed":false,"internalType":"address","name":"newUsernameGateway","type":"address"}],"name":"UsernameGatewaySet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"fromId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"toId","type":"uint256"},{"indexed":false,"internalType":"string","name":"username","type":"string"}],"name":"UsernameTransferred","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":"CHANGE_OPERATOR_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANGE_RECOVERY_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GRACE_PERIOD","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GUARDIAN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TRANSFER_AND_CHANGE_OPERATOR_AND_RECOVERY_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TRANSFER_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":[{"components":[{"internalType":"address","name":"custody","type":"address"},{"internalType":"string","name":"username","type":"string"},{"internalType":"address","name":"recovery","type":"address"}],"internalType":"struct IIdRegistry.BulkRegisterData[]","name":"data","type":"tuple[]"}],"name":"bulkRegisterIds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"custody","type":"address"},{"internalType":"string","name":"username","type":"string"}],"internalType":"struct IIdRegistry.BulkRegisterWithDefaultRecoveryData[]","name":"data","type":"tuple[]"},{"internalType":"address","name":"recovery","type":"address"}],"name":"bulkRegisterIdsWithDefaultRecovery","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"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":"struct IIdRegistry.BulkRegisterWithOperatorData[]","name":"data","type":"tuple[]"}],"name":"bulkRegisterIdsWithOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"custody","type":"address"},{"internalType":"string","name":"username","type":"string"},{"internalType":"address","name":"operator","type":"address"}],"internalType":"struct IIdRegistry.BulkRegisterWithOperatorAndDefaultRecoveryData[]","name":"data","type":"tuple[]"},{"internalType":"address","name":"recovery","type":"address"}],"name":"bulkRegisterIdsWithOperatorAndDefaultRecovery","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"actor","type":"address"},{"internalType":"address","name":"contractAddr","type":"address"},{"internalType":"bytes32","name":"rights","type":"bytes32"}],"name":"canAct","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cancelOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newOperator","type":"address"}],"name":"changeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"newOperator","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"changeOperatorFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newRecovery","type":"address"}],"name":"changeRecovery","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"newRecovery","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"changeRecoveryFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"completeOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"custodyOf","outputs":[{"internalType":"address","name":"custody","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"delegateRegistry","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"delegateRegistryFrozen","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","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":[],"name":"freezeDelegateRegistry","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"freezeIdGateway","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"freezeUsernameGateway","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"username","type":"string"}],"name":"getIdByUsername","outputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getUserById","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"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":"struct IIdRegistry.User","name":"user","type":"tuple"}],"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":[],"name":"idCounter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"idGateway","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"idGatewayFrozen","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"idOf","outputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"usernameHash","type":"bytes32"}],"name":"idOfUsernameHash","outputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"isGuardian","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isMigrated","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"migrate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"migratedAt","outputs":[{"internalType":"uint40","name":"","type":"uint40"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"migrator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"operatorOf","outputs":[{"internalType":"address","name":"operator","type":"address"}],"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":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"recover","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"recoveryDeadline","type":"uint256"},{"internalType":"bytes","name":"recoverySig","type":"bytes"},{"internalType":"uint256","name":"toDeadline","type":"uint256"},{"internalType":"bytes","name":"toSig","type":"bytes"}],"name":"recoverFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"recoveryOf","outputs":[{"internalType":"address","name":"recovery","type":"address"}],"stateMutability":"view","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"}],"name":"register","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":"delegateRegistry_","type":"address"}],"name":"setDelegateRegistry","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"counter","type":"uint256"}],"name":"setIdCounter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"idGateway_","type":"address"}],"name":"setIdGateway","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"migrator_","type":"address"}],"name":"setMigrator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"usernameGateway_","type":"address"}],"name":"setUsernameGateway","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"transfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"address","name":"newOperator","type":"address"},{"internalType":"address","name":"newRecovery","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"transferAndChangeOperatorAndRecovery","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"address","name":"newOperator","type":"address"},{"internalType":"address","name":"newRecovery","type":"address"},{"internalType":"uint256","name":"fromDeadline","type":"uint256"},{"internalType":"bytes","name":"fromSig","type":"bytes"},{"internalType":"uint256","name":"toDeadline","type":"uint256"},{"internalType":"bytes","name":"toSig","type":"bytes"}],"name":"transferAndChangeOperatorAndRecoveryFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"fromDeadline","type":"uint256"},{"internalType":"bytes","name":"fromSig","type":"bytes"},{"internalType":"uint256","name":"toDeadline","type":"uint256"},{"internalType":"bytes","name":"toSig","type":"bytes"}],"name":"transferFor","outputs":[],"stateMutability":"nonpayable","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":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"string","name":"newUsername","type":"string"}],"name":"unsafeChangeUsername","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"fromId","type":"uint256"},{"internalType":"uint256","name":"toId","type":"uint256"},{"internalType":"string","name":"newFromUsername","type":"string"}],"name":"unsafeTransferUsername","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"useNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"usernameGateway","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"usernameGatewayFrozen","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"usernameOf","outputs":[{"internalType":"string","name":"username","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes32","name":"digest","type":"bytes32"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"verifyCustodySignature","outputs":[{"internalType":"bool","name":"isValid","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes32","name":"digest","type":"bytes32"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"verifyIdSignature","outputs":[{"internalType":"bool","name":"isValid","type":"bool"}],"stateMutability":"view","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"}]