// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.0;
import "../Strings.sol";
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV // Deprecated in v4.8
}
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} else if (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} else if (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature` or error string. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.3._
*/
function tryRecover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address, RecoverError) {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*
* _Available since v4.2._
*/
function recover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*
* _Available since v4.3._
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address, RecoverError) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
/**
* @dev Returns an Ethereum Signed Message, created from `s`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
}
/**
* @dev Returns an Ethereum Signed Typed Data, created from a
* `domainSeparator` and a `structHash`. This produces hash corresponding
* to the one signed with the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
* JSON-RPC method as part of EIP-712.
*
* See {recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
// 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 (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (interfaces/IERC2981.sol)
pragma solidity ^0.8.0;
import "../utils/introspection/IERC165.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 IERC2981 is IERC165 {
/**
* @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
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the 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);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import { PublicDrop } from "../lib/LGArtLabDropStructs.sol";
import {
LGArtLabDropErrorsAndEvents
} from "../lib/LGArtLabDropErrorsAndEvents.sol";
interface ILGArtLabDrop is LGArtLabDropErrorsAndEvents {
/**
* @notice Mint a public drop.
*
* @param nftContract The nftContract
* @param projectId The project id to mint
* @param feeRecipient The fee recipient.
* @param minterIfNotPayer The mint recipient if different than the payer.
* @param tax The tax value for payment.
* @param quantity The mint quantity.
*/
function mintPublic(
address nftContract,
uint256 projectId,
address feeRecipient,
address minterIfNotPayer,
uint256 tax,
uint256 quantity
) external payable;
/**
* @notice Emits an event to notify update of the drop URI.
*
* This method assume msg.sender is an nft contract and its
* ERC165 interface id matches INonFungibleLGArtLabDropToken.
*
* Note: Be sure only authorized users can call this from
* token contracts that implement INonFungibleLGArtLabDropToken.
*
* @param dropURI The new drop URI.
*/
function updateDropURI(uint256 projectId, string calldata dropURI) external;
/**
* @notice Updates the public drop data for the nft contract
* and emits an event.
*
* This method assume msg.sender is an nft contract and its
* ERC165 interface id matches INonFungibleLGArtLabDropToken.
*
* Note: Be sure only authorized users can call this from
* token contracts that implement INonFungibleLGArtLabDropToken.
*
* @param publicDrops The public drop data.
*/
function updatePublicDrops(
uint256[] calldata projectIds,
PublicDrop[] calldata publicDrops
) external;
/**
* @notice Updates the creator payout address and emits an event.
*
* This method assume msg.sender is an nft contract and its
* ERC165 interface id matches INonFungibleLGArtLabDropToken.
*
* Note: Be sure only authorized users can call this from
* token contracts that implement INonFungibleLGArtLabDropToken.
*
* @param payoutAddress The creator payout address.
*/
function updateCreatorPayoutAddress(address payoutAddress) external;
/**
* @notice Updates the allowed fee recipient and emits an event.
*
* This method assume msg.sender is an nft contract and its
* ERC165 interface id matches INonFungibleLGArtLabDropToken.
*
* Note: Be sure only authorized users can call this from
* token contracts that implement INonFungibleLGArtLabDropToken.
*
* @param feeRecipient The fee recipient.
* @param allowed If the fee recipient is allowed.
*/
function updateAllowedFeeRecipient(
address feeRecipient,
bool allowed
) external;
/**
* @notice Updates the allowed payer and emits an event.
*
* This method assume msg.sender is an nft contract and its
* ERC165 interface id matches INonFungibleLGArtLabDropToken.
*
* Note: Be sure only authorized users can call this from
* token contracts that implement INonFungibleLGArtLabDropToken.
*
* @param payer The payer to add or remove.
* @param allowed Whether to add or remove the payer.
*/
function updatePayer(address payer, bool allowed) external;
/**
* @notice Returns the public drop data for the nft contract.
*
*/
function getPublicDrop(
address nftContract,
uint256 projectId
) external view returns (PublicDrop memory);
/**
* @notice Returns the creator payout address for the nft contract.
*
*/
function getCreatorPayoutAddress(
address nftContract
) external view returns (address);
/**
* @notice Returns if the specified fee recipient is allowed
* for the nft contract.
*
* @param feeRecipient The fee recipient.
*/
function getFeeRecipientIsAllowed(
address nftContract,
address feeRecipient
) external view returns (bool);
/**
* @notice Returns an enumeration of allowed fee recipients for an
* nft contract when fee recipients are enforced
*
*/
function getAllowedFeeRecipients(
address nftContract
) external view returns (address[] memory);
/**
* @notice Returns the payers for the nft contract.
*
*/
function getPayers(
address nftContract
) external view returns (address[] memory);
/**
* @notice Returns if the specified payer is allowed
* for the nft contract.
*
* @param payer The payer.
*/
function getPayerIsAllowed(
address nftContract,
address payer
) external view returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import { IERC2981 } from "openzeppelin-contracts/interfaces/IERC2981.sol";
interface ILGArtLabDropTokenContractMetadata is IERC2981 {
/**
* @notice Throw if the max supply exceeds uint64
*/
error CannotExceedMaxSupplyOfUint64(uint256 newMaxSupply);
/**
* @dev Revert with an error when attempting to set the provenance
* hash after the mint has started.
*/
error ProvenanceHashCannotBeSetAfterMintStarted();
/**
* @dev Revert if the royalty basis points is greater than 10_000.
*/
error InvalidRoyaltyBasisPoints(uint256 basisPoints);
/**
* @dev Revert if the royalty address is being set to the zero address.
*/
error RoyaltyAddressCannotBeZeroAddress();
/**
* @dev Emit an event for token metadata reveals/updates,
* according to EIP-4906.
*
* @param _fromTokenId The start token id.
* @param _toTokenId The end token id.
*/
event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);
/**
* @dev Emit an event when the URI for the collection-level metadata
* is updated.
*/
event ContractURIUpdated(string newContractURI);
/**
* @dev Emit an event when the max token supply is updated.
*/
event MaxSupplyUpdated(uint256 newMaxSupply);
/**
* @dev Emit an event when the project max token supply is updated.
*/
event ProjectMaxSupplyUpdated(uint256 projectId, uint256 newMaxSupply);
/**
* @dev Emit an event with the previous and new provenance hash after
* being updated.
*/
event ProvenanceHashUpdated(bytes32 previousHash, bytes32 newHash);
/**
* @dev Emit an event when the royalties info is updated.
*/
event RoyaltyInfoUpdated(address receiver, uint256 bps);
/**
* @notice A struct defining royalty info for the contract.
*/
struct RoyaltyInfo {
address royaltyAddress;
uint96 royaltyBps;
}
/**
* @notice Sets the base URI for the token metadata and emits an event.
*
* @param tokenURI The new base URI to set.
*/
function setBaseURI(string calldata tokenURI) external;
/**
* @notice Sets the contract URI for contract metadata.
*
* @param newContractURI The new contract URI.
*/
function setContractURI(string calldata newContractURI) external;
/**
* @notice Sets the max supply and emits an event.
*
* @param newMaxSupply The new max supply to set.
*/
function setMaxSupply(uint256 newMaxSupply) external;
/**
* @notice Sets the provenance hash and emits an event.
*
* The provenance hash is used for random reveals, which
* is a hash of the ordered metadata to show it has not been
* modified after mint started.
*
* This function will revert after the first item has been minted.
*
* @param newProvenanceHash The new provenance hash to set.
*/
function setProvenanceHash(
uint256 projectId,
bytes32 newProvenanceHash
) external;
/**
* @notice Sets the address and basis points for royalties.
*
* @param newInfo The struct to configure royalties.
*/
function setRoyaltyInfo(RoyaltyInfo calldata newInfo) external;
/**
* @notice Returns the base URI for token metadata.
*/
function baseURI(uint256 projectId) external view returns (string memory);
/**
* @notice Returns the contract URI.
*/
function contractURI() external view returns (string memory);
/**
* @notice Returns the max token supply.
*/
function maxSupply() external view returns (uint256);
/**
* @notice Returns the provenance hash.
* The provenance hash is used for random reveals, which
* is a hash of the ordered metadata to show it is unmodified
* after mint has started.
*/
function provenanceHash(uint256 projectId) external view returns (bytes32);
/**
* @notice Returns the address that receives royalties.
*/
function royaltyAddress() external view returns (address);
/**
* @notice Returns the royalty basis points out of 10_000.
*/
function royaltyBasisPoints() external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import {
ILGArtLabDropTokenContractMetadata
} from "./ILGArtLabDropTokenContractMetadata.sol";
import { PublicDrop } from "../lib/LGArtLabDropStructs.sol";
interface INonFungibleLGArtLabDropToken is ILGArtLabDropTokenContractMetadata {
/**
* @dev Revert with an error if a contract is not an allowed
* drop address.
*/
error OnlyAllowedDrop();
/**
* @dev Emit an event when allowed drop contracts are updated.
*/
event AllowedDropUpdated(address[] allowedDrop);
/**
* @notice Update the allowed drop contracts.
* Only the owner can use this function.
*
* @param allowedDrop The allowed drop addresses.
*/
function updateAllowedDrop(address[] calldata allowedDrop) external;
/**
* @notice Mint tokens, restricted to the drop contract.
*
* @dev NOTE: If a token registers itself with multiple drop
* contracts, the implementation of this function should guard
* against reentrancy. If the implementing token uses
* _safeMint(), or a feeRecipient with a malicious receive() hook
* is specified, the token or fee recipients may be able to execute
* another mint in the same transaction via a separate drop
* contract.
* This is dangerous if an implementing token does not correctly
* update the minterNumMinted and currentTotalSupply values before
* transferring minted tokens, as drop references these values
* to enforce token limits on a per-wallet and per-stage basis.
*
* @param projectId The project id to mint.
* @param minter The address to mint to.
*/
function mintDrop(uint256 projectId, address minter) external;
/**
* @notice Returns a set of mint stats for the address.
* This assists drop in enforcing maxSupply,
* maxTotalMintableByWallet, and maxTokenSupplyForStage checks.
*
* @dev NOTE: Implementing contracts should always update these numbers
* before transferring any tokens with _safeMint() to mitigate
* consequences of malicious onERC721Received() hooks.
*
* @param projectId The project id.
* @param minter The minter address.
*/
function getMintStats(
uint256 projectId,
address minter
)
external
view
returns (
uint256 minterNumMinted,
uint256 currentTotalSupply,
uint256 maxSupply
);
/**
* @notice Update the public drop data for this nft contract on drop.
* Only the owner can use this function.
*
* @param dropImpl The allowed drop contract.
* @param projectIds The project ids array.
* @param publicDrops The public drop data array.
*/
function updatePublicDrops(
address dropImpl,
uint256[] calldata projectIds,
PublicDrop[] calldata publicDrops
) external;
/**
* @notice Update the drop URI for this nft contract on drop.
* Only the owner can use this function.
*
* @param dropImpl The allowed drop contract.
* @param projectId The project id.
* @param dropURI The new drop URI.
*/
function updateDropURI(
address dropImpl,
uint256 projectId,
string calldata dropURI
) external;
/**
* @notice Update the creator payout address for this nft contract on
* drop.
* Only the owner can set the creator payout address.
*
* @param dropImpl The allowed drop contract.
* @param payoutAddress The new payout address.
*/
function updateCreatorPayoutAddress(
address dropImpl,
address payoutAddress
) external;
/**
* @notice Update the allowed fee recipient for this nft contract
* on drop.
*
* @param dropImpl The allowed drop contract.
* @param feeRecipient The new fee recipient.
* @param allowed Whether the fee recipient is allowed.
*/
function updateAllowedFeeRecipient(
address dropImpl,
address feeRecipient,
bool allowed
) external;
/**
* @notice Update the allowed payers for this nft contract on drop.
* Only the owner can use this function.
*
* @param dropImpl The allowed drop contract.
* @param payer The payer to update.
* @param allowed Whether the payer is allowed.
*/
function updatePayer(
address dropImpl,
address payer,
bool allowed
) external;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import { ILGArtLabDrop } from "./interfaces/ILGArtLabDrop.sol";
import {
INonFungibleLGArtLabDropToken
} from "./interfaces/INonFungibleLGArtLabDropToken.sol";
import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol";
import {
SafeERC20
} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import { Ownable } from "openzeppelin-contracts/access/Ownable.sol";
import { Pausable } from "openzeppelin-contracts/security/Pausable.sol";
import { PublicDrop, PaymentTokenType } from "./lib/LGArtLabDropStructs.sol";
import { SafeTransferLib } from "solmate/utils/SafeTransferLib.sol";
import { ReentrancyGuard } from "solmate/utils/ReentrancyGuard.sol";
import { IERC721 } from "openzeppelin-contracts/token/ERC721/IERC721.sol";
import {
IERC165
} from "openzeppelin-contracts/utils/introspection/IERC165.sol";
import { ECDSA } from "openzeppelin-contracts/utils/cryptography/ECDSA.sol";
import {
MerkleProof
} from "openzeppelin-contracts/utils/cryptography/MerkleProof.sol";
/**
* @title LGArtLabDrop
*/
contract LGArtLabDrop is ILGArtLabDrop, ReentrancyGuard, Pausable, Ownable {
using ECDSA for bytes32;
using SafeERC20 for IERC20;
address[6] private _paymentTokens;
/// @notice Track the public drops.
mapping(address => mapping(uint256 => PublicDrop)) private _publicDrops;
/// @notice Track the creator payout addresses.
mapping(address => address) private _creatorPayoutAddresses;
/// @notice Track the allowed fee recipients.
mapping(address => mapping(address => bool)) private _allowedFeeRecipients;
/// @notice Track the enumerated allowed fee recipients.
mapping(address => address[]) private _enumeratedFeeRecipients;
/// @notice Track the allowed payers.
mapping(address => mapping(address => bool)) private _allowedPayers;
/// @notice Track the enumerated allowed payers.
mapping(address => address[]) private _enumeratedPayers;
/// @notice Constant for an unlimited `maxTokenSupplyForStage`.
/// Used in `mintPublic` where no `maxTokenSupplyForStage`
/// is stored in the `PublicDrop` struct.
uint256 internal constant _UNLIMITED_MAX_TOKEN_SUPPLY_FOR_STAGE =
type(uint256).max;
/// @notice Constant for a public mint's `dropStageIndex`.
/// Used in `mintPublic` where no `dropStageIndex`
/// is stored in the `PublicDrop` struct.
uint256 internal constant _PUBLIC_DROP_STAGE_INDEX = 0;
/// @notice Constant for drop platform fee max limit bps value.
uint256 internal constant _MAX_LIMIT_FEE_BPS = 2_000;
/**
* @notice Ensure only tokens implementing INonFungibleLGArtLabDropToken can
* call the update methods.
*/
modifier onlyINonFungibleLGArtLabDropToken() virtual {
if (
!IERC165(msg.sender).supportsInterface(
type(INonFungibleLGArtLabDropToken).interfaceId
)
) {
revert OnlyINonFungibleLGArtLabDropToken(msg.sender);
}
_;
}
/**
* @notice Constructor for the contract deployment.
*/
constructor(
address[6] memory paymentTokens
) {
/**
* paymentTokens[0]: zero address(NATIVE TOKEN)
* paymentTokens[1]: WETH address
* paymentTokens[2]: USDC address
* paymentTokens[3]: USDT address
* paymentTokens[4]: DAI address
* paymentTokens[5]: any other ERC20 token address deployer wants
*/
for(uint8 i = 1; i < paymentTokens.length; i++) {
if(paymentTokens[i] == address(0)) {
revert PaymentTokenCannotBeZeroAddress();
}
_paymentTokens[i] = paymentTokens[i];
}
emit PaymentTokenInitialized(_paymentTokens);
}
/**
* @notice set payment token for drop contract.
*
* @param paymentTokenType The payment token type enum.
* @param tokenAddress The token address to set.
*/
function setPaymentToken(PaymentTokenType paymentTokenType, address tokenAddress) external onlyOwner {
if(paymentTokenType == PaymentTokenType.NATIVE) {
return;
}
if (tokenAddress == address(0)) {
revert PaymentTokenCannotBeZeroAddress();
}
_paymentTokens[uint256(paymentTokenType)] = tokenAddress;
emit PaymentTokenSet(paymentTokenType, tokenAddress);
}
function getPaymentTokens() external view returns(address[6] memory) {
return _paymentTokens;
}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
/**
* @notice Mint a public drop using payment token.
* @dev this drop contract uses ERC721 based token for mint.
*
* @param nftContract The nft contract to mint.
* @param projectId The project id to mint.
* @param feeRecipient The fee recipient.
* @param minterIfNotPayer The mint recipient if different than the payer.
* @param tax The local tax value for payment
* @param quantity The mint quantity.
*/
function mintPublic(
address nftContract,
uint256 projectId,
address feeRecipient,
address minterIfNotPayer,
uint256 tax,
uint256 quantity
) external payable whenNotPaused {
// Get the public drop data.
PublicDrop memory publicDrop = _publicDrops[nftContract][projectId];
// Ensure that the drop has started.
_checkActive(publicDrop.startTime, publicDrop.endTime);
// Put the mint price on the stack.
uint256 mintPrice = publicDrop.mintPrice;
// Get the minter address.
address minter = minterIfNotPayer != address(0)
? minterIfNotPayer
: msg.sender;
// Validate payment is correct for number minted.
if(publicDrop.paymentToken == PaymentTokenType.NATIVE) {
_checkCorrectNativeTokenPayment(quantity, mintPrice, tax);
} else {
_checkCorrectPayment(minter, quantity, mintPrice, tax, publicDrop.paymentToken);
}
// Ensure the payer is allowed if not the minter.
if (minter != msg.sender) {
if (!_allowedPayers[nftContract][msg.sender]) {
revert PayerNotAllowed();
}
}
// Check that the minter is allowed to mint the desired quantity.
_checkMintQuantity(
nftContract,
projectId,
minter,
quantity,
publicDrop.maxTotalMintableByWallet,
_UNLIMITED_MAX_TOKEN_SUPPLY_FOR_STAGE
);
// Check that the fee recipient is allowed if restricted.
_checkFeeRecipientIsAllowed(
nftContract,
feeRecipient,
publicDrop.restrictFeeRecipients
);
// Mint the token(s), split the payout, emit an event.
_mintAndPay(
nftContract,
projectId,
minter,
quantity,
mintPrice,
tax,
_PUBLIC_DROP_STAGE_INDEX,
publicDrop.feeBps,
feeRecipient,
publicDrop.paymentToken
);
}
/**
* @notice Check that the drop stage is active.
*
* @param startTime The drop stage start time.
* @param endTime The drop stage end time.
*/
function _checkActive(uint256 startTime, uint256 endTime) internal view {
if (
_cast(block.timestamp < startTime) |
_cast(block.timestamp > endTime) ==
1
) {
// Revert if the drop stage is not active.
revert NotActive(block.timestamp, startTime, endTime);
}
}
/**
* @notice Check that the fee recipient is allowed.
*
* @param nftContract The nft contract.
* @param feeRecipient The fee recipient.
* @param restrictFeeRecipients If the fee recipients are restricted.
*/
function _checkFeeRecipientIsAllowed(
address nftContract,
address feeRecipient,
bool restrictFeeRecipients
) internal view {
// Ensure the fee recipient is not the zero address.
if (feeRecipient == address(0)) {
revert FeeRecipientCannotBeZeroAddress();
}
// Revert if the fee recipient is restricted and not allowed.
if (restrictFeeRecipients) {
if (!_allowedFeeRecipients[nftContract][feeRecipient]) {
revert FeeRecipientNotAllowed();
}
}
}
/**
* @notice Check that the wallet is allowed to mint the desired quantity.
*
* @param nftContract The nft contract.
* @param projectId The project id.
* @param minter The mint recipient.
* @param quantity The number of tokens to mint.
* @param maxTotalMintableByWallet The max allowed mints per wallet.
* @param maxTokenSupplyForStage The max token supply for the drop stage.
*/
function _checkMintQuantity(
address nftContract,
uint256 projectId,
address minter,
uint256 quantity,
uint256 maxTotalMintableByWallet,
uint256 maxTokenSupplyForStage
) internal view {
// Mint quantity of zero is not valid.
if(quantity == 0) {
revert MintQuantityCannotBeZero();
}
// since it doesn't support batch mint, revert if mint quantity is not one.
if(quantity != 1) {
revert MintQuantityShouldBeOne();
}
// Get the mint stats.
(
uint256 minterNumMinted,
uint256 currentTotalSupply,
uint256 maxSupply
) = INonFungibleLGArtLabDropToken(nftContract).getMintStats(
projectId,
minter
);
// Ensure mint quantity doesn't exceed maxTotalMintableByWallet.
if (quantity + minterNumMinted > maxTotalMintableByWallet) {
revert MintQuantityExceedsMaxMintedPerWallet(
quantity + minterNumMinted,
maxTotalMintableByWallet
);
}
// Ensure mint quantity doesn't exceed maxSupply.
if (quantity + currentTotalSupply > maxSupply) {
revert MintQuantityExceedsMaxSupply(
quantity + currentTotalSupply,
maxSupply
);
}
// Ensure mint quantity doesn't exceed maxTokenSupplyForStage.
if (quantity + currentTotalSupply > maxTokenSupplyForStage) {
revert MintQuantityExceedsMaxTokenSupplyForStage(
quantity + currentTotalSupply,
maxTokenSupplyForStage
);
}
}
/**
* @notice Revert if the payment is not the quantity times the mint price.
*
* @param quantity The number of tokens to mint.
* @param mintPrice The mint price.
* @param tax The tax amount.
*/
function _checkCorrectNativeTokenPayment(
uint256 quantity,
uint256 mintPrice,
uint256 tax
) internal view {
if (msg.value != ((quantity * mintPrice) + tax)) {
revert IncorrectPayment(msg.value, (quantity * mintPrice) + tax);
}
}
/**
* @notice Revert if the payment is not the quantity times the mint price.
*
* @param minter The nft minter.
* @param quantity The number of tokens to mint.
* @param mintPrice The mint price.
* @param tax The tax amount.
* @param paymentToken The payment toke type enum value.
*/
function _checkCorrectPayment(
address minter,
uint256 quantity,
uint256 mintPrice,
uint256 tax,
PaymentTokenType paymentToken
) internal view {
uint256 paymentTokenIndex = uint256(paymentToken);
if (_paymentTokens[paymentTokenIndex] == address(0)) {
revert PaymentTokenCannotBeZeroAddress();
}
IERC20 token = IERC20(_paymentTokens[paymentTokenIndex]);
uint256 userBalance = token.balanceOf(minter);
uint256 userAllowance = token.allowance(minter, address(this));
uint256 requiredTokenAmount = (quantity * mintPrice) + tax;
// Revert if the payment amount doesn't match the total cost.
if (
userAllowance < requiredTokenAmount ||
userBalance < requiredTokenAmount
) {
revert InsufficientPaymentTokenBalanceOrAllowance(
userBalance,
userAllowance,
requiredTokenAmount
);
}
}
/**
* @notice Split the payment payout for the creator and fee recipient.
*
* @param nftContract The nft contract.
* @param feeRecipient The fee recipient.
* @param feeBps The fee basis points.
*/
function _splitPayoutNativeToken(
address nftContract,
address feeRecipient,
uint256 feeBps,
uint256 tax
) internal {
if (feeBps > 10_000) {
revert InvalidFeeBps(feeBps);
}
// Get the creator payout address.
address creatorPayoutAddress = _creatorPayoutAddresses[nftContract];
// Ensure the creator payout address is not the zero address.
if (creatorPayoutAddress == address(0)) {
revert CreatorPayoutAddressCannotBeZeroAddress();
}
// msg.value has already been validated by this point, so can use it directly.
// If the fee is zero, transfer to the creator (msg.value - tax) and transfer tax to the feeRecipient.
if (feeBps == 0) {
SafeTransferLib.safeTransferETH(creatorPayoutAddress, msg.value - tax);
if (tax > 0) {
SafeTransferLib.safeTransferETH(feeRecipient, tax);
}
return;
}
// Get the fee amount.
// Note that the fee amount is rounded down in favor of the creator.
uint256 feeAmount = ((msg.value - tax) * feeBps) / 10_000;
if (tax > msg.value - tax - feeAmount) {
revert InvalidTaxAmount(tax, msg.value - tax - feeAmount);
}
// Get the creator payout amount. Fee amount is <= (msg.value - tax) per above.
uint256 payoutAmount;
unchecked {
payoutAmount = msg.value - feeAmount - tax;
}
// Transfer the fee amount to the fee recipient.
if (feeAmount > 0) {
SafeTransferLib.safeTransferETH(feeRecipient, feeAmount);
}
// Transfer the tax value to the fee recipient.
if (tax > 0) {
SafeTransferLib.safeTransferETH(feeRecipient, tax);
}
// Transfer the creator payout amount to the creator.
SafeTransferLib.safeTransferETH(creatorPayoutAddress, payoutAmount);
}
/**
* @notice Split the payment payout for the creator and fee recipient.
*
* @param nftContract The nft contract.
* @param feeRecipient The fee recipient.
* @param mintPrice the mint price
* @param feeBps The fee basis points.
*/
function _splitPayout(
address nftContract,
address feeRecipient,
uint256 mintPrice,
uint256 feeBps,
uint256 tax,
PaymentTokenType paymentToken
) internal {
// Revert if the fee basis points is greater than 10_000.
if (feeBps > 10_000) {
revert InvalidFeeBps(feeBps);
}
// Get the creator payout address.
address creatorPayoutAddress = _creatorPayoutAddresses[nftContract];
uint256 paymentTokenIndex = uint256(paymentToken);
// Ensure the creator payout address is not the zero address.
if (creatorPayoutAddress == address(0)) {
revert CreatorPayoutAddressCannotBeZeroAddress();
}
if (_paymentTokens[paymentTokenIndex] == address(0)) {
revert PaymentTokenCannotBeZeroAddress();
}
IERC20 token = IERC20(_paymentTokens[paymentTokenIndex]);
// If the fee is zero, just transfer to the creator and return.
if (feeBps == 0) {
token.safeTransferFrom(msg.sender, creatorPayoutAddress, mintPrice);
if (tax > 0) {
token.safeTransferFrom(msg.sender, feeRecipient, tax);
}
return;
}
// Get the fee amount.
// Note that the fee amount is rounded down in favor of the creator.
uint256 feeAmount = (mintPrice * feeBps) / 10_000;
if (tax > mintPrice - feeAmount) {
revert InvalidTaxAmount(tax, mintPrice - feeAmount);
}
// Get the creator payout amount. Fee amount is <= (mintPrice - tax) per above.
uint256 payoutAmount;
unchecked {
payoutAmount = mintPrice - feeAmount;
}
// Transfer the fee amount to the fee recipient.
if (feeAmount > 0) {
token.safeTransferFrom(msg.sender, feeRecipient, feeAmount);
}
if (tax > 0) {
token.safeTransferFrom(msg.sender, feeRecipient, tax);
}
// Transfer the creator payout amount to the creator.
token.safeTransferFrom(msg.sender, creatorPayoutAddress, payoutAmount);
}
/**
* @notice Mints a number of tokens, splits the payment,
* and emits an event.
*
* @param nftContract The nft contract.
* @param projectId The project id.
* @param minter The mint recipient.
* @param quantity The number of tokens to mint.
* @param mintPrice The mint price.
* @param tax The tax value for payment.
* @param dropStageIndex The drop stage index.
* @param feeBps The fee basis points.
* @param feeRecipient The fee recipient.
*/
function _mintAndPay(
address nftContract,
uint256 projectId,
address minter,
uint256 quantity,
uint256 mintPrice,
uint256 tax,
uint256 dropStageIndex,
uint256 feeBps,
address feeRecipient,
PaymentTokenType paymentToken
) internal nonReentrant {
// Mint the token(s).
INonFungibleLGArtLabDropToken(nftContract).mintDrop(projectId, minter);
if(paymentToken == PaymentTokenType.NATIVE) {
_splitPayoutNativeToken(nftContract, feeRecipient, feeBps, tax);
} else {
_splitPayout(nftContract, feeRecipient, mintPrice, feeBps, tax, paymentToken);
}
// Emit an event for the mint.
emit DropMint(
nftContract,
projectId,
minter,
feeRecipient,
msg.sender,
quantity,
mintPrice,
feeBps,
dropStageIndex
);
}
/**
* @notice Returns the public drop data for the nft contract.
*
* @param nftContract The nft contract.
* @param projectId The project id.
*/
function getPublicDrop(
address nftContract,
uint256 projectId
) external view returns (PublicDrop memory) {
return _publicDrops[nftContract][projectId];
}
/**
* @notice Returns the creator payout address for the nft contract.
*
* @param nftContract The nft contract.
*/
function getCreatorPayoutAddress(
address nftContract
) external view returns (address) {
return _creatorPayoutAddresses[nftContract];
}
/**
* @notice Returns if the specified fee recipient is allowed
* for the nft contract.
*
* @param nftContract The nft contract.
* @param feeRecipient The fee recipient.
*/
function getFeeRecipientIsAllowed(
address nftContract,
address feeRecipient
) external view returns (bool) {
return _allowedFeeRecipients[nftContract][feeRecipient];
}
/**
* @notice Returns an enumeration of allowed fee recipients for an
* nft contract when fee recipients are enforced.
*
* @param nftContract The nft contract.
*/
function getAllowedFeeRecipients(
address nftContract
) external view returns (address[] memory) {
return _enumeratedFeeRecipients[nftContract];
}
/**
* @notice Returns the payers for the nft contract.
*
* @param nftContract The nft contract.
*/
function getPayers(
address nftContract
) external view returns (address[] memory) {
return _enumeratedPayers[nftContract];
}
/**
* @notice Returns if the specified payer is allowed
* for the nft contract.
*
* @param nftContract The nft contract.
* @param payer The payer.
*/
function getPayerIsAllowed(
address nftContract,
address payer
) external view returns (bool) {
return _allowedPayers[nftContract][payer];
}
/**
* @notice Emits an event to notify update of the drop URI.
*
* This method assume msg.sender is an nft contract and its
* ERC165 interface id matches INonFungibleLGArtLabDropToken.
*
* Note: Be sure only authorized users can call this from
* token contracts that implement INonFungibleLGArtLabDropToken.
*
* @param projectId The project id.
* @param dropURI The new drop URI.
*/
function updateDropURI(
uint256 projectId,
string calldata dropURI
) external onlyINonFungibleLGArtLabDropToken {
// Emit an event with the update.
emit DropURIUpdated(msg.sender, dropURI);
}
/**
* @notice Updates the public drop data for the nft contract
* and emits an event.
*
* This method assume msg.sender is an nft contract and its
* ERC165 interface id matches INonFungibleLGArtLabDropToken.
*
* Note: Be sure only authorized users can call this from
* token contracts that implement INonFungibleLGArtLabDropToken.
*
* @param projectIds The project id array.
* @param publicDrops The public drop data array.
*/
function updatePublicDrops(
uint256[] calldata projectIds,
PublicDrop[] calldata publicDrops
) external override onlyINonFungibleLGArtLabDropToken {
if (publicDrops.length > 10) {
revert ExceedsMaxDropUpdateLimit();
}
if (publicDrops.length != projectIds.length) {
revert ArrayLengthMismatch(publicDrops.length, projectIds.length);
}
for (uint8 i = 0; i < publicDrops.length; i++) {
if (publicDrops[i].feeBps > _MAX_LIMIT_FEE_BPS) {
// Revert if the fee basis points is greater than _MAX_LIMIT_FEE_BPS.
revert InvalidFeeBps(publicDrops[i].feeBps);
}
// Set the public drop data.
_publicDrops[msg.sender][projectIds[i]] = publicDrops[i];
}
// Emit an event with the update.
emit PublicDropUpdated(msg.sender, publicDrops);
}
/**
* @notice Updates the creator payout address and emits an event.
*
* This method assume msg.sender is an nft contract and its
* ERC165 interface id matches INonFungibleLGArtLabDropToken.
*
* Note: Be sure only authorized users can call this from
* token contracts that implement INonFungibleLGArtLabDropToken.
*
* @param payoutAddress The creator payout address.
*/
function updateCreatorPayoutAddress(
address payoutAddress
) external onlyINonFungibleLGArtLabDropToken {
if (payoutAddress == address(0)) {
revert CreatorPayoutAddressCannotBeZeroAddress();
}
// Set the creator payout address.
_creatorPayoutAddresses[msg.sender] = payoutAddress;
// Emit an event with the update.
emit CreatorPayoutAddressUpdated(msg.sender, payoutAddress);
}
/**
* @notice Updates the allowed fee recipient and emits an event.
*
* This method assume msg.sender is an nft contract and its
* ERC165 interface id matches INonFungibleLGArtLabDropToken.
*
* Note: Be sure only authorized users can call this from
* token contracts that implement INonFungibleLGArtLabDropToken.
*
* @param feeRecipient The fee recipient.
* @param allowed If the fee recipient is allowed.
*/
function updateAllowedFeeRecipient(
address feeRecipient,
bool allowed
) external onlyINonFungibleLGArtLabDropToken {
if (feeRecipient == address(0)) {
revert FeeRecipientCannotBeZeroAddress();
}
// Track the enumerated storage.
address[] storage enumeratedStorage = _enumeratedFeeRecipients[
msg.sender
];
mapping(address => bool)
storage feeRecipientsMap = _allowedFeeRecipients[msg.sender];
if (allowed) {
if (feeRecipientsMap[feeRecipient]) {
revert DuplicateFeeRecipient();
}
feeRecipientsMap[feeRecipient] = true;
enumeratedStorage.push(feeRecipient);
} else {
if (!feeRecipientsMap[feeRecipient]) {
revert FeeRecipientNotPresent();
}
delete _allowedFeeRecipients[msg.sender][feeRecipient];
_removeFromEnumeration(feeRecipient, enumeratedStorage);
}
// Emit an event with the update.
emit AllowedFeeRecipientUpdated(msg.sender, feeRecipient, allowed);
}
/**
* @notice Updates the allowed payer and emits an event.
*
* This method assume msg.sender is an nft contract and its
* ERC165 interface id matches INonFungibleLGArtLabDropToken.
*
* Note: Be sure only authorized users can call this from
* token contracts that implement INonFungibleLGArtLabDropToken.
*
* @param payer The payer to add or remove.
* @param allowed Whether to add or remove the payer.
*/
function updatePayer(
address payer,
bool allowed
) external onlyINonFungibleLGArtLabDropToken {
if (payer == address(0)) {
revert PayerCannotBeZeroAddress();
}
// Track the enumerated storage.
address[] storage enumeratedStorage = _enumeratedPayers[msg.sender];
mapping(address => bool) storage payersMap = _allowedPayers[msg.sender];
if (allowed) {
if (payersMap[payer]) {
revert DuplicatePayer();
}
payersMap[payer] = true;
enumeratedStorage.push(payer);
} else {
if (!payersMap[payer]) {
revert PayerNotPresent();
}
delete _allowedPayers[msg.sender][payer];
_removeFromEnumeration(payer, enumeratedStorage);
}
// Emit an event with the update.
emit PayerUpdated(msg.sender, payer, allowed);
}
/**
* @notice Remove an address from a supplied enumeration.
*
* @param toRemove The address to remove.
* @param enumeration The enumerated addresses to parse.
*/
function _removeFromEnumeration(
address toRemove,
address[] storage enumeration
) internal {
// Cache the length.
uint256 enumerationLength = enumeration.length;
for (uint256 i = 0; i < enumerationLength; ) {
// Check if the enumerated element is the one we are deleting.
if (enumeration[i] == toRemove) {
// Swap with the last element.
enumeration[i] = enumeration[enumerationLength - 1];
// Delete the (now duplicated) last element.
enumeration.pop();
// Exit the loop.
break;
}
unchecked {
++i;
}
}
}
/**
* @dev Internal pure function to cast a `bool` value to a `uint256` value.
*
* @param b The `bool` value to cast.
*
* @return u The `uint256` value.
*/
function _cast(bool b) internal pure returns (uint256 u) {
assembly {
u := b
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import { PublicDrop, PaymentTokenType } from "./LGArtLabDropStructs.sol";
interface LGArtLabDropErrorsAndEvents {
/**
* @dev Revert with an error if the drop stage is not active.
*/
error NotActive(
uint256 currentTimestamp,
uint256 startTimestamp,
uint256 endTimestamp
);
/**
* @dev Revert with an error if the mint quantity is not one.
*/
error MintQuantityShouldBeOne();
/**
* @dev Revert with an error if the mint quantity is zero.
*/
error MintQuantityCannotBeZero();
/**
* @dev Revert with an error if the mint quantity exceeds the max allowed
* to be minted per wallet.
*/
error MintQuantityExceedsMaxMintedPerWallet(uint256 total, uint256 allowed);
/**
* @dev Revert with an error if the mint quantity exceeds the max token
* supply.
*/
error MintQuantityExceedsMaxSupply(uint256 total, uint256 maxSupply);
/**
* @dev Revert with an error if the mint quantity exceeds the max token
* supply for the stage.
* Note: The `maxTokenSupplyForStage` for public mint is
* always `type(uint).max`.
*/
error MintQuantityExceedsMaxTokenSupplyForStage(
uint256 total,
uint256 maxTokenSupplyForStage
);
/**
* @dev Revert if the fee recipient is the zero address.
*/
error FeeRecipientCannotBeZeroAddress();
/**
* @dev Revert if the fee recipient is not already included.
*/
error FeeRecipientNotPresent();
/**
* @dev Revert if the fee basis points is greater than 10_000.
*/
error InvalidFeeBps(uint256 feeBps);
/**
* @dev Revert if the tax amount is greater than paymentAmount (mintPrice - feeAmount)
*/
error InvalidTaxAmount(uint256 taxAmount, uint256 paymentAmount);
/**
* @dev
*/
error ArrayLengthMismatch(uint256 length1, uint256 length2);
/**
* @dev Revert if exceeds max drop update limit.
*/
error ExceedsMaxDropUpdateLimit();
/**
* @dev Revert if the fee recipient is already included.
*/
error DuplicateFeeRecipient();
/**
* @dev Revert if the fee recipient is restricted and not allowed.
*/
error FeeRecipientNotAllowed();
/**
* @dev Revert if the creator payout address is the zero address.
*/
error CreatorPayoutAddressCannotBeZeroAddress();
/**
* @dev Revert if the payment token address is the zero address.
*/
error PaymentTokenCannotBeZeroAddress();
/**
* @dev Revert if the payment token balance of allowance is insufficient.
*/
error InsufficientPaymentTokenBalanceOrAllowance(
uint256 userBalance,
uint256 userAllowance,
uint256 requiredAmount
);
/**
* @dev Revert with an error if the received payment is incorrect.
*/
error IncorrectPayment(uint256 got, uint256 want);
/**
* @dev Revert if a supplied signer address is the zero address.
*/
error SignerCannotBeZeroAddress();
/**
* @dev Revert with an error if signer's signature is invalid.
*/
error InvalidSignature(address recoveredSigner);
/**
* @dev Revert with an error if a signer is not included in
* the enumeration when removing.
*/
error SignerNotPresent();
/**
* @dev Revert with an error if a payer is not included in
* the enumeration when removing.
*/
error PayerNotPresent();
/**
* @dev Revert with an error if a payer is already included in mapping
* when adding.
* Note: only applies when adding a single payer, as duplicates in
* enumeration can be removed with updatePayer.
*/
error DuplicatePayer();
/**
* @dev Revert with an error if the payer is not allowed. The minter must
* pay for their own mint.
*/
error PayerNotAllowed();
/**
* @dev Revert if a supplied payer address is the zero address.
*/
error PayerCannotBeZeroAddress();
/**
* @dev Revert with an error if the sender does not
* match the INonFungibleSeaDropToken interface.
*/
error OnlyINonFungibleLGArtLabDropToken(address sender);
/**
* @dev An event with details of a SeaDrop mint, for analytical purposes.
*
* @param nftContract The nft contract.
* @param projectId The project id.
* @param minter The mint recipient.
* @param feeRecipient The fee recipient.
* @param payer The address who payed for the tx.
* @param quantityMinted The number of tokens minted.
* @param unitMintPrice The amount paid for each token.
* @param feeBps The fee out of 10_000 basis points collected.
* @param dropStageIndex The drop stage index. Items minted
* through mintPublic() have
* dropStageIndex of 0.
*/
event DropMint(
address indexed nftContract,
uint256 indexed projectId,
address indexed minter,
address feeRecipient,
address payer,
uint256 quantityMinted,
uint256 unitMintPrice,
uint256 feeBps,
uint256 dropStageIndex
);
/**
* @dev Emit an event when payment token array initialized.
*/
event PaymentTokenInitialized(address[6] paymentTokens);
/**
* @dev Emit an event when payment token updated.
*/
event PaymentTokenSet(PaymentTokenType paymentTokenType, address tokenAddress);
/**
* @dev An event with updated public drop data for an nft contract.
*/
event PublicDropUpdated(
address indexed nftContract,
PublicDrop[] publicDrops
);
/**
* @dev An event with updated drop URI for an nft contract.
*/
event DropURIUpdated(address indexed nftContract, string newDropURI);
/**
* @dev An event with the updated creator payout address for an nft
* contract.
*/
event CreatorPayoutAddressUpdated(
address indexed nftContract,
address indexed newPayoutAddress
);
/**
* @dev An event with the updated allowed fee recipient for an nft
* contract.
*/
event AllowedFeeRecipientUpdated(
address indexed nftContract,
address indexed feeRecipient,
bool indexed allowed
);
/**
* @dev An event with the updated payer for an nft contract.
*/
event PayerUpdated(
address indexed nftContract,
address indexed payer,
bool indexed allowed
);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
/**
* @notice A struct defining public drop data.
* Designed to fit efficiently in one storage slot.
*
* @param mintPrice The mint price per token. (Up to 1.2m
* of native token, e.g. ETH, MATIC)
* @param startTime The start time, ensure this is not zero.
* @param endTIme The end time, ensure this is not zero.
* @param maxTotalMintableByWallet Maximum total number of mints a user is
* allowed. (The limit for this field is
* 2^16 - 1)
* @param feeBps Fee out of 10_000 basis points to be
* collected.
* @param restrictFeeRecipients If false, allow any fee recipient;
* if true, check fee recipient is allowed.
* @param paymentToken Drop payment token type enum value.
* see `enum PaymentTokenType`
*/
struct PublicDrop {
uint80 mintPrice; // 80/256 bits
uint48 startTime; // 128/256 bits
uint48 endTime; // 176/256 bits
uint16 maxTotalMintableByWallet; // 224/256 bits
uint16 feeBps; // 240/256 bits
bool restrictFeeRecipients; // 248/256 bits
PaymentTokenType paymentToken; // 256/256 bits
}
/**
* @notice A struct defining token's project info
*
* @param totalMinted
* @param maxSupply
* @param currentTokenIndex
*/
struct ProjectInfo {
uint256 totalMinted;
uint256 maxSupply;
uint256 currentTokenIndex;
}
/**
* @notice A enum defining drop payment token's type
*/
enum PaymentTokenType {
// 0: Native token. ETH on mainnet, MATIC on polygon, etc.
NATIVE,
// 1: Wrapped ETH
WETH,
// 2: USD Coin
USDC,
// 3: Tether
USDT,
// 4: DAI
DAI,
// 5: Custom ERC20 Token
CUSTOM
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1);
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator,
Rounding rounding
) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10**64) {
value /= 10**64;
result += 64;
}
if (value >= 10**32) {
value /= 10**32;
result += 32;
}
if (value >= 10**16) {
value /= 10**16;
result += 16;
}
if (value >= 10**8) {
value /= 10**8;
result += 8;
}
if (value >= 10**4) {
value /= 10**4;
result += 4;
}
if (value >= 10**2) {
value /= 10**2;
result += 2;
}
if (value >= 10**1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/cryptography/MerkleProof.sol)
pragma solidity ^0.8.0;
/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The proofs can be generated using the JavaScript library
* https://github.com/miguelmota/merkletreejs[merkletreejs].
* Note: the hashing algorithm should be keccak256 and pair sorting should be enabled.
*
* See `test/utils/cryptography/MerkleProof.test.js` for some examples.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the merkle tree could be reinterpreted as a leaf value.
*/
library MerkleProof {
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Calldata version of {verify}
*
* _Available since v4.7._
*/
function verifyCalldata(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leafs & pre-images are assumed to be sorted.
*
* _Available since v4.4._
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Calldata version of {processProof}
*
* _Available since v4.7._
*/
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if the `leaves` can be proved to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* _Available since v4.7._
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}
/**
* @dev Calldata version of {multiProofVerify}
*
* _Available since v4.7._
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and the sibling nodes in `proof`,
* consuming from one or the other at each step according to the instructions given by
* `proofFlags`.
*
* _Available since v4.7._
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the merkle tree.
uint256 leavesLen = leaves.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.
require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
return hashes[totalHashes - 1];
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Calldata version of {processMultiProof}
*
* _Available since v4.7._
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the merkle tree.
uint256 leavesLen = leaves.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.
require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
return hashes[totalHashes - 1];
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
bool private _paused;
/**
* @dev Initializes the contract in unpaused state.
*/
constructor() {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
require(!paused(), "Pausable: paused");
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
require(paused(), "Pausable: not paused");
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
uint256 private locked = 1;
modifier nonReentrant() virtual {
require(locked == 1, "REENTRANCY");
locked = 2;
_;
locked = 1;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
import "./math/Math.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
{
"compilationTarget": {
"src/LGArtLabDrop.sol": "LGArtLabDrop"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "none"
},
"optimizer": {
"enabled": true,
"runs": 1000000
},
"remappings": [
":@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/",
":ERC721A-Upgradeable/=lib/ERC721A-Upgradeable/contracts/",
":ERC721A/=lib/ERC721A/contracts/",
":create2-helpers/=lib/create2-helpers/",
":create2-scripts/=lib/create2-helpers/script/",
":drop/=src/",
":ds-test/=lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
":forge-std/=lib/forge-std/src/",
":murky/=lib/murky/src/",
":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/",
":operator-filter-registry/=lib/operator-filter-registry/src/",
":solmate/=lib/solmate/src/",
":utility-contracts/=lib/utility-contracts/src/"
]
}
[{"inputs":[{"internalType":"address[6]","name":"paymentTokens","type":"address[6]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"length1","type":"uint256"},{"internalType":"uint256","name":"length2","type":"uint256"}],"name":"ArrayLengthMismatch","type":"error"},{"inputs":[],"name":"CreatorPayoutAddressCannotBeZeroAddress","type":"error"},{"inputs":[],"name":"DuplicateFeeRecipient","type":"error"},{"inputs":[],"name":"DuplicatePayer","type":"error"},{"inputs":[],"name":"ExceedsMaxDropUpdateLimit","type":"error"},{"inputs":[],"name":"FeeRecipientCannotBeZeroAddress","type":"error"},{"inputs":[],"name":"FeeRecipientNotAllowed","type":"error"},{"inputs":[],"name":"FeeRecipientNotPresent","type":"error"},{"inputs":[{"internalType":"uint256","name":"got","type":"uint256"},{"internalType":"uint256","name":"want","type":"uint256"}],"name":"IncorrectPayment","type":"error"},{"inputs":[{"internalType":"uint256","name":"userBalance","type":"uint256"},{"internalType":"uint256","name":"userAllowance","type":"uint256"},{"internalType":"uint256","name":"requiredAmount","type":"uint256"}],"name":"InsufficientPaymentTokenBalanceOrAllowance","type":"error"},{"inputs":[{"internalType":"uint256","name":"feeBps","type":"uint256"}],"name":"InvalidFeeBps","type":"error"},{"inputs":[{"internalType":"address","name":"recoveredSigner","type":"address"}],"name":"InvalidSignature","type":"error"},{"inputs":[{"internalType":"uint256","name":"taxAmount","type":"uint256"},{"internalType":"uint256","name":"paymentAmount","type":"uint256"}],"name":"InvalidTaxAmount","type":"error"},{"inputs":[],"name":"MintQuantityCannotBeZero","type":"error"},{"inputs":[{"internalType":"uint256","name":"total","type":"uint256"},{"internalType":"uint256","name":"allowed","type":"uint256"}],"name":"MintQuantityExceedsMaxMintedPerWallet","type":"error"},{"inputs":[{"internalType":"uint256","name":"total","type":"uint256"},{"internalType":"uint256","name":"maxSupply","type":"uint256"}],"name":"MintQuantityExceedsMaxSupply","type":"error"},{"inputs":[{"internalType":"uint256","name":"total","type":"uint256"},{"internalType":"uint256","name":"maxTokenSupplyForStage","type":"uint256"}],"name":"MintQuantityExceedsMaxTokenSupplyForStage","type":"error"},{"inputs":[],"name":"MintQuantityShouldBeOne","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentTimestamp","type":"uint256"},{"internalType":"uint256","name":"startTimestamp","type":"uint256"},{"internalType":"uint256","name":"endTimestamp","type":"uint256"}],"name":"NotActive","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"OnlyINonFungibleLGArtLabDropToken","type":"error"},{"inputs":[],"name":"PayerCannotBeZeroAddress","type":"error"},{"inputs":[],"name":"PayerNotAllowed","type":"error"},{"inputs":[],"name":"PayerNotPresent","type":"error"},{"inputs":[],"name":"PaymentTokenCannotBeZeroAddress","type":"error"},{"inputs":[],"name":"SignerCannotBeZeroAddress","type":"error"},{"inputs":[],"name":"SignerNotPresent","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"address","name":"feeRecipient","type":"address"},{"indexed":true,"internalType":"bool","name":"allowed","type":"bool"}],"name":"AllowedFeeRecipientUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"address","name":"newPayoutAddress","type":"address"}],"name":"CreatorPayoutAddressUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"projectId","type":"uint256"},{"indexed":true,"internalType":"address","name":"minter","type":"address"},{"indexed":false,"internalType":"address","name":"feeRecipient","type":"address"},{"indexed":false,"internalType":"address","name":"payer","type":"address"},{"indexed":false,"internalType":"uint256","name":"quantityMinted","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unitMintPrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feeBps","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dropStageIndex","type":"uint256"}],"name":"DropMint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":false,"internalType":"string","name":"newDropURI","type":"string"}],"name":"DropURIUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"address","name":"payer","type":"address"},{"indexed":true,"internalType":"bool","name":"allowed","type":"bool"}],"name":"PayerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[6]","name":"paymentTokens","type":"address[6]"}],"name":"PaymentTokenInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"enum PaymentTokenType","name":"paymentTokenType","type":"uint8"},{"indexed":false,"internalType":"address","name":"tokenAddress","type":"address"}],"name":"PaymentTokenSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"components":[{"internalType":"uint80","name":"mintPrice","type":"uint80"},{"internalType":"uint48","name":"startTime","type":"uint48"},{"internalType":"uint48","name":"endTime","type":"uint48"},{"internalType":"uint16","name":"maxTotalMintableByWallet","type":"uint16"},{"internalType":"uint16","name":"feeBps","type":"uint16"},{"internalType":"bool","name":"restrictFeeRecipients","type":"bool"},{"internalType":"enum PaymentTokenType","name":"paymentToken","type":"uint8"}],"indexed":false,"internalType":"struct PublicDrop[]","name":"publicDrops","type":"tuple[]"}],"name":"PublicDropUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"}],"name":"getAllowedFeeRecipients","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"}],"name":"getCreatorPayoutAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"getFeeRecipientIsAllowed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"address","name":"payer","type":"address"}],"name":"getPayerIsAllowed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"}],"name":"getPayers","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPaymentTokens","outputs":[{"internalType":"address[6]","name":"","type":"address[6]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"projectId","type":"uint256"}],"name":"getPublicDrop","outputs":[{"components":[{"internalType":"uint80","name":"mintPrice","type":"uint80"},{"internalType":"uint48","name":"startTime","type":"uint48"},{"internalType":"uint48","name":"endTime","type":"uint48"},{"internalType":"uint16","name":"maxTotalMintableByWallet","type":"uint16"},{"internalType":"uint16","name":"feeBps","type":"uint16"},{"internalType":"bool","name":"restrictFeeRecipients","type":"bool"},{"internalType":"enum PaymentTokenType","name":"paymentToken","type":"uint8"}],"internalType":"struct PublicDrop","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"},{"internalType":"address","name":"minterIfNotPayer","type":"address"},{"internalType":"uint256","name":"tax","type":"uint256"},{"internalType":"uint256","name":"quantity","type":"uint256"}],"name":"mintPublic","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"enum PaymentTokenType","name":"paymentTokenType","type":"uint8"},{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"setPaymentToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"feeRecipient","type":"address"},{"internalType":"bool","name":"allowed","type":"bool"}],"name":"updateAllowedFeeRecipient","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"payoutAddress","type":"address"}],"name":"updateCreatorPayoutAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"string","name":"dropURI","type":"string"}],"name":"updateDropURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"payer","type":"address"},{"internalType":"bool","name":"allowed","type":"bool"}],"name":"updatePayer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"projectIds","type":"uint256[]"},{"components":[{"internalType":"uint80","name":"mintPrice","type":"uint80"},{"internalType":"uint48","name":"startTime","type":"uint48"},{"internalType":"uint48","name":"endTime","type":"uint48"},{"internalType":"uint16","name":"maxTotalMintableByWallet","type":"uint16"},{"internalType":"uint16","name":"feeBps","type":"uint16"},{"internalType":"bool","name":"restrictFeeRecipients","type":"bool"},{"internalType":"enum PaymentTokenType","name":"paymentToken","type":"uint8"}],"internalType":"struct PublicDrop[]","name":"publicDrops","type":"tuple[]"}],"name":"updatePublicDrops","outputs":[],"stateMutability":"nonpayable","type":"function"}]