// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Contract extension for ERC-2771.
/// @author 0xkuwabatake(@0xkuwabatake)
/// @author Modified from OpenZeppelin
/// (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.0/contracts/metatx/ERC2771Context.sol)
abstract contract ERC2771Context {
///////// STORAGE /////////////////////////////////////////////////////////////////////////////O-'
/// @dev The address of trusted forwarder.
address internal _trustedForwarder;
///////// CUSTOM ERROR ////////////////////////////////////////////////////////////////////////O-'
/// @dev Caller is unauthorized trusted forwarder.
error UnauthorizedForwarder();
///////// MODIFIER ////////////////////////////////////////////////////////////////////////////O-'
/// @dev Only trusted forwarder.
modifier onlyTrustedForwarder() {
if (msg.sender != _trustedForwarder) revert UnauthorizedForwarder();
_;
}
///////// PUBLIC GETTER FUNCTIONS /////////////////////////////////////////////////////////////O-'
/// @dev Returns the currently active trusted forwarder.
function trustedForwarder() public view returns (address) {
return _trustedForwarder;
}
/// @dev Indicates whether any particular address is the trusted forwarder.
/// See: https://eips.ethereum.org/EIPS/eip-2771#protocol-support-discovery-mechanism
function isTrustedForwarder(address forwarder) public view returns (bool) {
return forwarder == trustedForwarder();
}
///////// INTERNAL SETTER FUNCTIONS ///////////////////////////////////////////////////////////O-'
/// @dev Sets `forwarder` as trusted torwarder.
function _setTrustedForwarder(address forwarder) internal {
_trustedForwarder = forwarder;
}
/// @dev Reset trusted forwarder back to zero address (default).
function _resetTrustedForwarder() internal {
_trustedForwarder = address(0);
}
///////// INTERNAL GETTER FUNCTIONS ///////////////////////////////////////////////////////////O-'
/// @dev Override for `msg.sender`.
/// Defaults to the original `msg.sender` whenever a call is not performed by
/// the trusted forwarder or the calldata length is less than 20 bytes (an address length).
function _msgSender() internal view returns (address sender) {
if (isTrustedForwarder(msg.sender)) {
// The assembly code is more direct than the Solidity version using `abi.decode`.
/// @solidity memory-safe-assembly
assembly {
sender := shr(96, calldataload(sub(calldatasize(), 20)))
}
} else {
return msg.sender;
}
}
/// @dev Override for `msg.data`.
/// Defaults to the original `msg.data` whenever a call is not performed by
/// the trusted forwarder or the calldata length is less than 20 bytes (an address length).
function _msgData() internal view returns (bytes calldata) {
if (isTrustedForwarder(msg.sender)) {
return msg.data[:msg.data.length - 20];
} else {
return msg.data;
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple ERC2981 NFT Royalty Standard implementation.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC2981.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/common/ERC2981.sol)
abstract contract ERC2981 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The royalty fee numerator exceeds the fee denominator.
error RoyaltyOverflow();
/// @dev The royalty receiver cannot be the zero address.
error RoyaltyReceiverIsZeroAddress();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The default royalty info is given by:
/// ```
/// let packed := sload(_ERC2981_MASTER_SLOT_SEED)
/// let receiver := shr(96, packed)
/// let royaltyFraction := xor(packed, shl(96, receiver))
/// ```
///
/// The per token royalty info is given by.
/// ```
/// mstore(0x00, tokenId)
/// mstore(0x20, _ERC2981_MASTER_SLOT_SEED)
/// let packed := sload(keccak256(0x00, 0x40))
/// let receiver := shr(96, packed)
/// let royaltyFraction := xor(packed, shl(96, receiver))
/// ```
uint256 private constant _ERC2981_MASTER_SLOT_SEED = 0xaa4ec00224afccfdb7;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC2981 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Checks that `_feeDenominator` is non-zero.
constructor() {
require(_feeDenominator() != 0, "Fee denominator cannot be zero.");
}
/// @dev Returns the denominator for the royalty amount.
/// Defaults to 10000, which represents fees in basis points.
/// Override this function to return a custom amount if needed.
function _feeDenominator() internal pure virtual returns (uint96) {
return 10000;
}
/// @dev Returns true if this contract implements the interface defined by `interfaceId`.
/// See: https://eips.ethereum.org/EIPS/eip-165
/// This function call must use less than 30000 gas.
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
let s := shr(224, interfaceId)
// ERC165: 0x01ffc9a7, ERC2981: 0x2a55205a.
result := or(eq(s, 0x01ffc9a7), eq(s, 0x2a55205a))
}
}
/// @dev Returns the `receiver` and `royaltyAmount` for `tokenId` sold at `salePrice`.
function royaltyInfo(uint256 tokenId, uint256 salePrice)
public
view
virtual
returns (address receiver, uint256 royaltyAmount)
{
uint256 feeDenominator = _feeDenominator();
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, tokenId)
mstore(0x20, _ERC2981_MASTER_SLOT_SEED)
let packed := sload(keccak256(0x00, 0x40))
receiver := shr(96, packed)
if iszero(receiver) {
packed := sload(mload(0x20))
receiver := shr(96, packed)
}
let x := salePrice
let y := xor(packed, shl(96, receiver)) // `feeNumerator`.
// Overflow check, equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
// Out-of-gas revert. Should not be triggered in practice, but included for safety.
returndatacopy(returndatasize(), returndatasize(), mul(y, gt(x, div(not(0), y))))
royaltyAmount := div(mul(x, y), feeDenominator)
}
}
/// @dev Sets the default royalty `receiver` and `feeNumerator`.
///
/// Requirements:
/// - `receiver` must not be the zero address.
/// - `feeNumerator` must not be greater than the fee denominator.
function _setDefaultRoyalty(address receiver, uint96 feeNumerator) internal virtual {
uint256 feeDenominator = _feeDenominator();
/// @solidity memory-safe-assembly
assembly {
feeNumerator := shr(160, shl(160, feeNumerator))
if gt(feeNumerator, feeDenominator) {
mstore(0x00, 0x350a88b3) // `RoyaltyOverflow()`.
revert(0x1c, 0x04)
}
let packed := shl(96, receiver)
if iszero(packed) {
mstore(0x00, 0xb4457eaa) // `RoyaltyReceiverIsZeroAddress()`.
revert(0x1c, 0x04)
}
sstore(_ERC2981_MASTER_SLOT_SEED, or(packed, feeNumerator))
}
}
/// @dev Sets the default royalty `receiver` and `feeNumerator` to zero.
function _deleteDefaultRoyalty() internal virtual {
/// @solidity memory-safe-assembly
assembly {
sstore(_ERC2981_MASTER_SLOT_SEED, 0)
}
}
/// @dev Sets the royalty `receiver` and `feeNumerator` for `tokenId`.
///
/// Requirements:
/// - `receiver` must not be the zero address.
/// - `feeNumerator` must not be greater than the fee denominator.
function _setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator)
internal
virtual
{
uint256 feeDenominator = _feeDenominator();
/// @solidity memory-safe-assembly
assembly {
feeNumerator := shr(160, shl(160, feeNumerator))
if gt(feeNumerator, feeDenominator) {
mstore(0x00, 0x350a88b3) // `RoyaltyOverflow()`.
revert(0x1c, 0x04)
}
let packed := shl(96, receiver)
if iszero(packed) {
mstore(0x00, 0xb4457eaa) // `RoyaltyReceiverIsZeroAddress()`.
revert(0x1c, 0x04)
}
mstore(0x00, tokenId)
mstore(0x20, _ERC2981_MASTER_SLOT_SEED)
sstore(keccak256(0x00, 0x40), or(packed, feeNumerator))
}
}
/// @dev Sets the royalty `receiver` and `feeNumerator` for `tokenId` to zero.
function _resetTokenRoyalty(uint256 tokenId) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, tokenId)
mstore(0x20, _ERC2981_MASTER_SLOT_SEED)
sstore(keccak256(0x00, 0x40), 0)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple ERC721 implementation with storage hitchhiking.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC721.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC721/ERC721.sol)
///
/// @dev Note:
/// - The ERC721 standard allows for self-approvals.
/// For performance, this implementation WILL NOT revert for such actions.
/// Please add any checks with overrides if desired.
/// - For performance, methods are made payable where permitted by the ERC721 standard.
/// - The `safeTransfer` functions use the identity precompile (0x4)
/// to copy memory internally.
///
/// If you are overriding:
/// - NEVER violate the ERC721 invariant:
/// the balance of an owner MUST always be equal to their number of ownership slots.
/// The transfer functions do not have an underflow guard for user token balances.
/// - Make sure all variables written to storage are properly cleaned
// (e.g. the bool value for `isApprovedForAll` MUST be either 1 or 0 under the hood).
/// - Check that the overridden function is actually used in the function you want to
/// change the behavior of. Much of the code has been manually inlined for performance.
abstract contract ERC721 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev An account can hold up to 4294967295 tokens.
uint256 internal constant _MAX_ACCOUNT_BALANCE = 0xffffffff;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Only the token owner or an approved account can manage the token.
error NotOwnerNorApproved();
/// @dev The token does not exist.
error TokenDoesNotExist();
/// @dev The token already exists.
error TokenAlreadyExists();
/// @dev Cannot query the balance for the zero address.
error BalanceQueryForZeroAddress();
/// @dev Cannot mint or transfer to the zero address.
error TransferToZeroAddress();
/// @dev The token must be owned by `from`.
error TransferFromIncorrectOwner();
/// @dev The recipient's balance has overflowed.
error AccountBalanceOverflow();
/// @dev Cannot safely transfer to a contract that does not implement
/// the ERC721Receiver interface.
error TransferToNonERC721ReceiverImplementer();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Emitted when token `id` is transferred from `from` to `to`.
event Transfer(address indexed from, address indexed to, uint256 indexed id);
/// @dev Emitted when `owner` enables `account` to manage the `id` token.
event Approval(address indexed owner, address indexed account, uint256 indexed id);
/// @dev Emitted when `owner` enables or disables `operator` to manage all of their tokens.
event ApprovalForAll(address indexed owner, address indexed operator, bool isApproved);
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
/// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.
uint256 private constant _APPROVAL_EVENT_SIGNATURE =
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
/// @dev `keccak256(bytes("ApprovalForAll(address,address,bool)"))`.
uint256 private constant _APPROVAL_FOR_ALL_EVENT_SIGNATURE =
0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ownership data slot of `id` is given by:
/// ```
/// mstore(0x00, id)
/// mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
/// let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
/// ```
/// Bits Layout:
/// - [0..159] `addr`
/// - [160..255] `extraData`
///
/// The approved address slot is given by: `add(1, ownershipSlot)`.
///
/// See: https://notes.ethereum.org/%40vbuterin/verkle_tree_eip
///
/// The balance slot of `owner` is given by:
/// ```
/// mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
/// mstore(0x00, owner)
/// let balanceSlot := keccak256(0x0c, 0x1c)
/// ```
/// Bits Layout:
/// - [0..31] `balance`
/// - [32..255] `aux`
///
/// The `operator` approval slot of `owner` is given by:
/// ```
/// mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, operator))
/// mstore(0x00, owner)
/// let operatorApprovalSlot := keccak256(0x0c, 0x30)
/// ```
uint256 private constant _ERC721_MASTER_SLOT_SEED = 0x7d8825530a5a2e7a << 192;
/// @dev Pre-shifted and pre-masked constant.
uint256 private constant _ERC721_MASTER_SLOT_SEED_MASKED = 0x0a5a2e7a00000000;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC721 METADATA */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the token collection name.
function name() public view virtual returns (string memory);
/// @dev Returns the token collection symbol.
function symbol() public view virtual returns (string memory);
/// @dev Returns the Uniform Resource Identifier (URI) for token `id`.
function tokenURI(uint256 id) public view virtual returns (string memory);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC721 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the owner of token `id`.
///
/// Requirements:
/// - Token `id` must exist.
function ownerOf(uint256 id) public view virtual returns (address result) {
result = _ownerOf(id);
/// @solidity memory-safe-assembly
assembly {
if iszero(result) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Returns the number of tokens owned by `owner`.
///
/// Requirements:
/// - `owner` must not be the zero address.
function balanceOf(address owner) public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
// Revert if the `owner` is the zero address.
if iszero(owner) {
mstore(0x00, 0x8f4eb604) // `BalanceQueryForZeroAddress()`.
revert(0x1c, 0x04)
}
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
mstore(0x00, owner)
result := and(sload(keccak256(0x0c, 0x1c)), _MAX_ACCOUNT_BALANCE)
}
}
/// @dev Returns the account approved to manage token `id`.
///
/// Requirements:
/// - Token `id` must exist.
function getApproved(uint256 id) public view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
if iszero(shl(96, sload(ownershipSlot))) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
result := sload(add(1, ownershipSlot))
}
}
/// @dev Sets `account` as the approved account to manage token `id`.
///
/// Requirements:
/// - Token `id` must exist.
/// - The caller must be the owner of the token,
/// or an approved operator for the token owner.
///
/// Emits an {Approval} event.
function approve(address account, uint256 id) public payable virtual {
_approve(msg.sender, account, id);
}
/// @dev Returns whether `operator` is approved to manage the tokens of `owner`.
function isApprovedForAll(address owner, address operator)
public
view
virtual
returns (bool result)
{
/// @solidity memory-safe-assembly
assembly {
mstore(0x1c, operator)
mstore(0x08, _ERC721_MASTER_SLOT_SEED_MASKED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x30))
}
}
/// @dev Sets whether `operator` is approved to manage the tokens of the caller.
///
/// Emits an {ApprovalForAll} event.
function setApprovalForAll(address operator, bool isApproved) public virtual {
/// @solidity memory-safe-assembly
assembly {
// Convert to 0 or 1.
isApproved := iszero(iszero(isApproved))
// Update the `isApproved` for (`msg.sender`, `operator`).
mstore(0x1c, operator)
mstore(0x08, _ERC721_MASTER_SLOT_SEED_MASKED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x30), isApproved)
// Emit the {ApprovalForAll} event.
mstore(0x00, isApproved)
// forgefmt: disable-next-item
log3(0x00, 0x20, _APPROVAL_FOR_ALL_EVENT_SIGNATURE, caller(), shr(96, shl(96, operator)))
}
}
/// @dev Transfers token `id` from `from` to `to`.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - `from` must be the owner of the token.
/// - `to` cannot be the zero address.
/// - The caller must be the owner of the token, or be approved to manage the token.
///
/// Emits a {Transfer} event.
function transferFrom(address from, address to, uint256 id) public payable virtual {
_beforeTokenTransfer(from, to, id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
let bitmaskAddress := shr(96, not(0))
from := and(bitmaskAddress, from)
to := and(bitmaskAddress, to)
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, caller()))
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked := sload(ownershipSlot)
let owner := and(bitmaskAddress, ownershipPacked)
// Revert if the token does not exist, or if `from` is not the owner.
if iszero(mul(owner, eq(owner, from))) {
// `TokenDoesNotExist()`, `TransferFromIncorrectOwner()`.
mstore(shl(2, iszero(owner)), 0xceea21b6a1148100)
revert(0x1c, 0x04)
}
// Load, check, and update the token approval.
{
mstore(0x00, from)
let approvedAddress := sload(add(1, ownershipSlot))
// Revert if the caller is not the owner, nor approved.
if iszero(or(eq(caller(), from), eq(caller(), approvedAddress))) {
if iszero(sload(keccak256(0x0c, 0x30))) {
mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
revert(0x1c, 0x04)
}
}
// Delete the approved address if any.
if approvedAddress { sstore(add(1, ownershipSlot), 0) }
}
// Update with the new owner.
sstore(ownershipSlot, xor(ownershipPacked, xor(from, to)))
// Decrement the balance of `from`.
{
let fromBalanceSlot := keccak256(0x0c, 0x1c)
sstore(fromBalanceSlot, sub(sload(fromBalanceSlot), 1))
}
// Increment the balance of `to`.
{
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x1c)
let toBalanceSlotPacked := add(sload(toBalanceSlot), 1)
// Revert if `to` is the zero address, or if the account balance overflows.
if iszero(mul(to, and(toBalanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(toBalanceSlot, toBalanceSlotPacked)
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, id)
}
_afterTokenTransfer(from, to, id);
}
/// @dev Equivalent to `safeTransferFrom(from, to, id, "")`.
function safeTransferFrom(address from, address to, uint256 id) public payable virtual {
transferFrom(from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, "");
}
/// @dev Transfers token `id` from `from` to `to`.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - `from` must be the owner of the token.
/// - `to` cannot be the zero address.
/// - The caller must be the owner of the token, or be approved to manage the token.
/// - 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 id, bytes calldata data)
public
payable
virtual
{
transferFrom(from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
}
/// @dev Returns true if this contract implements the interface defined by `interfaceId`.
/// See: https://eips.ethereum.org/EIPS/eip-165
/// This function call must use less than 30000 gas.
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
let s := shr(224, interfaceId)
// ERC165: 0x01ffc9a7, ERC721: 0x80ac58cd, ERC721Metadata: 0x5b5e139f.
result := or(or(eq(s, 0x01ffc9a7), eq(s, 0x80ac58cd)), eq(s, 0x5b5e139f))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL QUERY FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns if token `id` exists.
function _exists(uint256 id) internal view virtual returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result := iszero(iszero(shl(96, sload(add(id, add(id, keccak256(0x00, 0x20)))))))
}
}
/// @dev Returns the owner of token `id`.
/// Returns the zero address instead of reverting if the token does not exist.
function _ownerOf(uint256 id) internal view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result := shr(96, shl(96, sload(add(id, add(id, keccak256(0x00, 0x20))))))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL DATA HITCHHIKING FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// For performance, no events are emitted for the hitchhiking setters.
// Please emit your own events if required.
/// @dev Returns the auxiliary data for `owner`.
/// Minting, transferring, burning the tokens of `owner` will not change the auxiliary data.
/// Auxiliary data can be set for any address, even if it does not have any tokens.
function _getAux(address owner) internal view virtual returns (uint224 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
mstore(0x00, owner)
result := shr(32, sload(keccak256(0x0c, 0x1c)))
}
}
/// @dev Set the auxiliary data for `owner` to `value`.
/// Minting, transferring, burning the tokens of `owner` will not change the auxiliary data.
/// Auxiliary data can be set for any address, even if it does not have any tokens.
function _setAux(address owner, uint224 value) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
mstore(0x00, owner)
let balanceSlot := keccak256(0x0c, 0x1c)
let packed := sload(balanceSlot)
sstore(balanceSlot, xor(packed, shl(32, xor(value, shr(32, packed)))))
}
}
/// @dev Returns the extra data for token `id`.
/// Minting, transferring, burning a token will not change the extra data.
/// The extra data can be set on a non-existent token.
function _getExtraData(uint256 id) internal view virtual returns (uint96 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result := shr(160, sload(add(id, add(id, keccak256(0x00, 0x20)))))
}
}
/// @dev Sets the extra data for token `id` to `value`.
/// Minting, transferring, burning a token will not change the extra data.
/// The extra data can be set on a non-existent token.
function _setExtraData(uint256 id, uint96 value) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let packed := sload(ownershipSlot)
sstore(ownershipSlot, xor(packed, shl(160, xor(value, shr(160, packed)))))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL MINT FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Mints token `id` to `to`.
///
/// Requirements:
///
/// - Token `id` must not exist.
/// - `to` cannot be the zero address.
///
/// Emits a {Transfer} event.
function _mint(address to, uint256 id) internal virtual {
_beforeTokenTransfer(address(0), to, id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
to := shr(96, shl(96, to))
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked := sload(ownershipSlot)
// Revert if the token already exists.
if shl(96, ownershipPacked) {
mstore(0x00, 0xc991cbb1) // `TokenAlreadyExists()`.
revert(0x1c, 0x04)
}
// Update with the owner.
sstore(ownershipSlot, or(ownershipPacked, to))
// Increment the balance of the owner.
{
mstore(0x00, to)
let balanceSlot := keccak256(0x0c, 0x1c)
let balanceSlotPacked := add(sload(balanceSlot), 1)
// Revert if `to` is the zero address, or if the account balance overflows.
if iszero(mul(to, and(balanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(balanceSlot, balanceSlotPacked)
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, 0, to, id)
}
_afterTokenTransfer(address(0), to, id);
}
/// @dev Mints token `id` to `to`, and updates the extra data for token `id` to `value`.
/// Does NOT check if token `id` already exists (assumes `id` is auto-incrementing).
///
/// Requirements:
///
/// - `to` cannot be the zero address.
///
/// Emits a {Transfer} event.
function _mintAndSetExtraDataUnchecked(address to, uint256 id, uint96 value) internal virtual {
_beforeTokenTransfer(address(0), to, id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
to := shr(96, shl(96, to))
// Update with the owner and extra data.
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
sstore(add(id, add(id, keccak256(0x00, 0x20))), or(shl(160, value), to))
// Increment the balance of the owner.
{
mstore(0x00, to)
let balanceSlot := keccak256(0x0c, 0x1c)
let balanceSlotPacked := add(sload(balanceSlot), 1)
// Revert if `to` is the zero address, or if the account balance overflows.
if iszero(mul(to, and(balanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(balanceSlot, balanceSlotPacked)
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, 0, to, id)
}
_afterTokenTransfer(address(0), to, id);
}
/// @dev Equivalent to `_safeMint(to, id, "")`.
function _safeMint(address to, uint256 id) internal virtual {
_safeMint(to, id, "");
}
/// @dev Mints token `id` to `to`.
///
/// Requirements:
///
/// - Token `id` must not exist.
/// - `to` cannot be the zero address.
/// - If `to` refers to a smart contract, it must implement
/// {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
///
/// Emits a {Transfer} event.
function _safeMint(address to, uint256 id, bytes memory data) internal virtual {
_mint(to, id);
if (_hasCode(to)) _checkOnERC721Received(address(0), to, id, data);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL BURN FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to `_burn(address(0), id)`.
function _burn(uint256 id) internal virtual {
_burn(address(0), id);
}
/// @dev Destroys token `id`, using `by`.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - If `by` is not the zero address,
/// it must be the owner of the token, or be approved to manage the token.
///
/// Emits a {Transfer} event.
function _burn(address by, uint256 id) internal virtual {
address owner = ownerOf(id);
_beforeTokenTransfer(owner, address(0), id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
by := shr(96, shl(96, by))
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked := sload(ownershipSlot)
// Reload the owner in case it is changed in `_beforeTokenTransfer`.
owner := shr(96, shl(96, ownershipPacked))
// Revert if the token does not exist.
if iszero(owner) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
// Load and check the token approval.
{
mstore(0x00, owner)
let approvedAddress := sload(add(1, ownershipSlot))
// If `by` is not the zero address, do the authorization check.
// Revert if the `by` is not the owner, nor approved.
if iszero(or(iszero(by), or(eq(by, owner), eq(by, approvedAddress)))) {
if iszero(sload(keccak256(0x0c, 0x30))) {
mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
revert(0x1c, 0x04)
}
}
// Delete the approved address if any.
if approvedAddress { sstore(add(1, ownershipSlot), 0) }
}
// Clear the owner.
sstore(ownershipSlot, xor(ownershipPacked, owner))
// Decrement the balance of `owner`.
{
let balanceSlot := keccak256(0x0c, 0x1c)
sstore(balanceSlot, sub(sload(balanceSlot), 1))
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, owner, 0, id)
}
_afterTokenTransfer(owner, address(0), id);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL APPROVAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns whether `account` is the owner of token `id`, or is approved to manage it.
///
/// Requirements:
/// - Token `id` must exist.
function _isApprovedOrOwner(address account, uint256 id)
internal
view
virtual
returns (bool result)
{
/// @solidity memory-safe-assembly
assembly {
result := 1
// Clear the upper 96 bits.
account := shr(96, shl(96, account))
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, account))
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let owner := shr(96, shl(96, sload(ownershipSlot)))
// Revert if the token does not exist.
if iszero(owner) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
// Check if `account` is the `owner`.
if iszero(eq(account, owner)) {
mstore(0x00, owner)
// Check if `account` is approved to manage the token.
if iszero(sload(keccak256(0x0c, 0x30))) {
result := eq(account, sload(add(1, ownershipSlot)))
}
}
}
}
/// @dev Returns the account approved to manage token `id`.
/// Returns the zero address instead of reverting if the token does not exist.
function _getApproved(uint256 id) internal view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result := sload(add(1, add(id, add(id, keccak256(0x00, 0x20)))))
}
}
/// @dev Equivalent to `_approve(address(0), account, id)`.
function _approve(address account, uint256 id) internal virtual {
_approve(address(0), account, id);
}
/// @dev Sets `account` as the approved account to manage token `id`, using `by`.
///
/// Requirements:
/// - Token `id` must exist.
/// - If `by` is not the zero address, `by` must be the owner
/// or an approved operator for the token owner.
///
/// Emits a {Approval} event.
function _approve(address by, address account, uint256 id) internal virtual {
assembly {
// Clear the upper 96 bits.
let bitmaskAddress := shr(96, not(0))
account := and(bitmaskAddress, account)
by := and(bitmaskAddress, by)
// Load the owner of the token.
mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let owner := and(bitmaskAddress, sload(ownershipSlot))
// Revert if the token does not exist.
if iszero(owner) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
// If `by` is not the zero address, do the authorization check.
// Revert if `by` is not the owner, nor approved.
if iszero(or(iszero(by), eq(by, owner))) {
mstore(0x00, owner)
if iszero(sload(keccak256(0x0c, 0x30))) {
mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
revert(0x1c, 0x04)
}
}
// Sets `account` as the approved account to manage `id`.
sstore(add(1, ownershipSlot), account)
// Emit the {Approval} event.
log4(codesize(), 0x00, _APPROVAL_EVENT_SIGNATURE, owner, account, id)
}
}
/// @dev Approve or remove the `operator` as an operator for `by`,
/// without authorization checks.
///
/// Emits an {ApprovalForAll} event.
function _setApprovalForAll(address by, address operator, bool isApproved) internal virtual {
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
by := shr(96, shl(96, by))
operator := shr(96, shl(96, operator))
// Convert to 0 or 1.
isApproved := iszero(iszero(isApproved))
// Update the `isApproved` for (`by`, `operator`).
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, operator))
mstore(0x00, by)
sstore(keccak256(0x0c, 0x30), isApproved)
// Emit the {ApprovalForAll} event.
mstore(0x00, isApproved)
log3(0x00, 0x20, _APPROVAL_FOR_ALL_EVENT_SIGNATURE, by, operator)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL TRANSFER FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to `_transfer(address(0), from, to, id)`.
function _transfer(address from, address to, uint256 id) internal virtual {
_transfer(address(0), from, to, id);
}
/// @dev Transfers token `id` from `from` to `to`.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - `from` must be the owner of the token.
/// - `to` cannot be the zero address.
/// - If `by` is not the zero address,
/// it must be the owner of the token, or be approved to manage the token.
///
/// Emits a {Transfer} event.
function _transfer(address by, address from, address to, uint256 id) internal virtual {
_beforeTokenTransfer(from, to, id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
let bitmaskAddress := shr(96, not(0))
from := and(bitmaskAddress, from)
to := and(bitmaskAddress, to)
by := and(bitmaskAddress, by)
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked := sload(ownershipSlot)
let owner := and(bitmaskAddress, ownershipPacked)
// Revert if the token does not exist, or if `from` is not the owner.
if iszero(mul(owner, eq(owner, from))) {
// `TokenDoesNotExist()`, `TransferFromIncorrectOwner()`.
mstore(shl(2, iszero(owner)), 0xceea21b6a1148100)
revert(0x1c, 0x04)
}
// Load, check, and update the token approval.
{
mstore(0x00, from)
let approvedAddress := sload(add(1, ownershipSlot))
// If `by` is not the zero address, do the authorization check.
// Revert if the `by` is not the owner, nor approved.
if iszero(or(iszero(by), or(eq(by, from), eq(by, approvedAddress)))) {
if iszero(sload(keccak256(0x0c, 0x30))) {
mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
revert(0x1c, 0x04)
}
}
// Delete the approved address if any.
if approvedAddress { sstore(add(1, ownershipSlot), 0) }
}
// Update with the new owner.
sstore(ownershipSlot, xor(ownershipPacked, xor(from, to)))
// Decrement the balance of `from`.
{
let fromBalanceSlot := keccak256(0x0c, 0x1c)
sstore(fromBalanceSlot, sub(sload(fromBalanceSlot), 1))
}
// Increment the balance of `to`.
{
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x1c)
let toBalanceSlotPacked := add(sload(toBalanceSlot), 1)
// Revert if `to` is the zero address, or if the account balance overflows.
if iszero(mul(to, and(toBalanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(toBalanceSlot, toBalanceSlotPacked)
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, id)
}
_afterTokenTransfer(from, to, id);
}
/// @dev Equivalent to `_safeTransfer(from, to, id, "")`.
function _safeTransfer(address from, address to, uint256 id) internal virtual {
_safeTransfer(from, to, id, "");
}
/// @dev Transfers token `id` from `from` to `to`.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - `from` must be the owner of the token.
/// - `to` cannot be the zero address.
/// - The caller must be the owner of the token, or be approved to manage the token.
/// - If `to` refers to a smart contract, it must implement
/// {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
///
/// Emits a {Transfer} event.
function _safeTransfer(address from, address to, uint256 id, bytes memory data)
internal
virtual
{
_transfer(address(0), from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
}
/// @dev Equivalent to `_safeTransfer(by, from, to, id, "")`.
function _safeTransfer(address by, address from, address to, uint256 id) internal virtual {
_safeTransfer(by, from, to, id, "");
}
/// @dev Transfers token `id` from `from` to `to`.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - `from` must be the owner of the token.
/// - `to` cannot be the zero address.
/// - If `by` is not the zero address,
/// it must be the owner of the token, or be approved to manage the token.
/// - If `to` refers to a smart contract, it must implement
/// {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
///
/// Emits a {Transfer} event.
function _safeTransfer(address by, address from, address to, uint256 id, bytes memory data)
internal
virtual
{
_transfer(by, from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HOOKS FOR OVERRIDING */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Hook that is called before any token transfers, including minting and burning.
function _beforeTokenTransfer(address from, address to, uint256 id) internal virtual {}
/// @dev Hook that is called after any token transfers, including minting and burning.
function _afterTokenTransfer(address from, address to, uint256 id) internal virtual {}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PRIVATE HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns if `a` has bytecode of non-zero length.
function _hasCode(address a) private view returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := extcodesize(a) // Can handle dirty upper bits.
}
}
/// @dev Perform a call to invoke {IERC721Receiver-onERC721Received} on `to`.
/// Reverts if the target does not support the function correctly.
function _checkOnERC721Received(address from, address to, uint256 id, bytes memory data)
private
{
/// @solidity memory-safe-assembly
assembly {
// Prepare the calldata.
let m := mload(0x40)
let onERC721ReceivedSelector := 0x150b7a02
mstore(m, onERC721ReceivedSelector)
mstore(add(m, 0x20), caller()) // The `operator`, which is always `msg.sender`.
mstore(add(m, 0x40), shr(96, shl(96, from)))
mstore(add(m, 0x60), id)
mstore(add(m, 0x80), 0x80)
let n := mload(data)
mstore(add(m, 0xa0), n)
if n { pop(staticcall(gas(), 4, add(data, 0x20), n, add(m, 0xc0), n)) }
// Revert if the call reverts.
if iszero(call(gas(), to, 0, add(m, 0x1c), add(n, 0xa4), m, 0x20)) {
if returndatasize() {
// Bubble up the revert if the call reverts.
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
}
// Load the returndata and compare it.
if iszero(eq(mload(m), shl(224, onERC721ReceivedSelector))) {
mstore(0x00, 0xd1a57ed6) // `TransferToNonERC721ReceiverImplementer()`.
revert(0x1c, 0x04)
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "solady/tokens/ERC721.sol";
import "./extensions/TierLifeCycle.sol";
/// @notice Simple but highly customized ERC721 contract extension to create a tier-based NFT with life cycle collection.
/// @author 0xkuwabatake(@0xkuwabatake)
abstract contract ERC721TLC is ERC721, TierLifeCycle {
///////// STORAGE /////////////////////////////////////////////////////////////////////////////O-'
/// @dev The next token ID to be minted.
uint256 private _currentIndex;
/// @dev Token name.
string private _name;
/// @dev Token symbol.
string private _symbol;
///////// CUSTOM EVENT ////////////////////////////////////////////////////////////////////////O-'
/// @dev Emitted when `tokenId` is minted at `atTimestamp` and set to `tierId`.
event TierSet (uint256 indexed tokenId, uint256 indexed tierId, uint256 indexed atTimeStamp);
///////// CONSTRUCTOR /////////////////////////////////////////////////////////////////////////O-'
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
_currentIndex = _startTokenId();
}
///////// PUBLIC GETTER FUNCTIONS /////////////////////////////////////////////////////////////O-'
/// @dev Returns the token collection name.
function name() public view virtual override returns (string memory) {
return _name;
}
/// @dev Returns the token collection symbol.
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
///////// ERC721T GETTERS /////////
/// @dev Returns tier ID from `tokenId`.
function tierId(uint256 tokenId) public view returns (uint256) {
uint96 _unpacked = _getExtraData(tokenId);
uint16 _tierId = uint16(_unpacked);
return uint256(_tierId);
}
/// @dev Returns token timestamp as seconds since unix epoch for `tokenId`.
function tokenTimestamp(uint256 tokenId) public view returns (uint256) {
uint96 _unpacked = _getExtraData(tokenId);
uint40 _tokenTimestamp = uint40(_unpacked >> 16);
return uint256(_tokenTimestamp);
}
/// @dev Returns tier IDs owned by `owner`.
function tiersOfOwner(address owner) public view returns (uint256[] memory) {
uint256[] memory _tokenIds = tokensOfOwner(owner);
uint256[] memory _tierIds = new uint256[](_tokenIds.length);
uint256 i;
while (i < _tierIds.length) {
_tierIds[i] = tierId(_tokenIds[i]);
unchecked { ++i; }
}
return _tierIds;
}
/// @dev Returns token IDs owned by `owner`.
function tokensOfOwner(address owner) public view returns (uint256[] memory) {
uint256 _balance = balanceOf(owner);
uint256[] memory _tokenIds = new uint256[](_balance);
uint256 i;
uint256 j = _startTokenId();
while (i < _balance) {
if (_exists(j) && ownerOf(j) == owner) {
_tokenIds[i++] = j;
}
unchecked { ++j; }
}
return _tokenIds;
}
/// @dev Returns the total number of tokens in existence.
function totalSupply() public view returns (uint256) {
return _sub(_currentIndex, _startTokenId());
}
///////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////////O-'
///////// INTERNAL ERC721T SAFE MINT LOGIC FUNCTION /////////
/// @dev Safe mints single quantity of token ID to `to` and set to `tier`.
/// Note: `tier` must not be zero and it must be validated at child contract.
/// See: {_setMintExtraData}, {ERC721 - _safeMint}.
function _safeMintTier(address to, uint256 tier) internal {
uint256 _tokenId = _nextTokenId();
unchecked {
++_currentIndex;
}
_setMintExtraData(_tokenId, tier);
_safeMint(to, _tokenId);
emit TierSet(_tokenId, tier, block.timestamp);
}
///////// INTERNAL ERC721T MINT EXTRA DATA SETTER //////////
/// @dev Sets mint extra data for `tokenId` to `tier`, `block.timestamp` and life cycle `tier`.
/// See: {TierLifeCycle - lifeCycle}, {ERC721 - _setExtraData}.
function _setMintExtraData(uint256 tokenId, uint256 tier) internal {
uint96 _packed = uint96(tier) | // 2 bytes - `tier`
uint96(block.timestamp) << 16 | // 5 bytes - block.timestamp
uint96(lifeCycle(tier)) << 56; // 5 bytes - life cycle for `tier`
_setExtraData(tokenId, _packed);
}
///////// INTERNAL LIFE CYCLE TOKEN GETTER /////////
/// @dev Returns life cycle for `tokenId` in total seconds.
/// Note: It's intended to be queried by a public function at {ERC721TLCToken - lifeCycleToken}.
function _lifeCycleToken(uint256 tokenId) internal view returns (uint256) {
uint96 _unpacked = _getExtraData(tokenId);
uint40 lifeCycleToken_ = uint40(_unpacked >> 56);
return uint256(lifeCycleToken_);
}
///////// INTERNAL TOKEN COUNTER GETTERS /////////
/// @dev Returns the next token ID to be minted.
function _nextTokenId() internal view returns (uint256) {
return _currentIndex;
}
/// @dev The starting token ID for sequential mints is 1 (one).
function _startTokenId() internal pure returns (uint256) {
return 1;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./ERC721TLCToken.sol";
import "../utils/TLCLib.sol";
/// @notice A customized ERC721TLC contract extension to manage NFT metadata based on its latest token life cycle status.
/// @author 0xkuwabatake (@0xkuwabatake)
abstract contract ERC721TLCDataURI is ERC721TLCToken {
using TLCLib for *;
///////// STRUCT //////////////////////////////////////////////////////////////////////////////O-'
/// @dev Custom type represents dataURI for tier ID NFT metadata.
struct DataURI {
string name;
string description;
string tierName;
string[2] images;
string[2] animationURLs;
}
///////// STORAGE /////////////////////////////////////////////////////////////////////////////O-'
/// @dev Mapping from `tierId` => `DataURI`.
mapping (uint256 => DataURI) private _tierDataURI;
///////// CUSTOM EVENT ////////////////////////////////////////////////////////////////////////O-'
/// @dev Emitted when `tierDataURI` is updated to `tierId`.
event TierDataURIUpdate (
uint256 indexed tierId,
string name,
string description,
string tierName,
string[2] images,
string[2] animationURLs
);
///////// PUBLIC GETTERS FUNCTIONS ////////////////////////////////////////////////////////////O-'
/// @dev Returns the Uniform Resource Identifier (URI) for `tokenId`.
///
/// Note:
/// The value returns dynamically based on `tokenId`'s status and its `_tierId`'s life cycle status.
/// - Expiry date's trait is only showing up with following conditions:
/// - when life cycle status is Live(3) and start of life cycle has started or
/// - when life cycle status is Paused(4) and hasn't passed the pause of life cycle timestamp or
/// - when life cycle status is Ending(5) and hasn't passed the end of life cycle timestamp.
///
/// See: {ERC721Metadata - tokenURI}, {TierLifeCycle - LifeCycleStatus}, {ERC721TLCToken - tokenStatus}.
/// ```
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
if (!_exists(tokenId)) _revert(TokenDoesNotExist.selector);
uint256 _tierId = tierId(tokenId);
if (
(lifeCycleStatus(_tierId) == LifeCycleStatus.Live && block.timestamp >= startOfLifeCycle(_tierId))
|| (lifeCycleStatus(_tierId) == LifeCycleStatus.Paused && block.timestamp <= pauseOfLifeCycle(_tierId))
|| (lifeCycleStatus(_tierId) == LifeCycleStatus.Ending && block.timestamp <= endOfLifeCycle(_tierId))
) {
return string(
abi.encodePacked(
"data:application/json;base64,",
TLCLib.toBase64(bytes(string.concat(_header(tokenId), _body(tokenId), _expiryDate(tokenId), '"}]}')))
)
);
} else {
return string(
abi.encodePacked(
"data:application/json;base64,",
TLCLib.toBase64(bytes(string.concat(_header(tokenId), _body(tokenId), '"}]}')))
)
);
}
}
/// @dev Returns DataURI for `tierId`.
function tierDataURI(uint256 tierId) public view returns (DataURI memory) {
return _tierDataURI[tierId];
}
///////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////////O-'
///////// INTERNAL TIER DATA URI SETTER /////////
/// @dev Sets tier DataURI for `tierId`.
/// See: {ERC721TLCToken - _emitMetadataUpdate}.
function _setTierDataURI(
uint256 tierId,
string memory name,
string memory description,
string memory tierName,
string[2] memory images,
string[2] memory animationURLs
) internal {
_tierDataURI[tierId] = DataURI(name, description, tierName, images, animationURLs);
emit TierDataURIUpdate(tierId, name, description, tierName, images, animationURLs);
if (totalSupply() != 0) _emitMetadataUpdate(_startTokenId(), totalSupply());
}
///////// PRIVATE FUNCTIONS ///////////////////////////////////////////////////////////////////O-'
///////// PRIVATE TOKEN METADATA GETTERS /////////
/// @dev Returns metadata header for `tokenId`.
/// See: {ERC721TLCToken - _tokenStatus}.
function _header(uint256 tokenId) private view returns (string memory) {
uint256 _tierId = tierId(tokenId);
uint256 _tokenStatus = _tokenStatus(tokenId);
return string(
abi.encodePacked(
'{"name":"',_tierDataURI[_tierId].name," #"
,TLCLib.toString(tokenId),
'","description":"'
,_tierDataURI[_tierId].description,'",',
'"image":"'
,_tierDataURI[_tierId].images[_tokenStatus],'",',
'"animation_url":"'
,_tierDataURI[_tierId].animationURLs[_tokenStatus]
)
);
}
/// @dev Returns metadata body for `tokenId`.
/// See: {ERC721TLCToken - tokenStatus}.
function _body(uint256 tokenId) private view returns (string memory) {
uint256 _tierId = tierId(tokenId);
string memory _tokenStatus = tokenStatus(tokenId);
return string(
abi.encodePacked(
'","attributes":[{"trait_type":"Tier ID","value":"'
,TLCLib.toString(_tierId),
'"},{"trait_type":"Tier","value":"'
,_tierDataURI[_tierId].tierName,
'"},{"trait_type":"Status","value":"'
,_tokenStatus
)
);
}
/// @dev Returns expiry date metadata for `tokenId`.
/// See: {_tokenExpiryDate}.
function _expiryDate(uint256 tokenId) private view returns (string memory) {
(uint256 year, uint256 month, uint256 day) = _tokenExpiryDate(tokenId);
string memory _year = TLCLib.toString(year);
string memory _month = TLCLib.toString(month);
string memory _day = TLCLib.toString(day);
// It follows the ISO 8601 standard (YYYY-MM-DD) by appending "0" prefix
// to month before october and to day before day tenth.
// Ref: https://www.iso.org/iso-8601-date-and-time-format.html
if (month < 10) _month = string.concat("0", TLCLib.toString(month));
if (day < 10) _day = string.concat("0", TLCLib.toString(day));
return string(
abi.encodePacked(
'"},{"trait_type":"Expiry Date","value":"'
,_year,'-'
,_month,'-'
,_day
)
);
}
/// @dev Returns expiry date for `tokenId` in (`year`,`month`,`day`).
/// See: {ERC721TLCToken - _endOfLifeCycleToken}.
function _tokenExpiryDate(uint256 tokenId)
private
view
returns (uint256 year, uint256 month, uint256 day)
{
return TLCLib.timestampToDate(endOfLifeCycleToken(tokenId));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "../ERC721TLC.sol";
/// @notice A customized ERC721TLC contract extension logic for tokenId`s life cycle management.
/// @author 0xkuwabatake (@0xkuwabatake)
abstract contract ERC721TLCToken is ERC721TLC {
///////// STORAGE /////////////////////////////////////////////////////////////////////////////O-'
/// @dev Mapping from `tierId` or `tierToMint` => `fee`.
///
/// Note:
/// - Index 1 - 10 are allocated for token life cycle update `_fee` from `tierId` #1 - #10.
/// - Index 11 - 17 are allocated for mint `_fee` from `tierId` #1 - #7 at child contract.
/// - Index 18 - 22 are allocated for mint `_fee` to mint `tierToMint` for the owner of tierId` #1 at child contract.
/// - Index 23 - 27 is allocated for mint fee to mint `tierToMint` for the owner of `tierId` #2 at child contract.
/// ```
LibMap.Uint64Map internal _fee;
///////// ERC-4906 EVENTS /////////////////////////////////////////////////////////////////////O-'
/// @dev Emitted when the metadata for `tokenId` is updated.
event MetadataUpdate(uint256 tokenId);
/// @dev Emitted when batch of metadata `fromTokenId` to `toTokenId` is updated.
event BatchMetadataUpdate(uint256 fromTokenId, uint256 toTokenId);
///////// CUSTOM EVENTS ///////////////////////////////////////////////////////////////////////O-'
/// @dev Emitted when token life cycle update `fee` for its `tierId` is updated`.
event TokenLifeCycleFeeUpdate(uint256 indexed tierId, uint256 indexed fee);
/// @dev Emitted when `tokenId` life cycle from `tierId` is updated at `currentTimestamp` with `currentLifeCycle`.
event TokenLifeCycleUpdate (
uint256 indexed tokenId,
uint256 indexed tierId,
uint256 indexed currentTimeStamp,
uint256 currentLifeCycle
);
///////// CUSTOM ERRORS ///////////////////////////////////////////////////////////////////////O-'
/// @dev Revert with an error if token life cycle is updated at unexpected time frame.
error InvalidTimeToUpdate();
/// @dev Revert with an error if ether balance is insufficient.
error InsufficientBalance();
/// @dev Revert with an error if token life cycle is unable to be updated.
error UnableToUpdate();
/// @dev Revert with an error if fee is invalid.
error InvalidFee();
///////// PUBLIC GETTER FUNCTIONS /////////////////////////////////////////////////////////////O-'
/// @dev Returns true if this contract implements the interface defined by `interfaceId`.
/// See: {IERC165-supportsInterface}.
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override
returns (bool result)
{
return
interfaceId == 0x49064906 || // ERC4906
ERC721.supportsInterface(interfaceId);
}
/// @dev Returns update token life cycle fee for `tierId`.
function updateFee(uint256 tierId) public view returns (uint256) {
return uint256(LibMap.get(_fee, tierId));
}
/// @dev Returns update token life cycle fee for `tokenId`.
///
/// Note:
/// - If Live(3): returns full update fee -- see {updateFee}.
/// - If Paused(4) / Ending(5) and current time (block.timestamp) is less than or equal to
/// defined pause or end of life cycle timestamp (offset), there would be a `_remainder`.
/// The `_remainder` is offset minus block.timestamp and its results will vary depending
/// on its value. Its return value will determine total value of token life cycle fee that
/// need to be paid of to continue its life cycle token.
/// - If Paused(4) / Ending(5) and current time (block.timestamp) is greater than offset, then
/// return 0 (zero) -- as mark of non-existent token update fee to be applied.
/// - If current life cycle status is not at one of three conditions above,
/// it will constantly return 0 (zero) -- see {_validateAllReturnZeroUpdateTokenFee}.
/// ```
function updateTokenFee(uint256 tokenId) public view returns (uint256 result) {
uint256 _tierId = tierId(tokenId);
_validateAllReturnZeroUpdateTokenFee(_tierId);
if (lifeCycleStatus(_tierId) == LifeCycleStatus.Live) {
return updateFee(_tierId);
}
if (lifeCycleStatus(_tierId) == LifeCycleStatus.Paused) {
if (block.timestamp <= pauseOfLifeCycle(_tierId)) {
uint256 _remainder = _sub(pauseOfLifeCycle(_tierId), block.timestamp);
if (_remainder >= lifeCycle(_tierId)) {
return updateFee(_tierId);
}
if (_remainder < lifeCycle(_tierId)) {
return _calculateProportionalUpdateFee(_tierId, pauseOfLifeCycle(_tierId));
}
if (_remainder == 0) {
return 0;
}
} else {
return 0;
}
}
if (lifeCycleStatus(_tierId) == LifeCycleStatus.Ending) {
if (block.timestamp <= endOfLifeCycle(_tierId)) {
uint256 _remainder = _sub(endOfLifeCycle(_tierId), block.timestamp);
if (_remainder >= lifeCycle(_tierId)) {
return updateFee(_tierId);
}
if (_remainder < lifeCycle(_tierId)) {
return _calculateProportionalUpdateFee(_tierId, endOfLifeCycle(_tierId));
}
if (_remainder == 0) {
return 0;
}
} else {
return 0;
}
}
}
/// @dev Returns current length of life cycle for `tokenId` in total seconds.
/// Note:
/// - Non-zero value does not guarantee that `tokenId` is in life cycle period (Live) and
/// it will constantly return 0 (zero) for non-existent `tokenId`.
/// See: {ERC721TLC - _lifeCycleToken}.
function lifeCycleToken(uint256 tokenId) public view returns (uint256) {
return _lifeCycleToken(tokenId);
}
/// @dev Returns start of life cycle for `tokenId`.
///
/// Note:
/// Start of life cycle token is mark time of the beginning of life cycle `tokenId` for
/// a particular life cycle token period. It will vary for each minted `tokenId`.
///
/// Conditions:
/// - It will return 0 (zero), if current life cycle `_tierId`'s status from `tokenId` and
/// current time (block.timestamp) comparing to defined start / pause / end of life cyle
/// timestamp as described at {_validateAllReturnZeroStartOrEndLifeCycleToken}.
///
/// - It will return non-zero value, if current life cycle `tierId`'s status is:
/// - at Live(3) and current time is equal to or greater than start of life cycle `_tierId` or
/// - at Paused(4) and current time is less than or equal to pause of life cycle `_tierId` or
/// - at Ending(5) and current time is less than or equal to end of life cycle `_tierId`.
///
/// - The non-zero value's rules are as followings:
/// - If token timestamp `tokenId` is greater than start of life cycle `_tierId`,
/// then start of life cycle `tokenId` is its current token timestamp --
/// see {ERC721TLC - tokenTimestamp}. At some point, all of start of life cycle `tokenId`
/// will fall into this condition as long as the life cycle period for its `_tierId` is
/// at Live(3) status.
/// - If token timestamp `tokenId` is less than or equal to start of life cycle `_tierId`,
/// then start of life cycle `tokenId` is start of life cycle `_tierId`.
/// ```
function startOfLifeCycleToken(uint256 tokenId) public view returns (uint256 result) {
uint256 _tierId = tierId(tokenId);
_validateAllReturnZeroStartOrEndLifeCycleToken(tokenId);
if (
(lifeCycleStatus(_tierId) == LifeCycleStatus.Live && block.timestamp >= startOfLifeCycle(_tierId))
|| (lifeCycleStatus(_tierId) == LifeCycleStatus.Paused && block.timestamp <= pauseOfLifeCycle(_tierId))
|| (lifeCycleStatus(_tierId) == LifeCycleStatus.Ending && block.timestamp <= endOfLifeCycle(_tierId))
) {
if (tokenTimestamp(tokenId) > startOfLifeCycle(_tierId)) {
return tokenTimestamp(tokenId);
} else {
return startOfLifeCycle(_tierId);
}
}
}
/// @dev Returns end of life cycle for `tokenId`.
///
/// Note:
/// End of life cycle token is mark time of the end of life cycle `tokenId` for
/// a particular life cycle token period. It will vary for each minted `tokenId`.
///
/// Conditions:
/// - It will return 0 (zero), if current life cycle `_tierId`'s status from `tokenId` and
/// current time (block.timestamp) comparing to defined start / pause / end of life cyle
/// timestamp as described at {_validateAllReturnZeroStartOrEndLifeCycleToken}.
///
/// - It will return non-zero value, if current life cycle `_tierId`'s status is:
/// - at Live(3) and current time is equal to or greater than start of life cycle `_tierId` or
/// - at Paused(4) and current time is less than or equal to pause of life cycle `_tierId` or
/// - at Ending(5) and current time is less than or equal to end of life cycle `_tierId`.
/// - See: {endOfLifeCycleTokenUnchecked}.
///
/// - The non-zero value's rules are as followings:
/// - If token timestamp `tokenId` is greater than start of life cycle `_tierId`,
/// then end of life cycle `tokenId` is the addition of its token timestamp and
/// life cycle token `tokenId` -- see {lifeCycleToken}. At some point,
/// all of end of life cycle `tokenId`will fall into this condition
/// as long as the life cycle period for its `_tierId` is at Live(3) status.
/// - If token timestamp for `tokenId` is less than or equal to start of life cycle `_tierId`,
/// then end of life cycle `tokenId` is the addition start of life cycle `_tierId`
/// and life cycle token `tokenId`.
/// ```
function endOfLifeCycleToken(uint256 tokenId) public view returns (uint256 result) {
uint256 _tierId = tierId(tokenId);
_validateAllReturnZeroStartOrEndLifeCycleToken(tokenId);
if (
(lifeCycleStatus(_tierId) == LifeCycleStatus.Live && block.timestamp >= startOfLifeCycle(_tierId))
|| (lifeCycleStatus(_tierId) == LifeCycleStatus.Paused && block.timestamp <= pauseOfLifeCycle(_tierId))
|| (lifeCycleStatus(_tierId) == LifeCycleStatus.Ending && block.timestamp <= endOfLifeCycle(_tierId))
) {
result = endOfLifeCycleTokenUnchecked(tokenId);
}
}
/// @dev Returns unchecked end of life cycle token for `tokenId`.
///
/// Note:
/// - The intention of this method is to be queried by offchain indexer to get the stored
/// value of end of life cycle `tokenId` especially when the {endOfLifeCycleToken}'s value
/// is overriden to zero value as defined at {_validateAllReturnZeroStartOrEndLifeCycleToken}.
/// ```
function endOfLifeCycleTokenUnchecked(uint256 tokenId) public view returns (uint256) {
uint256 _tierId = tierId(tokenId);
if (tokenTimestamp(tokenId) > startOfLifeCycle(_tierId)) {
return _add(tokenTimestamp(tokenId), lifeCycleToken(tokenId));
} else {
return _add(startOfLifeCycle(_tierId), lifeCycleToken(tokenId));
}
}
/// @dev Returns status for `tokenId` in string literal either is "Active" or "Inactive".
/// Note: The return value will be queried by {ERC721TLCDataURI - _body} metadata.
/// See: {_tokenStatus}.
function tokenStatus(uint256 tokenId) public view virtual returns (string memory result) {
if (!_exists(tokenId)) _revert(TokenDoesNotExist.selector);
uint256 _status = _tokenStatus(tokenId);
if (_status == 0) return "Active";
if (_status == 1) return "Inactive";
}
///////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////////O-'
///////// INTERNAL TOKEN LIFE CYCLE UPDATE OPERATION /////////
/// @dev Update token life cycle for `tokenId`.
/// See: {TierLifeCycle - lifeCycle}, {ERC721 - _setExtraData}.
function _updateTokenLifeCycle(uint256 tokenId) internal {
uint256 _tierId = tierId(tokenId);
uint96 _packed = uint96(_tierId) | // 2 bytes - existing `_tierId`
uint96(block.timestamp) << 16 | // 5 bytes - block.timestamp
uint96(lifeCycle(_tierId)) << 56; // 5 bytes - life cycle for `tierId`
_setExtraData(tokenId, _packed);
emit TokenLifeCycleUpdate(tokenId, _tierId, block.timestamp, lifeCycle(_tierId));
emit MetadataUpdate(tokenId);
}
///////// INTERNAL TOKEN LIFE CYCLE UPDATE FEE SETTER /////////
/// @dev Sets token life cycle update `fee` for `tierId`.
///
/// Note:
/// Update fee is a mandatory fee that need to be paid in native token (ether) either in full or
/// proportionally by token owners when updating their token life cycle
/// at the end of life cycle for their owned tokenId.
/// - It is mandatory to be initialized prior to {TierLifeCycle - _setStartOfLifeCycle} and
/// `fee` cannot be set to zero value or greater than 18.44 ether (see below) --
/// these conditions must be well-validated at child contract.
///
/// Requirements:
/// - Life cycle status must be at ReadyToStart(1) / Live (3) / Paused(4) / Ending (5).
/// - `fee` cannot be greater than 18446744073709551615 wei (18.446744073709551615 ether).
/// - If Live(3): non-zero `fee` is able to be reinitialized start from 48 hours before
/// the end of first life cycle period.
/// - If Paused(4): it only can be initialized when tx's block.timestamp is greater than defined
/// pause of life cycle timestamp.
/// - If Ending(5): it only can be initialized when tx's block.timestamp is greater than defined
/// end of life cycle timestamp and in this situation the `fee` must set back to 0 (zero) --
/// this condition must be well-validated at child contract.
/// ```
function _setUpdateFee(uint256 tierId, uint256 fee) internal {
_requireStatusIsReadyToStartOrLiveOrPausedOrEnding(tierId);
if (fee > 0xFFFFFFFFFFFFFFFF) _revert(InvalidFee.selector);
if (lifeCycleStatus(tierId) == LifeCycleStatus.Live) {
uint256 _endOfFirstLifeCyclePeriod = _add(startOfLifeCycle(tierId), lifeCycle(tierId));
if (block.timestamp < _sub(_endOfFirstLifeCyclePeriod, 172800)) {
_revert(InvalidTimeToInitialize.selector);
}
LibMap.set(_fee, tierId, uint64(fee));
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.Paused) {
if (block.timestamp <= pauseOfLifeCycle(tierId)) _revert(InvalidTimeToInitialize.selector);
LibMap.set(_fee, tierId, uint64(fee));
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.Ending) {
if (block.timestamp <= endOfLifeCycle(tierId)) _revert(InvalidTimeToInitialize.selector);
LibMap.set(_fee, tierId, uint64(fee));
}
LibMap.set(_fee, tierId, uint64(fee)); // ReadyToStart(1)
emit TokenLifeCycleFeeUpdate(tierId, fee);
}
///////// INTERNAL UPDATE NFT METADATA BASED ON ERC-4906 OPERATION /////////
/// @dev Emits metadata update event `fromTokenId` to `toTokenId` from ERC-4906.
/// See: https://eips.ethereum.org/EIPS/eip-4906#specification
function _emitMetadataUpdate(uint256 fromTokenId, uint256 toTokenId) internal {
if (fromTokenId == toTokenId) {
emit MetadataUpdate(fromTokenId);
} else {
emit BatchMetadataUpdate(fromTokenId, toTokenId);
}
}
///////// INTERNAL UPDATE TOKEN LIFE CYCLE FEE VALIDATOR /////////
/// @dev Update token life cycle fee for `tokenId` validator.
///
/// Requirements:
/// - LifeCycleStatus must be at Live(3) / Paused(4) / Ending(5).
/// - If Live(3): it can be initialized 2 hours (7200 seconds) before
/// end of life cycle token `tokenId` -- the condition is defined as constant to prevent
/// token owner update its token life cycle too early from end of life cycle token `tokenId`.
/// - If Paused(4) / Ending(5): beside of condition above, the current time must be validated
/// with end of life cycle `tokenId` and `offset` -- see {_validateOffset},
/// before comparing current time with `offset` to get the remainder as the basis of total fee
/// that needed to be paid -- see {_validateRemainder}.
/// ```
function _validateUpdateFee(uint256 tokenId) internal {
uint256 _tierId = tierId(tokenId);
if (lifeCycleStatus(_tierId) == LifeCycleStatus.Live) {
if (block.timestamp < _sub(endOfLifeCycleToken(tokenId), 7200)) {
_revert(InvalidTimeToUpdate.selector);
}
_validateFullUpdateFee(tokenId);
} else if (lifeCycleStatus(_tierId) == LifeCycleStatus.Paused) {
_validateOffset(tokenId, pauseOfLifeCycle(_tierId));
_validateRemainder(tokenId, pauseOfLifeCycle(_tierId));
} else if (lifeCycleStatus(_tierId) == LifeCycleStatus.Ending) {
_validateOffset(tokenId, endOfLifeCycle(_tierId));
_validateRemainder(tokenId, endOfLifeCycle(_tierId));
} else {
_revert(InvalidLifeCycleStatus.selector);
}
}
///////// INTERNAL MSG.VALUE COMPARE TO FEE VALIDATOR /////////
/// @dev msg.value compare to `fee` validator.
function _validateMsgValue(uint256 fee) internal {
if (msg.value < fee) _revert(InsufficientBalance.selector);
}
///////// INTERNAL TOKEN STATUS GETTER /////////
/// @dev Returns token status for `tokenId`, either in 0 (zero) or 1 (one).
///
/// Note:
/// - The return value will be queried and converted into string literal at {tokenStatus}.
/// - The return value will be queried "as is" at {ERC721TLCDataURI - _header} metadata.
///
/// Conditions:
/// - If both of start and end of life cycle `tokenId` are zero value, return 0.
/// - If both of start and end of life cycle `tokenId` are non-zero value and
/// current time (block.timestamp) is greater than end of life cycle `tokenId`, return 1 --
/// if not, return 0.
///```
function _tokenStatus(uint256 tokenId) internal view returns (uint256 result) {
if (startOfLifeCycleToken(tokenId) == 0) {
if (endOfLifeCycleToken(tokenId) == 0) {
return 0;
}
}
if (startOfLifeCycleToken(tokenId) != 0) {
if (endOfLifeCycleToken(tokenId) != 0) {
if (block.timestamp > endOfLifeCycleToken(tokenId)) {
return 1;
} else {
return 0;
}
}
}
}
///////// PRIVATE FUNCTIONS ///////////////////////////////////////////////////////////////////O-'
/// @dev LifeCycleStatus must be at ReadyToStart(1) / Live(3) / Paused(4) / Ending(5)
function _requireStatusIsReadyToStartOrLiveOrPausedOrEnding(uint256 tierId) private view {
if (lifeCycleStatus(tierId) == LifeCycleStatus.NotLive) {
_revert(InvalidLifeCycleStatus.selector);
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.ReadyToLive) {
_revert(InvalidLifeCycleStatus.selector);
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.Finished) {
_revert(InvalidLifeCycleStatus.selector);
}
}
///////// PRIVATE TOKEN LIFE CYCLE UPDATE FEE VALIDATORS /////////
/// @dev Validate `offset` to update token life cycle.
///
/// Note:
/// - `offset` is the defined pause of life cycle timestamp when life cycle status is Paused(4).
/// - `offset` is the defined end of life cycle timestamp when life cycle status is Ending(5).
///
/// Requirement:
/// - The operation can be initiated start 2 hours (7200 seconds) before
/// end of life cycle token `tokenId` and maximum until 1 minute (60 seconds) before
/// current time (block.timestamp) meets the `offset`.
/// ```
function _validateOffset(uint256 tokenId, uint256 offset) private view {
if (block.timestamp < _sub(endOfLifeCycleToken(tokenId), 7200)) {
_revert(InvalidTimeToUpdate.selector);
}
if (block.timestamp > _sub(offset, 60)) {
_revert(InvalidTimeToUpdate.selector);
}
}
/// @dev Validate remainder of token life cycle update fee based on its `offset`.
///
/// Conditions:
/// - If `_remainder` is greater than or equal to life cycle `_tierId`, {_validateFullUpdateFee}.
/// - If `_remainder` is less than life cycle `_tierId`, {_validateProportionalUpdateFee}.
/// - If `_remainder` is zero, token life cycle `tokenId` is no longer updateable.
/// ```
function _validateRemainder(uint256 tokenId, uint256 offset) private {
uint256 _tierId = tierId(tokenId);
uint256 _remainder = _sub(offset, block.timestamp);
if (_remainder >= lifeCycle(_tierId)) _validateFullUpdateFee(tokenId);
if (_remainder < lifeCycle(_tierId)) _validateProportionalUpdateFee(tokenId, offset);
if (_remainder == 0) _revert(UnableToUpdate.selector);
}
/// @dev Full token life cycle update fee for `tokenId` validator.
/// See: {updateFee}.
function _validateFullUpdateFee(uint256 tokenId) private {
uint256 _tierId = tierId(tokenId);
_validateMsgValue(updateFee(_tierId));
}
/// @dev Validate proportional token life cycle update fee for `tokenId` based on its `offset`.
/// See: {_calculateProportionalUpdateFee}.
function _validateProportionalUpdateFee(uint256 tokenId, uint256 offset) private {
uint256 _tierId = tierId(tokenId);
uint256 _proportionalFee = _calculateProportionalUpdateFee(_tierId, offset);
_validateMsgValue(_proportionalFee);
}
/// @dev Calculate proportional token life cycle update fee for `tierId` with `offset`.
function _calculateProportionalUpdateFee(uint256 tierId, uint256 offset)
private
view
returns (uint256 result)
{
// Proportional fee = (`offset` - block.timestamp) * updateFee(tier) / lifeCycle(tier)
uint256 _remainder = _sub(offset, block.timestamp);
result = _remainder * updateFee(tierId) / lifeCycle(tierId);
}
///////// PRIVATE START AND END OF LIFE CYCLE TOKEN ID VALIDATOR /////////
/// @dev All life cycle statuses which token life cycle update fee for `tierId` returns 0 (zero).
function _validateAllReturnZeroUpdateTokenFee(uint256 tierId)
private
view
returns (uint256 result)
{
if (lifeCycleStatus(tierId) == LifeCycleStatus.NotLive) return 0;
if (lifeCycleStatus(tierId) != LifeCycleStatus.Live) return 0;
if (lifeCycleStatus(tierId) != LifeCycleStatus.Paused) return 0;
if (lifeCycleStatus(tierId) != LifeCycleStatus.Ending) return 0;
}
/// @dev All life cycle statuses and conditions which start or end of life cycle `tokenId` returns 0 (zero).
///
/// - It will return zero value, if current life cycle `_tierId`'s status is:
/// - at NotLive(0) / ReadyToStart(1) / ReadyToLive(2) / Finished(6) or
/// - at Live(3) and current time is less than start of life cycle `_tierId` or
/// - at Paused(4) and current time is greater than pause of life cycle `_tierId` or
/// - at Ending(5) and current time is greater than end of life cycle `_tierId`.
///```
function _validateAllReturnZeroStartOrEndLifeCycleToken(uint256 tokenId)
private
view
returns (uint256 result)
{
uint256 _tierId = tierId(tokenId);
if (lifeCycleStatus(_tierId) == LifeCycleStatus.NotLive) return 0;
if (lifeCycleStatus(_tierId) == LifeCycleStatus.ReadyToStart) return 0;
if (lifeCycleStatus(_tierId) == LifeCycleStatus.ReadyToLive) return 0;
if (lifeCycleStatus(_tierId) == LifeCycleStatus.Finished) return 0;
if (
(lifeCycleStatus(_tierId) == LifeCycleStatus.Live && block.timestamp < startOfLifeCycle(_tierId))
|| (lifeCycleStatus(_tierId) == LifeCycleStatus.Paused && block.timestamp > pauseOfLifeCycle(_tierId))
|| (lifeCycleStatus(_tierId) == LifeCycleStatus.Ending && block.timestamp > endOfLifeCycle(_tierId))
) {
return 0;
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
interface ICreatorToken {
event TransferValidatorUpdated(address oldValidator, address newValidator);
function getTransferValidationFunction() external view returns (bytes4 functionSignature, bool isViewFunction);
function getTransferValidator() external view returns (address validator);
function setTransferValidator(address validator) external;
}
/// @notice ERC721 Contract extension for specific ERC721 validation transfer.
/// @author 0xkuwabatake(@0xkuwabatake)
/// @author Modified from ProjectOpenSea
/// (https://github.com/ProjectOpenSea/seadrop/blob/main/src/lib/ERC721TransferValidator.sol)
abstract contract ERC721TransferValidator is ICreatorToken {
///////// STORAGE /////////////////////////////////////////////////////////////////////////////O-'
/// @dev Transfer validator address.
address internal _transferValidator;
///////// PUBLIC GETTER FUNCTIONS /////////////////////////////////////////////////////////////O-'
/// @dev Returns the currently active transfer validator.
function getTransferValidator() public view returns (address) {
return _transferValidator;
}
/// @dev Returns the transfer validation function used.
function getTransferValidationFunction()
public
pure
returns (bytes4 functionSignature, bool isViewFunction)
{
functionSignature = 0xcaee23ea;
isViewFunction = true;
}
///////// INTERNAL SETTER FUNCTION ////////////////////////////////////////////////////////////O-'
/// @dev Sets `validator` as transfer validator.
function _setTransferValidator(address validator) internal {
emit TransferValidatorUpdated(_transferValidator, validator);
_transferValidator = validator;
}
/// @dev Reset transfer validator back to zero address (default).
function _resetTransferValidator() internal {
emit TransferValidatorUpdated(_transferValidator, address(0));
_transferValidator = address(0);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Library for bit twiddling and boolean operations.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibBit.sol)
/// @author Inspired by (https://graphics.stanford.edu/~seander/bithacks.html)
library LibBit {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* BIT TWIDDLING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Find last set.
/// Returns the index of the most significant bit of `x`,
/// counting from the least significant bit position.
/// If `x` is zero, returns 256.
function fls(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
r := or(shl(8, iszero(x)), shl(7, lt(0xffffffffffffffffffffffffffffffff, x)))
r := or(r, shl(6, lt(0xffffffffffffffff, 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
r := or(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
0x0706060506020504060203020504030106050205030304010505030400000000))
}
}
/// @dev Count leading zeros.
/// Returns the number of zeros preceding the most significant one bit.
/// If `x` is zero, returns 256.
function clz(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, 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
r := add(xor(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
0xf8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff)), iszero(x))
}
}
/// @dev Find first set.
/// Returns the index of the least significant bit of `x`,
/// counting from the least significant bit position.
/// If `x` is zero, returns 256.
/// Equivalent to `ctz` (count trailing zeros), which gives
/// the number of zeros following the least significant one bit.
function ffs(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
// Isolate the least significant bit.
x := and(x, add(not(x), 1))
// For the upper 3 bits of the result, use a De Bruijn-like lookup.
// Credit to adhusson: https://blog.adhusson.com/cheap-find-first-set-evm/
// forgefmt: disable-next-item
r := shl(5, shr(252, shl(shl(2, shr(250, mul(x,
0xb6db6db6ddddddddd34d34d349249249210842108c6318c639ce739cffffffff))),
0x8040405543005266443200005020610674053026020000107506200176117077)))
// For the lower 5 bits of the result, use a De Bruijn lookup.
// forgefmt: disable-next-item
r := or(r, byte(and(div(0xd76453e0, shr(r, x)), 0x1f),
0x001f0d1e100c1d070f090b19131c1706010e11080a1a141802121b1503160405))
}
}
/// @dev Returns the number of set bits in `x`.
function popCount(uint256 x) internal pure returns (uint256 c) {
/// @solidity memory-safe-assembly
assembly {
let max := not(0)
let isMax := eq(x, max)
x := sub(x, and(shr(1, x), div(max, 3)))
x := add(and(x, div(max, 5)), and(shr(2, x), div(max, 5)))
x := and(add(x, shr(4, x)), div(max, 17))
c := or(shl(8, isMax), shr(248, mul(x, div(max, 255))))
}
}
/// @dev Returns whether `x` is a power of 2.
function isPo2(uint256 x) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `x && !(x & (x - 1))`.
result := iszero(add(and(x, sub(x, 1)), iszero(x)))
}
}
/// @dev Returns `x` reversed at the bit level.
function reverseBits(uint256 x) internal pure returns (uint256 r) {
uint256 m0 = 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f;
uint256 m1 = m0 ^ (m0 << 2);
uint256 m2 = m1 ^ (m1 << 1);
r = reverseBytes(x);
r = (m2 & (r >> 1)) | ((m2 & r) << 1);
r = (m1 & (r >> 2)) | ((m1 & r) << 2);
r = (m0 & (r >> 4)) | ((m0 & r) << 4);
}
/// @dev Returns `x` reversed at the byte level.
function reverseBytes(uint256 x) internal pure returns (uint256 r) {
unchecked {
// Computing masks on-the-fly reduces bytecode size by about 200 bytes.
uint256 m0 = 0x100000000000000000000000000000001 * (~toUint(x == uint256(0)) >> 192);
uint256 m1 = m0 ^ (m0 << 32);
uint256 m2 = m1 ^ (m1 << 16);
uint256 m3 = m2 ^ (m2 << 8);
r = (m3 & (x >> 8)) | ((m3 & x) << 8);
r = (m2 & (r >> 16)) | ((m2 & r) << 16);
r = (m1 & (r >> 32)) | ((m1 & r) << 32);
r = (m0 & (r >> 64)) | ((m0 & r) << 64);
r = (r >> 128) | (r << 128);
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* BOOLEAN OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// A Solidity bool on the stack or memory is represented as a 256-bit word.
// Non-zero values are true, zero is false.
// A clean bool is either 0 (false) or 1 (true) under the hood.
// Usually, if not always, the bool result of a regular Solidity expression,
// or the argument of a public/external function will be a clean bool.
// You can usually use the raw variants for more performance.
// If uncertain, test (best with exact compiler settings).
// Or use the non-raw variants (compiler can sometimes optimize out the double `iszero`s).
/// @dev Returns `x & y`. Inputs must be clean.
function rawAnd(bool x, bool y) internal pure returns (bool z) {
/// @solidity memory-safe-assembly
assembly {
z := and(x, y)
}
}
/// @dev Returns `x & y`.
function and(bool x, bool y) internal pure returns (bool z) {
/// @solidity memory-safe-assembly
assembly {
z := and(iszero(iszero(x)), iszero(iszero(y)))
}
}
/// @dev Returns `x | y`. Inputs must be clean.
function rawOr(bool x, bool y) internal pure returns (bool z) {
/// @solidity memory-safe-assembly
assembly {
z := or(x, y)
}
}
/// @dev Returns `x | y`.
function or(bool x, bool y) internal pure returns (bool z) {
/// @solidity memory-safe-assembly
assembly {
z := or(iszero(iszero(x)), iszero(iszero(y)))
}
}
/// @dev Returns 1 if `b` is true, else 0. Input must be clean.
function rawToUint(bool b) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := b
}
}
/// @dev Returns 1 if `b` is true, else 0.
function toUint(bool b) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := iszero(iszero(b))
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {LibBit} from "./LibBit.sol";
/// @notice Library for storage of packed unsigned booleans.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibBitmap.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibBitmap.sol)
/// @author Modified from Solidity-Bits (https://github.com/estarriolvetch/solidity-bits/blob/main/contracts/BitMaps.sol)
library LibBitmap {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The constant returned when a bitmap scan does not find a result.
uint256 internal constant NOT_FOUND = type(uint256).max;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STRUCTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev A bitmap in storage.
struct Bitmap {
mapping(uint256 => uint256) map;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the boolean value of the bit at `index` in `bitmap`.
function get(Bitmap storage bitmap, uint256 index) internal view returns (bool isSet) {
// It is better to set `isSet` to either 0 or 1, than zero vs non-zero.
// Both cost the same amount of gas, but the former allows the returned value
// to be reused without cleaning the upper bits.
uint256 b = (bitmap.map[index >> 8] >> (index & 0xff)) & 1;
/// @solidity memory-safe-assembly
assembly {
isSet := b
}
}
/// @dev Updates the bit at `index` in `bitmap` to true.
function set(Bitmap storage bitmap, uint256 index) internal {
bitmap.map[index >> 8] |= (1 << (index & 0xff));
}
/// @dev Updates the bit at `index` in `bitmap` to false.
function unset(Bitmap storage bitmap, uint256 index) internal {
bitmap.map[index >> 8] &= ~(1 << (index & 0xff));
}
/// @dev Flips the bit at `index` in `bitmap`.
/// Returns the boolean result of the flipped bit.
function toggle(Bitmap storage bitmap, uint256 index) internal returns (bool newIsSet) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, bitmap.slot)
mstore(0x00, shr(8, index))
let storageSlot := keccak256(0x00, 0x40)
let shift := and(index, 0xff)
let storageValue := xor(sload(storageSlot), shl(shift, 1))
// It makes sense to return the `newIsSet`,
// as it allow us to skip an additional warm `sload`,
// and it costs minimal gas (about 15),
// which may be optimized away if the returned value is unused.
newIsSet := and(1, shr(shift, storageValue))
sstore(storageSlot, storageValue)
}
}
/// @dev Updates the bit at `index` in `bitmap` to `shouldSet`.
function setTo(Bitmap storage bitmap, uint256 index, bool shouldSet) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, bitmap.slot)
mstore(0x00, shr(8, index))
let storageSlot := keccak256(0x00, 0x40)
let storageValue := sload(storageSlot)
let shift := and(index, 0xff)
sstore(
storageSlot,
// Unsets the bit at `shift` via `and`, then sets its new value via `or`.
or(and(storageValue, not(shl(shift, 1))), shl(shift, iszero(iszero(shouldSet))))
)
}
}
/// @dev Consecutively sets `amount` of bits starting from the bit at `start`.
function setBatch(Bitmap storage bitmap, uint256 start, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let max := not(0)
let shift := and(start, 0xff)
mstore(0x20, bitmap.slot)
mstore(0x00, shr(8, start))
if iszero(lt(add(shift, amount), 257)) {
let storageSlot := keccak256(0x00, 0x40)
sstore(storageSlot, or(sload(storageSlot), shl(shift, max)))
let bucket := add(mload(0x00), 1)
let bucketEnd := add(mload(0x00), shr(8, add(amount, shift)))
amount := and(add(amount, shift), 0xff)
shift := 0
for {} iszero(eq(bucket, bucketEnd)) { bucket := add(bucket, 1) } {
mstore(0x00, bucket)
sstore(keccak256(0x00, 0x40), max)
}
mstore(0x00, bucket)
}
let storageSlot := keccak256(0x00, 0x40)
sstore(storageSlot, or(sload(storageSlot), shl(shift, shr(sub(256, amount), max))))
}
}
/// @dev Consecutively unsets `amount` of bits starting from the bit at `start`.
function unsetBatch(Bitmap storage bitmap, uint256 start, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let shift := and(start, 0xff)
mstore(0x20, bitmap.slot)
mstore(0x00, shr(8, start))
if iszero(lt(add(shift, amount), 257)) {
let storageSlot := keccak256(0x00, 0x40)
sstore(storageSlot, and(sload(storageSlot), not(shl(shift, not(0)))))
let bucket := add(mload(0x00), 1)
let bucketEnd := add(mload(0x00), shr(8, add(amount, shift)))
amount := and(add(amount, shift), 0xff)
shift := 0
for {} iszero(eq(bucket, bucketEnd)) { bucket := add(bucket, 1) } {
mstore(0x00, bucket)
sstore(keccak256(0x00, 0x40), 0)
}
mstore(0x00, bucket)
}
let storageSlot := keccak256(0x00, 0x40)
sstore(
storageSlot, and(sload(storageSlot), not(shl(shift, shr(sub(256, amount), not(0)))))
)
}
}
/// @dev Returns number of set bits within a range by
/// scanning `amount` of bits starting from the bit at `start`.
function popCount(Bitmap storage bitmap, uint256 start, uint256 amount)
internal
view
returns (uint256 count)
{
unchecked {
uint256 bucket = start >> 8;
uint256 shift = start & 0xff;
if (!(amount + shift < 257)) {
count = LibBit.popCount(bitmap.map[bucket] >> shift);
uint256 bucketEnd = bucket + ((amount + shift) >> 8);
amount = (amount + shift) & 0xff;
shift = 0;
for (++bucket; bucket != bucketEnd; ++bucket) {
count += LibBit.popCount(bitmap.map[bucket]);
}
}
count += LibBit.popCount((bitmap.map[bucket] >> shift) << (256 - amount));
}
}
/// @dev Returns the index of the most significant set bit in `[0..upTo]`.
/// If no set bit is found, returns `NOT_FOUND`.
function findLastSet(Bitmap storage bitmap, uint256 upTo)
internal
view
returns (uint256 setBitIndex)
{
setBitIndex = NOT_FOUND;
uint256 bucket = upTo >> 8;
uint256 bits;
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, bucket)
mstore(0x20, bitmap.slot)
let offset := and(0xff, not(upTo)) // `256 - (255 & upTo) - 1`.
bits := shr(offset, shl(offset, sload(keccak256(0x00, 0x40))))
if iszero(or(bits, iszero(bucket))) {
for {} 1 {} {
bucket := add(bucket, setBitIndex) // `sub(bucket, 1)`.
mstore(0x00, bucket)
bits := sload(keccak256(0x00, 0x40))
if or(bits, iszero(bucket)) { break }
}
}
}
if (bits != 0) {
setBitIndex = (bucket << 8) | LibBit.fls(bits);
/// @solidity memory-safe-assembly
assembly {
setBitIndex := or(setBitIndex, sub(0, gt(setBitIndex, upTo)))
}
}
}
/// @dev Returns the index of the least significant unset bit in `[begin..upTo]`.
/// If no unset bit is found, returns `NOT_FOUND`.
function findFirstUnset(Bitmap storage bitmap, uint256 begin, uint256 upTo)
internal
view
returns (uint256 unsetBitIndex)
{
unsetBitIndex = NOT_FOUND;
uint256 bucket = begin >> 8;
uint256 negBits;
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, bucket)
mstore(0x20, bitmap.slot)
let offset := and(0xff, begin)
negBits := shl(offset, shr(offset, not(sload(keccak256(0x00, 0x40)))))
if iszero(negBits) {
let lastBucket := shr(8, upTo)
for {} 1 {} {
bucket := add(bucket, 1)
mstore(0x00, bucket)
negBits := not(sload(keccak256(0x00, 0x40)))
if or(negBits, gt(bucket, lastBucket)) { break }
}
if gt(bucket, lastBucket) {
negBits := shl(and(0xff, not(upTo)), shr(and(0xff, not(upTo)), negBits))
}
}
}
if (negBits != 0) {
uint256 r = (bucket << 8) | LibBit.ffs(negBits);
/// @solidity memory-safe-assembly
assembly {
unsetBitIndex := or(r, sub(0, or(gt(r, upTo), lt(r, begin))))
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Library for storage of packed unsigned integers.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibMap.sol)
library LibMap {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STRUCTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev A uint8 map in storage.
struct Uint8Map {
mapping(uint256 => uint256) map;
}
/// @dev A uint16 map in storage.
struct Uint16Map {
mapping(uint256 => uint256) map;
}
/// @dev A uint32 map in storage.
struct Uint32Map {
mapping(uint256 => uint256) map;
}
/// @dev A uint40 map in storage. Useful for storing timestamps up to 34841 A.D.
struct Uint40Map {
mapping(uint256 => uint256) map;
}
/// @dev A uint64 map in storage.
struct Uint64Map {
mapping(uint256 => uint256) map;
}
/// @dev A uint128 map in storage.
struct Uint128Map {
mapping(uint256 => uint256) map;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* GETTERS / SETTERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the uint8 value at `index` in `map`.
function get(Uint8Map storage map, uint256 index) internal view returns (uint8 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, map.slot)
mstore(0x00, shr(5, index))
result := byte(and(31, not(index)), sload(keccak256(0x00, 0x40)))
}
}
/// @dev Updates the uint8 value at `index` in `map`.
function set(Uint8Map storage map, uint256 index, uint8 value) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, map.slot)
mstore(0x00, shr(5, index))
let s := keccak256(0x00, 0x40) // Storage slot.
mstore(0x00, sload(s))
mstore8(and(31, not(index)), value)
sstore(s, mload(0x00))
}
}
/// @dev Returns the uint16 value at `index` in `map`.
function get(Uint16Map storage map, uint256 index) internal view returns (uint16 result) {
result = uint16(map.map[index >> 4] >> ((index & 15) << 4));
}
/// @dev Updates the uint16 value at `index` in `map`.
function set(Uint16Map storage map, uint256 index, uint16 value) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, map.slot)
mstore(0x00, shr(4, index))
let s := keccak256(0x00, 0x40) // Storage slot.
let o := shl(4, and(index, 15)) // Storage slot offset (bits).
let v := sload(s) // Storage slot value.
let m := 0xffff // Value mask.
sstore(s, xor(v, shl(o, and(m, xor(shr(o, v), value)))))
}
}
/// @dev Returns the uint32 value at `index` in `map`.
function get(Uint32Map storage map, uint256 index) internal view returns (uint32 result) {
result = uint32(map.map[index >> 3] >> ((index & 7) << 5));
}
/// @dev Updates the uint32 value at `index` in `map`.
function set(Uint32Map storage map, uint256 index, uint32 value) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, map.slot)
mstore(0x00, shr(3, index))
let s := keccak256(0x00, 0x40) // Storage slot.
let o := shl(5, and(index, 7)) // Storage slot offset (bits).
let v := sload(s) // Storage slot value.
let m := 0xffffffff // Value mask.
sstore(s, xor(v, shl(o, and(m, xor(shr(o, v), value)))))
}
}
/// @dev Returns the uint40 value at `index` in `map`.
function get(Uint40Map storage map, uint256 index) internal view returns (uint40 result) {
unchecked {
result = uint40(map.map[index / 6] >> ((index % 6) * 40));
}
}
/// @dev Updates the uint40 value at `index` in `map`.
function set(Uint40Map storage map, uint256 index, uint40 value) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, map.slot)
mstore(0x00, div(index, 6))
let s := keccak256(0x00, 0x40) // Storage slot.
let o := mul(40, mod(index, 6)) // Storage slot offset (bits).
let v := sload(s) // Storage slot value.
let m := 0xffffffffff // Value mask.
sstore(s, xor(v, shl(o, and(m, xor(shr(o, v), value)))))
}
}
/// @dev Returns the uint64 value at `index` in `map`.
function get(Uint64Map storage map, uint256 index) internal view returns (uint64 result) {
result = uint64(map.map[index >> 2] >> ((index & 3) << 6));
}
/// @dev Updates the uint64 value at `index` in `map`.
function set(Uint64Map storage map, uint256 index, uint64 value) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, map.slot)
mstore(0x00, shr(2, index))
let s := keccak256(0x00, 0x40) // Storage slot.
let o := shl(6, and(index, 3)) // Storage slot offset (bits).
let v := sload(s) // Storage slot value.
let m := 0xffffffffffffffff // Value mask.
sstore(s, xor(v, shl(o, and(m, xor(shr(o, v), value)))))
}
}
/// @dev Returns the uint128 value at `index` in `map`.
function get(Uint128Map storage map, uint256 index) internal view returns (uint128 result) {
result = uint128(map.map[index >> 1] >> ((index & 1) << 7));
}
/// @dev Updates the uint128 value at `index` in `map`.
function set(Uint128Map storage map, uint256 index, uint128 value) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, map.slot)
mstore(0x00, shr(1, index))
let s := keccak256(0x00, 0x40) // Storage slot.
let o := shl(7, and(index, 1)) // Storage slot offset (bits).
let v := sload(s) // Storage slot value.
let m := 0xffffffffffffffffffffffffffffffff // Value mask.
sstore(s, xor(v, shl(o, and(m, xor(shr(o, v), value)))))
}
}
/// @dev Returns the value at `index` in `map`.
function get(mapping(uint256 => uint256) storage map, uint256 index, uint256 bitWidth)
internal
view
returns (uint256 result)
{
unchecked {
uint256 d = _rawDiv(256, bitWidth); // Bucket size.
uint256 m = (1 << bitWidth) - 1; // Value mask.
result = (map[_rawDiv(index, d)] >> (_rawMod(index, d) * bitWidth)) & m;
}
}
/// @dev Updates the value at `index` in `map`.
function set(
mapping(uint256 => uint256) storage map,
uint256 index,
uint256 value,
uint256 bitWidth
) internal {
unchecked {
uint256 d = _rawDiv(256, bitWidth); // Bucket size.
uint256 m = (1 << bitWidth) - 1; // Value mask.
uint256 o = _rawMod(index, d) * bitWidth; // Storage slot offset (bits).
map[_rawDiv(index, d)] ^= (((map[_rawDiv(index, d)] >> o) ^ value) & m) << o;
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* BINARY SEARCH */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// The following functions search in the range of [`start`, `end`)
// (i.e. `start <= index < end`).
// The range must be sorted in ascending order.
// `index` precedence: equal to > nearest before > nearest after.
// An invalid search range will simply return `(found = false, index = start)`.
/// @dev Returns whether `map` contains `needle`, and the index of `needle`.
function searchSorted(Uint8Map storage map, uint8 needle, uint256 start, uint256 end)
internal
view
returns (bool found, uint256 index)
{
return searchSorted(map.map, needle, start, end, 8);
}
/// @dev Returns whether `map` contains `needle`, and the index of `needle`.
function searchSorted(Uint16Map storage map, uint16 needle, uint256 start, uint256 end)
internal
view
returns (bool found, uint256 index)
{
return searchSorted(map.map, needle, start, end, 16);
}
/// @dev Returns whether `map` contains `needle`, and the index of `needle`.
function searchSorted(Uint32Map storage map, uint32 needle, uint256 start, uint256 end)
internal
view
returns (bool found, uint256 index)
{
return searchSorted(map.map, needle, start, end, 32);
}
/// @dev Returns whether `map` contains `needle`, and the index of `needle`.
function searchSorted(Uint40Map storage map, uint40 needle, uint256 start, uint256 end)
internal
view
returns (bool found, uint256 index)
{
return searchSorted(map.map, needle, start, end, 40);
}
/// @dev Returns whether `map` contains `needle`, and the index of `needle`.
function searchSorted(Uint64Map storage map, uint64 needle, uint256 start, uint256 end)
internal
view
returns (bool found, uint256 index)
{
return searchSorted(map.map, needle, start, end, 64);
}
/// @dev Returns whether `map` contains `needle`, and the index of `needle`.
function searchSorted(Uint128Map storage map, uint128 needle, uint256 start, uint256 end)
internal
view
returns (bool found, uint256 index)
{
return searchSorted(map.map, needle, start, end, 128);
}
/// @dev Returns whether `map` contains `needle`, and the index of `needle`.
function searchSorted(
mapping(uint256 => uint256) storage map,
uint256 needle,
uint256 start,
uint256 end,
uint256 bitWidth
) internal view returns (bool found, uint256 index) {
unchecked {
if (start >= end) end = start;
uint256 t;
uint256 o = start - 1; // Offset to derive the actual index.
uint256 l = 1; // Low.
uint256 d = _rawDiv(256, bitWidth); // Bucket size.
uint256 m = (1 << bitWidth) - 1; // Value mask.
uint256 h = end - start; // High.
while (true) {
index = (l & h) + ((l ^ h) >> 1);
if (l > h) break;
t = (map[_rawDiv(index + o, d)] >> (_rawMod(index + o, d) * bitWidth)) & m;
if (t == needle) break;
if (needle <= t) h = index - 1;
else l = index + 1;
}
/// @solidity memory-safe-assembly
assembly {
m := or(iszero(index), iszero(bitWidth))
found := iszero(or(xor(t, needle), m))
index := add(o, xor(index, mul(xor(index, 1), m)))
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PRIVATE HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns `x / y`, returning 0 if `y` is zero.
function _rawDiv(uint256 x, uint256 y) private pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := div(x, y)
}
}
/// @dev Returns `x % y`, returning 0 if `y` is zero.
function _rawMod(uint256 x, uint256 y) private pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mod(x, y)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Optimized sorts and operations for sorted arrays.
/// @author Solady (https://github.com/Vectorized/solady/blob/main/src/utils/LibSort.sol)
library LibSort {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INSERTION SORT */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// - Faster on small arrays (32 or lesser elements).
// - Faster on almost sorted arrays.
// - Smaller bytecode.
// - May be suitable for view functions intended for off-chain querying.
/// @dev Sorts the array in-place with insertion sort.
function insertionSort(uint256[] memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
let n := mload(a) // Length of `a`.
mstore(a, 0) // For insertion sort's inner loop to terminate.
let h := add(a, shl(5, n)) // High slot.
let s := 0x20
let w := not(0x1f)
for { let i := add(a, s) } 1 {} {
i := add(i, s)
if gt(i, h) { break }
let k := mload(i) // Key.
let j := add(i, w) // The slot before the current slot.
let v := mload(j) // The value of `j`.
if iszero(gt(v, k)) { continue }
for {} 1 {} {
mstore(add(j, s), v)
j := add(j, w) // `sub(j, 0x20)`.
v := mload(j)
if iszero(gt(v, k)) { break }
}
mstore(add(j, s), k)
}
mstore(a, n) // Restore the length of `a`.
}
}
/// @dev Sorts the array in-place with insertion sort.
function insertionSort(int256[] memory a) internal pure {
_flipSign(a);
insertionSort(_toUints(a));
_flipSign(a);
}
/// @dev Sorts the array in-place with insertion sort.
function insertionSort(address[] memory a) internal pure {
insertionSort(_toUints(a));
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTRO-QUICKSORT */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// - Faster on larger arrays (more than 32 elements).
// - Robust performance.
// - Larger bytecode.
/// @dev Sorts the array in-place with intro-quicksort.
function sort(uint256[] memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
let w := not(0x1f)
let s := 0x20
let n := mload(a) // Length of `a`.
mstore(a, 0) // For insertion sort's inner loop to terminate.
// Let the stack be the start of the free memory.
let stack := mload(0x40)
for {} iszero(lt(n, 2)) {} {
// Push `l` and `h` to the stack.
// The `shl` by 5 is equivalent to multiplying by `0x20`.
let l := add(a, s)
let h := add(a, shl(5, n))
let j := l
// forgefmt: disable-next-item
for {} iszero(or(eq(j, h), gt(mload(j), mload(add(j, s))))) {} {
j := add(j, s)
}
// If the array is already sorted.
if eq(j, h) { break }
j := h
// forgefmt: disable-next-item
for {} iszero(gt(mload(j), mload(add(j, w)))) {} {
j := add(j, w) // `sub(j, 0x20)`.
}
// If the array is reversed sorted.
if eq(j, l) {
for {} 1 {} {
let t := mload(l)
mstore(l, mload(h))
mstore(h, t)
h := add(h, w) // `sub(h, 0x20)`.
l := add(l, s)
if iszero(lt(l, h)) { break }
}
break
}
// Push `l` and `h` onto the stack.
mstore(stack, l)
mstore(add(stack, s), h)
stack := add(stack, 0x40)
break
}
for { let stackBottom := mload(0x40) } iszero(eq(stack, stackBottom)) {} {
// Pop `l` and `h` from the stack.
stack := sub(stack, 0x40)
let l := mload(stack)
let h := mload(add(stack, s))
// Do insertion sort if `h - l <= 0x20 * 12`.
// Threshold is fine-tuned via trial and error.
if iszero(gt(sub(h, l), 0x180)) {
// Hardcode sort the first 2 elements.
let i := add(l, s)
if iszero(lt(mload(l), mload(i))) {
let t := mload(i)
mstore(i, mload(l))
mstore(l, t)
}
for {} 1 {} {
i := add(i, s)
if gt(i, h) { break }
let k := mload(i) // Key.
let j := add(i, w) // The slot before the current slot.
let v := mload(j) // The value of `j`.
if iszero(gt(v, k)) { continue }
for {} 1 {} {
mstore(add(j, s), v)
j := add(j, w)
v := mload(j)
if iszero(gt(v, k)) { break }
}
mstore(add(j, s), k)
}
continue
}
// Pivot slot is the average of `l` and `h`.
let p := add(shl(5, shr(6, add(l, h))), and(31, l))
// Median of 3 with sorting.
{
function swap(a_, b_) -> _b, _a {
_b := a_
_a := b_
}
let e0 := mload(l)
let e1 := mload(h)
if iszero(lt(e0, e1)) { e1, e0 := swap(e0, e1) }
let e2 := mload(p)
if iszero(lt(e2, e1)) { e2, e1 := swap(e1, e2) }
if iszero(lt(e0, e2)) { e2, e0 := swap(e0, e2) }
mstore(p, e2)
mstore(h, e1)
mstore(l, e0)
}
// Hoare's partition.
{
// The value of the pivot slot.
let x := mload(p)
p := h
for { let i := l } 1 {} {
for {} 1 {} {
i := add(i, s)
if iszero(gt(x, mload(i))) { break }
}
let j := p
for {} 1 {} {
j := add(j, w)
if iszero(lt(x, mload(j))) { break }
}
p := j
if iszero(lt(i, p)) { break }
// Swap slots `i` and `p`.
let t := mload(i)
mstore(i, mload(p))
mstore(p, t)
}
}
// If slice on right of pivot is non-empty, push onto stack.
{
mstore(stack, add(p, s))
// Skip `mstore(add(stack, 0x20), h)`, as it is already on the stack.
stack := add(stack, shl(6, lt(add(p, s), h)))
}
// If slice on left of pivot is non-empty, push onto stack.
{
mstore(stack, l)
mstore(add(stack, s), p)
stack := add(stack, shl(6, gt(p, l)))
}
}
mstore(a, n) // Restore the length of `a`.
}
}
/// @dev Sorts the array in-place with intro-quicksort.
function sort(int256[] memory a) internal pure {
_flipSign(a);
sort(_toUints(a));
_flipSign(a);
}
/// @dev Sorts the array in-place with intro-quicksort.
function sort(address[] memory a) internal pure {
sort(_toUints(a));
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* OTHER USEFUL OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// For performance, the `uniquifySorted` methods will not revert if the
// array is not sorted -- it will simply remove consecutive duplicate elements.
/// @dev Removes duplicate elements from a ascendingly sorted memory array.
function uniquifySorted(uint256[] memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
// If the length of `a` is greater than 1.
if iszero(lt(mload(a), 2)) {
let x := add(a, 0x20)
let y := add(a, 0x40)
let end := add(a, shl(5, add(mload(a), 1)))
for {} 1 {} {
if iszero(eq(mload(x), mload(y))) {
x := add(x, 0x20)
mstore(x, mload(y))
}
y := add(y, 0x20)
if eq(y, end) { break }
}
mstore(a, shr(5, sub(x, a)))
}
}
}
/// @dev Removes duplicate elements from a ascendingly sorted memory array.
function uniquifySorted(int256[] memory a) internal pure {
uniquifySorted(_toUints(a));
}
/// @dev Removes duplicate elements from a ascendingly sorted memory array.
function uniquifySorted(address[] memory a) internal pure {
uniquifySorted(_toUints(a));
}
/// @dev Returns whether `a` contains `needle`, and the index of `needle`.
/// `index` precedence: equal to > nearest before > nearest after.
function searchSorted(uint256[] memory a, uint256 needle)
internal
pure
returns (bool found, uint256 index)
{
(found, index) = _searchSorted(a, needle, 0);
}
/// @dev Returns whether `a` contains `needle`, and the index of `needle`.
/// `index` precedence: equal to > nearest before > nearest after.
function searchSorted(int256[] memory a, int256 needle)
internal
pure
returns (bool found, uint256 index)
{
(found, index) = _searchSorted(_toUints(a), uint256(needle), 1 << 255);
}
/// @dev Returns whether `a` contains `needle`, and the index of `needle`.
/// `index` precedence: equal to > nearest before > nearest after.
function searchSorted(address[] memory a, address needle)
internal
pure
returns (bool found, uint256 index)
{
(found, index) = _searchSorted(_toUints(a), uint256(uint160(needle)), 0);
}
/// @dev Reverses the array in-place.
function reverse(uint256[] memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
if iszero(lt(mload(a), 2)) {
let s := 0x20
let w := not(0x1f)
let h := add(a, shl(5, mload(a)))
for { a := add(a, s) } 1 {} {
let t := mload(a)
mstore(a, mload(h))
mstore(h, t)
h := add(h, w)
a := add(a, s)
if iszero(lt(a, h)) { break }
}
}
}
}
/// @dev Reverses the array in-place.
function reverse(int256[] memory a) internal pure {
reverse(_toUints(a));
}
/// @dev Reverses the array in-place.
function reverse(address[] memory a) internal pure {
reverse(_toUints(a));
}
/// @dev Returns a copy of the array.
function copy(uint256[] memory a) internal pure returns (uint256[] memory result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
let end := add(add(result, 0x20), shl(5, mload(a)))
let o := result
for { let d := sub(a, result) } 1 {} {
mstore(o, mload(add(o, d)))
o := add(0x20, o)
if eq(o, end) { break }
}
mstore(0x40, o)
}
}
/// @dev Returns a copy of the array.
function copy(int256[] memory a) internal pure returns (int256[] memory result) {
result = _toInts(copy(_toUints(a)));
}
/// @dev Returns a copy of the array.
function copy(address[] memory a) internal pure returns (address[] memory result) {
result = _toAddresses(copy(_toUints(a)));
}
/// @dev Returns whether the array is sorted in ascending order.
function isSorted(uint256[] memory a) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := 1
if iszero(lt(mload(a), 2)) {
let end := add(a, shl(5, mload(a)))
for { a := add(a, 0x20) } 1 {} {
let p := mload(a)
a := add(a, 0x20)
result := iszero(gt(p, mload(a)))
if iszero(mul(result, xor(a, end))) { break }
}
}
}
}
/// @dev Returns whether the array is sorted in ascending order.
function isSorted(int256[] memory a) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := 1
if iszero(lt(mload(a), 2)) {
let end := add(a, shl(5, mload(a)))
for { a := add(a, 0x20) } 1 {} {
let p := mload(a)
a := add(a, 0x20)
result := iszero(sgt(p, mload(a)))
if iszero(mul(result, xor(a, end))) { break }
}
}
}
}
/// @dev Returns whether the array is sorted in ascending order.
function isSorted(address[] memory a) internal pure returns (bool result) {
result = isSorted(_toUints(a));
}
/// @dev Returns whether the array is strictly ascending (sorted and uniquified).
function isSortedAndUniquified(uint256[] memory a) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := 1
if iszero(lt(mload(a), 2)) {
let end := add(a, shl(5, mload(a)))
for { a := add(a, 0x20) } 1 {} {
let p := mload(a)
a := add(a, 0x20)
result := lt(p, mload(a))
if iszero(mul(result, xor(a, end))) { break }
}
}
}
}
/// @dev Returns whether the array is strictly ascending (sorted and uniquified).
function isSortedAndUniquified(int256[] memory a) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := 1
if iszero(lt(mload(a), 2)) {
let end := add(a, shl(5, mload(a)))
for { a := add(a, 0x20) } 1 {} {
let p := mload(a)
a := add(a, 0x20)
result := slt(p, mload(a))
if iszero(mul(result, xor(a, end))) { break }
}
}
}
}
/// @dev Returns whether the array is strictly ascending (sorted and uniquified).
function isSortedAndUniquified(address[] memory a) internal pure returns (bool result) {
result = isSortedAndUniquified(_toUints(a));
}
/// @dev Returns the sorted set difference of `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function difference(uint256[] memory a, uint256[] memory b)
internal
pure
returns (uint256[] memory c)
{
c = _difference(a, b, 0);
}
/// @dev Returns the sorted set difference between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function difference(int256[] memory a, int256[] memory b)
internal
pure
returns (int256[] memory c)
{
c = _toInts(_difference(_toUints(a), _toUints(b), 1 << 255));
}
/// @dev Returns the sorted set difference between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function difference(address[] memory a, address[] memory b)
internal
pure
returns (address[] memory c)
{
c = _toAddresses(_difference(_toUints(a), _toUints(b), 0));
}
/// @dev Returns the sorted set intersection between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function intersection(uint256[] memory a, uint256[] memory b)
internal
pure
returns (uint256[] memory c)
{
c = _intersection(a, b, 0);
}
/// @dev Returns the sorted set intersection between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function intersection(int256[] memory a, int256[] memory b)
internal
pure
returns (int256[] memory c)
{
c = _toInts(_intersection(_toUints(a), _toUints(b), 1 << 255));
}
/// @dev Returns the sorted set intersection between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function intersection(address[] memory a, address[] memory b)
internal
pure
returns (address[] memory c)
{
c = _toAddresses(_intersection(_toUints(a), _toUints(b), 0));
}
/// @dev Returns the sorted set union of `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function union(uint256[] memory a, uint256[] memory b)
internal
pure
returns (uint256[] memory c)
{
c = _union(a, b, 0);
}
/// @dev Returns the sorted set union of `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function union(int256[] memory a, int256[] memory b)
internal
pure
returns (int256[] memory c)
{
c = _toInts(_union(_toUints(a), _toUints(b), 1 << 255));
}
/// @dev Returns the sorted set union between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function union(address[] memory a, address[] memory b)
internal
pure
returns (address[] memory c)
{
c = _toAddresses(_union(_toUints(a), _toUints(b), 0));
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PRIVATE HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Reinterpret cast to an uint256 array.
function _toUints(int256[] memory a) private pure returns (uint256[] memory casted) {
/// @solidity memory-safe-assembly
assembly {
casted := a
}
}
/// @dev Reinterpret cast to an uint256 array.
function _toUints(address[] memory a) private pure returns (uint256[] memory casted) {
/// @solidity memory-safe-assembly
assembly {
// As any address written to memory will have the upper 96 bits
// of the word zeroized (as per Solidity spec), we can directly
// compare these addresses as if they are whole uint256 words.
casted := a
}
}
/// @dev Reinterpret cast to an int array.
function _toInts(uint256[] memory a) private pure returns (int256[] memory casted) {
/// @solidity memory-safe-assembly
assembly {
casted := a
}
}
/// @dev Reinterpret cast to an address array.
function _toAddresses(uint256[] memory a) private pure returns (address[] memory casted) {
/// @solidity memory-safe-assembly
assembly {
casted := a
}
}
/// @dev Converts an array of signed integers to unsigned
/// integers suitable for sorting or vice versa.
function _flipSign(int256[] memory a) private pure {
/// @solidity memory-safe-assembly
assembly {
let w := shl(255, 1)
for { let end := add(a, shl(5, mload(a))) } iszero(eq(a, end)) {} {
a := add(a, 0x20)
mstore(a, add(mload(a), w))
}
}
}
/// @dev Returns whether `a` contains `needle`, and the index of `needle`.
/// `index` precedence: equal to > nearest before > nearest after.
function _searchSorted(uint256[] memory a, uint256 needle, uint256 signed)
private
pure
returns (bool found, uint256 index)
{
/// @solidity memory-safe-assembly
assembly {
let w := not(0)
let l := 1
let h := mload(a)
let t := 0
for { needle := add(signed, needle) } 1 {} {
index := shr(1, add(l, h))
t := add(signed, mload(add(a, shl(5, index))))
if or(gt(l, h), eq(t, needle)) { break }
// Decide whether to search the left or right half.
if iszero(gt(needle, t)) {
h := add(index, w)
continue
}
l := add(index, 1)
}
// `index` will be zero in the case of an empty array,
// or when the value is less than the smallest value in the array.
found := eq(t, needle)
t := iszero(iszero(index))
index := mul(add(index, w), t)
found := and(found, t)
}
}
/// @dev Returns the sorted set difference of `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function _difference(uint256[] memory a, uint256[] memory b, uint256 signed)
private
pure
returns (uint256[] memory c)
{
/// @solidity memory-safe-assembly
assembly {
let s := 0x20
let aEnd := add(a, shl(5, mload(a)))
let bEnd := add(b, shl(5, mload(b)))
c := mload(0x40) // Set `c` to the free memory pointer.
a := add(a, s)
b := add(b, s)
let k := c
for {} iszero(or(gt(a, aEnd), gt(b, bEnd))) {} {
let u := mload(a)
let v := mload(b)
if iszero(xor(u, v)) {
a := add(a, s)
b := add(b, s)
continue
}
if iszero(lt(add(u, signed), add(v, signed))) {
b := add(b, s)
continue
}
k := add(k, s)
mstore(k, u)
a := add(a, s)
}
for {} iszero(gt(a, aEnd)) {} {
k := add(k, s)
mstore(k, mload(a))
a := add(a, s)
}
mstore(c, shr(5, sub(k, c))) // Store the length of `c`.
mstore(0x40, add(k, s)) // Allocate the memory for `c`.
}
}
/// @dev Returns the sorted set intersection between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function _intersection(uint256[] memory a, uint256[] memory b, uint256 signed)
private
pure
returns (uint256[] memory c)
{
/// @solidity memory-safe-assembly
assembly {
let s := 0x20
let aEnd := add(a, shl(5, mload(a)))
let bEnd := add(b, shl(5, mload(b)))
c := mload(0x40) // Set `c` to the free memory pointer.
a := add(a, s)
b := add(b, s)
let k := c
for {} iszero(or(gt(a, aEnd), gt(b, bEnd))) {} {
let u := mload(a)
let v := mload(b)
if iszero(xor(u, v)) {
k := add(k, s)
mstore(k, u)
a := add(a, s)
b := add(b, s)
continue
}
if iszero(lt(add(u, signed), add(v, signed))) {
b := add(b, s)
continue
}
a := add(a, s)
}
mstore(c, shr(5, sub(k, c))) // Store the length of `c`.
mstore(0x40, add(k, s)) // Allocate the memory for `c`.
}
}
/// @dev Returns the sorted set union of `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function _union(uint256[] memory a, uint256[] memory b, uint256 signed)
private
pure
returns (uint256[] memory c)
{
/// @solidity memory-safe-assembly
assembly {
let s := 0x20
let aEnd := add(a, shl(5, mload(a)))
let bEnd := add(b, shl(5, mload(b)))
c := mload(0x40) // Set `c` to the free memory pointer.
a := add(a, s)
b := add(b, s)
let k := c
for {} iszero(or(gt(a, aEnd), gt(b, bEnd))) {} {
let u := mload(a)
let v := mload(b)
if iszero(xor(u, v)) {
k := add(k, s)
mstore(k, u)
a := add(a, s)
b := add(b, s)
continue
}
if iszero(lt(add(u, signed), add(v, signed))) {
k := add(k, s)
mstore(k, v)
b := add(b, s)
continue
}
k := add(k, s)
mstore(k, u)
a := add(a, s)
}
for {} iszero(gt(a, aEnd)) {} {
k := add(k, s)
mstore(k, mload(a))
a := add(a, s)
}
for {} iszero(gt(b, bEnd)) {} {
k := add(k, s)
mstore(k, mload(b))
b := add(b, s)
}
mstore(c, shr(5, sub(k, c))) // Store the length of `c`.
mstore(0x40, add(k, s)) // Allocate the memory for `c`.
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/*
_______ _______ _______ _______
|\ /|\ /|\ /|\ /|
| +---+ | +---+ | +---+ | +---+ |
| | | | | | | | | | | | |
| |N | | |S | | |Y | | |S | |
| +---+ | +---+ | +---+ | +---+ |
|/_____\|/_____\|/_____\|/_____\|
*/
import "../ERC721TLC.sol";
import "../extensions/ERC721TLCDataURI.sol";
import "../extensions/ERC721TransferValidator.sol";
import "../extensions/ERC2771Context.sol";
import "solady/tokens/ERC2981.sol";
import "solady/auth/OwnableRoles.sol";
import "solady/utils/LibBitmap.sol";
import "solady/utils/LibSort.sol";
interface IERC721TransferValidator {
function validateTransfer(address caller, address from, address to, uint256 tokenId) external view;
}
/// @notice NFT Membership with Life Cycle implementation contract by Normies (https://normi.es)
/// @author 0xkuwabatake (@0xkuwabatake)
contract NormiesGenesis is
ERC721TLC,
ERC721TLCDataURI,
ERC721TransferValidator,
ERC2771Context,
ERC2981,
OwnableRoles
{
using LibBitmap for LibBitmap.Bitmap;
// ============================================================================================
// O-' STORAGE
// ============================================================================================
/// @dev Mapping from address => `tierId` => number minted token ID.
mapping(address => mapping(uint256 => uint256)) private _numberMinted;
/// @dev Mapping from `tierId` => merkle root.
mapping(uint256 => bytes32) private _merkleRoot;
/// @dev Mapping from `index` => `paused` or mint activity status.
///
/// Note:
/// - Index 0 is allocated for paused status toggle.
/// - Index 1 - 10 are allocated for mint activity status toggle from tier ID #1 - #10.
/// ```
LibBitmap.Bitmap private _status;
/// @dev Withdrawal address.
address public withdrawalAddress;
// ============================================================================================
// O-' CUSTOM EVENTS
// ============================================================================================
/// @dev Emitted when mint status for `tierId` is updated to `state`.
event MintStatusUpdate(uint256 indexed tierId, bool state);
/// @dev Emitted when `mintFee` for `tierId` is updated.
event MintFeeUpdate(uint256 indexed tierId, uint256 indexed mintFee);
/// @dev Emitted when `discountedMintFee` to mint `tierToMint` for `tierIsOwned` by genesis owner is updated.
event MintFeeForGenesisOwnerUpdate(
uint256 indexed tierIsOwned,
uint256 indexed tierToMint,
uint256 indexed discountedMintFee
);
// ============================================================================================
// O-' CUSTOM ERRORS
// ============================================================================================
/// @dev Revert with an error if an address exceeds maximum number minted for tier ID.
error ExceedsMaxNumberMinted();
/// @dev Revert with an error if the maximum number of airdrop recipients is exceeded.
error ExceedsMaxRecipients();
/// @dev Revert with an error if merkle proof is invalid.
error InvalidMerkleProof();
/// @dev Revert with an error if life cycle for tier ID is undefined.
error UndefinedLifeCycle();
/// @dev Revert with an error if mint status is not active.
error MintIsNotActive();
/// @dev Revert with an error if tier ID is invalid.
error InvalidTierId();
/// @dev Revert with an error if `discountBPS` exceeds maximum BPS (10000).
error ExceedsMaxBPS();
/// @dev Revert with an error if fee is undefined.
error UndefinedFee();
/// @dev Revert with an error if not the owner of token or tier ID.
error InvalidOwner();
/// @dev Revert with an error if it's at paused state.
error Paused();
// ============================================================================================
// O-' MODIFIERS
// ============================================================================================
/// @dev Tier ID must not be 0 (zero) and greater than 10 (ten).
modifier isValidTier(uint256 tierId) {
if (tierId == 0) _revert(InvalidTierId.selector);
if (tierId > 10) _revert(InvalidTierId.selector);
_;
}
/// @dev Tier ID for whitelist mint must not be less than 1 or greater than 2.
modifier isWhitelistMintTier(uint256 tierId) {
if (tierId < 1 || tierId > 2) _revert(InvalidTierId.selector);
_;
}
/// @dev Tier ID for public mint must not be less than 3 or greater than 8.
modifier isPublicMintTier(uint256 tierId) {
if (tierId < 3 || tierId > 7) _revert(InvalidTierId.selector);
_;
}
/// @dev Tier ID for claim (free mint) must not be less than 8 or greater than 10.
modifier isClaimTier(uint256 tierId) {
if (tierId < 8 || tierId > 10) _revert(InvalidTierId.selector);
_;
}
/// @dev Life cycle value for `tierId` must be non-zero.
/// See: {TierLifeCycle - _setLifeCycle}, {ERC721TLC - _setMintExtraData}.
modifier isDefinedLifeCycle(uint256 tierId) {
if (lifeCycle(tierId) == 0) _revert(UndefinedLifeCycle.selector);
_;
}
/// @dev Only mint status for `tierId` is active.
modifier onlyMintActive(uint256 tierId) {
if (!isMintActive(tierId)) _revert(MintIsNotActive.selector);
_;
}
/// @dev Only the owner of `tokenId`.
/// See: {ERC721 - ownerOf}.
modifier onlyTokenOwner(uint256 tokenId) {
if (msg.sender != ownerOf(tokenId)) _revert(InvalidOwner.selector);
_;
}
/// @dev When it is not at paused status.
modifier whenNotPaused() {
if (isItPaused()) _revert(Paused.selector);
_;
}
// ============================================================================================
// O-' CONSTRUCTOR
// ============================================================================================
constructor() ERC721TLC("Normies Spaceventure Genesis","NSYS") {
_initializeOwner(tx.origin);
_setTransferValidator(0xA000027A9B2802E1ddf7000061001e5c005A0000);
_setDefaultRoyalty(0x351e20B00e2B42CC34Aa58d0D96aA00d4D91dabc, 25);
_setWithdrawalAddress(0x351e20B00e2B42CC34Aa58d0D96aA00d4D91dabc);
}
// ============================================================================================
// O-' EXTERNAL FUNCTIONS
// ============================================================================================
///////// EXTERNAL UPDATE TOKEN LIFE CYCLE BY TOKEN OWNER FUNCTION /////////
/// @dev Update token life cycle for `tokenId` by the owner of `tokenId`.
function updateTokenLifeCycle(uint256 tokenId)
external
payable
onlyTokenOwner(tokenId)
whenNotPaused
{
_validateUpdateFee(tokenId);
_updateTokenLifeCycle(tokenId);
}
///////// EXTERNAL MINT FUNCTIONS /////////
/// @dev Mints one single token ID from `tierId` given `merkleProof`.
function whitelistMint(uint256 tierId, bytes32[] calldata merkleProof)
external
payable
isWhitelistMintTier(tierId)
isDefinedLifeCycle(tierId)
onlyMintActive(tierId)
whenNotPaused
{
_validateNumberMinted(msg.sender, tierId);
_validateMerkleProof(msg.sender, tierId, merkleProof);
_validateMintFee(tierId);
_safeMintTier(msg.sender, tierId);
}
/// @dev Mints one single token ID from `tierId` for public.
function publicMint(uint256 tierId)
external
payable
isPublicMintTier(tierId)
isDefinedLifeCycle(tierId)
onlyMintActive(tierId)
whenNotPaused
{
_validateNumberMinted(msg.sender, tierId);
_validateMintFee(tierId);
_safeMintTier(msg.sender, tierId);
}
/// @dev Mints one single token ID from `tierId` for genesis (tierId #1 and/or #2) NFT holder.
function genesisPublicMint(uint256 tierId)
external
payable
isPublicMintTier(tierId)
isDefinedLifeCycle(tierId)
onlyMintActive(tierId)
whenNotPaused
{
_validateNumberMinted(msg.sender, tierId);
_validateMintFeeForGenesisOwner(msg.sender, tierId);
_safeMintTier(msg.sender, tierId);
}
///////// EXTERNAL MINT REQUEST BY SIGNER AND MINT BY TRUSTED FORWARDER FUNCTION /////////
/// @dev Mints one single token ID from `tierId` by trusted forwarder contract to `signer`.
function claim(address signer, uint256 tierId)
external
isClaimTier(tierId)
isDefinedLifeCycle(tierId)
onlyTrustedForwarder
whenNotPaused
{
_validateNumberMinted(signer, tierId);
_safeMintTier(signer, tierId);
}
///////// EXTERNAL MINT BY OWNER/ADMIN FUNCTIONS /////////
/// @dev Mints one single token ID from `tierId` to `to`.
function mintTo(address to, uint256 tierId)
external
isValidTier(tierId)
isDefinedLifeCycle(tierId)
onlyOwnerOrRoles(1)
whenNotPaused
{
_validateNumberMinted(to, tierId);
_safeMintTier(to, tierId);
}
/// @dev Mints one single token ID from `tierId` to `recipients
/// Note: Maximum total recipients is 20 (twenty).
function airdrop(address[] calldata recipients, uint256 tierId)
external
isValidTier(tierId)
isDefinedLifeCycle(tierId)
onlyOwnerOrRoles(1)
whenNotPaused
{
if (recipients.length > 20) _revert(ExceedsMaxRecipients.selector);
uint256 i;
unchecked {
do {
_validateNumberMinted(recipients[i], tierId);
_safeMintTier(recipients[i], tierId);
++i;
} while (i < recipients.length);
}
}
///////// WITHDRAWAL FUNCTIONS /////////
/// @dev Withdraw all of the ether balance from contract to `withdrawalAddress`.
/// Note: It does not check if `withdrawalAddress` balance is zero.
/// See: {TLCLib - forceSafeTransferAllETH}.
function withdraw()
external
onlyOwner
{
TLCLib.forceSafeTransferAllETH(withdrawalAddress, 210000);
}
/// @dev See: {_setWithdrawalAddress}.
function setWithdrawalAddress(address addr)
external
onlyOwner
{
_setWithdrawalAddress(addr);
}
///////// TIER DATA URI AND NFT METADATA UPDATE SETTERS /////////
/// @dev See {ERC721TLCDataURI - _setTierDataURI}.
function setTierDataURI(
uint256 tierId,
string calldata name,
string calldata description,
string calldata tierName,
string[2] calldata images,
string[2] calldata animationURLs
)
external
isValidTier(tierId)
onlyRolesOrOwner(2)
{
_setTierDataURI(tierId, name, description, tierName, images, animationURLs);
}
/// @dev See {ERC721TLCToken - _emitMetadataUpdate}.
function emitMetadataUpdate(uint256 fromTokenId, uint256 toTokenId)
external
onlyOwnerOrRoles(2)
{
_emitMetadataUpdate(fromTokenId, toTokenId);
}
///////// NFT DROP SETTERS /////////
/// @dev Sets merkle `root` for `tierId`.
/// Note: It does not check if `root` is bytes32(0).
function setMerkleRoot(uint256 tierId, bytes32 root)
external
isWhitelistMintTier(tierId)
onlyRolesOrOwner(1)
{
_merkleRoot[tierId] = root;
}
/// @dev Sets mint `fee` for `tierId`.
function setMintFee(uint256 tierId, uint256 fee)
external
isValidTier(tierId)
onlyRolesOrOwner(1)
{
if (fee > 0xFFFFFFFFFFFFFFFF) _revert(InvalidFee.selector);
LibMap.set(_fee, _add(tierId, 10), uint64(fee));
emit MintFeeUpdate(tierId, fee);
}
/// @dev Sets mint `fee` to mint `tierToMint` with `totalDiscount` for the owner of tierId #1.
function setMintFeeForTierOneOwner(uint256 tierToMint, uint256 totalDiscount)
external
isPublicMintTier(tierToMint)
onlyRolesOrOwner(1)
{
uint256 _discountedFee = _calculateDiscountedMintFee(tierToMint, totalDiscount);
LibMap.set(_fee, _add(tierToMint, 15), uint64(_discountedFee));
emit MintFeeForGenesisOwnerUpdate(1, tierToMint, _discountedFee);
}
/// @dev Sets mint `fee` to mint `tierToMint` with `totalDiscount` for the owner of tierId #2.
function setMintFeeForTierTwoOwner(uint256 tierToMint, uint256 totalDiscount)
external
isPublicMintTier(tierToMint)
onlyRolesOrOwner(1)
{
uint256 _discountedFee = _calculateDiscountedMintFee(tierToMint, totalDiscount);
LibMap.set(_fee, _add(tierToMint, 20), uint64(_discountedFee));
emit MintFeeForGenesisOwnerUpdate(2, tierToMint, _discountedFee);
}
/// @dev Sets mint activity status for `tierId`.
function setMintStatus(uint256 tierId)
external
isValidTier(tierId)
onlyRolesOrOwner(1)
{
bool _state;
if (!isMintActive(tierId)) {
_state = true;
} else {
_state = false;
}
LibBitmap.toggle(_status, tierId);
emit MintStatusUpdate(tierId, _state);
}
///////// LIFE CYCLE FOR TIER ID SETTERS /////////
/// @dev See: {TierLIfeCycle- _setLifeCycle}.
function setLifeCycle(uint256 tierId, uint256 numberOfDays)
external
isValidTier(tierId)
onlyRolesOrOwner(2)
{
_setLifeCycle(tierId, numberOfDays);
}
/// See: {ERC721TLCToken - _setUpdateFee}.
function setUpdateFee(uint256 tierId, uint256 fee)
external
isValidTier(tierId)
onlyRolesOrOwner(1)
{
if (fee == 0) _revert(InvalidFee.selector);
_setUpdateFee(tierId, fee);
}
/// @dev See: {TierLifeCycle - _setStartOfLifeCycle}.
/// Note: {ERC721TLCToken - updateFee} for `tierId` must be non-zero value.
function setStartOfLifeCycle(uint256 tierId, uint256 timestamp)
external
isValidTier(tierId)
onlyRolesOrOwner(2)
{
if (updateFee(tierId) == 0) _revert(UndefinedFee.selector);
_setStartOfLifeCycle(tierId, timestamp);
}
/// @dev See: {TierLifeCycle - _setLifeCycleToLive}.
function setLifeCycleToLive(uint256 tierId)
external
isValidTier(tierId)
onlyRolesOrOwner(2)
{
_setLifeCycleToLive(tierId);
if (totalSupply() != 0) _emitMetadataUpdate(_startTokenId(), totalSupply());
}
/// @dev See: {TierLifeCycle - _pauseLifeCycle}.
function pauseLifeCycle(uint256 tierId, uint256 timestamp)
external
isValidTier(tierId)
onlyRolesOrOwner(2)
{
_pauseLifeCycle(tierId, timestamp);
}
/// @dev See: {TierLifeCycle - _unpauseLifeCycle}.
function unpauseLifeCycle(uint256 tierId)
external
isValidTier(tierId)
onlyRolesOrOwner(2)
{
_unpauseLifeCycle(tierId);
_emitMetadataUpdate(_startTokenId(), totalSupply());
}
/// @dev {TierLifeCycle - _setEndOfLifeCycle}.
function setEndOfLifeCycle(uint256 tierId, uint256 timestamp)
external
isValidTier(tierId)
onlyRolesOrOwner(2)
{
_setEndOfLifeCycle(tierId, timestamp);
}
/// @dev {TierLifeCycle - _finishLifeCycle}.
function finishLifeCycle(uint256 tierId)
external
isValidTier(tierId)
onlyRolesOrOwner(2)
{
_setUpdateFee(tierId, 0); // Reset token life cycle update fee for `tierId` to 0
_finishLifeCycle(tierId);
_emitMetadataUpdate(_startTokenId(), totalSupply());
}
///////// ERC2981 SETTER OPERATIONS /////////
/// @dev See {ERC2981 - _setDefaultRoyalty}.
function setDefaultRoyalty(address receiver, uint96 feeNumerator)
external
onlyRolesOrOwner(2)
{
_setDefaultRoyalty(receiver, feeNumerator);
}
/// @dev See {ERC2981 - _deleteDefaultRoyalty}.
function resetDefaultRoyalty()
external
onlyRolesOrOwner(2)
{
_deleteDefaultRoyalty();
}
///////// ERC721 TRANSFER VALIDATOR OPERATIONS /////////
/// @dev See: {ERC721TransferValidator - _setTransferValidator}.
function setTransferValidator(address validator)
external
onlyRolesOrOwner(2)
{
_setTransferValidator(validator);
}
/// @dev See: {ERC721TransferValidator - _resetTransferValidator}.
function resetTransferValidator()
external
onlyRolesOrOwner(2)
{
_resetTransferValidator();
}
///////// ERC2771 CONTEXT SETTER OPERATIONS /////////
/// @dev See {ERC2771Context - _setTrustedForwarder}
function setTrustedForwarder(address forwarder)
external
onlyOwnerOrRoles(1)
{
_setTrustedForwarder(forwarder);
}
/// @dev See {ERC2771Context - _resetTrustedForwarder}
function resetTrustedForwarder()
external
onlyOwnerOrRoles(1)
{
_resetTrustedForwarder();
}
///////// MULTICALL OPERATION /////////
/// @dev Receives and executes a batch of function calls on this contract.
/// @dev See: {TLCLib - multicall}.
function multicall(bytes[] calldata data)
external
onlyOwnerOrRoles(1)
returns (bytes[] memory)
{
TLCLib.multicall(data);
}
///////// PAUSED STATUS OPERATION /////////
/// @dev Sets paused status toggle.
function setPausedStatus()
external
onlyOwner
{
LibBitmap.toggle(_status, 0);
}
// ============================================================================================
// O-' PUBLIC GETTER FUNCTIONS
// ============================================================================================
//// @dev Returns true if this contract implements the interface defined by `interfaceId`.
/// See: https://eips.ethereum.org/EIPS/eip-165
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override (ERC2981, ERC721TLCToken, ERC721)
returns (bool result)
{
return
interfaceId == 0x2a55205a || // ERC2981
interfaceId == 0xad0d7f6c || // ICreatorToken
ERC721TLCToken.supportsInterface(interfaceId);
}
/// @dev Returns merkle root for `tierId`.
function merkleRoot(uint256 tierId) public view returns (bytes32) {
return _merkleRoot[tierId];
}
/// @dev Returns mint fee for `tierId`.
function mintFee(uint256 tierId) public view returns (uint256) {
return uint256(LibMap.get(_fee, _add(tierId, 10)));
}
/// @dev Returns discounted mint fee for `tierId` for the owner of `tierId` #1.
function mintFeeForTierOneOwner(uint256 tierId) public view returns (uint256) {
return uint256(LibMap.get(_fee, _add(tierId, 15)));
}
/// @dev Returns discounted mint fee for `tierId` for the owner of `tierId` #2.
function mintFeeForTierTwoOwner(uint256 tierId) public view returns (uint256) {
return uint256(LibMap.get(_fee, _add(tierId, 20)));
}
/// @dev Returns number minted per `tierId` for `addr`.
function numberMinted(address addr, uint256 tierId) public view returns (uint256) {
return _numberMinted[addr][tierId];
}
/// @dev Returns if `tierId` is owned by `addr`. true if it's owned, false otherwise.
/// See: {ERC721TLC - tiersOfOwner}.
function isTierOwned(address addr, uint256 tierId) public view returns (bool result) {
uint256[] memory _tiersOfOwner = tiersOfOwner(addr);
LibSort.sort(_tiersOfOwner);
(result, ) = LibSort.searchSorted(_tiersOfOwner, tierId);
}
/// @dev Returns mint activity status for `tierId`. true if it's active, false otherwise.
function isMintActive(uint256 tierId) public view returns (bool) {
return LibBitmap.get(_status, tierId);
}
/// @dev Returns paused status. true if it's paused, false otherwise.
function isItPaused() public view returns (bool) {
return LibBitmap.get(_status, 0);
}
// ============================================================================================
// O-' INTERNAL FUNCTIONS
// ============================================================================================
/// @dev See {ERC721 - _beforeTokenTransfer}.
function _beforeTokenTransfer(address from, address to, uint256 id)
internal
virtual
override
{
if (from != address(0)) {
if (to != address(0)) {
if (_transferValidator != address(0)) {
IERC721TransferValidator(_transferValidator).validateTransfer(
msg.sender, from, to, id
);
}
}
}
}
/// @dev Sets `addr` for withdrawal address.
function _setWithdrawalAddress(address addr) internal {
withdrawalAddress = addr;
}
// ============================================================================================
// O-' PRIVATE FUNCTIONS
// ============================================================================================
///////// PRIVATE DISCOUNTED MINT FEE SETTER //////////
/// @dev Calculate discounted mint fee for `tierId` with `totalDiscount` in basis points.
/// Note: Maximum basis points (BPS) is 10000.
function _calculateDiscountedMintFee(uint256 tierId, uint256 totalDiscount)
private
view
returns (uint256 result)
{
if (mintFee(tierId) == 0) _revert(UndefinedFee.selector);
if (totalDiscount > 10000) _revert(ExceedsMaxBPS.selector);
uint256 _discount = (mintFee(tierId) * totalDiscount) / 10000;
result = _sub(mintFee(tierId), _discount);
}
///////// PRIVATE MINT VALIDATOR LOGICS //////////
/// @dev Number minted from `addr` for `tierId` validator.
/// Note: Maximum number minted is 1 (one).
function _validateNumberMinted(address addr, uint256 tierId) private {
if (_numberMinted[addr][tierId] == 1) {
_revert(ExceedsMaxNumberMinted.selector);
}
unchecked {
++_numberMinted[addr][tierId];
}
}
/// @dev Merkle proof validator.
/// See: {TLCLib - verifyMerkle}.
function _validateMerkleProof(
address addr,
uint256 tierId,
bytes32[] calldata merkleProof
) private view {
// Leaf or node is the double hashes of (`addr`, `tierId`)
// Ref: https://www.rareskills.io/post/merkle-tree-second-preimage-attack
bytes32 _leaf = keccak256(bytes.concat(keccak256(abi.encode(addr, tierId))));
bool _isValid = TLCLib.verifyMerkle(merkleProof, _merkleRoot[tierId], _leaf);
if (!_isValid) _revert(InvalidMerkleProof.selector);
}
/// @dev Mint fee for `tierId` validator.
function _validateMintFee(uint256 tierId) private {
if (mintFee(tierId) != 0) {
_validateMsgValue(mintFee(tierId));
}
}
/// @dev Mint fee from `tierId` for the owner of tierId #1 and/or tierId #2 validator.
///
/// Conditions:
/// - Mint fee and mint fee for tier one owner and tier two owner for `tierId`
/// must be non-zero value.
/// - For the owner of tier #1 and #2, {mintFeeForTierOneOwner}.
/// - For the owner of tier #1 only, {mintFeeForTierOneOwner}.
/// - For the owner of tier #2 only, {mintFeeForTierTwoOwner}.
///```
function _validateMintFeeForGenesisOwner(address owner, uint256 tierId) private {
if (mintFee(tierId) == 0) _revert(UndefinedFee.selector);
if (mintFeeForTierOneOwner(tierId) == 0) _revert(UndefinedFee.selector);
if (mintFeeForTierTwoOwner(tierId) == 0) _revert(UndefinedFee.selector);
if (isTierOwned(owner, 1) && isTierOwned(owner, 2)) {
_validateMsgValue(mintFeeForTierOneOwner(tierId));
} else if (isTierOwned(owner, 1)) {
_validateMsgValue(mintFeeForTierOneOwner(tierId));
} else if (isTierOwned(owner, 2)) {
_validateMsgValue(mintFeeForTierTwoOwner(tierId));
} else {
_revert(InvalidOwner.selector);
}
}
}
// 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
pragma solidity ^0.8.4;
/// @notice Collection of single and non-dependable helper functions for tier-based NFT drop with life cycle.
/// @author 0xkuwabatake(@0xkuwabatake)
/// @author Modified from Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solady (https://github.com/vectorized/solady/blob/main/src/utils/MerkleProofLib.sol)
/// @author Modified from Solady (https://github.com/vectorized/solady/blob/main/src/utils/Base64.sol)
/// @author Modified from Solady (https://github.com/vectorized/solady/blob/main/src/utils/DateTimeLib.sol)
/// @author Modified from Solady (https://github.com/vectorized/solady/blob/main/src/utils/Multicallable.sol)
/// @author Modified from Chiru Labs (https://github.com/chiru-labs/ERC721A/blob/main/contracts/ERC721A.sol)
library TLCLib {
/// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
/// Source: https://github.com/Vectorized/solady/blob/main/src/utils/SafeTransferLib.sol#L153
function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
// For gas estimation.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) }
}
}
}
/// @dev Returns whether `leaf` exists in the Merkle tree with `root`, given `proof`.
/// Source: https://github.com/Vectorized/solady/blob/main/src/utils/MerkleProofLib.sol#L46
function verifyMerkle(bytes32[] calldata proof, bytes32 root, bytes32 leaf)
internal
pure
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
if proof.length {
// Left shift by 5 is equivalent to multiplying by 0x20.
let end := add(proof.offset, shl(5, proof.length))
// Initialize `offset` to the offset of `proof` in the calldata.
let offset := proof.offset
// Iterate over proof elements to compute root hash.
for {} 1 {} {
// Slot of `leaf` in scratch space.
// If the condition is true: 0x20, otherwise: 0x00.
let scratch := shl(5, gt(leaf, calldataload(offset)))
// Store elements to hash contiguously in scratch space.
// Scratch space is 64 bytes (0x00 - 0x3f) and both elements are 32 bytes.
mstore(scratch, leaf)
mstore(xor(scratch, 0x20), calldataload(offset))
// Reuse `leaf` to store the hash to reduce stack operations.
leaf := keccak256(0x00, 0x40)
offset := add(offset, 0x20)
if iszero(lt(offset, end)) { break }
}
}
isValid := eq(leaf, root)
}
}
/// @dev Encodes `data` using the base64 encoding described in RFC 4648.
/// Source: https://github.com/Vectorized/solady/blob/main/src/utils/Base64.sol#L13
function toBase64(bytes memory data)
internal
pure
returns (string memory result)
{
/// @solidity memory-safe-assembly
assembly {
let dataLength := mload(data)
if dataLength {
// Multiply by 4/3 rounded up.
// The `shl(2, ...)` is equivalent to multiplying by 4.
let encodedLength := shl(2, div(add(dataLength, 2), 3))
// Set `result` to point to the start of the free memory.
result := mload(0x40)
// Store the table into the scratch space.
// Offsetted by -1 byte so that the `mload` will load the character.
// We will rewrite the free memory pointer at `0x40` later with
// the allocated size.
// The magic constant 0x0670 will turn "-_" into "+/".
mstore(0x1f, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef")
mstore(0x3f, xor("ghijklmnopqrstuvwxyz0123456789-_", mul(iszero(false), 0x0670)))
// Skip the first slot, which stores the length.
let ptr := add(result, 0x20)
let end := add(ptr, encodedLength)
let dataEnd := add(add(0x20, data), dataLength)
let dataEndValue := mload(dataEnd) // Cache the value at the `dataEnd` slot.
mstore(dataEnd, 0x00) // Zeroize the `dataEnd` slot to clear dirty bits.
// Run over the input, 3 bytes at a time.
for {} 1 {} {
data := add(data, 3) // Advance 3 bytes.
let input := mload(data)
// Write 4 bytes. Optimized for fewer stack operations.
mstore8(0, mload(and(shr(18, input), 0x3F)))
mstore8(1, mload(and(shr(12, input), 0x3F)))
mstore8(2, mload(and(shr(6, input), 0x3F)))
mstore8(3, mload(and(input, 0x3F)))
mstore(ptr, mload(0x00))
ptr := add(ptr, 4) // Advance 4 bytes.
if iszero(lt(ptr, end)) { break }
}
mstore(dataEnd, dataEndValue) // Restore the cached value at `dataEnd`.
mstore(0x40, add(end, 0x20)) // Allocate the memory.
// Equivalent to `o = [0, 2, 1][dataLength % 3]`.
let o := div(2, mod(dataLength, 3))
// Offset `ptr` and pad with '='. We can simply write over the end.
mstore(sub(ptr, o), shl(240, 0x3d3d))
// Set `o` to zero if there is padding.
o := mul(iszero(iszero(false)), o)
mstore(sub(ptr, o), 0) // Zeroize the slot after the string.
mstore(result, sub(encodedLength, o)) // Store the length.
}
}
}
/// @dev Converts a uint256 to its ASCII string decimal representation.
/// Source: https://github.com/chiru-labs/ERC721A/blob/main/contracts/ERC721A.sol#L1266
function toString(uint256 value) internal pure returns (string memory str) {
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. Total: 5 * 0x20 = 0xa0.
let m := add(mload(0x40), 0xa0)
// Update the free memory pointer to allocate.
mstore(0x40, m)
// Assign the `str` to the end.
str := sub(m, 0x20)
// Zeroize the slot after the string.
mstore(str, 0)
// Cache the end of the memory to calculate the length later.
let end := str
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
// prettier-ignore
for { let temp := value } 1 {} {
str := sub(str, 1)
// Write the character to the pointer.
// The ASCII index of the '0' character is 48.
mstore8(str, add(48, mod(temp, 10)))
// Keep dividing `temp` until zero.
temp := div(temp, 10)
// prettier-ignore
if iszero(temp) { break }
}
let length := sub(end, str)
// Move the pointer 32 bytes leftwards to make room for the length.
str := sub(str, 0x20)
// Store the length.
mstore(str, length)
}
}
/// @dev Returns (`year`,`month`,`day`) from the given unix timestamp.
/// Source: https://github.com/Vectorized/solady/blob/main/src/utils/DateTimeLib.sol#L118
function timestampToDate(uint256 timestamp)
internal
pure
returns (uint256 year, uint256 month, uint256 day)
{
(year, month, day) = epochDayToDate(timestamp / 86400);
}
/// @dev Returns (`year`,`month`,`day`) from the number of days since 1970-01-01.
/// Source: https://github.com/Vectorized/solady/blob/main/src/utils/DateTimeLib.sol#L83
function epochDayToDate(uint256 epochDay)
internal
pure
returns (uint256 year, uint256 month, uint256 day)
{
/// @solidity memory-safe-assembly
assembly {
epochDay := add(epochDay, 719468)
let doe := mod(epochDay, 146097)
let yoe :=
div(sub(sub(add(doe, div(doe, 36524)), div(doe, 1460)), eq(doe, 146096)), 365)
let doy := sub(doe, sub(add(mul(365, yoe), shr(2, yoe)), div(yoe, 100)))
let mp := div(add(mul(5, doy), 2), 153)
day := add(sub(doy, shr(11, add(mul(mp, 62719), 769))), 1)
month := byte(mp, shl(160, 0x030405060708090a0b0c0102))
year := add(add(yoe, mul(div(epochDay, 146097), 400)), lt(month, 3))
}
}
/// @dev `DELEGATECALL` with the current contract to each calldata in `data`.
/// Source: https://github.com/Vectorized/solady/blob/main/src/utils/Multicallable.sol#L32
function multicall(bytes[] calldata data) internal returns (bytes[] memory) {
assembly {
mstore(0x00, 0x20)
mstore(0x20, data.length) // Store `data.length` into `results`.
// Early return if no data.
if iszero(data.length) { return(0x00, 0x40) }
let results := 0x40
// `shl` 5 is equivalent to multiplying by 0x20.
let end := shl(5, data.length)
// Copy the offsets from calldata into memory.
calldatacopy(0x40, data.offset, end)
// Offset into `results`.
let resultsOffset := end
// Pointer to the end of `results`.
end := add(results, end)
for {} 1 {} {
// The offset of the current bytes in the calldata.
let o := add(data.offset, mload(results))
let m := add(resultsOffset, 0x40)
// Copy the current bytes from calldata to the memory.
calldatacopy(
m,
add(o, 0x20), // The offset of the current bytes' bytes.
calldataload(o) // The length of the current bytes.
)
if iszero(delegatecall(gas(), address(), m, calldataload(o), codesize(), 0x00)) {
// Bubble up the revert if the delegatecall reverts.
returndatacopy(0x00, 0x00, returndatasize())
revert(0x00, returndatasize())
}
// Append the current `resultsOffset` into `results`.
mstore(results, resultsOffset)
results := add(results, 0x20)
// Append the `returndatasize()`, and the return data.
mstore(m, returndatasize())
returndatacopy(add(m, 0x20), 0x00, returndatasize())
// Advance the `resultsOffset` by `returndatasize() + 0x20`,
// rounded up to the next multiple of 32.
resultsOffset :=
and(add(add(resultsOffset, returndatasize()), 0x3f), 0xffffffffffffffe0)
if iszero(lt(results, end)) { break }
}
return(0x00, add(resultsOffset, 0x40))
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "solady/utils/LibMap.sol";
/// @notice A contract extension logic for tier-based life cycle status management.
/// @author 0xkuwabatake(@0xkuwabatake)
abstract contract TierLifeCycle {
using LibMap for uint256;
///////// ENUM ////////////////////////////////////////////////////////////////////////////////O-'
/// @dev Life Cycle status.
///
/// Note:
/// - #0 - NotLive : initial state of a life cycle.
/// - #1 - ReadyToStart : length of life cycle in total seconds is defined and it's ready to start.
/// - #2 - ReadyToLive : start of life cycle timestamp is defined and it's ready to live.
/// - #3 - Live : life cycle is at live period.
/// - #4 - Paused : life cycle period is at paused period.
/// - #5 - Ending : end of life cycle timestamp is defined (life cycle is at ending period).
/// - #6 - Finished : life cycle period is finished and it can never go back to re-live.
/// ```
enum LifeCycleStatus {
NotLive,
ReadyToStart,
ReadyToLive,
Live,
Paused,
Ending,
Finished
}
///////// STORAGE /////////////////////////////////////////////////////////////////////////////O-'
/// Mapping from `tierId` => `LifeCycleStatus`.
mapping (uint256 => LifeCycleStatus) private _lifeCycleStatus;
/// @dev Mapping from `tierId` => life cycle related values.
///
/// Note:
/// - Index 1 - 10 are allocated for life cycle in total seconds for `tierId` #1 - #10.
/// - Index 11 - 20 are allocated for start of life cycle timestamp for `tieId` #1 - #10.
/// - Index 21 - 30 are allocated for pause of life cycle timestamp for `tierId` #1 - #10.
/// - Index 31 - 40 are allocated for end of life cycle timestamp for `tierId` #1 - #10.
/// ```
LibMap.Uint40Map private _lifeCycle;
///////// CUSTOM EVENTS ///////////////////////////////////////////////////////////////////////O-'
/// @dev Emitted when life cycle for `tierId` is updated in `totalSeconds`.
event LifeCycleUpdate(uint256 indexed tierId, uint256 indexed totalSeconds);
/// @dev Emitted when start of life cycle for `tierId` will start at `timestamp`.
event StartOfLifeCycleUpdate(uint256 indexed tierId, uint256 indexed timestamp);
/// @dev Emitted when life cycle status for `tierId` is Live at `atTimestamp`.
event LifeCycleIsLive(uint256 indexed tierId, uint256 indexed atTimestamp);
/// @dev Emitted when life cycle status for `tierId` will be paused at `timestamp`.
event LifeCycleIsPaused(uint256 indexed tierId, uint256 indexed atTimestamp);
/// @dev Emitted when life cycle status for `tierId` is unpaused at `atTimestamp`.
event LifeCycleIsUnpaused(uint256 indexed tierId, uint256 indexed atTimestamp);
/// @dev Emitted when end of life cycle for `tierId` is set at `timestamp`.
event EndOfLifeCycleSet(uint256 indexed tierId, uint256 indexed timestamp);
/// @dev Emitted when Life cycle status for `tierId` is Finished at `atTimestamp`.
event LifeCycleIsFinished(uint256 indexed tierId, uint256 indexed atTimestamp);
///////// CUSTOM ERRORS ///////////////////////////////////////////////////////////////////////O-'
/// @dev Revert with an error if LifeCycleStatus is invalid.
error InvalidLifeCycleStatus();
/// @dev Revert with an error if some state changes is initialize at invalid time.
error InvalidTimeToInitialize();
/// @dev Revert with an error if `numberOfDays` value is invalid.
error InvalidNumberOfDays();
/// @dev Revert with an error if timestamp is invalid.
error InvalidTimestamp();
///////// PUBLIC GETTER FUNCTIONS /////////////////////////////////////////////////////////////O-'
/// @dev Returns life cycle status for `tierId` in LifeCycleStatus's key value (uint8).
function lifeCycleStatus(uint256 tierId) public view returns (LifeCycleStatus) {
return _lifeCycleStatus[tierId];
}
/// @dev Returns life cycle for `tierId` in total seconds.
function lifeCycle(uint256 tierId) public view returns (uint256) {
return uint256(LibMap.get(_lifeCycle, tierId));
}
/// @dev Returns start of life cycle for `tierId` at timestamp as seconds since unix epoch.
function startOfLifeCycle(uint256 tierId) public view returns (uint256) {
return uint256(LibMap.get(_lifeCycle, _add(tierId, 10)));
}
/// @dev Returns life cycle for `tierId` is paused at timestamp as seconds since unix epoch.
function pauseOfLifeCycle(uint256 tierId) public view returns (uint256) {
return uint256(LibMap.get(_lifeCycle, _add(tierId, 20)));
}
/// @dev Returns end of life cycle for `tierId` at timestamp as seconds since unix epoch.
function endOfLifeCycle(uint256 tierId) public view returns (uint256) {
return uint256(LibMap.get(_lifeCycle, _add(tierId, 30)));
}
///////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////////O-'
///////// INTERNAL LIFE CYCLE FOR A TIER ID SETTERS /////////
/// @dev Sets life cycle for `tierId`.
///
/// Note:
/// Life cycle is length of a life cycle period in total seconds.
/// - It is mandatory to be defined before any type of mint events for `tierId`.
/// A non-zero value of it is needed as part of additional mint extra data that
/// would be initialized when every token is minted under `tierId` --
/// see {ERC721TLC - _safeMintTier} and {ERC721TLC - _setMintExtraData}. This condition
/// must be well validated at child contract.
/// - Once it is defined, it cannot be reset back to zero even after the life cyle period
/// is Finished(6) -- see {_finishLifeCycle}.
///
/// Requirements:
/// - LifeCycleStatus must be at: NotLive(0) / Live(3) / Paused(4).
/// - `numberOfDays` must be at least equal to or greater than 30 days (2592000 in total seconds).
/// - If Live(3): `numberOfDays` is able to be reinitialized start from 48 hours before
/// the end of first life cycle period.
/// - If Paused(4): `numberOfDays` is able to be reinitialized after tx's block.timestamp
/// is greater than pause of life cycle timestamp that had been defined.
/// ```
function _setLifeCycle(uint256 tierId, uint256 numberOfDays) internal {
_requireStatusIsNotLiveOrLiveOrPaused(tierId);
if (numberOfDays < 30) _revert(InvalidNumberOfDays.selector);
uint256 _totalSeconds = numberOfDays * 86400;
if (lifeCycleStatus(tierId) == LifeCycleStatus.NotLive) {
_lifeCycleStatus[tierId] = LifeCycleStatus.ReadyToStart; // NotLive(0) => ReadyToStart(1)
LibMap.set(_lifeCycle, tierId, uint40(_totalSeconds));
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.Live) {
uint256 _endOfFirstLifeCyclePeriod = _add(startOfLifeCycle(tierId), lifeCycle(tierId));
if (block.timestamp < _sub(_endOfFirstLifeCyclePeriod, 172800)) {
_revert(InvalidTimeToInitialize.selector);
}
LibMap.set(_lifeCycle, tierId, uint40(_totalSeconds));
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.Paused) {
if (block.timestamp <= pauseOfLifeCycle(tierId)) _revert(InvalidTimeToInitialize.selector);
LibMap.set(_lifeCycle, tierId, uint40(_totalSeconds));
}
emit LifeCycleUpdate(tierId, _totalSeconds);
}
/// @dev Sets start of life cycle for `tierId`.
///
/// Note:
/// Start of life cycle is a mark time of the beginning of life cycle period.
/// - Once it had been defined and life cycle status is Live(3), it can never be reinitialized.
/// - It only can reset back to zero value after {_finishLifeCycle}.
///
/// Requirements:
/// - LifeCycleStatus must be at: ReadyToStart(1) / ReadyToLive(2).
/// - `timestamp` is valid if following what is defined at {_requireValidTimestamp}.
/// - If ReadyToLive(2): `timestamp` is able to be reinitialized -- just in case
/// the tx's block.timestamp when calling {_setLifeCycleToLive} is greater than
/// the previous start of life cycle timestamp that had been defined.
/// ```
function _setStartOfLifeCycle(uint256 tierId, uint256 timestamp) internal {
_requireStatusIsReadyToStartOrReadyToLive(tierId);
_requireValidTimestamp(timestamp);
if (lifeCycleStatus(tierId) == LifeCycleStatus.ReadyToStart) {
_lifeCycleStatus[tierId] = LifeCycleStatus.ReadyToLive; // ReadyToStart(1) => ReadyToLive(2)
LibMap.set(_lifeCycle, _add(tierId, 10), uint40(timestamp));
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.ReadyToLive) {
LibMap.set(_lifeCycle, _add(tierId, 10), uint40(timestamp));
}
emit StartOfLifeCycleUpdate(tierId, timestamp);
}
/// @dev Sets life cycle status for `tierId` to Live(3).
///
/// Note:
/// - The intention of this method is to change the status from ReadyToLive(2) to Live(3) with
/// its defined start of life cycle timestamp as the starting time.
/// - When it goes Live(3), all of existing (minted) & non-existent token ID's status
/// under `tierId` will start to have their own start and end of life cycle token relatively
/// based on its current token timestamp comparing to defined start of life cycle timestamp --
/// see: {ERC721TLCToken - startOfLifeCycleToken}, {ERC721TLCToken - endOfLifeCycleToken}.
///
/// Requirements:
/// - LifeCycleStatus must be at ReadyToLive(2).
/// - Tx's block.timestamp when calling this method must not be greater than
/// the defined start of life cycle timestamp. If it is violated, it still can be reinitiated
/// after start of life cycle is being reinitialized -- see {_setStartOfLifeCycle}.
/// ```
function _setLifeCycleToLive(uint256 tierId) internal {
if (lifeCycleStatus(tierId) != LifeCycleStatus.ReadyToLive) {
_revert(InvalidLifeCycleStatus.selector);
}
if (block.timestamp > startOfLifeCycle(tierId)) _revert(InvalidTimeToInitialize.selector);
_lifeCycleStatus[tierId] = LifeCycleStatus.Live; // ReadyToLive(2) => Live(3)
emit LifeCycleIsLive(tierId, block.timestamp);
}
/// @dev Sets life cycle status for `tierId` to Paused(4).
///
/// Note:
/// Pause of a life cycle is a mark time for the ongoing life cycle period to be stopped
/// temporarily.
/// - Once it had been defined, the fee to update token life cycle would be calculated
/// proportionally when the current time (block.timestamp) is started to have a remainder
/// which less than its life cycle token -- see {ERC721TLCToken - updateTokenFee}.
/// - When the current time had passed its defined pause of life cycle timestamp,
/// all of the token statuses would immediately go back to `Active` as per default --
/// see {ERC721TLCToken - tokenStatus}.
/// - After current time had passed its defined pause of life cycle timestamp,
/// there's an option to finish the life cycle period permanently -- see {finishLifeCycle}.
///
/// Requirements:
/// - LifeCycleStatus must be at Live(3).
/// - `timestamp` is valid if following what is defined at {_requireValidTimestamp}.
/// - If the current time is at the first of life cycle period, it only can be initialized
/// start from 48 hours (172800 seconds) before the end of its period.
/// ```
function _pauseLifeCycle(uint256 tierId, uint256 timestamp) internal {
_requireStatusIsLive(tierId);
_require48HrsBeforeEndOfFirstLifeCyclePeriod(tierId);
_requireValidTimestamp(timestamp);
_lifeCycleStatus[tierId] = LifeCycleStatus.Paused; // Live(3) => Paused(4)
LibMap.set(_lifeCycle, _add(tierId, 20), uint40(timestamp));
emit LifeCycleIsPaused(tierId, timestamp);
}
/// @dev Unpause life cycle status for `tierId`.
///
/// Note:
/// The intention of this method is to continue the lifecycle period that had been stopped
/// temporarily (paused) to go back to Live(3) status -- see {_pauseOfLifeCycle}.
/// - It can be unpaused at anytime after it'd been initialized.
/// When its unpaused, the defined pause of life cycle would set back to zero and
/// life cycle status would immediately go back to Live(3) -- see {_unpauseLifeCycle}.
///
/// Requirement:
/// - LifeCycleStatus must be at Paused(4).
/// ```
function _unpauseLifeCycle(uint256 tierId) internal {
if (lifeCycleStatus(tierId) != LifeCycleStatus.Paused) {
_revert(InvalidLifeCycleStatus.selector);
}
_lifeCycleStatus[tierId] = LifeCycleStatus.Live; // Paused(4) => Live(3)
LibMap.set(_lifeCycle, _add(tierId, 20), 0);
emit LifeCycleIsUnpaused(tierId, block.timestamp);
}
/// @dev Sets end of life cycle for `tierId`.
///
/// Note:
/// End of life cycle is a mark time of the ending of life cycle period.
/// - Once it had been defined, it cannot be cancelled, therefore use it wisely!
/// - Once it had been defined, the fee to update token life cycle would be calculated
/// proportionally when the current time is started to have a remainder which less than
/// length of life cycle token - see {ERC721TLCToken - updateTokenFee}.
/// - When current time had passed its defined end of life cycle timestamp,
/// all of the token statuses would immediately go back to `Active` as per default
/// -- see {ERC721TLCToken - tokenStatus}.
/// - When current time had passed its defined end of life cycle timestamp,
/// it's strongly recommended to finish the life cycle period once for all
/// -- see {finishLifeCycle}.
///
/// Requirements:
/// - LifeCycleStatus must be at Live(3).
/// - `timestamp` is valid if following what is defined at {_requireValidTimestamp}.
/// - If the current time is at the first of life cycle period, it only can be initialized
/// start from 48 hours (172800 seconds) before the end of its first period.
/// ```
function _setEndOfLifeCycle(uint256 tierId, uint256 timestamp) internal {
_requireStatusIsLive(tierId);
_require48HrsBeforeEndOfFirstLifeCyclePeriod(tierId);
_requireValidTimestamp(timestamp);
_lifeCycleStatus[tierId] = LifeCycleStatus.Ending; // Live(3) => Ending(5)
LibMap.set(_lifeCycle, _add(tierId, 30), uint40(timestamp));
emit EndOfLifeCycleSet(tierId, timestamp);
}
/// @dev Finish life cycle for `tierId`.
///
/// Note:
/// The intention of this method is to change status from Paused(4) / Ending(5) to Finished(6)
/// and reset start and end life cycle for `tierId` values that had been defined back to zero,
/// except the current value of life cycle for `tierId` -- see: {_setLifeCycle}.
///
/// Requirements:
/// - LifeCycleStatus must be at Paused(4) / Ending (5).
/// - If Paused(4): it can be initialized when tx's block.timestamp is greater than defined
/// pause of life cycle timestamp.
/// - If Ending(5): it can be initialized when tx's block.timestamp is greater than defined
/// end of life cycle timestamp.
/// ```
function _finishLifeCycle(uint256 tierId) internal {
_requireStatusIsPausedOrEnding(tierId);
if (lifeCycleStatus(tierId) == LifeCycleStatus.Paused) {
if (block.timestamp <= pauseOfLifeCycle(tierId)) _revert(InvalidTimeToInitialize.selector);
_lifeCycleStatus[tierId] = LifeCycleStatus.Finished; // Paused(4) => Finished(6)
// Reset pause of life cycle and start of life cycle back to zero
LibMap.set(_lifeCycle, _add(tierId, 20), 0);
LibMap.set(_lifeCycle, _add(tierId, 10), 0);
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.Ending) {
if (block.timestamp <= endOfLifeCycle(tierId)) _revert(InvalidTimeToInitialize.selector);
_lifeCycleStatus[tierId] = LifeCycleStatus.Finished; // Ending(5) => Finished(6)
// Reset end of life cycle and start of life cycle back to zero
LibMap.set(_lifeCycle, _add(tierId, 30), 0);
LibMap.set(_lifeCycle, _add(tierId, 10), 0);
}
emit LifeCycleIsFinished(tierId, block.timestamp);
}
///////// INTERNAL HELPER FUNCTIONS /////////
/// @dev Unchecked arithmetic for adding two numbers.
function _add(uint256 a, uint256 b) internal pure returns (uint256 c) {
unchecked {
c = a + b;
}
}
/// @dev Unchecked arithmetic for subtracting two numbers.
function _sub(uint256 a, uint256 b) internal pure returns (uint256 c) {
unchecked {
c = a - b;
}
}
/// @dev Helper function for more efficient reverts.
function _revert(bytes4 errorSelector) internal pure {
assembly {
mstore(0x00, errorSelector)
revert(0x00, 0x04)
}
}
///////// PRIVATE LIFE CYCLE STATUS FOR TIER ID VALIDATORS /////////
/// @dev LifeCycleStatus must be at Live(3) for `tierId`.
function _requireStatusIsLive(uint256 tierId) private view {
if (lifeCycleStatus(tierId) != LifeCycleStatus.Live) {
_revert(InvalidLifeCycleStatus.selector);
}
}
/// @dev LifeCycleStatus must be at NotLive(0) / Live(3) / Paused(4)
function _requireStatusIsNotLiveOrLiveOrPaused(uint256 tierId) private view {
if (lifeCycleStatus(tierId) == LifeCycleStatus.ReadyToStart) {
_revert(InvalidLifeCycleStatus.selector);
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.ReadyToLive) {
_revert(InvalidLifeCycleStatus.selector);
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.Ending) {
_revert(InvalidLifeCycleStatus.selector);
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.Finished) {
_revert(InvalidLifeCycleStatus.selector);
}
}
/// @dev LifeCycleStatus must be at ReadyToStart(1) / ReadyToLive(2) for `tierId`.
function _requireStatusIsReadyToStartOrReadyToLive(uint256 tierId) private view {
if (lifeCycleStatus(tierId) == LifeCycleStatus.NotLive) {
_revert(InvalidLifeCycleStatus.selector);
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.Live) {
_revert(InvalidLifeCycleStatus.selector);
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.Paused) {
_revert(InvalidLifeCycleStatus.selector);
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.Ending) {
_revert(InvalidLifeCycleStatus.selector);
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.Finished) {
_revert(InvalidLifeCycleStatus.selector);
}
}
/// @dev LifeCycleStatus must be at Paused(4) / Ending(5) for `tierId`.
function _requireStatusIsPausedOrEnding(uint256 tierId) private view {
if (lifeCycleStatus(tierId) == LifeCycleStatus.NotLive) {
_revert(InvalidLifeCycleStatus.selector);
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.ReadyToStart) {
_revert(InvalidLifeCycleStatus.selector);
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.ReadyToLive) {
_revert(InvalidLifeCycleStatus.selector);
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.Live) {
_revert(InvalidLifeCycleStatus.selector);
}
if (lifeCycleStatus(tierId) == LifeCycleStatus.Finished) {
_revert(InvalidLifeCycleStatus.selector);
}
}
/// @dev Timestamp must be greater than block.timestamp but less than 1099511627775 (36812 AD).
function _requireValidTimestamp(uint256 timestamp) private view {
if (timestamp <= block.timestamp) _revert(InvalidTimestamp.selector);
if (timestamp > 0xFFFFFFFFFF) _revert(InvalidTimestamp.selector);
}
/// @dev Current time must have passed 48 hours before the end of first life cycle period.
/// Note: first of life cycle period is start of life cycle timestamp plus life cycle in total seconds.
function _require48HrsBeforeEndOfFirstLifeCyclePeriod(uint256 tierId) private view {
uint256 _endOfFirstLifeCyclePeriod = _add(startOfLifeCycle(tierId), lifeCycle(tierId));
if (block.timestamp < _sub(_endOfFirstLifeCyclePeriod, 172800)) {
_revert(InvalidTimeToInitialize.selector);
}
}
}
{
"compilationTarget": {
"src/implementations/NormiesGenesis.sol": "NormiesGenesis"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":forge-std/=lib/forge-std/src/",
":solady/=lib/solady/src/"
]
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AccountBalanceOverflow","type":"error"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"BalanceQueryForZeroAddress","type":"error"},{"inputs":[],"name":"ExceedsMaxBPS","type":"error"},{"inputs":[],"name":"ExceedsMaxNumberMinted","type":"error"},{"inputs":[],"name":"ExceedsMaxRecipients","type":"error"},{"inputs":[],"name":"InsufficientBalance","type":"error"},{"inputs":[],"name":"InvalidFee","type":"error"},{"inputs":[],"name":"InvalidLifeCycleStatus","type":"error"},{"inputs":[],"name":"InvalidMerkleProof","type":"error"},{"inputs":[],"name":"InvalidNumberOfDays","type":"error"},{"inputs":[],"name":"InvalidOwner","type":"error"},{"inputs":[],"name":"InvalidTierId","type":"error"},{"inputs":[],"name":"InvalidTimeToInitialize","type":"error"},{"inputs":[],"name":"InvalidTimeToUpdate","type":"error"},{"inputs":[],"name":"InvalidTimestamp","type":"error"},{"inputs":[],"name":"MintIsNotActive","type":"error"},{"inputs":[],"name":"NewOwnerIsZeroAddress","type":"error"},{"inputs":[],"name":"NoHandoverRequest","type":"error"},{"inputs":[],"name":"NotOwnerNorApproved","type":"error"},{"inputs":[],"name":"Paused","type":"error"},{"inputs":[],"name":"RoyaltyOverflow","type":"error"},{"inputs":[],"name":"RoyaltyReceiverIsZeroAddress","type":"error"},{"inputs":[],"name":"TokenAlreadyExists","type":"error"},{"inputs":[],"name":"TokenDoesNotExist","type":"error"},{"inputs":[],"name":"TransferFromIncorrectOwner","type":"error"},{"inputs":[],"name":"TransferToNonERC721ReceiverImplementer","type":"error"},{"inputs":[],"name":"TransferToZeroAddress","type":"error"},{"inputs":[],"name":"UnableToUpdate","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"UnauthorizedForwarder","type":"error"},{"inputs":[],"name":"UndefinedFee","type":"error"},{"inputs":[],"name":"UndefinedLifeCycle","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"isApproved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"fromTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"toTokenId","type":"uint256"}],"name":"BatchMetadataUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tierId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"EndOfLifeCycleSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tierId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"atTimestamp","type":"uint256"}],"name":"LifeCycleIsFinished","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tierId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"atTimestamp","type":"uint256"}],"name":"LifeCycleIsLive","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tierId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"atTimestamp","type":"uint256"}],"name":"LifeCycleIsPaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tierId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"atTimestamp","type":"uint256"}],"name":"LifeCycleIsUnpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tierId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"totalSeconds","type":"uint256"}],"name":"LifeCycleUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"MetadataUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tierIsOwned","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"tierToMint","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"discountedMintFee","type":"uint256"}],"name":"MintFeeForGenesisOwnerUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tierId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"mintFee","type":"uint256"}],"name":"MintFeeUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tierId","type":"uint256"},{"indexed":false,"internalType":"bool","name":"state","type":"bool"}],"name":"MintStatusUpdate","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":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"roles","type":"uint256"}],"name":"RolesUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tierId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"StartOfLifeCycleUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tierId","type":"uint256"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"string","name":"description","type":"string"},{"indexed":false,"internalType":"string","name":"tierName","type":"string"},{"indexed":false,"internalType":"string[2]","name":"images","type":"string[2]"},{"indexed":false,"internalType":"string[2]","name":"animationURLs","type":"string[2]"}],"name":"TierDataURIUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"tierId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"atTimeStamp","type":"uint256"}],"name":"TierSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tierId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"TokenLifeCycleFeeUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"tierId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"currentTimeStamp","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"currentLifeCycle","type":"uint256"}],"name":"TokenLifeCycleUpdate","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":"oldValidator","type":"address"},{"indexed":false,"internalType":"address","name":"newValidator","type":"address"}],"name":"TransferValidatorUpdated","type":"event"},{"inputs":[{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"airdrop","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cancelOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"signer","type":"address"},{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"completeOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"fromTokenId","type":"uint256"},{"internalType":"uint256","name":"toTokenId","type":"uint256"}],"name":"emitMetadataUpdate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"endOfLifeCycle","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"endOfLifeCycleToken","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"endOfLifeCycleTokenUnchecked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"finishLifeCycle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"genesisPublicMint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"result","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTransferValidationFunction","outputs":[{"internalType":"bytes4","name":"functionSignature","type":"bytes4"},{"internalType":"bool","name":"isViewFunction","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getTransferValidator","outputs":[{"internalType":"address","name":"","type":"address"}],"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":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"result","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isItPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"isMintActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"isTierOwned","outputs":[{"internalType":"bool","name":"result","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"forwarder","type":"address"}],"name":"isTrustedForwarder","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"lifeCycle","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"lifeCycleStatus","outputs":[{"internalType":"enum TierLifeCycle.LifeCycleStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"lifeCycleToken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"merkleRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"mintFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"mintFeeForTierOneOwner","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"mintFeeForTierTwoOwner","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"mintTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"numberMinted","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"result","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"ownerOf","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":[{"internalType":"uint256","name":"tierId","type":"uint256"},{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"pauseLifeCycle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"pauseOfLifeCycle","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"publicMint","outputs":[],"stateMutability":"payable","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":[],"name":"resetDefaultRoyalty","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"resetTransferValidator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"resetTrustedForwarder","outputs":[],"stateMutability":"nonpayable","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":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"salePrice","type":"uint256"}],"name":"royaltyInfo","outputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"royaltyAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"isApproved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint96","name":"feeNumerator","type":"uint96"}],"name":"setDefaultRoyalty","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"},{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"setEndOfLifeCycle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"},{"internalType":"uint256","name":"numberOfDays","type":"uint256"}],"name":"setLifeCycle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"setLifeCycleToLive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"},{"internalType":"bytes32","name":"root","type":"bytes32"}],"name":"setMerkleRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"},{"internalType":"uint256","name":"fee","type":"uint256"}],"name":"setMintFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierToMint","type":"uint256"},{"internalType":"uint256","name":"totalDiscount","type":"uint256"}],"name":"setMintFeeForTierOneOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierToMint","type":"uint256"},{"internalType":"uint256","name":"totalDiscount","type":"uint256"}],"name":"setMintFeeForTierTwoOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"setMintStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"setPausedStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"},{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"setStartOfLifeCycle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"description","type":"string"},{"internalType":"string","name":"tierName","type":"string"},{"internalType":"string[2]","name":"images","type":"string[2]"},{"internalType":"string[2]","name":"animationURLs","type":"string[2]"}],"name":"setTierDataURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"validator","type":"address"}],"name":"setTransferValidator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"forwarder","type":"address"}],"name":"setTrustedForwarder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"},{"internalType":"uint256","name":"fee","type":"uint256"}],"name":"setUpdateFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setWithdrawalAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"startOfLifeCycle","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"startOfLifeCycleToken","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"result","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"tierDataURI","outputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"description","type":"string"},{"internalType":"string","name":"tierName","type":"string"},{"internalType":"string[2]","name":"images","type":"string[2]"},{"internalType":"string[2]","name":"animationURLs","type":"string[2]"}],"internalType":"struct ERC721TLCDataURI.DataURI","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tierId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"tiersOfOwner","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenStatus","outputs":[{"internalType":"string","name":"result","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"tokensOfOwner","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"trustedForwarder","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"unpauseLifeCycle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"updateFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"updateTokenFee","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"updateTokenLifeCycle","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"},{"internalType":"bytes32[]","name":"merkleProof","type":"bytes32[]"}],"name":"whitelistMint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawalAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]