// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
library DelegateCashLib {
address internal constant REGISTRY_V2 = 0x00000000000000447e69651d841bD8D104Bed493;
function checkDelegateForAll(address delegate, address vault) internal view returns (bool result) {
assembly {
// Cache the free memory pointer.
let m := mload(0x40)
// Store the function selector of `checkDelegateForAll(address,address,bytes32)`.
mstore(0x00, 0xe839bd53)
// Store the `delegate`.
mstore(0x20, delegate)
// Store the `vault`.
mstore(0x40, vault)
// The `bytes32 right` argument points to 0x60, which is zero.
// Arguments are evaulated last to first.
result := and(
// The returndata is 1, which represents a bool true.
eq(mload(0x00), 1),
// The staticcall is successful.
staticcall(gas(), REGISTRY_V2, 0x1c, 0x64, 0x00, 0x20)
)
// Restore the free memory pointer.
mstore(0x40, m)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Contract for EIP-712 typed structured data hashing and signing.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/EIP712.sol)
/// @author Modified from Solbase (https://github.com/Sol-DAO/solbase/blob/main/src/utils/EIP712.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol)
///
/// @dev Note, this implementation:
/// - Uses `address(this)` for the `verifyingContract` field.
/// - Does NOT use the optional EIP-712 salt.
/// - Does NOT use any EIP-712 extensions.
/// This is for simplicity and to save gas.
/// If you need to customize, please fork / modify accordingly.
abstract contract EIP712 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS AND IMMUTABLES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
bytes32 internal constant _DOMAIN_TYPEHASH =
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
uint256 private immutable _cachedThis;
uint256 private immutable _cachedChainId;
bytes32 private immutable _cachedNameHash;
bytes32 private immutable _cachedVersionHash;
bytes32 private immutable _cachedDomainSeparator;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTRUCTOR */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Cache the hashes for cheaper runtime gas costs.
/// In the case of upgradeable contracts (i.e. proxies),
/// or if the chain id changes due to a hard fork,
/// the domain separator will be seamlessly calculated on-the-fly.
constructor() {
_cachedThis = uint256(uint160(address(this)));
_cachedChainId = block.chainid;
string memory name;
string memory version;
if (!_domainNameAndVersionMayChange()) (name, version) = _domainNameAndVersion();
bytes32 nameHash = _domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(name));
bytes32 versionHash =
_domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(version));
_cachedNameHash = nameHash;
_cachedVersionHash = versionHash;
bytes32 separator;
if (!_domainNameAndVersionMayChange()) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Load the free memory pointer.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), nameHash)
mstore(add(m, 0x40), versionHash)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
separator := keccak256(m, 0xa0)
}
}
_cachedDomainSeparator = separator;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* FUNCTIONS TO OVERRIDE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Please override this function to return the domain name and version.
/// ```
/// function _domainNameAndVersion()
/// internal
/// pure
/// virtual
/// returns (string memory name, string memory version)
/// {
/// name = "Solady";
/// version = "1";
/// }
/// ```
///
/// Note: If the returned result may change after the contract has been deployed,
/// you must override `_domainNameAndVersionMayChange()` to return true.
function _domainNameAndVersion()
internal
view
virtual
returns (string memory name, string memory version);
/// @dev Returns if `_domainNameAndVersion()` may change
/// after the contract has been deployed (i.e. after the constructor).
/// Default: false.
function _domainNameAndVersionMayChange() internal pure virtual returns (bool result) {}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HASHING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the EIP-712 domain separator.
function _domainSeparator() internal view virtual returns (bytes32 separator) {
if (_domainNameAndVersionMayChange()) {
separator = _buildDomainSeparator();
} else {
separator = _cachedDomainSeparator;
if (_cachedDomainSeparatorInvalidated()) separator = _buildDomainSeparator();
}
}
/// @dev Returns the hash of the fully encoded EIP-712 message for this domain,
/// given `structHash`, as defined in
/// https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct.
///
/// The hash can be used together with {ECDSA-recover} to obtain the signer of a message:
/// ```
/// bytes32 digest = _hashTypedData(keccak256(abi.encode(
/// keccak256("Mail(address to,string contents)"),
/// mailTo,
/// keccak256(bytes(mailContents))
/// )));
/// address signer = ECDSA.recover(digest, signature);
/// ```
function _hashTypedData(bytes32 structHash) internal view virtual returns (bytes32 digest) {
// We will use `digest` to store the domain separator to save a bit of gas.
if (_domainNameAndVersionMayChange()) {
digest = _buildDomainSeparator();
} else {
digest = _cachedDomainSeparator;
if (_cachedDomainSeparatorInvalidated()) digest = _buildDomainSeparator();
}
/// @solidity memory-safe-assembly
assembly {
// Compute the digest.
mstore(0x00, 0x1901000000000000) // Store "\x19\x01".
mstore(0x1a, digest) // Store the domain separator.
mstore(0x3a, structHash) // Store the struct hash.
digest := keccak256(0x18, 0x42)
// Restore the part of the free memory slot that was overwritten.
mstore(0x3a, 0)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EIP-5267 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev See: https://eips.ethereum.org/EIPS/eip-5267
function eip712Domain()
public
view
virtual
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
fields = hex"0f"; // `0b01111`.
(name, version) = _domainNameAndVersion();
chainId = block.chainid;
verifyingContract = address(this);
salt = salt; // `bytes32(0)`.
extensions = extensions; // `new uint256[](0)`.
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PRIVATE HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the EIP-712 domain separator.
function _buildDomainSeparator() private view returns (bytes32 separator) {
// We will use `separator` to store the name hash to save a bit of gas.
bytes32 versionHash;
if (_domainNameAndVersionMayChange()) {
(string memory name, string memory version) = _domainNameAndVersion();
separator = keccak256(bytes(name));
versionHash = keccak256(bytes(version));
} else {
separator = _cachedNameHash;
versionHash = _cachedVersionHash;
}
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Load the free memory pointer.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), separator) // Name hash.
mstore(add(m, 0x40), versionHash)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
separator := keccak256(m, 0xa0)
}
}
/// @dev Returns if the cached domain separator has been invalidated.
function _cachedDomainSeparatorInvalidated() private view returns (bool result) {
uint256 cachedChainId = _cachedChainId;
uint256 cachedThis = _cachedThis;
/// @solidity memory-safe-assembly
assembly {
result := iszero(and(eq(chainid(), cachedChainId), eq(address(), cachedThis)))
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165Upgradeable {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (interfaces/IERC2981.sol)
pragma solidity ^0.8.0;
import "../utils/introspection/IERC165Upgradeable.sol";
/**
* @dev Interface for the NFT Royalty Standard.
*
* A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal
* support for royalty payments across all NFT marketplaces and ecosystem participants.
*
* _Available since v4.5._
*/
interface IERC2981Upgradeable is IERC165Upgradeable {
/**
* @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
* exchange. The royalty amount is denominated and should be paid in that same unit of exchange.
*/
function royaltyInfo(uint256 tokenId, uint256 salePrice)
external
view
returns (address receiver, uint256 royaltyAmount);
}
// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs
pragma solidity ^0.8.4;
/**
* @dev Interface of ERC721A.
*/
interface IERC721AUpgradeable {
/**
* The caller must own the token or be an approved operator.
*/
error ApprovalCallerNotOwnerNorApproved();
/**
* The token does not exist.
*/
error ApprovalQueryForNonexistentToken();
/**
* Cannot query the balance for the zero address.
*/
error BalanceQueryForZeroAddress();
/**
* Cannot mint to the zero address.
*/
error MintToZeroAddress();
/**
* The quantity of tokens minted must be more than zero.
*/
error MintZeroQuantity();
/**
* The token does not exist.
*/
error OwnerQueryForNonexistentToken();
/**
* The caller must own the token or be an approved operator.
*/
error TransferCallerNotOwnerNorApproved();
/**
* The token must be owned by `from`.
*/
error TransferFromIncorrectOwner();
/**
* Cannot safely transfer to a contract that does not implement the
* ERC721Receiver interface.
*/
error TransferToNonERC721ReceiverImplementer();
/**
* Cannot transfer to the zero address.
*/
error TransferToZeroAddress();
/**
* The token does not exist.
*/
error URIQueryForNonexistentToken();
/**
* The `quantity` minted with ERC2309 exceeds the safety limit.
*/
error MintERC2309QuantityExceedsLimit();
/**
* The `extraData` cannot be set on an unintialized ownership slot.
*/
error OwnershipNotInitializedForExtraData();
// =============================================================
// STRUCTS
// =============================================================
struct TokenOwnership {
// The address of the owner.
address addr;
// Stores the start time of ownership with minimal overhead for tokenomics.
uint64 startTimestamp;
// Whether the token has been burned.
bool burned;
// Arbitrary data similar to `startTimestamp` that can be set via {_extraData}.
uint24 extraData;
}
// =============================================================
// TOKEN COUNTERS
// =============================================================
/**
* @dev Returns the total number of tokens in existence.
* Burned tokens will reduce the count.
* To get the total number of tokens minted, please see {_totalMinted}.
*/
function totalSupply() external view returns (uint256);
// =============================================================
// IERC165
// =============================================================
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
* to learn more about how these ids are created.
*
* This function call must use less than 30000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
// =============================================================
// IERC721
// =============================================================
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables
* (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in `owner`'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`,
* checking first that contract recipients are aware of the ERC721 protocol
* to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be have been allowed to move
* this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement
* {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external payable;
/**
* @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external payable;
/**
* @dev Transfers `tokenId` from `from` to `to`.
*
* WARNING: Usage of this method is discouraged, use {safeTransferFrom}
* whenever possible.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token
* by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) external payable;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the
* zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external payable;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom}
* for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool _approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}.
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
// =============================================================
// IERC721Metadata
// =============================================================
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
// =============================================================
// IERC2309
// =============================================================
/**
* @dev Emitted when tokens in `fromTokenId` to `toTokenId`
* (inclusive) is transferred from `from` to `to`, as defined in the
* [ERC2309](https://eips.ethereum.org/EIPS/eip-2309) standard.
*
* See {_mintERC2309} for more details.
*/
event ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed from, address indexed to);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
/**
* @title IMetadataModule
* @notice The interface for custom metadata modules.
*/
interface IMetadataModule {
/**
* @dev When implemented, SoundEdition's `tokenURI` redirects execution to this `tokenURI`.
* @param tokenId The token ID to retrieve the token URI for.
* @return The token URI string.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
import { IERC721AUpgradeable } from "chiru-labs/ERC721A-Upgradeable/IERC721AUpgradeable.sol";
import { IERC2981Upgradeable } from "openzeppelin-upgradeable/interfaces/IERC2981Upgradeable.sol";
import { IERC165Upgradeable } from "openzeppelin-upgradeable/utils/introspection/IERC165Upgradeable.sol";
import { IMetadataModule } from "./IMetadataModule.sol";
/**
* @title ISoundEditionV2_1
* @notice The interface for Sound edition contracts.
*/
interface ISoundEditionV2_1 is IERC721AUpgradeable, IERC2981Upgradeable {
// =============================================================
// STRUCTS
// =============================================================
/**
* @dev The information pertaining to a tier.
*/
struct TierInfo {
// The tier.
uint8 tier;
// The current max mintable amount.
uint32 maxMintable;
// The lower bound of the maximum number of tokens that can be minted for the tier.
uint32 maxMintableLower;
// The upper bound of the maximum number of tokens that can be minted for the tier.
uint32 maxMintableUpper;
// The timestamp (in seconds since unix epoch) after which the
// max amount of tokens mintable for the tier will drop from
// `maxMintableUpper` to `maxMintableLower`.
uint32 cutoffTime;
// The total number of tokens minted for the tier.
uint32 minted;
// The mint randomness for the tier.
uint256 mintRandomness;
// Whether the tier mints have concluded.
bool mintConcluded;
// Whether the tier has mint randomness enabled.
bool mintRandomnessEnabled;
// Whether the tier is frozen.
bool isFrozen;
}
/**
* @dev A struct containing the arguments for creating a tier.
*/
struct TierCreation {
// The tier.
uint8 tier;
// The lower bound of the maximum number of tokens that can be minted for the tier.
uint32 maxMintableLower;
// The upper bound of the maximum number of tokens that can be minted for the tier.
uint32 maxMintableUpper;
// The timestamp (in seconds since unix epoch) after which the
// max amount of tokens mintable for the tier will drop from
// `maxMintableUpper` to `maxMintableLower`.
uint32 cutoffTime;
// Whether the tier has mint randomness enabled.
bool mintRandomnessEnabled;
// Whether the tier is frozen.
bool isFrozen;
}
/**
* @dev The information pertaining to this edition.
*/
struct EditionInfo {
// Base URI for the metadata.
string baseURI;
// Contract URI for OpenSea storefront.
string contractURI;
// Name of the collection.
string name;
// Symbol of the collection.
string symbol;
// Address that receives primary and secondary royalties.
address fundingRecipient;
// Address of the metadata module. Optional.
address metadataModule;
// Whether the metadata is frozen.
bool isMetadataFrozen;
// Whether the ability to create tiers is frozen.
bool isCreateTierFrozen;
// The royalty BPS (basis points).
uint16 royaltyBPS;
// Next token ID to be minted.
uint256 nextTokenId;
// Total number of tokens burned.
uint256 totalBurned;
// Total number of tokens minted.
uint256 totalMinted;
// Total number of tokens currently in existence.
uint256 totalSupply;
// An array of tier info. From lowest (0-indexed) to highest.
TierInfo[] tierInfo;
}
/**
* @dev A struct containing the arguments for initialization.
*/
struct EditionInitialization {
// Name of the collection.
string name;
// Symbol of the collection.
string symbol;
// Address of the metadata module. Optional.
address metadataModule;
// Base URI for the metadata.
string baseURI;
// Contract URI for OpenSea storefront.
string contractURI;
// Address that receives primary and secondary royalties.
address fundingRecipient;
// The royalty BPS (basis points).
uint16 royaltyBPS;
// Whether the metadata is frozen.
bool isMetadataFrozen;
// Whether the ability to create tiers is frozen.
bool isCreateTierFrozen;
// An array of tier creation structs. From lowest (0-indexed) to highest.
TierCreation[] tierCreations;
}
// =============================================================
// EVENTS
// =============================================================
/**
* @dev Emitted when the metadata module is set.
* @param metadataModule the address of the metadata module.
*/
event MetadataModuleSet(address metadataModule);
/**
* @dev Emitted when the `baseURI` is set.
* @param baseURI the base URI of the edition.
*/
event BaseURISet(string baseURI);
/**
* @dev Emitted when the `contractURI` is set.
* @param contractURI The contract URI of the edition.
*/
event ContractURISet(string contractURI);
/**
* @dev Emitted when the metadata is frozen (e.g.: `baseURI` can no longer be changed).
* @param metadataModule The address of the metadata module.
* @param baseURI The base URI of the edition.
* @param contractURI The contract URI of the edition.
*/
event MetadataFrozen(address metadataModule, string baseURI, string contractURI);
/**
* @dev Emitted when the ability to create tier is removed.
*/
event CreateTierFrozen();
/**
* @dev Emitted when the `fundingRecipient` is set.
* @param recipient The address of the funding recipient.
*/
event FundingRecipientSet(address recipient);
/**
* @dev Emitted when the `royaltyBPS` is set.
* @param bps The new royalty, measured in basis points.
*/
event RoyaltySet(uint16 bps);
/**
* @dev Emitted when the tier's maximum mintable token quantity range is set.
* @param tier The tier.
* @param lower The lower limit of the maximum number of tokens that can be minted.
* @param upper The upper limit of the maximum number of tokens that can be minted.
*/
event MaxMintableRangeSet(uint8 tier, uint32 lower, uint32 upper);
/**
* @dev Emitted when the tier's cutoff time set.
* @param tier The tier.
* @param cutoff The timestamp.
*/
event CutoffTimeSet(uint8 tier, uint32 cutoff);
/**
* @dev Emitted when the `mintRandomnessEnabled` for the tier is set.
* @param tier The tier.
* @param enabled The boolean value.
*/
event MintRandomnessEnabledSet(uint8 tier, bool enabled);
/**
* @dev Emitted upon initialization.
* @param init The initialization data.
*/
event SoundEditionInitialized(EditionInitialization init);
/**
* @dev Emitted when a tier is created.
* @param creation The tier creation data.
*/
event TierCreated(TierCreation creation);
/**
* @dev Emitted when a tier is frozen.
* @param tier The tier.
*/
event TierFrozen(uint8 tier);
/**
* @dev Emitted upon ETH withdrawal.
* @param recipient The recipient of the withdrawal.
* @param amount The amount withdrawn.
* @param caller The account that initiated the withdrawal.
*/
event ETHWithdrawn(address recipient, uint256 amount, address caller);
/**
* @dev Emitted upon ERC20 withdrawal.
* @param recipient The recipient of the withdrawal.
* @param tokens The addresses of the ERC20 tokens.
* @param amounts The amount of each token withdrawn.
* @param caller The account that initiated the withdrawal.
*/
event ERC20Withdrawn(address recipient, address[] tokens, uint256[] amounts, address caller);
/**
* @dev Emitted upon a mint.
* @param tier The tier.
* @param to The address to mint to.
* @param quantity The number of minted.
* @param fromTokenId The first token ID minted.
* @param fromTierTokenIdIndex The first token index in the tier.
*/
event Minted(uint8 tier, address to, uint256 quantity, uint256 fromTokenId, uint32 fromTierTokenIdIndex);
/**
* @dev Emitted upon an airdrop.
* @param tier The tier.
* @param to The recipients of the airdrop.
* @param quantity The number of tokens airdropped to each address in `to`.
* @param fromTokenId The first token ID minted to the first address in `to`.
* @param fromTierTokenIdIndex The first token index in the tier.
*/
event Airdropped(uint8 tier, address[] to, uint256 quantity, uint256 fromTokenId, uint32 fromTierTokenIdIndex);
/**
* @dev EIP-4906 event to signal marketplaces to refresh the metadata.
* @param fromTokenId The starting token ID.
* @param toTokenId The ending token ID.
*/
event BatchMetadataUpdate(uint256 fromTokenId, uint256 toTokenId);
// =============================================================
// ERRORS
// =============================================================
/**
* @dev The edition's metadata is frozen (e.g.: `baseURI` can no longer be changed).
*/
error MetadataIsFrozen();
/**
* @dev The ability to create tiers is frozen.
*/
error CreateTierIsFrozen();
/**
* @dev The given `royaltyBPS` is invalid.
*/
error InvalidRoyaltyBPS();
/**
* @dev A minimum of one tier must be provided to initialize a Sound Edition.
*/
error ZeroTiersProvided();
/**
* @dev The requested quantity exceeds the edition's remaining mintable token quantity.
*/
error ExceedsAvailableSupply();
/**
* @dev The given `fundingRecipient` address is invalid.
*/
error InvalidFundingRecipient();
/**
* @dev The `maxMintableLower` must not be greater than `maxMintableUpper`.
*/
error InvalidMaxMintableRange();
/**
* @dev The mint has already concluded.
*/
error MintHasConcluded();
/**
* @dev The mint has not concluded.
*/
error MintNotConcluded();
/**
* @dev Cannot perform the operation after a token has been minted.
*/
error MintsAlreadyExist();
/**
* @dev Cannot perform the operation after a token has been minted in the tier.
*/
error TierMintsAlreadyExist();
/**
* @dev The token IDs must be in strictly ascending order.
*/
error TokenIdsNotStrictlyAscending();
/**
* @dev The tier does not exist.
*/
error TierDoesNotExist();
/**
* @dev The tier already exists.
*/
error TierAlreadyExists();
/**
* @dev The tier is frozen.
*/
error TierIsFrozen();
/**
* @dev One of more of the tokens do not have the correct token tier.
*/
error InvalidTokenTier();
/**
* @dev Please wait for a while before you burn.
*/
error CannotBurnImmediately();
/**
* @dev The token for the tier query doesn't exist.
*/
error TierQueryForNonexistentToken();
// =============================================================
// PUBLIC / EXTERNAL WRITE FUNCTIONS
// =============================================================
/**
* @dev Initializes the contract.
* @param init The initialization struct.
*/
function initialize(EditionInitialization calldata init) external;
/**
* @dev Mints `quantity` tokens to addrress `to`
* Each token will be assigned a token ID that is consecutively increasing.
*
* Calling conditions:
* - The caller must be the owner of the contract, or have either the
* `ADMIN_ROLE`, `MINTER_ROLE`, which can be granted via {grantRole}.
* Multiple minters, such as different minter contracts,
* can be authorized simultaneously.
*
* @param tier The tier.
* @param to Address to mint to.
* @param quantity Number of tokens to mint.
* @return fromTokenId The first token ID minted.
*/
function mint(
uint8 tier,
address to,
uint256 quantity
) external payable returns (uint256 fromTokenId);
/**
* @dev Mints `quantity` tokens to each of the addresses in `to`.
*
* Calling conditions:
* - The caller must be the owner of the contract, or have the
* `ADMIN_ROLE`, which can be granted via {grantRole}.
*
* @param tier The tier.
* @param to Address to mint to.
* @param quantity Number of tokens to mint.
* @return fromTokenId The first token ID minted.
*/
function airdrop(
uint8 tier,
address[] calldata to,
uint256 quantity
) external payable returns (uint256 fromTokenId);
/**
* @dev Withdraws collected ETH royalties to the fundingRecipient.
*/
function withdrawETH() external;
/**
* @dev Withdraws collected ERC20 royalties to the fundingRecipient.
* @param tokens array of ERC20 tokens to withdraw
*/
function withdrawERC20(address[] calldata tokens) external;
/**
* @dev Sets metadata module.
*
* Calling conditions:
* - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
*
* @param metadataModule Address of metadata module.
*/
function setMetadataModule(address metadataModule) external;
/**
* @dev Sets global base URI.
*
* Calling conditions:
* - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
*
* @param baseURI The base URI to be set.
*/
function setBaseURI(string memory baseURI) external;
/**
* @dev Sets contract URI.
*
* Calling conditions:
* - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
*
* @param contractURI The contract URI to be set.
*/
function setContractURI(string memory contractURI) external;
/**
* @dev Freezes metadata by preventing any more changes to base URI.
*
* Calling conditions:
* - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
*/
function freezeMetadata() external;
/**
* @dev Freezes the max tier by preventing any more tiers from being added,
*
* Calling conditions:
* - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
*/
function freezeCreateTier() external;
/**
* @dev Sets funding recipient address.
*
* Calling conditions:
* - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
*
* @param fundingRecipient Address to be set as the new funding recipient.
*/
function setFundingRecipient(address fundingRecipient) external;
/**
* @dev Creates a new split wallet via the SplitMain contract, then sets it as the `fundingRecipient`.
*
* Calling conditions:
* - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
*
* @param splitMain The address of the SplitMain contract.
* @param splitData The calldata to forward to the SplitMain contract to create a split.
* @return split The address of the new split contract.
*/
function createSplit(address splitMain, bytes calldata splitData) external returns (address split);
/**
* @dev Sets royalty amount in bps (basis points).
*
* Calling conditions:
* - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
*
* @param bps The new royalty basis points to be set.
*/
function setRoyalty(uint16 bps) external;
/**
* @dev Freezes the tier.
*
* Calling conditions:
* - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
*
* @param tier The tier.
*/
function freezeTier(uint8 tier) external;
/**
* @dev Sets the edition max mintable range.
*
* Calling conditions:
* - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
*
* @param tier The tier.
* @param lower The lower limit of the maximum number of tokens that can be minted.
* @param upper The upper limit of the maximum number of tokens that can be minted.
*/
function setMaxMintableRange(
uint8 tier,
uint32 lower,
uint32 upper
) external;
/**
* @dev Sets the timestamp after which, the `editionMaxMintable` drops
* from `editionMaxMintableUpper` to `editionMaxMintableLower.
*
* Calling conditions:
* - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
*
* @param tier The tier.
* @param cutoffTime The timestamp.
*/
function setCutoffTime(uint8 tier, uint32 cutoffTime) external;
/**
* @dev Sets whether the `mintRandomness` is enabled.
*
* Calling conditions:
* - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
*
* @param tier The tier.
* @param enabled The boolean value.
*/
function setMintRandomnessEnabled(uint8 tier, bool enabled) external;
/**
* @dev Adds a new tier.
*
* Calling conditions:
* - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
*
* @param creation The tier creation data.
*/
function createTier(TierCreation calldata creation) external;
/**
* @dev Emits an event to signal to marketplaces to refresh all the metadata.
*/
function emitAllMetadataUpdate() external;
// =============================================================
// PUBLIC / EXTERNAL VIEW FUNCTIONS
// =============================================================
/**
* @dev Returns the edition info.
* @return info The latest value.
*/
function editionInfo() external view returns (EditionInfo memory info);
/**
* @dev Returns the tier info.
* @param tier The tier.
* @return info The latest value.
*/
function tierInfo(uint8 tier) external view returns (TierInfo memory info);
/**
* @dev Returns the GA tier, which is 0.
* @return The constant value.
*/
function GA_TIER() external pure returns (uint8);
/**
* @dev Basis points denominator used in fee calculations.
* @return The constant value.
*/
function BPS_DENOMINATOR() external pure returns (uint16);
/**
* @dev Returns the minter role flag.
* Note: This constant will always be 2 for past and future sound protocol contracts.
* @return The constant value.
*/
function MINTER_ROLE() external view returns (uint256);
/**
* @dev Returns the admin role flag.
* Note: This constant will always be 1 for past and future sound protocol contracts.
* @return The constant value.
*/
function ADMIN_ROLE() external view returns (uint256);
/**
* @dev Returns the tier of the `tokenId`.
* @param tokenId The token ID.
* @return The latest value.
*/
function tokenTier(uint256 tokenId) external view returns (uint8);
/**
* @dev Returns the tier of the `tokenId`.
* Note: Will NOT revert if any `tokenId` does not exist.
* If the token has not been minted, the tier will be zero.
* If the token is burned, the tier will be the tier before it was burned.
* @param tokenId The token ID.
* @return The latest value.
*/
function explicitTokenTier(uint256 tokenId) external view returns (uint8);
/**
* @dev Returns the tiers of the `tokenIds`.
* Note: Will NOT revert if any `tokenId` does not exist.
* If the token has not been minted, the tier will be zero.
* If the token is burned, the tier will be the tier before it was burned.
* @param tokenIds The token IDs.
* @return The latest values.
*/
function tokenTiers(uint256[] calldata tokenIds) external view returns (uint8[] memory);
/**
* @dev Returns an array of all the token IDs in the tier.
* @param tier The tier.
* @return tokenIds The array of token IDs in the tier.
*/
function tierTokenIds(uint8 tier) external view returns (uint256[] memory tokenIds);
/**
* @dev Returns an array of all the token IDs in the tier, within the range [start, stop).
* @param tier The tier.
* @param start The start of the range. Inclusive.
* @param stop The end of the range. Exclusive.
* @return tokenIds The array of token IDs in the tier.
*/
function tierTokenIdsIn(
uint8 tier,
uint256 start,
uint256 stop
) external view returns (uint256[] memory tokenIds);
/**
* @dev Returns the index of `tokenId` in it's tier token ID array.
* @param tokenId The token ID to find.
* @return The index of `tokenId`. If not found, returns `type(uint256).max`.
*/
function tierTokenIdIndex(uint256 tokenId) external view returns (uint256);
/**
* @dev Returns the maximum amount of tokens mintable for the tier.
* @param tier The tier.
* @return The configured value.
*/
function maxMintable(uint8 tier) external view returns (uint32);
/**
* @dev Returns the upper bound for the maximum tokens that can be minted for the tier.
* @param tier The tier.
* @return The configured value.
*/
function maxMintableUpper(uint8 tier) external view returns (uint32);
/**
* @dev Returns the lower bound for the maximum tokens that can be minted for the tier.
* @param tier The tier.
* @return The configured value.
*/
function maxMintableLower(uint8 tier) external view returns (uint32);
/**
* @dev Returns the timestamp after which `maxMintable` drops from
* `maxMintableUpper` to `maxMintableLower`.
* @param tier The tier.
* @return The configured value.
*/
function cutoffTime(uint8 tier) external view returns (uint32);
/**
* @dev Returns the number of tokens minted for the tier.
* @param tier The tier.
* @return The latest value.
*/
function tierMinted(uint8 tier) external view returns (uint32);
/**
* @dev Returns the mint randomness for the tier.
* @param tier The tier.
* @return The latest value.
*/
function mintRandomness(uint8 tier) external view returns (uint256);
/**
* @dev Returns the one-of-one token ID for the tier.
* @param tier The tier.
* @return The latest value.
*/
function mintRandomnessOneOfOne(uint8 tier) external view returns (uint32);
/**
* @dev Returns whether the `mintRandomness` has been enabled.
* @return The configured value.
*/
function mintRandomnessEnabled(uint8 tier) external view returns (bool);
/**
* @dev Returns whether the mint has been concluded for the tier.
* @param tier The tier.
* @return The latest value.
*/
function mintConcluded(uint8 tier) external view returns (bool);
/**
* @dev Returns the base token URI for the collection.
* @return The configured value.
*/
function baseURI() external view returns (string memory);
/**
* @dev Returns the contract URI to be used by Opensea.
* See: https://docs.opensea.io/docs/contract-level-metadata
* @return The configured value.
*/
function contractURI() external view returns (string memory);
/**
* @dev Returns the address of the funding recipient.
* @return The configured value.
*/
function fundingRecipient() external view returns (address);
/**
* @dev Returns the address of the metadata module.
* @return The configured value.
*/
function metadataModule() external view returns (address);
/**
* @dev Returns the royalty basis points.
* @return The configured value.
*/
function royaltyBPS() external view returns (uint16);
/**
* @dev Returns whether the tier is frozen.
* @return The configured value.
*/
function isFrozen(uint8 tier) external view returns (bool);
/**
* @dev Returns whether the metadata module is frozen.
* @return The configured value.
*/
function isMetadataFrozen() external view returns (bool);
/**
* @dev Returns whether the ability to create tiers is frozen.
* @return The configured value.
*/
function isCreateTierFrozen() external view returns (bool);
/**
* @dev Returns the next token ID to be minted.
* @return The latest value.
*/
function nextTokenId() external view returns (uint256);
/**
* @dev Returns the number of tokens minted by `owner`.
* @param owner Address to query for number minted.
* @return The latest value.
*/
function numberMinted(address owner) external view returns (uint256);
/**
* @dev Returns the number of tokens burned by `owner`.
* @param owner Address to query for number burned.
* @return The latest value.
*/
function numberBurned(address owner) external view returns (uint256);
/**
* @dev Returns the total amount of tokens minted.
* @return The latest value.
*/
function totalMinted() external view returns (uint256);
/**
* @dev Returns the total amount of tokens burned.
* @return The latest value.
*/
function totalBurned() external view returns (uint256);
/**
* @dev Returns the token URI of `tokenId`, but without reverting if
* the token does not exist.
* @return The latest value.
*/
function explicitTokenURI(uint256 tokenId) external view returns (string memory);
/**
* @dev Informs other contracts which interfaces this contract supports.
* Required by https://eips.ethereum.org/EIPS/eip-165
* @param interfaceId The interface id to check.
* @return Whether the `interfaceId` is supported.
*/
function supportsInterface(bytes4 interfaceId)
external
view
override(IERC721AUpgradeable, IERC165Upgradeable)
returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
import { IERC165 } from "openzeppelin/utils/introspection/IERC165.sol";
/**
* @title ISuperMinterV2
* @notice The interface for the generalized minter.
*/
interface ISuperMinterV2 is IERC165 {
// =============================================================
// STRUCTS
// =============================================================
/**
* @dev A struct containing the arguments to create a mint.
*/
struct MintCreation {
// The edition address.
address edition;
// The base price per token.
// For `VERIFY_SIGNATURE`, this will be the minimum limit of the signed price.
// Will be 0 if the `tier` is `GA_TIER`.
uint96 price;
// The start time of the mint.
uint32 startTime;
// The end time of the mint.
uint32 endTime;
// The maximum number of tokens an account can mint in this mint.
uint32 maxMintablePerAccount;
// The maximum number of tokens mintable.
uint32 maxMintable;
// The affiliate fee BPS.
uint16 affiliateFeeBPS;
// The affiliate Merkle root, if any.
bytes32 affiliateMerkleRoot;
// The tier of the mint.
uint8 tier;
// The address of the platform.
address platform;
// The mode of the mint. Options: `DEFAULT`, `VERIFY_MERKLE`, `VERIFY_SIGNATURE`.
uint8 mode;
// The Merkle root hash, required if `mode` is `VERIFY_MERKLE`.
bytes32 merkleRoot;
}
/**
* @dev A struct containing the arguments for mint-to.
*/
struct MintTo {
// The mint ID.
address edition;
// The tier of the mint.
uint8 tier;
// The edition-tier schedule number.
uint8 scheduleNum;
// The address to mint to.
address to;
// The number of tokens to mint.
uint32 quantity;
// The allowlisted address. Used if `mode` is `VERIFY_MERKLE`.
address allowlisted;
// The allowlisted quantity. Used if `mode` is `VERIFY_MERKLE`.
// A default zero value means no limit.
uint32 allowlistedQuantity;
// The allowlist Merkle proof.
bytes32[] allowlistProof;
// The signed price. Used if `mode` is `VERIFY_SIGNATURE`.
uint96 signedPrice;
// The signed quantity. Used if `mode` is `VERIFY_SIGNATURE`.
uint32 signedQuantity;
// The signed claimed ticket. Used if `mode` is `VERIFY_SIGNATURE`.
uint32 signedClaimTicket;
// The expiry timestamp for the signature. Used if `mode` is `VERIFY_SIGNATURE`.
uint32 signedDeadline;
// The signature by the signer. Used if `mode` is `VERIFY_SIGNATURE`.
bytes signature;
// The affiliate address. Optional.
address affiliate;
// The Merkle proof for the affiliate.
bytes32[] affiliateProof;
// The attribution ID, optional.
uint256 attributionId;
}
/**
* @dev A struct containing the arguments for platformAirdrop.
*/
struct PlatformAirdrop {
// The mint ID.
address edition;
// The tier of the mint.
uint8 tier;
// The edition-tier schedule number.
uint8 scheduleNum;
// The addresses to mint to.
address[] to;
// The signed quantity.
uint32 signedQuantity;
// The signed claimed ticket. Used if `mode` is `VERIFY_SIGNATURE`.
uint32 signedClaimTicket;
// The expiry timestamp for the signature. Used if `mode` is `VERIFY_SIGNATURE`.
uint32 signedDeadline;
// The signature by the signer. Used if `mode` is `VERIFY_SIGNATURE`.
bytes signature;
}
/**
* @dev A struct containing the total prices and fees.
*/
struct TotalPriceAndFees {
// The required Ether value.
// (`subTotal + platformTxFlatFee + artistReward + affiliateReward + platformReward`).
uint256 total;
// The total price before any additive fees.
uint256 subTotal;
// The price per token.
uint256 unitPrice;
// The final artist fee (inclusive of `finalArtistReward`).
uint256 finalArtistFee;
// The total affiliate fee (inclusive of `finalAffiliateReward`).
uint256 finalAffiliateFee;
// The final platform fee
// (inclusive of `finalPlatformReward`, `perTxFlat`, sum of `perMintBPS`).
uint256 finalPlatformFee;
}
/**
* @dev A struct containing the log data for the `Minted` event.
*/
struct MintedLogData {
// The number of tokens minted.
uint32 quantity;
// The starting token ID minted.
uint256 fromTokenId;
// The allowlisted address.
address allowlisted;
// The allowlisted quantity.
uint32 allowlistedQuantity;
// The signed quantity.
uint32 signedQuantity;
// The signed claim ticket.
uint32 signedClaimTicket;
// The affiliate address.
address affiliate;
// Whether the affiliate address is affiliated.
bool affiliated;
// The total price paid, inclusive of all fees.
uint256 requiredEtherValue;
// The price per token.
uint256 unitPrice;
// The final artist fee (inclusive of `finalArtistReward`).
uint256 finalArtistFee;
// The total affiliate fee (inclusive of `finalAffiliateReward`).
uint256 finalAffiliateFee;
// The final platform fee
// (inclusive of `finalPlatformReward`, `perTxFlat`, sum of `perMintBPS`).
uint256 finalPlatformFee;
}
/**
* @dev A struct to hold the fee configuration for a platform and a tier.
*/
struct PlatformFeeConfig {
// The amount of reward to give to the artist per mint.
uint96 artistMintReward;
// The amount of reward to give to the affiliate per mint.
uint96 affiliateMintReward;
// The amount of reward to give to the platform per mint.
uint96 platformMintReward;
// If the price is greater than this, the rewards will become the threshold variants.
uint96 thresholdPrice;
// The amount of reward to give to the artist (`unitPrice >= thresholdPrice`).
uint96 thresholdArtistMintReward;
// The amount of reward to give to the affiliate (`unitPrice >= thresholdPrice`).
uint96 thresholdAffiliateMintReward;
// The amount of reward to give to the platform (`unitPrice >= thresholdPrice`).
uint96 thresholdPlatformMintReward;
// The per-transaction flat fee.
uint96 platformTxFlatFee;
// The per-token fee BPS.
uint16 platformMintFeeBPS;
// Whether the fees are active.
bool active;
}
/**
* @dev A struct containing the mint information.
*/
struct MintInfo {
// The mint ID.
address edition;
// The tier of the mint.
uint8 tier;
// The edition-tier schedule number.
uint8 scheduleNum;
// The platform address.
address platform;
// The base price per token.
// For `VERIFY_SIGNATURE` this will be the minimum limit of the signed price.
// If the `tier` is `GA_TIER`, and the `mode` is NOT `VERIFY_SIGNATURE`,
// this value will be the GA price instead.
uint96 price;
// The start time of the mint.
uint32 startTime;
// The end time of the mint.
uint32 endTime;
// The maximum number of tokens an account can mint in this mint.
uint32 maxMintablePerAccount;
// The maximum number of tokens mintable.
uint32 maxMintable;
// The total number of tokens minted.
uint32 minted;
// The affiliate fee BPS.
uint16 affiliateFeeBPS;
// The mode of the mint.
uint8 mode;
// Whether the mint is paused.
bool paused;
// Whether the mint already has mints.
bool hasMints;
// The affiliate Merkle root, if any.
bytes32 affiliateMerkleRoot;
// The Merkle root hash, required if `mode` is `VERIFY_MERKLE`.
bytes32 merkleRoot;
// The signer address, used if `mode` is `VERIFY_SIGNATURE` or `PLATFORM_AIRDROP`.
address signer;
}
// =============================================================
// EVENTS
// =============================================================
/**
* @dev Emitted when a new mint is created.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param creation The mint creation struct.
*/
event MintCreated(address indexed edition, uint8 tier, uint8 scheduleNum, MintCreation creation);
/**
* @dev Emitted when a mint is paused or un-paused.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param paused Whether the mint is paused.
*/
event PausedSet(address indexed edition, uint8 tier, uint8 scheduleNum, bool paused);
/**
* @dev Emitted when the time range of a mint is updated.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param startTime The start time.
* @param endTime The end time.
*/
event TimeRangeSet(address indexed edition, uint8 tier, uint8 scheduleNum, uint32 startTime, uint32 endTime);
/**
* @dev Emitted when the base per-token price of a mint is updated.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param price The base per-token price.
*/
event PriceSet(address indexed edition, uint8 tier, uint8 scheduleNum, uint96 price);
/**
* @dev Emitted when the max mintable per account for a mint is updated.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param value The max mintable per account.
*/
event MaxMintablePerAccountSet(address indexed edition, uint8 tier, uint8 scheduleNum, uint32 value);
/**
* @dev Emitted when the max mintable for a mint is updated.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param value The max mintable for the mint.
*/
event MaxMintableSet(address indexed edition, uint8 tier, uint8 scheduleNum, uint32 value);
/**
* @dev Emitted when the Merkle root of a mint is updated.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param merkleRoot The Merkle root of the mint.
*/
event MerkleRootSet(address indexed edition, uint8 tier, uint8 scheduleNum, bytes32 merkleRoot);
/**
* @dev Emitted when the affiliate fee BPS for a mint is updated.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param bps The affiliate fee BPS.
*/
event AffiliateFeeSet(address indexed edition, uint8 tier, uint8 scheduleNum, uint16 bps);
/**
* @dev Emitted when the affiliate Merkle root for a mint is updated.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param root The affiliate Merkle root hash.
*/
event AffiliateMerkleRootSet(address indexed edition, uint8 tier, uint8 scheduleNum, bytes32 root);
/**
* @dev Emitted when tokens are minted.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param to The recipient of the tokens minted.
* @param data The mint-to log data.
* @param attributionId The optional attribution ID.
*/
event Minted(
address indexed edition,
uint8 tier,
uint8 scheduleNum,
address indexed to,
MintedLogData data,
uint256 indexed attributionId
);
/**
* @dev Emitted when tokens are platform airdropped.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param to The recipients of the tokens minted.
* @param signedQuantity The amount of tokens per address.
* @param fromTokenId The first token ID minted.
*/
event PlatformAirdropped(
address indexed edition,
uint8 tier,
uint8 scheduleNum,
address[] to,
uint32 signedQuantity,
uint256 fromTokenId
);
/**
* @dev Emitted when the platform fee configuration for `tier` is updated.
* @param platform The platform address.
* @param tier The tier of the mint.
* @param config The platform fee configuration.
*/
event PlatformFeeConfigSet(address indexed platform, uint8 tier, PlatformFeeConfig config);
/**
* @dev Emitted when the default platform fee configuration is updated.
* @param platform The platform address.
* @param config The platform fee configuration.
*/
event DefaultPlatformFeeConfigSet(address indexed platform, PlatformFeeConfig config);
/**
* @dev Emitted when affiliate fees are withdrawn.
* @param affiliate The recipient of the fees.
* @param accrued The amount of Ether accrued and withdrawn.
*/
event AffiliateFeesWithdrawn(address indexed affiliate, uint256 accrued);
/**
* @dev Emitted when platform fees are withdrawn.
* @param platform The platform address.
* @param accrued The amount of Ether accrued and withdrawn.
*/
event PlatformFeesWithdrawn(address indexed platform, uint256 accrued);
/**
* @dev Emitted when the platform fee recipient address is updated.
* @param platform The platform address.
* @param recipient The platform fee recipient address.
*/
event PlatformFeeAddressSet(address indexed platform, address recipient);
/**
* @dev Emitted when the per-token price for the GA tier is set.
* @param platform The platform address.
* @param price The price per token for the GA tier.
*/
event GAPriceSet(address indexed platform, uint96 price);
/**
* @dev Emitted when the signer for a platform is set.
* @param platform The platform address.
* @param signer The signer for the platform.
*/
event PlatformSignerSet(address indexed platform, address signer);
// =============================================================
// ERRORS
// =============================================================
/**
* @dev Exact payment required.
* @param paid The amount of Ether paid.
* @param required The amount of Ether required.
*/
error WrongPayment(uint256 paid, uint256 required);
/**
* @dev The mint is not opened.
* @param blockTimestamp The current block timestamp.
* @param startTime The opening time of the mint.
* @param endTime The closing time of the mint.
*/
error MintNotOpen(uint256 blockTimestamp, uint32 startTime, uint32 endTime);
/**
* @dev The mint is paused.
*/
error MintPaused();
/**
* @dev Cannot perform the operation when any mints exist.
*/
error MintsAlreadyExist();
/**
* @dev The time range is invalid.
*/
error InvalidTimeRange();
/**
* @dev The max mintable range is invalid.
*/
error InvalidMaxMintableRange();
/**
* @dev The affiliate fee BPS cannot exceed the limit.
*/
error InvalidAffiliateFeeBPS();
/**
* @dev The affiliate fee BPS cannot exceed the limit.
*/
error InvalidPlatformFeeBPS();
/**
* @dev The affiliate fee BPS cannot exceed the limit.
*/
error InvalidPlatformFlatFee();
/**
* @dev Cannot mint more than the maximum limit per account.
*/
error ExceedsMaxPerAccount();
/**
* @dev Cannot mint more than the maximum supply.
*/
error ExceedsMintSupply();
/**
* @dev Cannot mint more than the signed quantity.
*/
error ExceedsSignedQuantity();
/**
* @dev The signature is invalid.
*/
error InvalidSignature();
/**
* @dev The signature has expired.
*/
error SignatureExpired();
/**
* @dev The signature claim ticket has already been used.
*/
error SignatureAlreadyUsed();
/**
* @dev The Merkle root cannot be empty.
*/
error MerkleRootIsEmpty();
/**
* @dev The Merkle proof is invalid.
*/
error InvalidMerkleProof();
/**
* @dev The caller has not been delegated via delegate cash.
*/
error CallerNotDelegated();
/**
* @dev The max mintable amount per account cannot be zero.
*/
error MaxMintablePerAccountIsZero();
/**
* @dev The max mintable value cannot be zero.
*/
error MaxMintableIsZero();
/**
* @dev The plaform fee address cannot be the zero address.
*/
error PlatformFeeAddressIsZero();
/**
* @dev The mint does not exist.
*/
error MintDoesNotExist();
/**
* @dev The affiliate provided is invalid.
*/
error InvalidAffiliate();
/**
* @dev The mint mode provided is invalid.
*/
error InvalidMode();
/**
* @dev The signed price is too low.
*/
error SignedPriceTooLow();
/**
* @dev The platform fee configuration provided is invalid.
*/
error InvalidPlatformFeeConfig();
/**
* @dev The parameter cannot be configured.
*/
error NotConfigurable();
// =============================================================
// PUBLIC / EXTERNAL WRITE FUNCTIONS
// =============================================================
/**
* @dev Creates a mint.
* @param c The mint creation struct.
* @return scheduleNum The mint ID.
*/
function createEditionMint(MintCreation calldata c) external returns (uint8 scheduleNum);
/**
* @dev Performs a mint.
* @param p The mint-to parameters.
* @return fromTokenId The first token ID minted.
*/
function mintTo(MintTo calldata p) external payable returns (uint256 fromTokenId);
/**
* @dev Performs a platform airdrop.
* @param p The platform airdrop parameters.
* @return fromTokenId The first token ID minted.
*/
function platformAirdrop(PlatformAirdrop calldata p) external returns (uint256 fromTokenId);
/**
* @dev Sets the price of the mint.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param price The price per token.
*/
function setPrice(
address edition,
uint8 tier,
uint8 scheduleNum,
uint96 price
) external;
/**
* @dev Pause or unpase the the mint.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param paused Whether to pause the mint.
*/
function setPaused(
address edition,
uint8 tier,
uint8 scheduleNum,
bool paused
) external;
/**
* @dev Sets the time range for the the mint.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param startTime The mint start time.
* @param endTime The mint end time.
*/
function setTimeRange(
address edition,
uint8 tier,
uint8 scheduleNum,
uint32 startTime,
uint32 endTime
) external;
/**
* @dev Sets the start time for the the mint.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param startTime The mint start time.
*/
function setStartTime(
address edition,
uint8 tier,
uint8 scheduleNum,
uint32 startTime
) external;
/**
* @dev Sets the affiliate fee BPS for the mint.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param bps The fee BPS.
*/
function setAffiliateFee(
address edition,
uint8 tier,
uint8 scheduleNum,
uint16 bps
) external;
/**
* @dev Sets the affiliate Merkle root for the mint.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param root The affiliate Merkle root.
*/
function setAffiliateMerkleRoot(
address edition,
uint8 tier,
uint8 scheduleNum,
bytes32 root
) external;
/**
* @dev Sets the max mintable per account.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param value The max mintable per account.
*/
function setMaxMintablePerAccount(
address edition,
uint8 tier,
uint8 scheduleNum,
uint32 value
) external;
/**
* @dev Sets the max mintable for the mint.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param value The max mintable for the mint.
*/
function setMaxMintable(
address edition,
uint8 tier,
uint8 scheduleNum,
uint32 value
) external;
/**
* @dev Sets the mode for the mint. The mint mode must be `VERIFY_MERKLE`.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param merkleRoot The Merkle root of the mint.
*/
function setMerkleRoot(
address edition,
uint8 tier,
uint8 scheduleNum,
bytes32 merkleRoot
) external;
/**
* @dev Withdraws all accrued fees of the affiliate, to the affiliate.
* @param affiliate The affiliate address.
*/
function withdrawForAffiliate(address affiliate) external;
/**
* @dev Withdraws all accrued fees of the platform, to the their fee address.
* @param platform The platform address.
*/
function withdrawForPlatform(address platform) external;
/**
* @dev Allows the caller, as a platform, to set their fee address
* @param recipient The platform fee address of the caller.
*/
function setPlatformFeeAddress(address recipient) external;
/**
* @dev Allows the caller, as a platform, to set their per-tier fee configuration.
* @param tier The tier of the mint.
* @param c The platform fee configuration struct.
*/
function setPlatformFeeConfig(uint8 tier, PlatformFeeConfig memory c) external;
/**
* @dev Allows the caller, as a platform, to set their default fee configuration.
* @param c The platform fee configuration struct.
*/
function setDefaultPlatformFeeConfig(PlatformFeeConfig memory c) external;
/**
* @dev Allows the platform to set the price for the GA tier.
* @param price The price per token for the GA tier.
*/
function setGAPrice(uint96 price) external;
/**
* @dev Allows the platform to set their signer.
* @param signer The signer for the platform.
*/
function setPlatformSigner(address signer) external;
// =============================================================
// PUBLIC / EXTERNAL VIEW FUNCTIONS
// =============================================================
/**
* @dev Returns the GA tier. Which is 0.
* @return The constant value.
*/
function GA_TIER() external pure returns (uint8);
/**
* @dev The EIP-712 typehash for signed mints.
* @return The constant value.
*/
function MINT_TO_TYPEHASH() external pure returns (bytes32);
/**
* @dev The EIP-712 typehash for platform airdrop mints.
* @return The constant value.
*/
function PLATFORM_AIRDROP_TYPEHASH() external pure returns (bytes32);
/**
* @dev The default mint mode.
* @return The constant value.
*/
function DEFAULT() external pure returns (uint8);
/**
* @dev The mint mode for Merkle drops.
* @return The constant value.
*/
function VERIFY_MERKLE() external pure returns (uint8);
/**
* @dev The mint mode for Merkle drops.
* @return The constant value.
*/
function VERIFY_SIGNATURE() external pure returns (uint8);
/**
* @dev The mint mode for platform airdrop.
* @return The constant value.
*/
function PLATFORM_AIRDROP() external pure returns (uint8);
/**
* @dev The denominator used in BPS fee calculations.
* @return The constant value.
*/
function BPS_DENOMINATOR() external pure returns (uint16);
/**
* @dev The maximum affiliate fee BPS.
* @return The constant value.
*/
function MAX_AFFILIATE_FEE_BPS() external pure returns (uint16);
/**
* @dev The maximum per-mint platform fee BPS.
* @return The constant value.
*/
function MAX_PLATFORM_PER_MINT_FEE_BPS() external pure returns (uint16);
/**
* @dev The maximum per-mint reward. Applies to artists, affiliates, platform.
* @return The constant value.
*/
function MAX_PER_MINT_REWARD() external pure returns (uint96);
/**
* @dev The maximum platform per-transaction flat fee.
* @return The constant value.
*/
function MAX_PLATFORM_PER_TX_FLAT_FEE() external pure returns (uint96);
/**
* @dev Returns the amount of fees accrued by the platform.
* @param platform The platform address.
* @return The latest value.
*/
function platformFeesAccrued(address platform) external view returns (uint256);
/**
* @dev Returns the fee recipient for the platform.
* @param platform The platform address.
* @return The configured value.
*/
function platformFeeAddress(address platform) external view returns (address);
/**
* @dev Returns the amount of fees accrued by the affiliate.
* @param affiliate The affiliate address.
* @return The latest value.
*/
function affiliateFeesAccrued(address affiliate) external view returns (uint256);
/**
* @dev Returns the EIP-712 digest of the mint-to data for signature mints.
* @param p The mint-to parameters.
* @return The computed value.
*/
function computeMintToDigest(MintTo calldata p) external view returns (bytes32);
/**
* @dev Returns the EIP-712 digest of the mint-to data for platform airdrops.
* @param p The platform airdrop parameters.
* @return The computed value.
*/
function computePlatformAirdropDigest(PlatformAirdrop calldata p) external view returns (bytes32);
/**
* @dev Returns the total price and fees for the mint.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param quantity How many tokens to mint.
* @param hasValidAffiliate Whether there is a valid affiliate for the mint.
* @return A struct containing the total price and fees.
*/
function totalPriceAndFees(
address edition,
uint8 tier,
uint8 scheduleNum,
uint32 quantity,
bool hasValidAffiliate
) external view returns (TotalPriceAndFees memory);
/**
* @dev Returns the total price and fees for the mint.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param quantity How many tokens to mint.
* @param signedPrice The signed price.
* @param hasValidAffiliate Whether there is a valid affiliate for the mint.
* @return A struct containing the total price and fees.
*/
function totalPriceAndFeesWithSignedPrice(
address edition,
uint8 tier,
uint8 scheduleNum,
uint32 quantity,
uint96 signedPrice,
bool hasValidAffiliate
) external view returns (TotalPriceAndFees memory);
/**
* @dev Returns the GA price for the platform.
* @param platform The platform address.
* @return The configured value.
*/
function gaPrice(address platform) external view returns (uint96);
/**
* @dev Returns the signer for the platform.
* @param platform The platform address.
* @return The configured value.
*/
function platformSigner(address platform) external view returns (address);
/**
* @dev Returns the next mint schedule number for the edition-tier.
* @param edition The Sound Edition address.
* @param tier The tier.
* @return The next schedule number for the edition-tier.
*/
function nextScheduleNum(address edition, uint8 tier) external view returns (uint8);
/**
* @dev Returns the number of tokens minted by `collector` for the mint.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param collector The address which tokens are minted to,
* or in the case of `VERIFY_MERKLE`, is the allowlisted address.
* @return The number of tokens minted.
*/
function numberMinted(
address edition,
uint8 tier,
uint8 scheduleNum,
address collector
) external view returns (uint32);
/**
* @dev Returns whether the affiliate is affiliated for the mint
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param affiliate The affiliate address.
* @param affiliateProof The Merkle proof for the affiliate.
* @return The result.
*/
function isAffiliatedWithProof(
address edition,
uint8 tier,
uint8 scheduleNum,
address affiliate,
bytes32[] calldata affiliateProof
) external view returns (bool);
/**
* @dev Returns whether the affiliate is affiliated for the mint.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param affiliate The affiliate address.
* @return A boolean on whether the affiliate is affiliated for the mint.
*/
function isAffiliated(
address edition,
uint8 tier,
uint8 scheduleNum,
address affiliate
) external view returns (bool);
/**
* @dev Returns whether the claim tickets have been used.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @param claimTickets An array of claim tickets.
* @return An array of bools, where true means that a ticket has been used.
*/
function checkClaimTickets(
address edition,
uint8 tier,
uint8 scheduleNum,
uint32[] calldata claimTickets
) external view returns (bool[] memory);
/**
* @dev Returns the platform fee configuration for the tier.
* @param platform The platform address.
* @param tier The tier of the mint.
* @return The platform fee configuration struct.
*/
function platformFeeConfig(address platform, uint8 tier) external view returns (PlatformFeeConfig memory);
/**
* @dev Returns the default platform fee configuration.
* @param platform The platform address.
* @return The platform fee configuration struct.
*/
function defaultPlatformFeeConfig(address platform) external view returns (PlatformFeeConfig memory);
/**
* @dev Returns the effective platform fee configuration.
* @param platform The platform address.
* @param tier The tier of the mint.
* @return The platform fee configuration struct.
*/
function effectivePlatformFeeConfig(address platform, uint8 tier) external view returns (PlatformFeeConfig memory);
/**
* @dev Returns an array of mint information structs pertaining to the mint.
* @param edition The Sound Edition address.
* @return An array of mint information structs.
*/
function mintInfoList(address edition) external view returns (MintInfo[] memory);
/**
* @dev Returns information pertaining to the mint.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @return The mint info struct.
*/
function mintInfo(
address edition,
uint8 tier,
uint8 scheduleNum
) external view returns (MintInfo memory);
/**
* @dev Retuns the EIP-712 name for the contract.
* @return The constant value.
*/
function name() external pure returns (string memory);
/**
* @dev Retuns the EIP-712 version for the contract.
* @return The constant value.
*/
function version() external pure returns (string memory);
}
// 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.
let b := and(x, add(not(x), 1))
r := or(shl(8, iszero(x)), shl(7, lt(0xffffffffffffffffffffffffffffffff, b)))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, b))))
r := or(r, shl(5, lt(0xffffffff, shr(r, b))))
// For the remaining 32 bits, use a De Bruijn lookup.
// forgefmt: disable-next-item
r := or(r, byte(and(div(0xd76453e0, shr(r, b)), 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) {
/// @solidity memory-safe-assembly
assembly {
// Computing masks on-the-fly reduces bytecode size by about 500 bytes.
let m := not(0)
r := x
for { let s := 128 } 1 {} {
m := xor(m, shl(s, m))
r := or(and(shr(s, r), m), and(shl(s, r), not(m)))
s := shr(1, s)
if iszero(s) { break }
}
}
}
/// @dev Returns `x` reversed at the byte level.
function reverseBytes(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
// Computing masks on-the-fly reduces bytecode size by about 200 bytes.
let m := not(0)
r := x
for { let s := 128 } 1 {} {
m := xor(m, shl(s, m))
r := or(and(shr(s, r), m), and(shl(s, r), not(m)))
s := shr(1, s)
if eq(s, 4) { break }
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* 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 before the bit at `before`.
/// If no set bit is found, returns `NOT_FOUND`.
function findLastSet(Bitmap storage bitmap, uint256 before)
internal
view
returns (uint256 setBitIndex)
{
uint256 bucket;
uint256 bucketBits;
/// @solidity memory-safe-assembly
assembly {
setBitIndex := not(0)
bucket := shr(8, before)
mstore(0x00, bucket)
mstore(0x20, bitmap.slot)
let offset := and(0xff, not(before)) // `256 - (255 & before) - 1`.
bucketBits := shr(offset, shl(offset, sload(keccak256(0x00, 0x40))))
if iszero(or(bucketBits, iszero(bucket))) {
for {} 1 {} {
bucket := add(bucket, setBitIndex) // `sub(bucket, 1)`.
mstore(0x00, bucket)
bucketBits := sload(keccak256(0x00, 0x40))
if or(bucketBits, iszero(bucket)) { break }
}
}
}
if (bucketBits != 0) {
setBitIndex = (bucket << 8) | LibBit.fls(bucketBits);
/// @solidity memory-safe-assembly
assembly {
setBitIndex := or(setBitIndex, sub(0, gt(setBitIndex, before)))
}
}
}
}
// 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;
/**
* @title LibMulticaller
* @author vectorized.eth
* @notice Library to read the `msg.sender` of the multicaller with sender contract.
*
* @dev Note:
* The functions in this library do NOT guard against reentrancy.
* A single transaction can recurse through different Multicallers
* (e.g. `MulticallerWithSender -> contract -> MulticallerWithSigner -> contract`).
*
* Think of these functions like `msg.sender`.
*
* If your contract `C` can handle reentrancy safely with plain old `msg.sender`
* for any `A -> C -> B -> C`, you should be fine substituting `msg.sender` with these functions.
*/
library LibMulticaller {
/**
* @dev The address of the multicaller contract.
*/
address internal constant MULTICALLER = 0x0000000000002Bdbf1Bf3279983603Ec279CC6dF;
/**
* @dev The address of the multicaller with sender contract.
*/
address internal constant MULTICALLER_WITH_SENDER = 0x00000000002Fd5Aeb385D324B580FCa7c83823A0;
/**
* @dev The address of the multicaller with signer contract.
*/
address internal constant MULTICALLER_WITH_SIGNER = 0x000000000000D9ECebf3C23529de49815Dac1c4c;
/**
* @dev Returns the caller of `aggregateWithSender` on `MULTICALLER_WITH_SENDER`.
*/
function multicallerSender() internal view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x00)
if iszero(staticcall(gas(), MULTICALLER_WITH_SENDER, codesize(), 0x00, 0x00, 0x20)) {
revert(codesize(), codesize()) // For better gas estimation.
}
result := mload(0x00)
}
}
/**
* @dev Returns the signer of `aggregateWithSigner` on `MULTICALLER_WITH_SIGNER`.
*/
function multicallerSigner() internal view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x00)
if iszero(staticcall(gas(), MULTICALLER_WITH_SIGNER, codesize(), 0x00, 0x00, 0x20)) {
revert(codesize(), codesize()) // For better gas estimation.
}
result := mload(0x00)
}
}
/**
* @dev Returns the caller of `aggregateWithSender` on `MULTICALLER_WITH_SENDER`,
* if the current context's `msg.sender` is `MULTICALLER_WITH_SENDER`.
* Otherwise, returns `msg.sender`.
*/
function sender() internal view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, caller())
let withSender := MULTICALLER_WITH_SENDER
if eq(caller(), withSender) {
if iszero(staticcall(gas(), withSender, codesize(), 0x00, 0x00, 0x20)) {
revert(codesize(), codesize()) // For better gas estimation.
}
}
result := mload(0x00)
}
}
/**
* @dev Returns the caller of `aggregateWithSigner` on `MULTICALLER_WITH_SIGNER`,
* if the current context's `msg.sender` is `MULTICALLER_WITH_SIGNER`.
* Otherwise, returns `msg.sender`.
*/
function signer() internal view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, caller())
let withSigner := MULTICALLER_WITH_SIGNER
if eq(caller(), withSigner) {
if iszero(staticcall(gas(), withSigner, codesize(), 0x00, 0x00, 0x20)) {
revert(codesize(), codesize()) // For better gas estimation.
}
}
result := mload(0x00)
}
}
/**
* @dev Returns the caller of `aggregateWithSender` on `MULTICALLER_WITH_SENDER`,
* if the current context's `msg.sender` is `MULTICALLER_WITH_SENDER`.
* Returns the signer of `aggregateWithSigner` on `MULTICALLER_WITH_SIGNER`,
* if the current context's `msg.sender` is `MULTICALLER_WITH_SIGNER`.
* Otherwise, returns `msg.sender`.
*/
function senderOrSigner() internal view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, caller())
let withSender := MULTICALLER_WITH_SENDER
if eq(caller(), withSender) {
if iszero(staticcall(gas(), withSender, codesize(), 0x00, 0x00, 0x20)) {
revert(codesize(), codesize()) // For better gas estimation.
}
}
let withSigner := MULTICALLER_WITH_SIGNER
if eq(caller(), withSigner) {
if iszero(staticcall(gas(), withSigner, codesize(), 0x00, 0x00, 0x20)) {
revert(codesize(), codesize()) // For better gas estimation.
}
}
result := mload(0x00)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
/**
* @title LibOps
* @dev Shared utilities.
*/
library LibOps {
// =============================================================
// ERRORS
// =============================================================
/**
* @dev Error for overflows.
*/
error Overflow();
/**
* @dev Error for unauthorized access.
*/
error Unauthorized();
// =============================================================
// CONSTANTS
// =============================================================
/**
* @dev A role every minter module must have in order to mint new tokens.
* IMPORTANT: This constant must NEVER be changed!
* It will always be 2 across all past and future sound protocol contracts.
*/
uint256 internal constant MINTER_ROLE = 1 << 1;
/**
* @dev A role the owner can grant for performing admin actions.
* IMPORTANT: This constant must NEVER be changed!
* It will always be 1 across all past and future sound protocol contracts.
*/
uint256 internal constant ADMIN_ROLE = 1 << 0;
/**
* @dev Basis points denominator used in fee calculations.
* IMPORTANT: This constant must NEVER be changed!
* It will always be 10000 across all past and future sound protocol contracts.
*/
uint16 internal constant BPS_DENOMINATOR = 10000;
// =============================================================
// FUNCTIONS
// =============================================================
/**
* @dev `isOn ? flag : 0`.
*/
function toFlag(bool isOn, uint8 flag) internal pure returns (uint8 result) {
assembly {
result := mul(iszero(iszero(isOn)), flag)
}
}
/**
* @dev `(flags & flag != 0) != isOn ? flags ^ flag : flags`.
* Sets `flag` in `flags` to 1 if `isOn` is true.
* Sets `flag` in `flags` to 0 if `isOn` is false.
*/
function setFlagTo(
uint8 flags,
uint8 flag,
bool isOn
) internal pure returns (uint8 result) {
assembly {
result := xor(flags, mul(xor(iszero(and(0xff, and(flags, flag))), iszero(isOn)), flag))
}
}
/**
* @dev `x > y ? x : y`.
*/
function max(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
z := xor(x, mul(xor(x, y), gt(y, x)))
}
}
/**
* @dev `x < y ? x : y`.
*/
function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
z := xor(x, mul(xor(x, y), lt(y, x)))
}
}
/**
* @dev `(a * b) / d`. Returns 0 if `d` is zero.
*/
function rawMulDiv(
uint256 a,
uint256 b,
uint256 d
) internal pure returns (uint256 z) {
assembly {
z := div(mul(a, b), d)
}
}
/**
* @dev `a / d`. Returns 0 if `d` is zero.
*/
function rawMod(uint256 a, uint256 d) internal pure returns (uint256 z) {
assembly {
z := mod(a, d)
}
}
/**
* @dev `a | b`.
*/
function or(bool a, bool b) internal pure returns (bool z) {
assembly {
z := or(iszero(iszero(a)), iszero(iszero(b)))
}
}
/**
* @dev `a | b | c`.
*/
function or(
bool a,
bool b,
bool c
) internal pure returns (bool z) {
z = or(a, or(b, c));
}
/**
* @dev `a | b | c | d`.
*/
function or(
bool a,
bool b,
bool c,
bool d
) internal pure returns (bool z) {
z = or(a, or(b, or(c, d)));
}
/**
* @dev `a & b`.
*/
function and(bool a, bool b) internal pure returns (bool z) {
assembly {
z := and(iszero(iszero(a)), iszero(iszero(b)))
}
}
/**
* @dev `x == 0 ? type(uint256).max : x`
*/
function maxIfZero(uint256 x) internal pure returns (uint256 z) {
assembly {
z := sub(x, iszero(x))
}
}
/**
* @dev Packs an address and an index to create an unique identifier.
* @param a The address.
* @param i The index.
* @return result The packed result.
*/
function packId(address a, uint96 i) internal pure returns (uint256 result) {
assembly {
result := or(shl(96, a), shr(160, shl(160, i)))
}
}
/**
* @dev Packs `edition`, `tier`, `scheduleNum` to create an unique identifier.
* @param edition The address of the Sound Edition.
* @param tier The tier.
* @param scheduleNum The edition-tier schedule number.
* @return result The packed result.
*/
function packId(
address edition,
uint8 tier,
uint8 scheduleNum
) internal pure returns (uint256 result) {
assembly {
mstore(0x00, shl(96, edition))
mstore8(0x1e, tier)
mstore8(0x1f, scheduleNum)
result := mload(0x00)
}
}
/**
* @dev `revert Overflow()`.
*/
function revertOverflow() internal pure {
assembly {
mstore(0x00, 0x35278d12) // `Overflow()`.
revert(0x1c, 0x04)
}
}
/**
* @dev `revert Unauthorized()`.
*/
function revertUnauthorized() internal pure {
assembly {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Library for compressing and decompressing bytes.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibZip.sol)
/// @author Calldata compression by clabby (https://github.com/clabby/op-kompressor)
/// @author FastLZ by ariya (https://github.com/ariya/FastLZ)
///
/// @dev Note:
/// The accompanying solady.js library includes implementations of
/// FastLZ and calldata operations for convenience.
library LibZip {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* FAST LZ OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// LZ77 implementation based on FastLZ.
// Equivalent to level 1 compression and decompression at the following commit:
// https://github.com/ariya/FastLZ/commit/344eb4025f9ae866ebf7a2ec48850f7113a97a42
// Decompression is backwards compatible.
/// @dev Returns the compressed `data`.
function flzCompress(bytes memory data) internal pure returns (bytes memory result) {
/// @solidity memory-safe-assembly
assembly {
function ms8(d_, v_) -> _d {
mstore8(d_, v_)
_d := add(d_, 1)
}
function u24(p_) -> _u {
let w := mload(p_)
_u := or(shl(16, byte(2, w)), or(shl(8, byte(1, w)), byte(0, w)))
}
function cmp(p_, q_, e_) -> _l {
for { e_ := sub(e_, q_) } lt(_l, e_) { _l := add(_l, 1) } {
e_ := mul(iszero(byte(0, xor(mload(add(p_, _l)), mload(add(q_, _l))))), e_)
}
}
function literals(runs_, src_, dest_) -> _o {
for { _o := dest_ } iszero(lt(runs_, 0x20)) { runs_ := sub(runs_, 0x20) } {
mstore(ms8(_o, 31), mload(src_))
_o := add(_o, 0x21)
src_ := add(src_, 0x20)
}
if iszero(runs_) { leave }
mstore(ms8(_o, sub(runs_, 1)), mload(src_))
_o := add(1, add(_o, runs_))
}
function match(l_, d_, o_) -> _o {
for { d_ := sub(d_, 1) } iszero(lt(l_, 263)) { l_ := sub(l_, 262) } {
o_ := ms8(ms8(ms8(o_, add(224, shr(8, d_))), 253), and(0xff, d_))
}
if iszero(lt(l_, 7)) {
_o := ms8(ms8(ms8(o_, add(224, shr(8, d_))), sub(l_, 7)), and(0xff, d_))
leave
}
_o := ms8(ms8(o_, add(shl(5, l_), shr(8, d_))), and(0xff, d_))
}
function setHash(i_, v_) {
let p := add(mload(0x40), shl(2, i_))
mstore(p, xor(mload(p), shl(224, xor(shr(224, mload(p)), v_))))
}
function getHash(i_) -> _h {
_h := shr(224, mload(add(mload(0x40), shl(2, i_))))
}
function hash(v_) -> _r {
_r := and(shr(19, mul(2654435769, v_)), 0x1fff)
}
function setNextHash(ip_, ipStart_) -> _ip {
setHash(hash(u24(ip_)), sub(ip_, ipStart_))
_ip := add(ip_, 1)
}
codecopy(mload(0x40), codesize(), 0x8000) // Zeroize the hashmap.
let op := add(mload(0x40), 0x8000)
let a := add(data, 0x20)
let ipStart := a
let ipLimit := sub(add(ipStart, mload(data)), 13)
for { let ip := add(2, a) } lt(ip, ipLimit) {} {
let r := 0
let d := 0
for {} 1 {} {
let s := u24(ip)
let h := hash(s)
r := add(ipStart, getHash(h))
setHash(h, sub(ip, ipStart))
d := sub(ip, r)
if iszero(lt(ip, ipLimit)) { break }
ip := add(ip, 1)
if iszero(gt(d, 0x1fff)) { if eq(s, u24(r)) { break } }
}
if iszero(lt(ip, ipLimit)) { break }
ip := sub(ip, 1)
if gt(ip, a) { op := literals(sub(ip, a), a, op) }
let l := cmp(add(r, 3), add(ip, 3), add(ipLimit, 9))
op := match(l, d, op)
ip := setNextHash(setNextHash(add(ip, l), ipStart), ipStart)
a := ip
}
op := literals(sub(add(ipStart, mload(data)), a), a, op)
result := mload(0x40)
let t := add(result, 0x8000)
let n := sub(op, t)
mstore(result, n) // Store the length.
// Copy the result to compact the memory, overwriting the hashmap.
let o := add(result, 0x20)
for { let i } lt(i, n) { i := add(i, 0x20) } { mstore(add(o, i), mload(add(t, i))) }
mstore(add(o, n), 0) // Zeroize the slot after the string.
mstore(0x40, add(add(o, n), 0x20)) // Allocate the memory.
}
}
/// @dev Returns the decompressed `data`.
function flzDecompress(bytes memory data) internal pure returns (bytes memory result) {
/// @solidity memory-safe-assembly
assembly {
let n := 0
let end := add(add(data, 0x20), mload(data))
result := mload(0x40)
let op := add(result, 0x20)
for { data := add(data, 0x20) } lt(data, end) {} {
let w := mload(data)
let c := byte(0, w)
let t := shr(5, c)
if iszero(t) {
mstore(add(op, n), mload(add(data, 1)))
data := add(data, add(2, c))
n := add(n, add(1, c))
continue
}
let g := eq(t, 7)
let l := add(2, xor(t, mul(g, xor(t, add(7, byte(1, w))))))
for {
let s := add(add(shl(8, and(0x1f, c)), byte(add(1, g), w)), 1)
let r := add(op, sub(n, s))
let o := add(op, n)
let f := xor(s, mul(gt(s, 0x20), xor(s, 0x20)))
let j := 0
} 1 {} {
mstore(add(o, j), mload(add(r, j)))
j := add(j, f)
if iszero(lt(j, l)) { break }
}
data := add(data, add(2, g))
n := add(n, l)
}
mstore(result, n) // Store the length.
let o := add(add(result, 0x20), n)
mstore(o, 0) // Zeroize the slot after the string.
mstore(0x40, add(o, 0x20)) // Allocate the memory.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CALLDATA OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// Calldata compression and decompression using selective run length encoding:
// - Sequences of 0x00 (up to 128 consecutive).
// - Sequences of 0xff (up to 32 consecutive).
//
// A run length encoded block consists of two bytes:
// (0) 0x00
// (1) A control byte with the following bit layout:
// - [7] `0: 0x00, 1: 0xff`.
// - [0..6] `runLength - 1`.
//
// The first 4 bytes are bitwise negated so that the compressed calldata
// can be dispatched into the `fallback` and `receive` functions.
/// @dev Returns the compressed `data`.
function cdCompress(bytes memory data) internal pure returns (bytes memory result) {
/// @solidity memory-safe-assembly
assembly {
function rle(v_, o_, d_) -> _o, _d {
mstore(o_, shl(240, or(and(0xff, add(d_, 0xff)), and(0x80, v_))))
_o := add(o_, 2)
}
result := mload(0x40)
let o := add(result, 0x20)
let z := 0 // Number of consecutive 0x00.
let y := 0 // Number of consecutive 0xff.
for { let end := add(data, mload(data)) } iszero(eq(data, end)) {} {
data := add(data, 1)
let c := byte(31, mload(data))
if iszero(c) {
if y { o, y := rle(0xff, o, y) }
z := add(z, 1)
if eq(z, 0x80) { o, z := rle(0x00, o, 0x80) }
continue
}
if eq(c, 0xff) {
if z { o, z := rle(0x00, o, z) }
y := add(y, 1)
if eq(y, 0x20) { o, y := rle(0xff, o, 0x20) }
continue
}
if y { o, y := rle(0xff, o, y) }
if z { o, z := rle(0x00, o, z) }
mstore8(o, c)
o := add(o, 1)
}
if y { o, y := rle(0xff, o, y) }
if z { o, z := rle(0x00, o, z) }
// Bitwise negate the first 4 bytes.
mstore(add(result, 4), not(mload(add(result, 4))))
mstore(result, sub(o, add(result, 0x20))) // Store the length.
mstore(o, 0) // Zeroize the slot after the string.
mstore(0x40, add(o, 0x20)) // Allocate the memory.
}
}
/// @dev Returns the decompressed `data`.
function cdDecompress(bytes memory data) internal pure returns (bytes memory result) {
/// @solidity memory-safe-assembly
assembly {
if mload(data) {
result := mload(0x40)
let o := add(result, 0x20)
let s := add(data, 4)
let v := mload(s)
let end := add(data, mload(data))
mstore(s, not(v)) // Bitwise negate the first 4 bytes.
for {} lt(data, end) {} {
data := add(data, 1)
let c := byte(31, mload(data))
if iszero(c) {
data := add(data, 1)
let d := byte(31, mload(data))
// Fill with either 0xff or 0x00.
mstore(o, not(0))
if iszero(gt(d, 0x7f)) { codecopy(o, codesize(), add(d, 1)) }
o := add(o, add(and(d, 0x7f), 1))
continue
}
mstore8(o, c)
o := add(o, 1)
}
mstore(s, v) // Restore the first 4 bytes.
mstore(result, sub(o, add(result, 0x20))) // Store the length.
mstore(o, 0) // Zeroize the slot after the string.
mstore(0x40, add(o, 0x20)) // Allocate the memory.
}
}
}
/// @dev To be called in the `receive` and `fallback` functions.
/// ```
/// receive() external payable { LibZip.cdFallback(); }
/// fallback() external payable { LibZip.cdFallback(); }
/// ```
/// For efficiency, this function will directly return the results, terminating the context.
/// If called internally, it must be called at the end of the function.
function cdFallback() internal {
assembly {
if iszero(calldatasize()) { return(calldatasize(), calldatasize()) }
let o := 0
let f := not(3) // For negating the first 4 bytes.
for { let i := 0 } lt(i, calldatasize()) {} {
let c := byte(0, xor(add(i, f), calldataload(i)))
i := add(i, 1)
if iszero(c) {
let d := byte(0, xor(add(i, f), calldataload(i)))
i := add(i, 1)
// Fill with either 0xff or 0x00.
mstore(o, not(0))
if iszero(gt(d, 0x7f)) { codecopy(o, codesize(), add(d, 1)) }
o := add(o, add(and(d, 0x7f), 1))
continue
}
mstore8(o, c)
o := add(o, 1)
}
let success := delegatecall(gas(), address(), 0x00, o, codesize(), 0x00)
returndatacopy(0x00, 0x00, returndatasize())
if iszero(success) { revert(0x00, returndatasize()) }
return(0x00, returndatasize())
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Gas optimized verification of proof of inclusion for a leaf in a Merkle tree.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/MerkleProofLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/MerkleProofLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/MerkleProof.sol)
library MerkleProofLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MERKLE PROOF VERIFICATION OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns whether `leaf` exists in the Merkle tree with `root`, given `proof`.
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf)
internal
pure
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
if mload(proof) {
// Initialize `offset` to the offset of `proof` elements in memory.
let offset := add(proof, 0x20)
// Left shift by 5 is equivalent to multiplying by 0x20.
let end := add(offset, shl(5, mload(proof)))
// 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, mload(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), mload(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 Returns whether `leaf` exists in the Merkle tree with `root`, given `proof`.
function verifyCalldata(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 Returns whether all `leaves` exist in the Merkle tree with `root`,
/// given `proof` and `flags`.
///
/// Note:
/// - Breaking the invariant `flags.length == (leaves.length - 1) + proof.length`
/// will always return false.
/// - The sum of the lengths of `proof` and `leaves` must never overflow.
/// - Any non-zero word in the `flags` array is treated as true.
/// - The memory offset of `proof` must be non-zero
/// (i.e. `proof` is not pointing to the scratch space).
function verifyMultiProof(
bytes32[] memory proof,
bytes32 root,
bytes32[] memory leaves,
bool[] memory flags
) internal pure returns (bool isValid) {
// Rebuilds the root by consuming and producing values on a queue.
// The queue starts with the `leaves` array, and goes into a `hashes` array.
// After the process, the last element on the queue is verified
// to be equal to the `root`.
//
// The `flags` array denotes whether the sibling
// should be popped from the queue (`flag == true`), or
// should be popped from the `proof` (`flag == false`).
/// @solidity memory-safe-assembly
assembly {
// Cache the lengths of the arrays.
let leavesLength := mload(leaves)
let proofLength := mload(proof)
let flagsLength := mload(flags)
// Advance the pointers of the arrays to point to the data.
leaves := add(0x20, leaves)
proof := add(0x20, proof)
flags := add(0x20, flags)
// If the number of flags is correct.
for {} eq(add(leavesLength, proofLength), add(flagsLength, 1)) {} {
// For the case where `proof.length + leaves.length == 1`.
if iszero(flagsLength) {
// `isValid = (proof.length == 1 ? proof[0] : leaves[0]) == root`.
isValid := eq(mload(xor(leaves, mul(xor(proof, leaves), proofLength))), root)
break
}
// The required final proof offset if `flagsLength` is not zero, otherwise zero.
let proofEnd := add(proof, shl(5, proofLength))
// We can use the free memory space for the queue.
// We don't need to allocate, since the queue is temporary.
let hashesFront := mload(0x40)
// Copy the leaves into the hashes.
// Sometimes, a little memory expansion costs less than branching.
// Should cost less, even with a high free memory offset of 0x7d00.
leavesLength := shl(5, leavesLength)
for { let i := 0 } iszero(eq(i, leavesLength)) { i := add(i, 0x20) } {
mstore(add(hashesFront, i), mload(add(leaves, i)))
}
// Compute the back of the hashes.
let hashesBack := add(hashesFront, leavesLength)
// This is the end of the memory for the queue.
// We recycle `flagsLength` to save on stack variables (sometimes save gas).
flagsLength := add(hashesBack, shl(5, flagsLength))
for {} 1 {} {
// Pop from `hashes`.
let a := mload(hashesFront)
// Pop from `hashes`.
let b := mload(add(hashesFront, 0x20))
hashesFront := add(hashesFront, 0x40)
// If the flag is false, load the next proof,
// else, pops from the queue.
if iszero(mload(flags)) {
// Loads the next proof.
b := mload(proof)
proof := add(proof, 0x20)
// Unpop from `hashes`.
hashesFront := sub(hashesFront, 0x20)
}
// Advance to the next flag.
flags := add(flags, 0x20)
// Slot of `a` in scratch space.
// If the condition is true: 0x20, otherwise: 0x00.
let scratch := shl(5, gt(a, b))
// Hash the scratch space and push the result onto the queue.
mstore(scratch, a)
mstore(xor(scratch, 0x20), b)
mstore(hashesBack, keccak256(0x00, 0x40))
hashesBack := add(hashesBack, 0x20)
if iszero(lt(hashesBack, flagsLength)) { break }
}
isValid :=
and(
// Checks if the last value in the queue is same as the root.
eq(mload(sub(hashesBack, 0x20)), root),
// And whether all the proofs are used, if required.
eq(proofEnd, proof)
)
break
}
}
}
/// @dev Returns whether all `leaves` exist in the Merkle tree with `root`,
/// given `proof` and `flags`.
///
/// Note:
/// - Breaking the invariant `flags.length == (leaves.length - 1) + proof.length`
/// will always return false.
/// - Any non-zero word in the `flags` array is treated as true.
/// - The calldata offset of `proof` must be non-zero
/// (i.e. `proof` is from a regular Solidity function with a 4-byte selector).
function verifyMultiProofCalldata(
bytes32[] calldata proof,
bytes32 root,
bytes32[] calldata leaves,
bool[] calldata flags
) internal pure returns (bool isValid) {
// Rebuilds the root by consuming and producing values on a queue.
// The queue starts with the `leaves` array, and goes into a `hashes` array.
// After the process, the last element on the queue is verified
// to be equal to the `root`.
//
// The `flags` array denotes whether the sibling
// should be popped from the queue (`flag == true`), or
// should be popped from the `proof` (`flag == false`).
/// @solidity memory-safe-assembly
assembly {
// If the number of flags is correct.
for {} eq(add(leaves.length, proof.length), add(flags.length, 1)) {} {
// For the case where `proof.length + leaves.length == 1`.
if iszero(flags.length) {
// `isValid = (proof.length == 1 ? proof[0] : leaves[0]) == root`.
// forgefmt: disable-next-item
isValid := eq(
calldataload(
xor(leaves.offset, mul(xor(proof.offset, leaves.offset), proof.length))
),
root
)
break
}
// The required final proof offset if `flagsLength` is not zero, otherwise zero.
let proofEnd := add(proof.offset, shl(5, proof.length))
// We can use the free memory space for the queue.
// We don't need to allocate, since the queue is temporary.
let hashesFront := mload(0x40)
// Copy the leaves into the hashes.
// Sometimes, a little memory expansion costs less than branching.
// Should cost less, even with a high free memory offset of 0x7d00.
calldatacopy(hashesFront, leaves.offset, shl(5, leaves.length))
// Compute the back of the hashes.
let hashesBack := add(hashesFront, shl(5, leaves.length))
// This is the end of the memory for the queue.
// We recycle `flagsLength` to save on stack variables (sometimes save gas).
flags.length := add(hashesBack, shl(5, flags.length))
// We don't need to make a copy of `proof.offset` or `flags.offset`,
// as they are pass-by-value (this trick may not always save gas).
for {} 1 {} {
// Pop from `hashes`.
let a := mload(hashesFront)
// Pop from `hashes`.
let b := mload(add(hashesFront, 0x20))
hashesFront := add(hashesFront, 0x40)
// If the flag is false, load the next proof,
// else, pops from the queue.
if iszero(calldataload(flags.offset)) {
// Loads the next proof.
b := calldataload(proof.offset)
proof.offset := add(proof.offset, 0x20)
// Unpop from `hashes`.
hashesFront := sub(hashesFront, 0x20)
}
// Advance to the next flag offset.
flags.offset := add(flags.offset, 0x20)
// Slot of `a` in scratch space.
// If the condition is true: 0x20, otherwise: 0x00.
let scratch := shl(5, gt(a, b))
// Hash the scratch space and push the result onto the queue.
mstore(scratch, a)
mstore(xor(scratch, 0x20), b)
mstore(hashesBack, keccak256(0x00, 0x40))
hashesBack := add(hashesBack, 0x20)
if iszero(lt(hashesBack, flags.length)) { break }
}
isValid :=
and(
// Checks if the last value in the queue is same as the root.
eq(mload(sub(hashesBack, 0x20)), root),
// And whether all the proofs are used, if required.
eq(proofEnd, proof.offset)
)
break
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EMPTY CALLDATA HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an empty calldata bytes32 array.
function emptyProof() internal pure returns (bytes32[] calldata proof) {
/// @solidity memory-safe-assembly
assembly {
proof.length := 0
}
}
/// @dev Returns an empty calldata bytes32 array.
function emptyLeaves() internal pure returns (bytes32[] calldata leaves) {
/// @solidity memory-safe-assembly
assembly {
leaves.length := 0
}
}
/// @dev Returns an empty calldata bool array.
function emptyFlags() internal pure returns (bool[] calldata flags) {
/// @solidity memory-safe-assembly
assembly {
flags.length := 0
}
}
}
// 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/Ownable.sol)
/// @dev While the ownable portion follows [EIP-173](https://eips.ethereum.org/EIPS/eip-173)
/// for compatibility, the nomenclature for the 2-step ownership handover and roles
/// 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 Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
/// - For ERC20s, this implementation won't check that a token has code,
/// responsibility is delegated to the caller.
library SafeTransferLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ETH transfer has failed.
error ETHTransferFailed();
/// @dev The ERC20 `transferFrom` has failed.
error TransferFromFailed();
/// @dev The ERC20 `transfer` has failed.
error TransferFailed();
/// @dev The ERC20 `approve` has failed.
error ApproveFailed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;
/// @dev Suggested gas stipend for contract receiving ETH to perform a few
/// storage reads and writes, but low enough to prevent griefing.
uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ETH OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
//
// The regular variants:
// - Forwards all remaining gas to the target.
// - Reverts if the target reverts.
// - Reverts if the current contract has insufficient balance.
//
// The force variants:
// - Forwards with an optional gas stipend
// (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
// - If the target reverts, or if the gas stipend is exhausted,
// creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
// Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
// - Reverts if the current contract has insufficient balance.
//
// The try variants:
// - Forwards with a mandatory gas stipend.
// - Instead of reverting, returns whether the transfer succeeded.
/// @dev Sends `amount` (in wei) ETH to `to`.
function safeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Sends all the ETH in the current contract to `to`.
function safeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// Transfer all the ETH and check if it succeeded or not.
if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
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`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// forgefmt: disable-next-item
if iszero(call(GAS_STIPEND_NO_GRIEF, 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`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
}
}
/// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
function trySafeTransferAllETH(address to, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for
/// the current contract to manage.
function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends all of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have their entire balance approved for
/// the current contract to manage.
function safeTransferAllFrom(address token, address from, address to)
internal
returns (uint256 amount)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransfer(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sends all of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransferAll(address token, address to) internal returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
mstore(0x20, address()) // Store the address of the current contract.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x14, to) // Store the `to` argument.
amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// Reverts upon failure.
function safeApprove(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
/// then retries the approval again (some tokens, e.g. USDT, requires this).
/// Reverts upon failure.
function safeApproveWithRetry(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, retrying upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x34, 0) // Store 0 for the `amount`.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
mstore(0x34, amount) // Store back the original `amount`.
// Retry the approval, reverting upon failure.
if iszero(
and(
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Returns the amount of ERC20 `token` owned by `account`.
/// Returns zero if the `token` does not exist.
function balanceOf(address token, address account) internal view returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, account) // Store the `account` argument.
mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
amount :=
mul(
mload(0x20),
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Signature verification helper that supports both ECDSA signatures from EOAs
/// and ERC1271 signatures from smart contract wallets like Argent and Gnosis safe.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SignatureCheckerLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol)
///
/// @dev Note:
/// - The signature checking functions use the ecrecover precompile (0x1).
/// - The `bytes memory signature` variants use the identity precompile (0x4)
/// to copy memory internally.
/// - Unlike ECDSA signatures, contract signatures are revocable.
/// - As of Solady version 0.0.134, all `bytes signature` variants accept both
/// regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures.
/// See: https://eips.ethereum.org/EIPS/eip-2098
/// This is for calldata efficiency on smart accounts prevalent on L2s.
///
/// WARNING! Do NOT use signatures as unique identifiers:
/// - Use a nonce in the digest to prevent replay attacks on the same contract.
/// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts.
/// EIP-712 also enables readable signing of typed data for better user safety.
/// This implementation does NOT check if a signature is non-malleable.
library SignatureCheckerLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SIGNATURE CHECKING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns whether `signature` is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x40, mload(add(signature, 0x20))) // `r`.
if eq(mload(signature), 64) {
let vs := mload(add(signature, 0x40))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
if eq(mload(signature), 65) {
mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`.
mstore(0x60, mload(add(signature, 0x40))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
// Copy the `signature` over.
let n := add(0x20, mload(signature))
pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n))
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(returndatasize(), 0x44), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
break
}
}
}
/// @dev Returns whether `signature` is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNowCalldata(address signer, bytes32 hash, bytes calldata signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
if eq(signature.length, 64) {
let vs := calldataload(add(signature.offset, 0x20))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, calldataload(signature.offset)) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
if eq(signature.length, 65) {
mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`.
calldatacopy(0x40, signature.offset, 0x40) // `r`, `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), signature.length)
// Copy the `signature` over.
calldatacopy(add(m, 0x64), signature.offset, signature.length)
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(signature.length, 0x64), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
break
}
}
}
/// @dev Returns whether the signature (`r`, `vs`) is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, r) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), mload(0x60)) // `s`.
mstore8(add(m, 0xa4), mload(0x20)) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
}
/// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x20, and(v, 0xff)) // `v`.
mstore(0x40, r) // `r`.
mstore(0x60, s) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), s) // `s`.
mstore8(add(m, 0xa4), v) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC1271 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract.
function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes memory signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
// Copy the `signature` over.
let n := add(0x20, mload(signature))
pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n))
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(returndatasize(), 0x44), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract.
function isValidERC1271SignatureNowCalldata(
address signer,
bytes32 hash,
bytes calldata signature
) internal view returns (bool isValid) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), signature.length)
// Copy the `signature` over.
calldatacopy(add(m, 0x64), signature.offset, signature.length)
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(signature.length, 0x64), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/// @dev Returns whether the signature (`r`, `vs`) is valid for `hash`
/// for an ERC1271 `signer` contract.
function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), shr(1, shl(1, vs))) // `s`.
mstore8(add(m, 0xa4), add(shr(255, vs), 27)) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `hash`
/// for an ERC1271 `signer` contract.
function isValidERC1271SignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), s) // `s`.
mstore8(add(m, 0xa4), v) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HASHING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an Ethereum Signed Message, created from a `hash`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
/// JSON-RPC method as part of EIP-191.
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, hash) // Store into scratch space for keccak256.
mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes.
result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`.
}
}
/// @dev Returns an Ethereum Signed Message, created from `s`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
/// JSON-RPC method as part of EIP-191.
/// Note: Supports lengths of `s` up to 999999 bytes.
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let sLength := mload(s)
let o := 0x20
mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded.
mstore(0x00, 0x00)
// Convert the `s.length` to ASCII decimal representation: `base10(s.length)`.
for { let temp := sLength } 1 {} {
o := sub(o, 1)
mstore8(o, add(48, mod(temp, 10)))
temp := div(temp, 10)
if iszero(temp) { break }
}
let n := sub(0x3a, o) // Header length: `26 + 32 - o`.
// Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20))
mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header.
result := keccak256(add(s, sub(0x20, n)), add(n, sLength))
mstore(s, sLength) // Restore the length.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EMPTY CALLDATA HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an empty calldata bytes.
function emptySignature() internal pure returns (bytes calldata signature) {
/// @solidity memory-safe-assembly
assembly {
signature.length := 0
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
import { Ownable, OwnableRoles } from "solady/auth/OwnableRoles.sol";
import { ISoundEditionV2_1 } from "@core/interfaces/ISoundEditionV2_1.sol";
import { ISuperMinterV2 } from "@modules/interfaces/ISuperMinterV2.sol";
import { IERC165 } from "openzeppelin/utils/introspection/IERC165.sol";
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
import { EIP712 } from "solady/utils/EIP712.sol";
import { MerkleProofLib } from "solady/utils/MerkleProofLib.sol";
import { LibBitmap } from "solady/utils/LibBitmap.sol";
import { SignatureCheckerLib } from "solady/utils/SignatureCheckerLib.sol";
import { LibZip } from "solady/utils/LibZip.sol";
import { LibMap } from "solady/utils/LibMap.sol";
import { DelegateCashLib } from "@modules/utils/DelegateCashLib.sol";
import { LibOps } from "@core/utils/LibOps.sol";
import { LibMulticaller } from "multicaller/LibMulticaller.sol";
/**
* @title SuperMinterV2
* @dev The `SuperMinterV2` class is a generalized minter.
*/
contract SuperMinterV2 is ISuperMinterV2, EIP712 {
using LibBitmap for *;
using MerkleProofLib for *;
using LibMap for *;
// =============================================================
// STRUCTS
// =============================================================
/**
* @dev A struct to hold the mint data in storage.
*/
struct MintData {
// The platform address.
address platform;
// The price per token.
uint96 price;
// The start time of the mint.
uint32 startTime;
// The end time of the mint.
uint32 endTime;
// The maximum number of tokens an account can mint in this mint.
uint32 maxMintablePerAccount;
// The maximum tokens mintable.
uint32 maxMintable;
// The total number of tokens minted.
uint32 minted;
// The affiliate fee BPS.
uint16 affiliateFeeBPS;
// The offset to the next mint data in the linked list.
uint16 next;
// The head of the mint data linked list.
// Only stored in the 0-th mint data per edition.
uint16 head;
// The total number of mint data.
// Only stored in the 0-th mint data per edition.
uint16 numMintData;
// The total number of mints for the edition-tier.
// Only stored in the 0-th mint data per edition-tier.
uint8 nextScheduleNum;
// The mode of the mint.
uint8 mode;
// The packed boolean flags.
uint8 flags;
// The affiliate Merkle root, if any.
bytes32 affiliateMerkleRoot;
// The Merkle root hash, required if `mode` is `VERIFY_MERKLE`.
bytes32 merkleRoot;
}
// =============================================================
// CONSTANTS
// =============================================================
/**
* @dev The GA tier. Which is 0.
*/
uint8 public constant GA_TIER = 0;
/**
* @dev For EIP-712 signature digest calculation.
*/
bytes32 public constant MINT_TO_TYPEHASH =
// prettier-ignore
keccak256(
"MintTo("
"address edition,"
"uint8 tier,"
"uint8 scheduleNum,"
"address to,"
"uint32 signedQuantity,"
"uint32 signedClaimTicket,"
"uint96 signedPrice,"
"uint32 signedDeadline,"
"address affiliate"
")"
);
/**
* @dev For EIP-712 platform airdrop signature digest calculation.
*/
bytes32 public constant PLATFORM_AIRDROP_TYPEHASH =
// prettier-ignore
keccak256(
"PlatformAirdrop("
"address edition,"
"uint8 tier,"
"uint8 scheduleNum,"
"address[] to,"
"uint32 signedQuantity,"
"uint32 signedClaimTicket,"
"uint32 signedDeadline"
")"
);
/**
* @dev For EIP-712 signature digest calculation.
*/
bytes32 public constant DOMAIN_TYPEHASH = _DOMAIN_TYPEHASH;
/**
* @dev The default value for options.
*/
uint8 public constant DEFAULT = 0;
/**
* @dev The Merkle drop mint mode.
*/
uint8 public constant VERIFY_MERKLE = 1;
/**
* @dev The Signature mint mint mode.
*/
uint8 public constant VERIFY_SIGNATURE = 2;
/**
* @dev The platform airdrop mint mode.
*/
uint8 public constant PLATFORM_AIRDROP = 3;
/**
* @dev The denominator of all BPS calculations.
*/
uint16 public constant BPS_DENOMINATOR = LibOps.BPS_DENOMINATOR;
/**
* @dev The maximum affiliate fee BPS.
*/
uint16 public constant MAX_AFFILIATE_FEE_BPS = 1000;
/**
* @dev The maximum platform per-mint fee BPS.
*/
uint16 public constant MAX_PLATFORM_PER_MINT_FEE_BPS = 1000;
/**
* @dev The maximum per-mint reward. Applies to artists, affiliates, platform.
*/
uint96 public constant MAX_PER_MINT_REWARD = 0.1 ether;
/**
* @dev The maximum platform per-transaction flat fee.
*/
uint96 public constant MAX_PLATFORM_PER_TX_FLAT_FEE = 0.1 ether;
/**
* @dev The boolean flag on whether the mint has been created.
*/
uint8 internal constant _MINT_CREATED_FLAG = 1 << 0;
/**
* @dev The boolean flag on whether the mint is paused.
*/
uint8 internal constant _MINT_PAUSED_FLAG = 1 << 1;
/**
* @dev The boolean flag on whether the signer is the platform's signer.
*/
uint8 internal constant _USE_PLATFORM_SIGNER_FLAG = 1 << 2;
/**
* @dev The index for the per-platform default fee config.
* We use 256, as the tier is uint8, which ranges from 0 to 255.
*/
uint16 internal constant _DEFAULT_FEE_CONFIG_INDEX = 256;
// =============================================================
// STORAGE
// =============================================================
/**
* @dev A mapping of `platform` => `feesAccrued`.
*/
mapping(address => uint256) public platformFeesAccrued;
/**
* @dev A mapping of `platform` => `feeRecipient`.
*/
mapping(address => address) public platformFeeAddress;
/**
* @dev A mapping of `affiliate` => `feesAccrued`.
*/
mapping(address => uint256) public affiliateFeesAccrued;
/**
* @dev A mapping of `platform` => `price`.
*/
mapping(address => uint96) public gaPrice;
/**
* @dev A mapping of `platform` => `platformSigner`.
*/
mapping(address => address) public platformSigner;
/**
* @dev A mapping of `mintId` => `mintData`.
*/
mapping(uint256 => MintData) internal _mintData;
/**
* @dev A mapping of `platformTierId` => `platformFeeConfig`.
*/
mapping(uint256 => PlatformFeeConfig) internal _platformFeeConfigs;
/**
* @dev A mapping of `to` => `mintId` => `numberMinted`.
*/
mapping(address => LibMap.Uint32Map) internal _numberMinted;
/**
* @dev A mapping of `mintId` => `signedClaimedTicket` => `claimed`.
*/
mapping(uint256 => LibBitmap.Bitmap) internal _claimsBitmaps;
// =============================================================
// PUBLIC / EXTERNAL WRITE FUNCTIONS
// =============================================================
/**
* @inheritdoc ISuperMinterV2
*/
function createEditionMint(MintCreation memory c) public returns (uint8 scheduleNum) {
_requireOnlyEditionOwnerOrAdmin(c.edition);
_validateAffiliateFeeBPS(c.affiliateFeeBPS);
uint8 mode = c.mode;
if (mode == DEFAULT) {
c.merkleRoot = bytes32(0);
} else if (mode == VERIFY_MERKLE) {
_validateMerkleRoot(c.merkleRoot);
} else if (mode == VERIFY_SIGNATURE) {
c.merkleRoot = bytes32(0);
c.maxMintablePerAccount = type(uint32).max;
} else if (mode == PLATFORM_AIRDROP) {
c.merkleRoot = bytes32(0);
c.maxMintablePerAccount = type(uint32).max;
c.price = 0; // Platform airdrop mode doesn't have a price.
} else {
revert InvalidMode();
}
// If GA, overwrite any immutable variables as required.
if (c.tier == GA_TIER) {
c.endTime = type(uint32).max;
c.maxMintablePerAccount = type(uint32).max;
// We allow the `price` to be the minimum price if the `mode` is `VERIFY_SIGNATURE`.
// Otherwise, the actual default price is the live value of `gaPrice[platform]`,
// and we'll simply set it to zero to avoid a SLOAD.
if (mode != VERIFY_SIGNATURE) c.price = 0;
// Set `maxMintable` to the maximum only if `mode` is `DEFAULT`.
if (mode == DEFAULT) c.maxMintable = type(uint32).max;
}
_validateTimeRange(c.startTime, c.endTime);
_validateMaxMintablePerAccount(c.maxMintablePerAccount);
_validateMaxMintable(c.maxMintable);
unchecked {
MintData storage tierHead = _mintData[LibOps.packId(c.edition, c.tier, 0)];
MintData storage editionHead = _mintData[LibOps.packId(c.edition, 0)];
scheduleNum = tierHead.nextScheduleNum;
uint256 n = scheduleNum;
if (++n >= 1 << 8) LibOps.revertOverflow();
tierHead.nextScheduleNum = uint8(n);
n = editionHead.numMintData;
if (++n >= 1 << 16) LibOps.revertOverflow();
editionHead.numMintData = uint16(n);
uint256 mintId = LibOps.packId(c.edition, c.tier, scheduleNum);
MintData storage d = _mintData[mintId];
d.platform = c.platform;
d.price = c.price;
d.startTime = c.startTime;
d.endTime = c.endTime;
d.maxMintablePerAccount = c.maxMintablePerAccount;
d.maxMintable = c.maxMintable;
d.affiliateFeeBPS = c.affiliateFeeBPS;
d.mode = c.mode;
d.flags = _MINT_CREATED_FLAG;
d.next = editionHead.head;
editionHead.head = uint16((uint256(c.tier) << 8) | uint256(scheduleNum));
// Skip writing zeros, to avoid cold SSTOREs.
if (c.affiliateMerkleRoot != bytes32(0)) d.affiliateMerkleRoot = c.affiliateMerkleRoot;
if (c.merkleRoot != bytes32(0)) d.merkleRoot = c.merkleRoot;
emit MintCreated(c.edition, c.tier, scheduleNum, c);
}
}
/**
* @inheritdoc ISuperMinterV2
*/
function mintTo(MintTo calldata p) public payable returns (uint256 fromTokenId) {
MintData storage d = _getMintData(LibOps.packId(p.edition, p.tier, p.scheduleNum));
/* ------------------- CHECKS AND UPDATES ------------------- */
_requireMintOpen(d);
// Perform the sub workflows depending on the mint mode.
uint8 mode = d.mode;
if (mode == VERIFY_MERKLE) _verifyMerkle(d, p);
else if (mode == VERIFY_SIGNATURE) _verifyAndClaimSignature(d, p);
else if (mode == PLATFORM_AIRDROP) revert InvalidMode();
_incrementMinted(mode, d, p);
/* ----------------- COMPUTE AND ACCRUE FEES ---------------- */
MintedLogData memory l;
// Blocking same address self referral is left curved, but we do anyway.
l.affiliate = p.to == p.affiliate ? address(0) : p.affiliate;
// Affiliate check.
l.affiliated = _isAffiliatedWithProof(d, l.affiliate, p.affiliateProof);
TotalPriceAndFees memory f = _totalPriceAndFees(p.tier, d, p.quantity, p.signedPrice, l.affiliated);
if (msg.value != f.total) revert WrongPayment(msg.value, f.total); // Require exact payment.
l.finalArtistFee = f.finalArtistFee;
l.finalPlatformFee = f.finalPlatformFee;
l.finalAffiliateFee = f.finalAffiliateFee;
// Platform and affilaite fees are accrued mappings.
// Artist earnings are directly forwarded to the nft contract in mint call below.
// Overflow not possible since all fees are uint96s.
unchecked {
if (l.finalAffiliateFee != 0) {
affiliateFeesAccrued[p.affiliate] += l.finalAffiliateFee;
}
if (l.finalPlatformFee != 0) {
platformFeesAccrued[d.platform] += l.finalPlatformFee;
}
}
/* ------------------------- MINT --------------------------- */
ISoundEditionV2_1 edition = ISoundEditionV2_1(p.edition);
l.quantity = p.quantity;
l.fromTokenId = edition.mint{ value: l.finalArtistFee }(p.tier, p.to, p.quantity);
l.allowlisted = p.allowlisted;
l.allowlistedQuantity = p.allowlistedQuantity;
l.signedClaimTicket = p.signedClaimTicket;
l.requiredEtherValue = f.total;
l.unitPrice = f.unitPrice;
emit Minted(p.edition, p.tier, p.scheduleNum, p.to, l, p.attributionId);
return l.fromTokenId;
}
/**
* @inheritdoc ISuperMinterV2
*/
function platformAirdrop(PlatformAirdrop calldata p) public returns (uint256 fromTokenId) {
MintData storage d = _getMintData(LibOps.packId(p.edition, p.tier, p.scheduleNum));
/* ------------------- CHECKS AND UPDATES ------------------- */
_requireMintOpen(d);
if (d.mode != PLATFORM_AIRDROP) revert InvalidMode();
_verifyAndClaimPlatfromAidropSignature(d, p);
_incrementPlatformAirdropMinted(d, p);
/* ------------------------- MINT --------------------------- */
ISoundEditionV2_1 edition = ISoundEditionV2_1(p.edition);
fromTokenId = edition.airdrop(p.tier, p.to, p.signedQuantity);
emit PlatformAirdropped(p.edition, p.tier, p.scheduleNum, p.to, p.signedQuantity, fromTokenId);
}
// Per edition mint parameter setters:
// -----------------------------------
// These functions can only be called by the owner or admin of the edition.
/**
* @inheritdoc ISuperMinterV2
*/
function setPrice(
address edition,
uint8 tier,
uint8 scheduleNum,
uint96 price
) public onlyEditionOwnerOrAdmin(edition) {
uint256 mintId = LibOps.packId(edition, tier, scheduleNum);
MintData storage d = _getMintData(mintId);
// If the tier is GA and the `mode` is `VERIFY_SIGNATURE`, we'll use `gaPrice[platform]`.
if (tier == GA_TIER && d.mode != VERIFY_SIGNATURE) revert NotConfigurable();
// Platform airdropped mints will not have a price.
if (d.mode == PLATFORM_AIRDROP) revert NotConfigurable();
d.price = price;
emit PriceSet(edition, tier, scheduleNum, price);
}
/**
* @inheritdoc ISuperMinterV2
*/
function setPaused(
address edition,
uint8 tier,
uint8 scheduleNum,
bool paused
) public onlyEditionOwnerOrAdmin(edition) {
uint256 mintId = LibOps.packId(edition, tier, scheduleNum);
MintData storage d = _getMintData(mintId);
d.flags = LibOps.setFlagTo(d.flags, _MINT_PAUSED_FLAG, paused);
emit PausedSet(edition, tier, scheduleNum, paused);
}
/**
* @inheritdoc ISuperMinterV2
*/
function setTimeRange(
address edition,
uint8 tier,
uint8 scheduleNum,
uint32 startTime,
uint32 endTime
) public onlyEditionOwnerOrAdmin(edition) {
uint256 mintId = LibOps.packId(edition, tier, scheduleNum);
MintData storage d = _getMintData(mintId);
// For GA tier, `endTime` will always be `type(uint32).max`.
if (tier == GA_TIER && endTime != type(uint32).max) revert NotConfigurable();
_validateTimeRange(startTime, endTime);
d.startTime = startTime;
d.endTime = endTime;
emit TimeRangeSet(edition, tier, scheduleNum, startTime, endTime);
}
/**
* @inheritdoc ISuperMinterV2
*/
function setStartTime(
address edition,
uint8 tier,
uint8 scheduleNum,
uint32 startTime
) public {
uint256 mintId = LibOps.packId(edition, tier, scheduleNum);
setTimeRange(edition, tier, scheduleNum, startTime, _mintData[mintId].endTime);
}
/**
* @inheritdoc ISuperMinterV2
*/
function setAffiliateFee(
address edition,
uint8 tier,
uint8 scheduleNum,
uint16 bps
) public onlyEditionOwnerOrAdmin(edition) {
uint256 mintId = LibOps.packId(edition, tier, scheduleNum);
MintData storage d = _getMintData(mintId);
_validateAffiliateFeeBPS(bps);
d.affiliateFeeBPS = bps;
emit AffiliateFeeSet(edition, tier, scheduleNum, bps);
}
/**
* @inheritdoc ISuperMinterV2
*/
function setAffiliateMerkleRoot(
address edition,
uint8 tier,
uint8 scheduleNum,
bytes32 root
) public onlyEditionOwnerOrAdmin(edition) {
uint256 mintId = LibOps.packId(edition, tier, scheduleNum);
MintData storage d = _getMintData(mintId);
d.affiliateMerkleRoot = root;
emit AffiliateMerkleRootSet(edition, tier, scheduleNum, root);
}
/**
* @inheritdoc ISuperMinterV2
*/
function setMaxMintablePerAccount(
address edition,
uint8 tier,
uint8 scheduleNum,
uint32 value
) public onlyEditionOwnerOrAdmin(edition) {
uint256 mintId = LibOps.packId(edition, tier, scheduleNum);
MintData storage d = _getMintData(mintId);
// GA tier will have `type(uint32).max`.
if (tier == GA_TIER) revert NotConfigurable();
// Signature mints will have `type(uint32).max`.
if (d.mode == VERIFY_SIGNATURE) revert NotConfigurable();
// Platform airdrops will have `type(uint32).max`.
if (d.mode == PLATFORM_AIRDROP) revert NotConfigurable();
_validateMaxMintablePerAccount(value);
d.maxMintablePerAccount = value;
emit MaxMintablePerAccountSet(edition, tier, scheduleNum, value);
}
/**
* @inheritdoc ISuperMinterV2
*/
function setMaxMintable(
address edition,
uint8 tier,
uint8 scheduleNum,
uint32 value
) public onlyEditionOwnerOrAdmin(edition) {
uint256 mintId = LibOps.packId(edition, tier, scheduleNum);
MintData storage d = _getMintData(mintId);
// We allow edits for GA tier, if the `mode` is not `DEFAULT`.
if (tier == GA_TIER && d.mode == DEFAULT) revert NotConfigurable();
_validateMaxMintable(value);
d.maxMintable = value;
emit MaxMintableSet(edition, tier, scheduleNum, value);
}
/**
* @inheritdoc ISuperMinterV2
*/
function setMerkleRoot(
address edition,
uint8 tier,
uint8 scheduleNum,
bytes32 merkleRoot
) public onlyEditionOwnerOrAdmin(edition) {
uint256 mintId = LibOps.packId(edition, tier, scheduleNum);
MintData storage d = _getMintData(mintId);
if (d.mode != VERIFY_MERKLE) revert NotConfigurable();
_validateMerkleRoot(merkleRoot);
d.merkleRoot = merkleRoot;
emit MerkleRootSet(edition, tier, scheduleNum, merkleRoot);
}
// Withdrawal functions:
// ---------------------
// These functions can be called by anyone.
/**
* @inheritdoc ISuperMinterV2
*/
function withdrawForAffiliate(address affiliate) public {
uint256 accrued = affiliateFeesAccrued[affiliate];
if (accrued != 0) {
affiliateFeesAccrued[affiliate] = 0;
SafeTransferLib.forceSafeTransferETH(affiliate, accrued);
emit AffiliateFeesWithdrawn(affiliate, accrued);
}
}
/**
* @inheritdoc ISuperMinterV2
*/
function withdrawForPlatform(address platform) public {
address recipient = platformFeeAddress[platform];
_validatePlatformFeeAddress(recipient);
uint256 accrued = platformFeesAccrued[platform];
if (accrued != 0) {
platformFeesAccrued[platform] = 0;
SafeTransferLib.forceSafeTransferETH(recipient, accrued);
emit PlatformFeesWithdrawn(platform, accrued);
}
}
// Platform fee functions:
// -----------------------
// These functions enable any caller to set their own platform fees.
/**
* @inheritdoc ISuperMinterV2
*/
function setPlatformFeeAddress(address recipient) public {
address sender = LibMulticaller.senderOrSigner();
_validatePlatformFeeAddress(recipient);
platformFeeAddress[sender] = recipient;
emit PlatformFeeAddressSet(sender, recipient);
}
/**
* @inheritdoc ISuperMinterV2
*/
function setPlatformFeeConfig(uint8 tier, PlatformFeeConfig memory c) public {
address sender = LibMulticaller.senderOrSigner();
_validatePlatformFeeConfig(c);
_platformFeeConfigs[LibOps.packId(sender, tier)] = c;
emit PlatformFeeConfigSet(sender, tier, c);
}
/**
* @inheritdoc ISuperMinterV2
*/
function setDefaultPlatformFeeConfig(PlatformFeeConfig memory c) public {
address sender = LibMulticaller.senderOrSigner();
_validatePlatformFeeConfig(c);
_platformFeeConfigs[LibOps.packId(sender, _DEFAULT_FEE_CONFIG_INDEX)] = c;
emit DefaultPlatformFeeConfigSet(sender, c);
}
/**
* @inheritdoc ISuperMinterV2
*/
function setGAPrice(uint96 price) public {
address sender = LibMulticaller.senderOrSigner();
gaPrice[sender] = price;
emit GAPriceSet(sender, price);
}
/**
* @inheritdoc ISuperMinterV2
*/
function setPlatformSigner(address signer) public {
address sender = LibMulticaller.senderOrSigner();
platformSigner[sender] = signer;
emit PlatformSignerSet(sender, signer);
}
// Misc functions:
// ---------------
/**
* @dev For calldata compression.
*/
fallback() external payable {
LibZip.cdFallback();
}
/**
* @dev For calldata compression.
*/
receive() external payable {
LibZip.cdFallback();
}
// =============================================================
// PUBLIC / EXTERNAL VIEW FUNCTIONS
// =============================================================
/**
* @inheritdoc ISuperMinterV2
*/
function computeMintToDigest(MintTo calldata p) public view returns (bytes32) {
// prettier-ignore
return
_hashTypedData(keccak256(abi.encode(
MINT_TO_TYPEHASH,
p.edition,
p.tier,
p.scheduleNum,
p.to,
p.signedQuantity,
p.signedClaimTicket,
p.signedPrice,
p.signedDeadline,
p.affiliate
)));
}
/**
* @inheritdoc ISuperMinterV2
*/
function computePlatformAirdropDigest(PlatformAirdrop calldata p) public view returns (bytes32) {
// prettier-ignore
return
_hashTypedData(keccak256(abi.encode(
PLATFORM_AIRDROP_TYPEHASH,
p.edition,
p.tier,
p.scheduleNum,
keccak256(abi.encodePacked(p.to)),
p.signedQuantity,
p.signedClaimTicket,
p.signedDeadline
)));
}
/**
* @inheritdoc ISuperMinterV2
*/
function totalPriceAndFees(
address edition,
uint8 tier,
uint8 scheduleNum,
uint32 quantity,
bool hasValidAffiliate
) public view returns (TotalPriceAndFees memory) {
return totalPriceAndFeesWithSignedPrice(edition, tier, scheduleNum, quantity, 0, hasValidAffiliate);
}
/**
* @inheritdoc ISuperMinterV2
*/
function totalPriceAndFeesWithSignedPrice(
address edition,
uint8 tier,
uint8 scheduleNum,
uint32 quantity,
uint96 signedPrice,
bool hasValidAffiliate
) public view returns (TotalPriceAndFees memory) {
uint256 mintId = LibOps.packId(edition, tier, scheduleNum);
return _totalPriceAndFees(tier, _getMintData(mintId), quantity, signedPrice, hasValidAffiliate);
}
/**
* @inheritdoc ISuperMinterV2
*/
function nextScheduleNum(address edition, uint8 tier) public view returns (uint8) {
return _mintData[LibOps.packId(edition, tier, 0)].nextScheduleNum;
}
/**
* @inheritdoc ISuperMinterV2
*/
function numberMinted(
address edition,
uint8 tier,
uint8 scheduleNum,
address collector
) external view returns (uint32) {
uint256 mintId = LibOps.packId(edition, tier, scheduleNum);
return _numberMinted[collector].get(mintId);
}
/**
* @inheritdoc ISuperMinterV2
*/
function isAffiliatedWithProof(
address edition,
uint8 tier,
uint8 scheduleNum,
address affiliate,
bytes32[] calldata affiliateProof
) public view virtual returns (bool) {
uint256 mintId = LibOps.packId(edition, tier, scheduleNum);
return _isAffiliatedWithProof(_getMintData(mintId), affiliate, affiliateProof);
}
/**
* @inheritdoc ISuperMinterV2
*/
function isAffiliated(
address edition,
uint8 tier,
uint8 scheduleNum,
address affiliate
) public view virtual returns (bool) {
return isAffiliatedWithProof(edition, tier, scheduleNum, affiliate, MerkleProofLib.emptyProof());
}
/**
* @inheritdoc ISuperMinterV2
*/
function checkClaimTickets(
address edition,
uint8 tier,
uint8 scheduleNum,
uint32[] calldata claimTickets
) public view returns (bool[] memory claimed) {
uint256 mintId = LibOps.packId(edition, tier, scheduleNum);
LibBitmap.Bitmap storage bitmap = _claimsBitmaps[mintId];
claimed = new bool[](claimTickets.length);
unchecked {
for (uint256 i; i != claimTickets.length; i++) {
claimed[i] = bitmap.get(claimTickets[i]);
}
}
}
/**
* @inheritdoc ISuperMinterV2
*/
function platformFeeConfig(address platform, uint8 tier) public view returns (PlatformFeeConfig memory) {
return _platformFeeConfigs[LibOps.packId(platform, tier)];
}
/**
* @inheritdoc ISuperMinterV2
*/
function defaultPlatformFeeConfig(address platform) public view returns (PlatformFeeConfig memory) {
return _platformFeeConfigs[LibOps.packId(platform, _DEFAULT_FEE_CONFIG_INDEX)];
}
/**
* @inheritdoc ISuperMinterV2
*/
function effectivePlatformFeeConfig(address platform, uint8 tier) public view returns (PlatformFeeConfig memory) {
PlatformFeeConfig memory c = _platformFeeConfigs[LibOps.packId(platform, tier)];
if (!c.active) c = _platformFeeConfigs[LibOps.packId(platform, _DEFAULT_FEE_CONFIG_INDEX)];
if (!c.active) delete c; // Set all values to zero.
return c;
}
/**
* @inheritdoc ISuperMinterV2
*/
function mintInfoList(address edition) public view returns (MintInfo[] memory a) {
unchecked {
MintData storage editionHead = _mintData[LibOps.packId(edition, 0)];
uint256 n = editionHead.numMintData; // Linked-list length.
uint16 p = editionHead.head; // Current linked-list pointer.
a = new MintInfo[](n);
// Traverse the linked-list and fill the array in reverse.
// Front: earliest added mint schedule. Back: latest added mint schedule.
while (n != 0) {
MintData storage d = _mintData[LibOps.packId(edition, p)];
a[--n] = mintInfo(edition, uint8(p >> 8), uint8(p));
p = d.next;
}
}
}
/**
* @inheritdoc ISuperMinterV2
*/
function mintInfo(
address edition,
uint8 tier,
uint8 scheduleNum
) public view returns (MintInfo memory info) {
uint256 mintId = LibOps.packId(edition, tier, scheduleNum);
MintData storage d = _getMintData(mintId);
info.edition = edition;
info.tier = tier;
info.scheduleNum = scheduleNum;
info.platform = d.platform;
info.price = tier == GA_TIER && d.mode != VERIFY_SIGNATURE ? gaPrice[d.platform] : d.price;
info.startTime = d.startTime;
info.endTime = d.endTime;
info.maxMintablePerAccount = d.maxMintablePerAccount;
info.maxMintable = d.maxMintable;
info.minted = d.minted;
info.affiliateFeeBPS = d.affiliateFeeBPS;
info.mode = d.mode;
info.paused = _isPaused(d);
info.affiliateMerkleRoot = d.affiliateMerkleRoot;
info.merkleRoot = d.merkleRoot;
info.signer = platformSigner[d.platform];
}
/**
* @inheritdoc ISuperMinterV2
*/
function name() external pure returns (string memory name_) {
(name_, ) = _domainNameAndVersion();
}
/**
* @inheritdoc ISuperMinterV2
*/
function version() external pure returns (string memory version_) {
(, version_) = _domainNameAndVersion();
}
/**
* @inheritdoc IERC165
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return
LibOps.or(interfaceId == type(ISuperMinterV2).interfaceId, interfaceId == this.supportsInterface.selector);
}
// =============================================================
// INTERNAL / PRIVATE HELPERS
// =============================================================
// Validations:
// ------------
/**
* @dev Guards a function to make it callable only by the edition's owner or admin.
* @param edition The edition address.
*/
modifier onlyEditionOwnerOrAdmin(address edition) {
_requireOnlyEditionOwnerOrAdmin(edition);
_;
}
/**
* @dev Requires that the caller is the owner or admin of `edition`.
* @param edition The edition address.
*/
function _requireOnlyEditionOwnerOrAdmin(address edition) internal view {
address sender = LibMulticaller.senderOrSigner();
if (sender != OwnableRoles(edition).owner())
if (!OwnableRoles(edition).hasAnyRole(sender, LibOps.ADMIN_ROLE)) LibOps.revertUnauthorized();
}
/**
* @dev Validates that `startTime <= endTime`.
* @param startTime The start time of the mint.
* @param endTime The end time of the mint.
*/
function _validateTimeRange(uint32 startTime, uint32 endTime) internal pure {
if (startTime > endTime) revert InvalidTimeRange();
}
/**
* @dev Validates that the max mintable amount per account is not zero.
* @param value The max mintable amount.
*/
function _validateMaxMintablePerAccount(uint32 value) internal pure {
if (value == 0) revert MaxMintablePerAccountIsZero();
}
/**
* @dev Validates that the max mintable per schedule.
* @param value The max mintable amount.
*/
function _validateMaxMintable(uint32 value) internal pure {
if (value == 0) revert MaxMintableIsZero();
}
/**
* @dev Validates that the Merkle root is not empty.
* @param merkleRoot The Merkle root.
*/
function _validateMerkleRoot(bytes32 merkleRoot) internal pure {
if (merkleRoot == bytes32(0)) revert MerkleRootIsEmpty();
}
/**
* @dev Validates that the affiliate fee BPS does not exceed the max threshold.
* @param bps The affiliate fee BPS.
*/
function _validateAffiliateFeeBPS(uint16 bps) internal pure {
if (bps > MAX_AFFILIATE_FEE_BPS) revert InvalidAffiliateFeeBPS();
}
/**
* @dev Validates the platform fee configuration.
* @param c The platform fee configuration.
*/
function _validatePlatformFeeConfig(PlatformFeeConfig memory c) internal pure {
if (
LibOps.or(
LibOps.or(
c.platformTxFlatFee > MAX_PLATFORM_PER_TX_FLAT_FEE,
c.platformMintFeeBPS > MAX_PLATFORM_PER_MINT_FEE_BPS
),
LibOps.or(
c.artistMintReward > MAX_PER_MINT_REWARD,
c.affiliateMintReward > MAX_PER_MINT_REWARD,
c.platformMintReward > MAX_PER_MINT_REWARD
),
LibOps.or(
c.thresholdArtistMintReward > MAX_PER_MINT_REWARD,
c.thresholdAffiliateMintReward > MAX_PER_MINT_REWARD,
c.thresholdPlatformMintReward > MAX_PER_MINT_REWARD
)
)
) revert InvalidPlatformFeeConfig();
}
/**
* @dev Validates that the platform fee address is not the zero address.
* @param a The platform fee address.
*/
function _validatePlatformFeeAddress(address a) internal pure {
if (a == address(0)) revert PlatformFeeAddressIsZero();
}
// EIP-712:
// --------
/**
* @dev Override for EIP-712.
* @return name_ The EIP-712 name.
* @return version_ The EIP-712 version.
*/
function _domainNameAndVersion()
internal
pure
virtual
override
returns (string memory name_, string memory version_)
{
name_ = "SuperMinter";
version_ = "1_1";
}
// Minting:
// --------
/**
* @dev Increments the number minted in the mint and the number minted by the collector.
* @param mode The mint mode.
* @param d The mint data storage pointer.
* @param p The mint-to parameters.
*/
function _incrementMinted(
uint8 mode,
MintData storage d,
MintTo calldata p
) internal {
unchecked {
// Increment the number minted in the mint.
uint256 n = uint256(d.minted) + uint256(p.quantity); // The next `minted`.
if (n > d.maxMintable) revert ExceedsMintSupply();
d.minted = uint32(n);
// Increment the number minted by the collector.
uint256 mintId = LibOps.packId(p.edition, p.tier, p.scheduleNum);
if (mode == VERIFY_MERKLE) {
LibMap.Uint32Map storage m = _numberMinted[p.allowlisted];
n = uint256(m.get(mintId)) + uint256(p.quantity);
// Check that `n` does not exceed either the default limit,
// or the limit in the Merkle leaf if a non-zero value is provided.
if (LibOps.or(n > d.maxMintablePerAccount, n > p.allowlistedQuantity)) revert ExceedsMaxPerAccount();
m.set(mintId, uint32(n));
} else {
LibMap.Uint32Map storage m = _numberMinted[p.to];
n = uint256(m.get(mintId)) + uint256(p.quantity);
if (n > d.maxMintablePerAccount) revert ExceedsMaxPerAccount();
m.set(mintId, uint32(n));
}
}
}
/**
* @dev Increments the number minted in the mint and the number minted by the collector.
* @param d The mint data storage pointer.
* @param p The platform airdrop parameters.
*/
function _incrementPlatformAirdropMinted(MintData storage d, PlatformAirdrop calldata p) internal {
unchecked {
uint256 mintId = LibOps.packId(p.edition, p.tier, p.scheduleNum);
uint256 toLength = p.to.length;
// Increment the number minted in the mint.
uint256 n = uint256(d.minted) + toLength * uint256(p.signedQuantity); // The next `minted`.
if (n > d.maxMintable) revert ExceedsMintSupply();
d.minted = uint32(n);
// Increment the number minted by the collectors.
for (uint256 i; i != toLength; ++i) {
LibMap.Uint32Map storage m = _numberMinted[p.to[i]];
m.set(mintId, uint32(uint256(m.get(mintId)) + uint256(p.signedQuantity)));
}
}
}
/**
* @dev Requires that the mint is open and not paused.
* @param d The mint data storage pointer.
*/
function _requireMintOpen(MintData storage d) internal view {
if (LibOps.or(block.timestamp < d.startTime, block.timestamp > d.endTime))
revert MintNotOpen(block.timestamp, d.startTime, d.endTime);
if (_isPaused(d)) revert MintPaused(); // Check if the mint is not paused.
}
/**
* @dev Verify the signature, and mark the signed claim ticket as claimed.
* @param d The mint data storage pointer.
* @param p The mint-to parameters.
*/
function _verifyAndClaimSignature(MintData storage d, MintTo calldata p) internal {
if (p.quantity > p.signedQuantity) revert ExceedsSignedQuantity();
address signer = platformSigner[d.platform];
if (!SignatureCheckerLib.isValidSignatureNowCalldata(signer, computeMintToDigest(p), p.signature))
revert InvalidSignature();
if (block.timestamp > p.signedDeadline) revert SignatureExpired();
uint256 mintId = LibOps.packId(p.edition, p.tier, p.scheduleNum);
if (!_claimsBitmaps[mintId].toggle(p.signedClaimTicket)) revert SignatureAlreadyUsed();
}
/**
* @dev Verify the platform airdrop signature, and mark the signed claim ticket as claimed.
* @param d The mint data storage pointer.
* @param p The platform airdrop parameters.
*/
function _verifyAndClaimPlatfromAidropSignature(MintData storage d, PlatformAirdrop calldata p) internal {
// Unlike regular signature mints, platform airdrops only use `signedQuantity`.
address signer = platformSigner[d.platform];
if (!SignatureCheckerLib.isValidSignatureNowCalldata(signer, computePlatformAirdropDigest(p), p.signature))
revert InvalidSignature();
if (block.timestamp > p.signedDeadline) revert SignatureExpired();
uint256 mintId = LibOps.packId(p.edition, p.tier, p.scheduleNum);
if (!_claimsBitmaps[mintId].toggle(p.signedClaimTicket)) revert SignatureAlreadyUsed();
}
/**
* @dev Verify the Merkle proof.
* @param d The mint data storage pointer.
* @param p The mint-to parameters.
*/
function _verifyMerkle(MintData storage d, MintTo calldata p) internal view {
uint32 allowlistedQuantity = p.allowlistedQuantity;
address allowlisted = p.allowlisted;
// Revert if `allowlisted` is the zero address to prevent libraries
// that fill up partial Merkle trees with empty leafs from screwing things up.
if (allowlisted == address(0)) revert InvalidMerkleProof();
// If `allowlistedQuantity` is the max limit, we've got to check two cases for backwards compatibility.
if (allowlistedQuantity == type(uint32).max) {
// Revert if neither `keccak256(abi.encodePacked(allowlisted))` nor
// `keccak256(abi.encodePacked(allowlisted, uint32(0)))` are in the Merkle tree.
if (
!p.allowlistProof.verifyCalldata(d.merkleRoot, _leaf(allowlisted)) &&
!p.allowlistProof.verifyCalldata(d.merkleRoot, _leaf(allowlisted, type(uint32).max))
) revert InvalidMerkleProof();
} else {
// Revert if `keccak256(abi.encodePacked(allowlisted, uint32(allowlistedQuantity)))`
// is not in the Merkle tree.
if (!p.allowlistProof.verifyCalldata(d.merkleRoot, _leaf(allowlisted, allowlistedQuantity)))
revert InvalidMerkleProof();
}
// To mint, either the sender or `to` must be equal to `allowlisted`,
address sender = LibMulticaller.senderOrSigner();
if (!LibOps.or(sender == allowlisted, p.to == allowlisted)) {
// or the sender must be a delegate of `allowlisted`.
if (!DelegateCashLib.checkDelegateForAll(sender, allowlisted)) revert CallerNotDelegated();
}
}
/**
* @dev Returns the total price and fees for the mint.
* @param tier The tier.
* @param d The mint data storage pointer.
* @param quantity How many tokens to mint.
* @param signedPrice The signed price. Only for `VERIFY_SIGNATURE`.
* @return f A struct containing the total price and fees.
*/
function _totalPriceAndFees(
uint8 tier,
MintData storage d,
uint32 quantity,
uint96 signedPrice,
bool hasValidAffiliate
) internal view returns (TotalPriceAndFees memory f) {
// All flat prices are stored as uint96s in storage.
// The quantity is a uint32. Multiplications between a uint96 and uint32 won't overflow.
unchecked {
PlatformFeeConfig memory c = effectivePlatformFeeConfig(d.platform, tier);
// For signature mints, even if it is GA tier, we will use the signed price.
if (d.mode == VERIFY_SIGNATURE) {
if (signedPrice < d.price) revert SignedPriceTooLow(); // Enforce the price floor.
f.unitPrice = signedPrice;
} else if (tier == GA_TIER) {
f.unitPrice = gaPrice[d.platform]; // Else if GA tier, use `gaPrice[platform]`.
} else {
f.unitPrice = d.price; // Else, use the `price`.
}
// The total price before any additive fees.
f.subTotal = f.unitPrice * uint256(quantity);
// Artist earns `subTotal` minus any basis points (BPS) split with affiliates and platform
f.finalArtistFee = f.subTotal;
// `affiliateBPSFee` is deducted from the `finalArtistFee`.
if (d.affiliateFeeBPS != 0 && hasValidAffiliate) {
uint256 affiliateBPSFee = LibOps.rawMulDiv(f.subTotal, d.affiliateFeeBPS, BPS_DENOMINATOR);
f.finalArtistFee -= affiliateBPSFee;
f.finalAffiliateFee = affiliateBPSFee;
}
// `platformBPSFee` is deducted from the `finalArtistFee`.
if (c.platformMintFeeBPS != 0) {
uint256 platformBPSFee = LibOps.rawMulDiv(f.subTotal, c.platformMintFeeBPS, BPS_DENOMINATOR);
f.finalArtistFee -= platformBPSFee;
f.finalPlatformFee = platformBPSFee;
}
// Protocol rewards are additive to `unitPrice` and paid by the buyer.
// There are 2 sets of rewards, one for prices below `thresholdPrice` and one for prices above.
if (f.unitPrice <= c.thresholdPrice) {
f.finalArtistFee += c.artistMintReward * uint256(quantity);
f.finalPlatformFee += c.platformMintReward * uint256(quantity);
// The platform is the affiliate if no affiliate is provided.
if (hasValidAffiliate) {
f.finalAffiliateFee += c.affiliateMintReward * uint256(quantity);
} else {
f.finalPlatformFee += c.affiliateMintReward * uint256(quantity);
}
} else {
f.finalArtistFee += c.thresholdArtistMintReward * uint256(quantity);
f.finalPlatformFee += c.thresholdPlatformMintReward * uint256(quantity);
// The platform is the affiliate if no affiliate is provided
if (hasValidAffiliate) {
f.finalAffiliateFee += c.thresholdAffiliateMintReward * uint256(quantity);
} else {
f.finalPlatformFee += c.thresholdAffiliateMintReward * uint256(quantity);
}
}
// Per-transaction flat fee.
f.finalPlatformFee += c.platformTxFlatFee;
// The total is the final value which the minter has to pay. It includes all fees.
f.total = f.finalArtistFee + f.finalAffiliateFee + f.finalPlatformFee;
}
}
/**
* @dev Returns whether the affiliate is affiliated for the mint
* @param d The mint data storage pointer.
* @param affiliate The affiliate address.
* @param affiliateProof The Merkle proof for the affiliate.
* @return The result.
*/
function _isAffiliatedWithProof(
MintData storage d,
address affiliate,
bytes32[] calldata affiliateProof
) internal view virtual returns (bool) {
bytes32 root = d.affiliateMerkleRoot;
// If the root is empty, then use the default logic.
if (root == bytes32(0)) return affiliate != address(0);
// Otherwise, check if the affiliate is in the Merkle tree.
// The check that that affiliate is not a zero address is to prevent libraries
// that fill up partial Merkle trees with empty leafs from screwing things up.
return LibOps.and(affiliate != address(0), affiliateProof.verifyCalldata(root, _leaf(affiliate)));
}
// Utilities:
// ----------
/**
* @dev Equivalent to `keccak256(abi.encodePacked(allowlisted))`.
* @param allowlisted The allowlisted address.
* @return result The leaf in the Merkle tree.
*/
function _leaf(address allowlisted) internal pure returns (bytes32 result) {
assembly {
mstore(0x00, allowlisted)
result := keccak256(0x0c, 0x14)
}
}
/**
* @dev Equivalent to `keccak256(abi.encodePacked(allowlisted, allowlistedQuantity))`.
* @param allowlisted The allowlisted address.
* @param allowlistedQuantity Number of mints allowlisted.
* @return result The leaf in the Merkle tree.
*/
function _leaf(address allowlisted, uint32 allowlistedQuantity) internal pure returns (bytes32 result) {
assembly {
mstore(0x04, allowlistedQuantity)
mstore(0x00, allowlisted)
result := keccak256(0x0c, 0x18)
}
}
/**
* @dev Retrieves the mint data from storage, reverting if the mint does not exist.
* @param mintId The mint ID.
* @return d The storage pointer to the mint data.
*/
function _getMintData(uint256 mintId) internal view returns (MintData storage d) {
d = _mintData[mintId];
if (d.flags & _MINT_CREATED_FLAG == 0) revert MintDoesNotExist();
}
/**
* @dev Returns whether the mint is paused.
* @param d The storage pointer to the mint data.
* @return Whether the mint is paused.
*/
function _isPaused(MintData storage d) internal view returns (bool) {
return d.flags & _MINT_PAUSED_FLAG != 0;
}
}
{
"compilationTarget": {
"contracts/modules/SuperMinterV2.sol": "SuperMinterV2"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@core/=contracts/core/",
":@modules/=contracts/modules/",
":ERC721A-Upgradeable/=lib/ERC721A-Upgradeable/contracts/",
":chiru-labs/ERC721A-Upgradeable/=lib/ERC721A-Upgradeable/contracts/",
":closedsea/=lib/closedsea/src/",
":ds-test/=lib/solady/lib/ds-test/src/",
":erc4626-tests/=lib/closedsea/lib/openzeppelin-contracts/lib/erc4626-tests/",
":erc721a-upgradeable/=lib/multicaller/lib/erc721a-upgradeable/contracts/",
":erc721a/=lib/multicaller/lib/erc721a/contracts/",
":forge-std/=lib/forge-std/src/",
":multicaller/=lib/multicaller/src/",
":murky/=lib/murky/src/",
":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
":openzeppelin/=lib/openzeppelin-contracts/contracts/",
":operator-filter-registry/=lib/closedsea/lib/operator-filter-registry/",
":preapprove/=lib/preapprove/src/",
":solady/=lib/solady/src/"
]
}
[{"inputs":[],"name":"CallerNotDelegated","type":"error"},{"inputs":[],"name":"ExceedsMaxPerAccount","type":"error"},{"inputs":[],"name":"ExceedsMintSupply","type":"error"},{"inputs":[],"name":"ExceedsSignedQuantity","type":"error"},{"inputs":[],"name":"InvalidAffiliate","type":"error"},{"inputs":[],"name":"InvalidAffiliateFeeBPS","type":"error"},{"inputs":[],"name":"InvalidMaxMintableRange","type":"error"},{"inputs":[],"name":"InvalidMerkleProof","type":"error"},{"inputs":[],"name":"InvalidMode","type":"error"},{"inputs":[],"name":"InvalidPlatformFeeBPS","type":"error"},{"inputs":[],"name":"InvalidPlatformFeeConfig","type":"error"},{"inputs":[],"name":"InvalidPlatformFlatFee","type":"error"},{"inputs":[],"name":"InvalidSignature","type":"error"},{"inputs":[],"name":"InvalidTimeRange","type":"error"},{"inputs":[],"name":"MaxMintableIsZero","type":"error"},{"inputs":[],"name":"MaxMintablePerAccountIsZero","type":"error"},{"inputs":[],"name":"MerkleRootIsEmpty","type":"error"},{"inputs":[],"name":"MintDoesNotExist","type":"error"},{"inputs":[{"internalType":"uint256","name":"blockTimestamp","type":"uint256"},{"internalType":"uint32","name":"startTime","type":"uint32"},{"internalType":"uint32","name":"endTime","type":"uint32"}],"name":"MintNotOpen","type":"error"},{"inputs":[],"name":"MintPaused","type":"error"},{"inputs":[],"name":"MintsAlreadyExist","type":"error"},{"inputs":[],"name":"NotConfigurable","type":"error"},{"inputs":[],"name":"PlatformFeeAddressIsZero","type":"error"},{"inputs":[],"name":"SignatureAlreadyUsed","type":"error"},{"inputs":[],"name":"SignatureExpired","type":"error"},{"inputs":[],"name":"SignedPriceTooLow","type":"error"},{"inputs":[{"internalType":"uint256","name":"paid","type":"uint256"},{"internalType":"uint256","name":"required","type":"uint256"}],"name":"WrongPayment","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"edition","type":"address"},{"indexed":false,"internalType":"uint8","name":"tier","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"indexed":false,"internalType":"uint16","name":"bps","type":"uint16"}],"name":"AffiliateFeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"affiliate","type":"address"},{"indexed":false,"internalType":"uint256","name":"accrued","type":"uint256"}],"name":"AffiliateFeesWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"edition","type":"address"},{"indexed":false,"internalType":"uint8","name":"tier","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"indexed":false,"internalType":"bytes32","name":"root","type":"bytes32"}],"name":"AffiliateMerkleRootSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"platform","type":"address"},{"components":[{"internalType":"uint96","name":"artistMintReward","type":"uint96"},{"internalType":"uint96","name":"affiliateMintReward","type":"uint96"},{"internalType":"uint96","name":"platformMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdPrice","type":"uint96"},{"internalType":"uint96","name":"thresholdArtistMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdAffiliateMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdPlatformMintReward","type":"uint96"},{"internalType":"uint96","name":"platformTxFlatFee","type":"uint96"},{"internalType":"uint16","name":"platformMintFeeBPS","type":"uint16"},{"internalType":"bool","name":"active","type":"bool"}],"indexed":false,"internalType":"struct ISuperMinterV2.PlatformFeeConfig","name":"config","type":"tuple"}],"name":"DefaultPlatformFeeConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"platform","type":"address"},{"indexed":false,"internalType":"uint96","name":"price","type":"uint96"}],"name":"GAPriceSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"edition","type":"address"},{"indexed":false,"internalType":"uint8","name":"tier","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"indexed":false,"internalType":"uint32","name":"value","type":"uint32"}],"name":"MaxMintablePerAccountSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"edition","type":"address"},{"indexed":false,"internalType":"uint8","name":"tier","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"indexed":false,"internalType":"uint32","name":"value","type":"uint32"}],"name":"MaxMintableSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"edition","type":"address"},{"indexed":false,"internalType":"uint8","name":"tier","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"indexed":false,"internalType":"bytes32","name":"merkleRoot","type":"bytes32"}],"name":"MerkleRootSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"edition","type":"address"},{"indexed":false,"internalType":"uint8","name":"tier","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"components":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint96","name":"price","type":"uint96"},{"internalType":"uint32","name":"startTime","type":"uint32"},{"internalType":"uint32","name":"endTime","type":"uint32"},{"internalType":"uint32","name":"maxMintablePerAccount","type":"uint32"},{"internalType":"uint32","name":"maxMintable","type":"uint32"},{"internalType":"uint16","name":"affiliateFeeBPS","type":"uint16"},{"internalType":"bytes32","name":"affiliateMerkleRoot","type":"bytes32"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"address","name":"platform","type":"address"},{"internalType":"uint8","name":"mode","type":"uint8"},{"internalType":"bytes32","name":"merkleRoot","type":"bytes32"}],"indexed":false,"internalType":"struct ISuperMinterV2.MintCreation","name":"creation","type":"tuple"}],"name":"MintCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"edition","type":"address"},{"indexed":false,"internalType":"uint8","name":"tier","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"components":[{"internalType":"uint32","name":"quantity","type":"uint32"},{"internalType":"uint256","name":"fromTokenId","type":"uint256"},{"internalType":"address","name":"allowlisted","type":"address"},{"internalType":"uint32","name":"allowlistedQuantity","type":"uint32"},{"internalType":"uint32","name":"signedQuantity","type":"uint32"},{"internalType":"uint32","name":"signedClaimTicket","type":"uint32"},{"internalType":"address","name":"affiliate","type":"address"},{"internalType":"bool","name":"affiliated","type":"bool"},{"internalType":"uint256","name":"requiredEtherValue","type":"uint256"},{"internalType":"uint256","name":"unitPrice","type":"uint256"},{"internalType":"uint256","name":"finalArtistFee","type":"uint256"},{"internalType":"uint256","name":"finalAffiliateFee","type":"uint256"},{"internalType":"uint256","name":"finalPlatformFee","type":"uint256"}],"indexed":false,"internalType":"struct ISuperMinterV2.MintedLogData","name":"data","type":"tuple"},{"indexed":true,"internalType":"uint256","name":"attributionId","type":"uint256"}],"name":"Minted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"edition","type":"address"},{"indexed":false,"internalType":"uint8","name":"tier","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"indexed":false,"internalType":"bool","name":"paused","type":"bool"}],"name":"PausedSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"edition","type":"address"},{"indexed":false,"internalType":"uint8","name":"tier","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"indexed":false,"internalType":"address[]","name":"to","type":"address[]"},{"indexed":false,"internalType":"uint32","name":"signedQuantity","type":"uint32"},{"indexed":false,"internalType":"uint256","name":"fromTokenId","type":"uint256"}],"name":"PlatformAirdropped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"platform","type":"address"},{"indexed":false,"internalType":"address","name":"recipient","type":"address"}],"name":"PlatformFeeAddressSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"platform","type":"address"},{"indexed":false,"internalType":"uint8","name":"tier","type":"uint8"},{"components":[{"internalType":"uint96","name":"artistMintReward","type":"uint96"},{"internalType":"uint96","name":"affiliateMintReward","type":"uint96"},{"internalType":"uint96","name":"platformMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdPrice","type":"uint96"},{"internalType":"uint96","name":"thresholdArtistMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdAffiliateMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdPlatformMintReward","type":"uint96"},{"internalType":"uint96","name":"platformTxFlatFee","type":"uint96"},{"internalType":"uint16","name":"platformMintFeeBPS","type":"uint16"},{"internalType":"bool","name":"active","type":"bool"}],"indexed":false,"internalType":"struct ISuperMinterV2.PlatformFeeConfig","name":"config","type":"tuple"}],"name":"PlatformFeeConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"platform","type":"address"},{"indexed":false,"internalType":"uint256","name":"accrued","type":"uint256"}],"name":"PlatformFeesWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"platform","type":"address"},{"indexed":false,"internalType":"address","name":"signer","type":"address"}],"name":"PlatformSignerSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"edition","type":"address"},{"indexed":false,"internalType":"uint8","name":"tier","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"indexed":false,"internalType":"uint96","name":"price","type":"uint96"}],"name":"PriceSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"edition","type":"address"},{"indexed":false,"internalType":"uint8","name":"tier","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"indexed":false,"internalType":"uint32","name":"startTime","type":"uint32"},{"indexed":false,"internalType":"uint32","name":"endTime","type":"uint32"}],"name":"TimeRangeSet","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"BPS_DENOMINATOR","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DOMAIN_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GA_TIER","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_AFFILIATE_FEE_BPS","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_PER_MINT_REWARD","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_PLATFORM_PER_MINT_FEE_BPS","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_PLATFORM_PER_TX_FLAT_FEE","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINT_TO_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLATFORM_AIRDROP","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLATFORM_AIRDROP_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VERIFY_MERKLE","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VERIFY_SIGNATURE","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"affiliateFeesAccrued","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"uint32[]","name":"claimTickets","type":"uint32[]"}],"name":"checkClaimTickets","outputs":[{"internalType":"bool[]","name":"claimed","type":"bool[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint32","name":"quantity","type":"uint32"},{"internalType":"address","name":"allowlisted","type":"address"},{"internalType":"uint32","name":"allowlistedQuantity","type":"uint32"},{"internalType":"bytes32[]","name":"allowlistProof","type":"bytes32[]"},{"internalType":"uint96","name":"signedPrice","type":"uint96"},{"internalType":"uint32","name":"signedQuantity","type":"uint32"},{"internalType":"uint32","name":"signedClaimTicket","type":"uint32"},{"internalType":"uint32","name":"signedDeadline","type":"uint32"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"address","name":"affiliate","type":"address"},{"internalType":"bytes32[]","name":"affiliateProof","type":"bytes32[]"},{"internalType":"uint256","name":"attributionId","type":"uint256"}],"internalType":"struct ISuperMinterV2.MintTo","name":"p","type":"tuple"}],"name":"computeMintToDigest","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"address[]","name":"to","type":"address[]"},{"internalType":"uint32","name":"signedQuantity","type":"uint32"},{"internalType":"uint32","name":"signedClaimTicket","type":"uint32"},{"internalType":"uint32","name":"signedDeadline","type":"uint32"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct ISuperMinterV2.PlatformAirdrop","name":"p","type":"tuple"}],"name":"computePlatformAirdropDigest","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint96","name":"price","type":"uint96"},{"internalType":"uint32","name":"startTime","type":"uint32"},{"internalType":"uint32","name":"endTime","type":"uint32"},{"internalType":"uint32","name":"maxMintablePerAccount","type":"uint32"},{"internalType":"uint32","name":"maxMintable","type":"uint32"},{"internalType":"uint16","name":"affiliateFeeBPS","type":"uint16"},{"internalType":"bytes32","name":"affiliateMerkleRoot","type":"bytes32"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"address","name":"platform","type":"address"},{"internalType":"uint8","name":"mode","type":"uint8"},{"internalType":"bytes32","name":"merkleRoot","type":"bytes32"}],"internalType":"struct ISuperMinterV2.MintCreation","name":"c","type":"tuple"}],"name":"createEditionMint","outputs":[{"internalType":"uint8","name":"scheduleNum","type":"uint8"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"platform","type":"address"}],"name":"defaultPlatformFeeConfig","outputs":[{"components":[{"internalType":"uint96","name":"artistMintReward","type":"uint96"},{"internalType":"uint96","name":"affiliateMintReward","type":"uint96"},{"internalType":"uint96","name":"platformMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdPrice","type":"uint96"},{"internalType":"uint96","name":"thresholdArtistMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdAffiliateMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdPlatformMintReward","type":"uint96"},{"internalType":"uint96","name":"platformTxFlatFee","type":"uint96"},{"internalType":"uint16","name":"platformMintFeeBPS","type":"uint16"},{"internalType":"bool","name":"active","type":"bool"}],"internalType":"struct ISuperMinterV2.PlatformFeeConfig","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"platform","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"}],"name":"effectivePlatformFeeConfig","outputs":[{"components":[{"internalType":"uint96","name":"artistMintReward","type":"uint96"},{"internalType":"uint96","name":"affiliateMintReward","type":"uint96"},{"internalType":"uint96","name":"platformMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdPrice","type":"uint96"},{"internalType":"uint96","name":"thresholdArtistMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdAffiliateMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdPlatformMintReward","type":"uint96"},{"internalType":"uint96","name":"platformTxFlatFee","type":"uint96"},{"internalType":"uint16","name":"platformMintFeeBPS","type":"uint16"},{"internalType":"bool","name":"active","type":"bool"}],"internalType":"struct ISuperMinterV2.PlatformFeeConfig","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"gaPrice","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"address","name":"affiliate","type":"address"}],"name":"isAffiliated","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"address","name":"affiliate","type":"address"},{"internalType":"bytes32[]","name":"affiliateProof","type":"bytes32[]"}],"name":"isAffiliatedWithProof","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"}],"name":"mintInfo","outputs":[{"components":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"address","name":"platform","type":"address"},{"internalType":"uint96","name":"price","type":"uint96"},{"internalType":"uint32","name":"startTime","type":"uint32"},{"internalType":"uint32","name":"endTime","type":"uint32"},{"internalType":"uint32","name":"maxMintablePerAccount","type":"uint32"},{"internalType":"uint32","name":"maxMintable","type":"uint32"},{"internalType":"uint32","name":"minted","type":"uint32"},{"internalType":"uint16","name":"affiliateFeeBPS","type":"uint16"},{"internalType":"uint8","name":"mode","type":"uint8"},{"internalType":"bool","name":"paused","type":"bool"},{"internalType":"bool","name":"hasMints","type":"bool"},{"internalType":"bytes32","name":"affiliateMerkleRoot","type":"bytes32"},{"internalType":"bytes32","name":"merkleRoot","type":"bytes32"},{"internalType":"address","name":"signer","type":"address"}],"internalType":"struct ISuperMinterV2.MintInfo","name":"info","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"edition","type":"address"}],"name":"mintInfoList","outputs":[{"components":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"address","name":"platform","type":"address"},{"internalType":"uint96","name":"price","type":"uint96"},{"internalType":"uint32","name":"startTime","type":"uint32"},{"internalType":"uint32","name":"endTime","type":"uint32"},{"internalType":"uint32","name":"maxMintablePerAccount","type":"uint32"},{"internalType":"uint32","name":"maxMintable","type":"uint32"},{"internalType":"uint32","name":"minted","type":"uint32"},{"internalType":"uint16","name":"affiliateFeeBPS","type":"uint16"},{"internalType":"uint8","name":"mode","type":"uint8"},{"internalType":"bool","name":"paused","type":"bool"},{"internalType":"bool","name":"hasMints","type":"bool"},{"internalType":"bytes32","name":"affiliateMerkleRoot","type":"bytes32"},{"internalType":"bytes32","name":"merkleRoot","type":"bytes32"},{"internalType":"address","name":"signer","type":"address"}],"internalType":"struct ISuperMinterV2.MintInfo[]","name":"a","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint32","name":"quantity","type":"uint32"},{"internalType":"address","name":"allowlisted","type":"address"},{"internalType":"uint32","name":"allowlistedQuantity","type":"uint32"},{"internalType":"bytes32[]","name":"allowlistProof","type":"bytes32[]"},{"internalType":"uint96","name":"signedPrice","type":"uint96"},{"internalType":"uint32","name":"signedQuantity","type":"uint32"},{"internalType":"uint32","name":"signedClaimTicket","type":"uint32"},{"internalType":"uint32","name":"signedDeadline","type":"uint32"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"address","name":"affiliate","type":"address"},{"internalType":"bytes32[]","name":"affiliateProof","type":"bytes32[]"},{"internalType":"uint256","name":"attributionId","type":"uint256"}],"internalType":"struct ISuperMinterV2.MintTo","name":"p","type":"tuple"}],"name":"mintTo","outputs":[{"internalType":"uint256","name":"fromTokenId","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"name_","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"}],"name":"nextScheduleNum","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"address","name":"collector","type":"address"}],"name":"numberMinted","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"address[]","name":"to","type":"address[]"},{"internalType":"uint32","name":"signedQuantity","type":"uint32"},{"internalType":"uint32","name":"signedClaimTicket","type":"uint32"},{"internalType":"uint32","name":"signedDeadline","type":"uint32"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct ISuperMinterV2.PlatformAirdrop","name":"p","type":"tuple"}],"name":"platformAirdrop","outputs":[{"internalType":"uint256","name":"fromTokenId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"platformFeeAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"platform","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"}],"name":"platformFeeConfig","outputs":[{"components":[{"internalType":"uint96","name":"artistMintReward","type":"uint96"},{"internalType":"uint96","name":"affiliateMintReward","type":"uint96"},{"internalType":"uint96","name":"platformMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdPrice","type":"uint96"},{"internalType":"uint96","name":"thresholdArtistMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdAffiliateMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdPlatformMintReward","type":"uint96"},{"internalType":"uint96","name":"platformTxFlatFee","type":"uint96"},{"internalType":"uint16","name":"platformMintFeeBPS","type":"uint16"},{"internalType":"bool","name":"active","type":"bool"}],"internalType":"struct ISuperMinterV2.PlatformFeeConfig","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"platformFeesAccrued","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"platformSigner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"uint16","name":"bps","type":"uint16"}],"name":"setAffiliateFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"bytes32","name":"root","type":"bytes32"}],"name":"setAffiliateMerkleRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint96","name":"artistMintReward","type":"uint96"},{"internalType":"uint96","name":"affiliateMintReward","type":"uint96"},{"internalType":"uint96","name":"platformMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdPrice","type":"uint96"},{"internalType":"uint96","name":"thresholdArtistMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdAffiliateMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdPlatformMintReward","type":"uint96"},{"internalType":"uint96","name":"platformTxFlatFee","type":"uint96"},{"internalType":"uint16","name":"platformMintFeeBPS","type":"uint16"},{"internalType":"bool","name":"active","type":"bool"}],"internalType":"struct ISuperMinterV2.PlatformFeeConfig","name":"c","type":"tuple"}],"name":"setDefaultPlatformFeeConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96","name":"price","type":"uint96"}],"name":"setGAPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"uint32","name":"value","type":"uint32"}],"name":"setMaxMintable","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"uint32","name":"value","type":"uint32"}],"name":"setMaxMintablePerAccount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"bytes32","name":"merkleRoot","type":"bytes32"}],"name":"setMerkleRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"bool","name":"paused","type":"bool"}],"name":"setPaused","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"}],"name":"setPlatformFeeAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"tier","type":"uint8"},{"components":[{"internalType":"uint96","name":"artistMintReward","type":"uint96"},{"internalType":"uint96","name":"affiliateMintReward","type":"uint96"},{"internalType":"uint96","name":"platformMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdPrice","type":"uint96"},{"internalType":"uint96","name":"thresholdArtistMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdAffiliateMintReward","type":"uint96"},{"internalType":"uint96","name":"thresholdPlatformMintReward","type":"uint96"},{"internalType":"uint96","name":"platformTxFlatFee","type":"uint96"},{"internalType":"uint16","name":"platformMintFeeBPS","type":"uint16"},{"internalType":"bool","name":"active","type":"bool"}],"internalType":"struct ISuperMinterV2.PlatformFeeConfig","name":"c","type":"tuple"}],"name":"setPlatformFeeConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"signer","type":"address"}],"name":"setPlatformSigner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"uint96","name":"price","type":"uint96"}],"name":"setPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"uint32","name":"startTime","type":"uint32"}],"name":"setStartTime","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"uint32","name":"startTime","type":"uint32"},{"internalType":"uint32","name":"endTime","type":"uint32"}],"name":"setTimeRange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"uint32","name":"quantity","type":"uint32"},{"internalType":"bool","name":"hasValidAffiliate","type":"bool"}],"name":"totalPriceAndFees","outputs":[{"components":[{"internalType":"uint256","name":"total","type":"uint256"},{"internalType":"uint256","name":"subTotal","type":"uint256"},{"internalType":"uint256","name":"unitPrice","type":"uint256"},{"internalType":"uint256","name":"finalArtistFee","type":"uint256"},{"internalType":"uint256","name":"finalAffiliateFee","type":"uint256"},{"internalType":"uint256","name":"finalPlatformFee","type":"uint256"}],"internalType":"struct ISuperMinterV2.TotalPriceAndFees","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"edition","type":"address"},{"internalType":"uint8","name":"tier","type":"uint8"},{"internalType":"uint8","name":"scheduleNum","type":"uint8"},{"internalType":"uint32","name":"quantity","type":"uint32"},{"internalType":"uint96","name":"signedPrice","type":"uint96"},{"internalType":"bool","name":"hasValidAffiliate","type":"bool"}],"name":"totalPriceAndFeesWithSignedPrice","outputs":[{"components":[{"internalType":"uint256","name":"total","type":"uint256"},{"internalType":"uint256","name":"subTotal","type":"uint256"},{"internalType":"uint256","name":"unitPrice","type":"uint256"},{"internalType":"uint256","name":"finalArtistFee","type":"uint256"},{"internalType":"uint256","name":"finalAffiliateFee","type":"uint256"},{"internalType":"uint256","name":"finalPlatformFee","type":"uint256"}],"internalType":"struct ISuperMinterV2.TotalPriceAndFees","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"version_","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"affiliate","type":"address"}],"name":"withdrawForAffiliate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"platform","type":"address"}],"name":"withdrawForPlatform","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]