From 9270d1865fc62239e79e3c18de548ff8e7bc4825 Mon Sep 17 00:00:00 2001 From: Dan Oved Date: Thu, 3 Aug 2023 16:08:19 -0700 Subject: [PATCH] wip on move premint to creator attribution style --- script/EstimatePreminterGas.s.sol | 90 ------- src/deployment/DeploymentConfig.sol | 9 - src/interfaces/IZoraCreator1155.sol | 3 + src/nft/ZoraCreator1155Impl.sol | 81 +++++- src/premint/EIP712UpgradeableWithChainId.sol | 106 -------- src/premint/ZoraCreator1155Delegation.sol | 226 ++++++++++++++++ src/premint/ZoraCreator1155Preminter.sol | 269 ++----------------- src/utils/PublicMulticall.sol | 10 + test/premint/ZoraCreator1155Preminter.t.sol | 50 ++-- 9 files changed, 355 insertions(+), 489 deletions(-) delete mode 100644 script/EstimatePreminterGas.s.sol delete mode 100644 src/premint/EIP712UpgradeableWithChainId.sol create mode 100644 src/premint/ZoraCreator1155Delegation.sol diff --git a/script/EstimatePreminterGas.s.sol b/script/EstimatePreminterGas.s.sol deleted file mode 100644 index e95737921..000000000 --- a/script/EstimatePreminterGas.s.sol +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import "forge-std/Script.sol"; -import "forge-std/console2.sol"; - -import {ZoraDeployerBase} from "./ZoraDeployerBase.sol"; -import {ChainConfig, Deployment} from "../src/deployment/DeploymentConfig.sol"; - -import {ZoraCreator1155FactoryImpl} from "../src/factory/ZoraCreator1155FactoryImpl.sol"; -import {Zora1155Factory} from "../src/proxies/Zora1155Factory.sol"; -import {ZoraCreator1155Impl} from "../src/nft/ZoraCreator1155Impl.sol"; -import {ICreatorRoyaltiesControl} from "../src/interfaces/ICreatorRoyaltiesControl.sol"; -import {IZoraCreator1155Factory} from "../src/interfaces/IZoraCreator1155Factory.sol"; -import {IMinter1155} from "../src/interfaces/IMinter1155.sol"; -import {IZoraCreator1155} from "../src/interfaces/IZoraCreator1155.sol"; -import {ProxyShim} from "../src/utils/ProxyShim.sol"; -import {ZoraCreatorFixedPriceSaleStrategy} from "../src/minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.sol"; -import {ZoraCreatorMerkleMinterStrategy} from "../src/minters/merkle/ZoraCreatorMerkleMinterStrategy.sol"; -import {ZoraCreatorRedeemMinterFactory} from "../src/minters/redeem/ZoraCreatorRedeemMinterFactory.sol"; -import {ZoraCreator1155Preminter} from "../src/premint/ZoraCreator1155Preminter.sol"; - -contract EstimatePreminterGas is ZoraDeployerBase { - function run() public { - Deployment memory deployment = getDeployment(); - - address deployer = vm.envAddress("DEPLOYER"); - - ZoraCreator1155FactoryImpl factory = ZoraCreator1155FactoryImpl(deployment.factoryProxy); - - console.log("deploying preminter contract"); - vm.startBroadcast(deployer); - - ZoraCreator1155Preminter preminter = new ZoraCreator1155Preminter(); - preminter.initialize(factory); - - vm.stopBroadcast(); - - // now generate a signature - - ZoraCreator1155Preminter.ContractCreationConfig memory contractConfig = ZoraCreator1155Preminter.ContractCreationConfig({ - contractAdmin: deployer, - contractName: "blah", - contractURI: "blah.contract" - }); - // configuration of token to create - ZoraCreator1155Preminter.TokenCreationConfig memory tokenConfig = ZoraCreator1155Preminter.TokenCreationConfig({ - tokenURI: "blah.token", - maxSupply: 10, - maxTokensPerAddress: 5, - pricePerToken: 0, - mintStart: 0, - mintDuration: 365 days, - royaltyBPS: 10, - royaltyRecipient: deployer, - royaltyMintSchedule: 20 - }); - // how many tokens are minted to the executor - uint256 quantityToMint = 1; - uint32 uid = 100; - uint32 version = 0; - ZoraCreator1155Preminter.PremintConfig memory premintConfig = ZoraCreator1155Preminter.PremintConfig({ - contractConfig: contractConfig, - tokenConfig: tokenConfig, - uid: uid, - deleted: false, - version: version - }); - - uint256 valueToSend = quantityToMint * ZoraCreator1155Impl(address(factory.implementation())).mintFee(); - - bytes32 digest = preminter.premintHashData(premintConfig, address(preminter), chainId()); - - uint256 privateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); - - bytes memory signature = abi.encodePacked(r, s, v); - - string memory comment = "we love it!"; - - console.log("executing premint"); - // now do an on-chain premint - vm.startBroadcast(deployer); - - preminter.premint{value: valueToSend}(premintConfig, signature, quantityToMint, comment); - - vm.stopBroadcast(); - } -} diff --git a/src/deployment/DeploymentConfig.sol b/src/deployment/DeploymentConfig.sol index 9d8323f6c..7cbfe1ecf 100644 --- a/src/deployment/DeploymentConfig.sol +++ b/src/deployment/DeploymentConfig.sol @@ -29,11 +29,8 @@ struct Deployment { address factoryImpl; /// @notice Factory proxy contract that creates zora drops style NFT contracts address factoryProxy; -<<<<<<< HEAD -======= /// @notice Preminter contract address address preminter; ->>>>>>> 9ec0422 (Premint) } abstract contract DeploymentConfig is CommonBase { @@ -57,10 +54,7 @@ abstract contract DeploymentConfig is CommonBase { string constant CONTRACT_1155_IMPL = "CONTRACT_1155_IMPL"; string constant FACTORY_IMPL = "FACTORY_IMPL"; string constant FACTORY_PROXY = "FACTORY_PROXY"; -<<<<<<< HEAD -======= string constant PREMINTER = "PREMINTER"; ->>>>>>> 9ec0422 (Premint) /// @notice Return a prefixed key for reading with a ".". /// @param key key to prefix @@ -88,10 +82,7 @@ abstract contract DeploymentConfig is CommonBase { deployment.contract1155Impl = json.readAddress(getKeyPrefix(CONTRACT_1155_IMPL)); deployment.factoryImpl = json.readAddress(getKeyPrefix(FACTORY_IMPL)); deployment.factoryProxy = json.readAddress(getKeyPrefix(FACTORY_PROXY)); -<<<<<<< HEAD -======= deployment.preminter = json.readAddress(getKeyPrefix(PREMINTER)); ->>>>>>> 9ec0422 (Premint) } } diff --git a/src/interfaces/IZoraCreator1155.sol b/src/interfaces/IZoraCreator1155.sol index dca40f4de..f7bec7341 100644 --- a/src/interfaces/IZoraCreator1155.sol +++ b/src/interfaces/IZoraCreator1155.sol @@ -9,6 +9,7 @@ import {IMinter1155} from "../interfaces/IMinter1155.sol"; import {IOwnable} from "../interfaces/IOwnable.sol"; import {IVersionedContract} from "./IVersionedContract.sol"; import {ICreatorRoyaltiesControl} from "../interfaces/ICreatorRoyaltiesControl.sol"; +import {PremintConfig} from "../premint/ZoraCreator1155Delegation.sol"; /* @@ -103,6 +104,8 @@ interface IZoraCreator1155 is IZoraCreator1155TypesV1, IVersionedContract, IOwna /// @param maxSupply maxSupply for the token, set to 0 for open edition function setupNewToken(string memory tokenURI, uint256 maxSupply) external returns (uint256 tokenId); + function delegateSetupNewToken(PremintConfig calldata premintConfig, bytes calldata signature) external returns (uint256 newTokenId); + function updateTokenURI(uint256 tokenId, string memory _newURI) external; function updateContractMetadata(string memory _newURI, string memory _newName) external; diff --git a/src/nft/ZoraCreator1155Impl.sol b/src/nft/ZoraCreator1155Impl.sol index 09de301c7..8bdf74024 100644 --- a/src/nft/ZoraCreator1155Impl.sol +++ b/src/nft/ZoraCreator1155Impl.sol @@ -26,6 +26,7 @@ import {PublicMulticall} from "../utils/PublicMulticall.sol"; import {SharedBaseConstants} from "../shared/SharedBaseConstants.sol"; import {TransferHelperUtils} from "../utils/TransferHelperUtils.sol"; import {ZoraCreator1155StorageV1} from "./ZoraCreator1155StorageV1.sol"; +import {ZoraCreator1155Attribution, TokenSetup, PremintConfig} from "../premint/ZoraCreator1155Delegation.sol"; /// Imagine. Mint. Enjoy. /// @title ZoraCreator1155Impl @@ -206,6 +207,19 @@ contract ZoraCreator1155Impl is _; } + /// @notice Modifier checking if the user is an admin or has a role + /// @dev This reverts if the msg.sender is not an admin for the given token id or contract + /// @param tokenId tokenId to check + /// @param role role to check + modifier onlyAdminOrRoleInternal( + address msgSender, + uint256 tokenId, + uint256 role + ) { + _requireAdminOrRole(msgSender, tokenId, role); + _; + } + /// @notice Modifier checking if the user is an admin /// @dev This reverts if the msg.sender is not an admin for the given token id or contract /// @param tokenId tokenId to check @@ -247,18 +261,23 @@ contract ZoraCreator1155Impl is /// @notice Set up a new token /// @param newURI The URI for the token /// @param maxSupply The maximum supply of the token - function setupNewToken( + function setupNewToken(string memory newURI, uint256 maxSupply) public nonReentrant returns (uint256) { + return _setupNewTokenInternal(msg.sender, newURI, maxSupply); + } + + function _setupNewTokenInternal( + address msgSender, string memory newURI, uint256 maxSupply - ) public onlyAdminOrRole(CONTRACT_BASE_ID, PERMISSION_BIT_MINTER) nonReentrant returns (uint256) { + ) internal onlyAdminOrRoleInternal(msgSender, CONTRACT_BASE_ID, PERMISSION_BIT_MINTER) returns (uint256) { uint256 tokenId = _setupNewToken(newURI, maxSupply); // Allow the token creator to administrate this token - _addPermission(tokenId, msg.sender, PERMISSION_BIT_ADMIN); + _addPermission(tokenId, msgSender, PERMISSION_BIT_ADMIN); if (bytes(newURI).length > 0) { emit URI(newURI, tokenId); } - emit SetupNewToken(tokenId, msg.sender, newURI, maxSupply); + emit SetupNewToken(tokenId, msgSender, newURI, maxSupply); return tokenId; } @@ -635,4 +654,58 @@ contract ZoraCreator1155Impl is revert(); } } + + /* start eip712 functionality */ + bytes32 private constant _HASHED_NAME = keccak256(bytes("Preminter")); + bytes32 private constant _HASHED_VERSION = keccak256(bytes("1")); + bytes32 private constant _TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + + mapping(uint32 => bool) public uidUsed; + + // todo: move to its own contract + error PremintAlreadyExecuted(); + error MintNotYetStarted(); + + event CreatorAttribution(bytes32 structHash, bytes32 domainName, bytes32 version, bytes signature); + + function delegateSetupNewToken(PremintConfig calldata premintConfig, bytes calldata signature) public returns (uint256 newTokenId) { + if (premintConfig.tokenConfig.mintStart != 0 && premintConfig.tokenConfig.mintStart > block.timestamp) { + // if the mint start is in the future, then revert + revert MintNotYetStarted(); + } + + if (premintConfig.deleted) { + // if the signature says to be deleted, then dont execute any further minting logic + return 0; + } + + // check that uid hasn't been used + if (uidUsed[premintConfig.uid]) { + revert PremintAlreadyExecuted(); + } else { + uidUsed[premintConfig.uid] = true; + } + + bytes32 hashedPremintConfig = ZoraCreator1155Attribution.hashPremintConfig(premintConfig); + + // this is what attributes this token to have been created by the original creator + emit CreatorAttribution(hashedPremintConfig, ZoraCreator1155Attribution.HASHED_NAME, ZoraCreator1155Attribution.HASHED_VERSION, signature); + + // recover the signer from the data + address recoveredSigner = ZoraCreator1155Attribution.recoverSignerHashed(hashedPremintConfig, signature, address(this)); + + // get the new token id - it will fail if the recovered signer does not have PERMISSION_BIT_MINTER permission + newTokenId = _setupNewTokenInternal(recoveredSigner, premintConfig.tokenConfig.tokenURI, premintConfig.tokenConfig.maxSupply); + + // msg.sender should now have admin role on that token (lets make sure to remove it at the end of this call) + + // invoke setup actions for new token, to save contract size, first get them from an external lib + bytes[] memory tokenSetupActions = TokenSetup.makeSetupNewTokenCalls(newTokenId, recoveredSigner, premintConfig.tokenConfig); + + // then invoke them, calling account should be original msg.sender; + multicallInternal(tokenSetupActions); + + // remove the token creator as admin of the newly created token: + _removePermission(newTokenId, msg.sender, PERMISSION_BIT_ADMIN); + } } diff --git a/src/premint/EIP712UpgradeableWithChainId.sol b/src/premint/EIP712UpgradeableWithChainId.sol deleted file mode 100644 index 067a663f7..000000000 --- a/src/premint/EIP712UpgradeableWithChainId.sol +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/EIP712.sol) - -pragma solidity ^0.8.17; - -import {ECDSAUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/ECDSAUpgradeable.sol"; -import {Initializable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; - -/** - * @dev Same as OpenZeppelins' EIP712Upgradeable but allows the chain id to be passed as an argument, - * enabling a message to be signed to execute on on another chain - */ -abstract contract EIP712UpgradeableWithChainId is Initializable { - /* solhint-disable var-name-mixedcase */ - bytes32 private _HASHED_NAME; - bytes32 private _HASHED_VERSION; - bytes32 private constant _TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - - /* solhint-enable var-name-mixedcase */ - - /** - * @dev Initializes the domain separator and parameter caches. - * - * The meaning of `name` and `version` is specified in - * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: - * - * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. - * - `version`: the current major version of the signing domain. - * - * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart - * contract upgrade]. - */ - function __EIP712_init(string memory name, string memory version) internal onlyInitializing { - __EIP712_init_unchained(name, version); - } - - function __EIP712_init_unchained(string memory name, string memory version) internal onlyInitializing { - bytes32 hashedName = keccak256(bytes(name)); - bytes32 hashedVersion = keccak256(bytes(version)); - _HASHED_NAME = hashedName; - _HASHED_VERSION = hashedVersion; - } - - /** - * @dev Returns the domain separator for the specified chain. - */ - function _domainSeparatorV4(uint256 chainId, address verifyingContract) internal view returns (bytes32) { - return _buildDomainSeparator(_TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash(), verifyingContract, chainId); - } - - function _buildDomainSeparator( - bytes32 typeHash, - bytes32 nameHash, - bytes32 versionHash, - address verifyingContract, - uint256 chainId - ) private pure returns (bytes32) { - return keccak256(abi.encode(typeHash, nameHash, versionHash, chainId, verifyingContract)); - } - - /** - * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this - * function returns the hash of the fully encoded EIP712 message for this domain. - * - * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: - * - * ```solidity - * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( - * keccak256("Mail(address to,string contents)"), - * mailTo, - * keccak256(bytes(mailContents)) - * ))); - * address signer = ECDSA.recover(digest, signature); - * ``` - */ - function _hashTypedDataV4(bytes32 structHash, address verifyingContract, uint256 chainId) internal view virtual returns (bytes32) { - return ECDSAUpgradeable.toTypedDataHash(_domainSeparatorV4(chainId, verifyingContract), structHash); - } - - /** - * @dev The hash of the name parameter for the EIP712 domain. - * - * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs - * are a concern. - */ - function _EIP712NameHash() internal view virtual returns (bytes32) { - return _HASHED_NAME; - } - - /** - * @dev The hash of the version parameter for the EIP712 domain. - * - * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs - * are a concern. - */ - function _EIP712VersionHash() internal view virtual returns (bytes32) { - return _HASHED_VERSION; - } - - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - */ - uint256[50] private __gap; -} diff --git a/src/premint/ZoraCreator1155Delegation.sol b/src/premint/ZoraCreator1155Delegation.sol new file mode 100644 index 000000000..3928e439e --- /dev/null +++ b/src/premint/ZoraCreator1155Delegation.sol @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {IMinter1155} from "../interfaces/IMinter1155.sol"; +import {IZoraCreator1155} from "../interfaces/IZoraCreator1155.sol"; +import {ICreatorRoyaltiesControl} from "../interfaces/ICreatorRoyaltiesControl.sol"; +import {ECDSAUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/ECDSAUpgradeable.sol"; +import {ZoraCreatorFixedPriceSaleStrategy} from "../minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.sol"; + +struct ContractCreationConfig { + // Creator/admin of the created contract. Must match the account that signed the message + address contractAdmin; + // Metadata URI for the created contract + string contractURI; + // Name of the created contract + string contractName; +} + +struct TokenCreationConfig { + // Metadata URI for the created token + string tokenURI; + // Max supply of the created token + uint256 maxSupply; + // Max tokens that can be minted for an address, 0 if unlimited + uint64 maxTokensPerAddress; + // Price per token in eth wei. 0 for a free mint. + uint96 pricePerToken; + // The start time of the mint, 0 for immediate. Prevents signatures from being used until the start time. + uint64 mintStart; + // The duration of the mint, starting from the first mint of this token. 0 for infinite + uint64 mintDuration; + // RoyaltyMintSchedule for created tokens. Every nth token will go to the royalty recipient. + uint32 royaltyMintSchedule; + // RoyaltyBPS for created tokens. The royalty amount in basis points for secondary sales. + uint32 royaltyBPS; + // RoyaltyRecipient for created tokens. The address that will receive the royalty payments. + address royaltyRecipient; + // Fixed price minter address + address fixedPriceMinter; +} + +struct PremintConfig { + // The config for the contract to be created + ContractCreationConfig contractConfig; + // The config for the token to be created + TokenCreationConfig tokenConfig; + // Unique id of the token, used to ensure that multiple signatures can't be used to create the same intended token. + // only one signature per token id, scoped to the contract hash can be executed. + uint32 uid; + // Version of this premint, scoped to the uid and contract. Not used for logic in the contract, but used externally to track the newest version + uint32 version; + // If executing this signature results in preventing any signature with this uid from being minted. + bool deleted; +} + +/// @title Enables a creator to signal intent to create a Zora erc1155 contract or new token on that +/// contract by signing a transaction but not paying gas, and have a third party/collector pay the gas +/// by executing the transaction. Incentivizes the third party to execute the transaction by offering +/// a reward in the form of minted tokens. +/// @author @oveddan +library ZoraCreator1155Attribution { + /* start eip712 functionality */ + bytes32 public constant HASHED_NAME = keccak256(bytes("Preminter")); + bytes32 public constant HASHED_VERSION = keccak256(bytes("1")); + bytes32 public constant TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + + /** + * @dev Returns the domain separator for the specified chain. + */ + function _domainSeparatorV4(uint256 chainId, address verifyingContract) internal pure returns (bytes32) { + return _buildDomainSeparator(HASHED_NAME, HASHED_VERSION, verifyingContract, chainId); + } + + function _buildDomainSeparator(bytes32 nameHash, bytes32 versionHash, address verifyingContract, uint256 chainId) private pure returns (bytes32) { + return keccak256(abi.encode(TYPE_HASH, nameHash, versionHash, chainId, verifyingContract)); + } + + function _hashTypedDataV4(bytes32 structHash, address verifyingContract, uint256 chainId) private pure returns (bytes32) { + return ECDSAUpgradeable.toTypedDataHash(_domainSeparatorV4(chainId, verifyingContract), structHash); + } + + /* end eip712 functionality */ + + function recoverSigner(PremintConfig calldata premintConfig, bytes calldata signature, address erc1155Contract) public view returns (address signatory) { + bytes32 encoded = hashPremintConfig(premintConfig); + // first validate the signature - the creator must match the signer of the message + return recoverSignerHashed(encoded, signature, erc1155Contract); + } + + function recoverSignerHashed(bytes32 hashedPremintConfig, bytes calldata signature, address erc1155Contract) public view returns (address signatory) { + // first validate the signature - the creator must match the signer of the message + bytes32 digest = premintHashData( + hashedPremintConfig, + // here we pass the current contract and chain id, ensuring that the message + // only works for the current chain and contract id + erc1155Contract, + block.chainid + ); + + signatory = ECDSAUpgradeable.recover(digest, signature); + } + + /// Gets hash data to sign for a premint. Allows specifying a different chain id and contract address so that the signature + /// can be verified on a different chain. + /// @param verifyingContract Contract address that signature is to be verified against + /// @param chainId Chain id that signature is to be verified on + function premintHashData(bytes32 encoded, address verifyingContract, uint256 chainId) public pure returns (bytes32) { + // build the struct hash to be signed + // here we pass the chain id, allowing the message to be signed for another chain + return _hashTypedDataV4(encoded, verifyingContract, chainId); + } + + bytes32 constant CONTRACT_AND_TOKEN_DOMAIN = + keccak256( + "Premint(ContractCreationConfig contractConfig,TokenCreationConfig tokenConfig,uint32 uid,uint32 version,bool deleted)ContractCreationConfig(address contractAdmin,string contractURI,string contractName)TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 mintStart,uint64 mintDuration,uint32 royaltyMintSchedule,uint32 royaltyBPS,address royaltyRecipient)" + ); + + function hashPremintConfig(PremintConfig calldata premintConfig) public pure returns (bytes32) { + return + keccak256( + abi.encode( + CONTRACT_AND_TOKEN_DOMAIN, + _hashContract(premintConfig.contractConfig), + _hashToken(premintConfig.tokenConfig), + premintConfig.uid, + premintConfig.version, + premintConfig.deleted + ) + ); + } + + bytes32 constant TOKEN_DOMAIN = + keccak256( + "TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 mintStart,uint64 mintDuration,uint32 royaltyMintSchedule,uint32 royaltyBPS,address royaltyRecipient)" + ); + + function _hashToken(TokenCreationConfig calldata tokenConfig) private pure returns (bytes32) { + return + keccak256( + abi.encode( + TOKEN_DOMAIN, + _stringHash(tokenConfig.tokenURI), + tokenConfig.maxSupply, + tokenConfig.maxTokensPerAddress, + tokenConfig.pricePerToken, + tokenConfig.mintStart, + tokenConfig.mintDuration, + tokenConfig.royaltyMintSchedule, + tokenConfig.royaltyBPS, + tokenConfig.royaltyRecipient + ) + ); + } + + bytes32 constant CONTRACT_DOMAIN = keccak256("ContractCreationConfig(address contractAdmin,string contractURI,string contractName)"); + + function _hashContract(ContractCreationConfig calldata contractConfig) private pure returns (bytes32) { + return + keccak256( + abi.encode(CONTRACT_DOMAIN, contractConfig.contractAdmin, _stringHash(contractConfig.contractURI), _stringHash(contractConfig.contractName)) + ); + } + + function _stringHash(string calldata value) private pure returns (bytes32) { + return keccak256(bytes(value)); + } +} + +library TokenSetup { + uint256 constant PERMISSION_BIT_MINTER = 2 ** 2; + + function makeSetupNewTokenCalls( + uint256 newTokenId, + address contractAdmin, + TokenCreationConfig calldata tokenConfig + ) external view returns (bytes[] memory calls) { + address fixedPriceMinterAddress = tokenConfig.fixedPriceMinter; + // build array of the calls to make + // get setup actions and invoke them + // set up the sales strategy + // first, grant the fixed price sale strategy minting capabilities on the token + calls[0] = abi.encodeWithSelector(IZoraCreator1155.addPermission.selector, newTokenId, fixedPriceMinterAddress, PERMISSION_BIT_MINTER); + + // set the sales config on that token + calls[1] = abi.encodeWithSelector( + IZoraCreator1155.callSale.selector, + newTokenId, + IMinter1155(fixedPriceMinterAddress), + abi.encodeWithSelector( + ZoraCreatorFixedPriceSaleStrategy.setSale.selector, + newTokenId, + _buildNewSalesConfig(contractAdmin, tokenConfig.pricePerToken, tokenConfig.maxTokensPerAddress, tokenConfig.mintDuration) + ) + ); + + // set the royalty config on that token: + calls[2] = abi.encodeWithSelector( + IZoraCreator1155.updateRoyaltiesForToken.selector, + newTokenId, + ICreatorRoyaltiesControl.RoyaltyConfiguration({ + royaltyBPS: tokenConfig.royaltyBPS, + royaltyRecipient: tokenConfig.royaltyRecipient, + royaltyMintSchedule: tokenConfig.royaltyMintSchedule + }) + ); + } + + function _buildNewSalesConfig( + address creator, + uint96 pricePerToken, + uint64 maxTokensPerAddress, + uint64 duration + ) private view returns (ZoraCreatorFixedPriceSaleStrategy.SalesConfig memory) { + uint64 saleStart = uint64(block.timestamp); + uint64 saleEnd = duration == 0 ? type(uint64).max : saleStart + duration; + + return + ZoraCreatorFixedPriceSaleStrategy.SalesConfig({ + pricePerToken: pricePerToken, + saleStart: saleStart, + saleEnd: saleEnd, + maxTokensPerAddress: maxTokensPerAddress, + fundsRecipient: creator + }); + } +} diff --git a/src/premint/ZoraCreator1155Preminter.sol b/src/premint/ZoraCreator1155Preminter.sol index 3f27818e6..d96c53159 100644 --- a/src/premint/ZoraCreator1155Preminter.sol +++ b/src/premint/ZoraCreator1155Preminter.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.17; import {ICreatorRoyaltiesControl} from "../interfaces/ICreatorRoyaltiesControl.sol"; -import {EIP712UpgradeableWithChainId} from "./EIP712UpgradeableWithChainId.sol"; import {ECDSAUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/ECDSAUpgradeable.sol"; import {Ownable2StepUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; import {ReentrancyGuardUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/security/ReentrancyGuardUpgradeable.sol"; @@ -11,13 +10,14 @@ import {IZoraCreator1155Factory} from "../interfaces/IZoraCreator1155Factory.sol import {SharedBaseConstants} from "../shared/SharedBaseConstants.sol"; import {ZoraCreatorFixedPriceSaleStrategy} from "../minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.sol"; import {IMinter1155} from "../interfaces/IMinter1155.sol"; +import {PremintConfig, ContractCreationConfig, TokenCreationConfig} from "./ZoraCreator1155Delegation.sol"; /// @title Enables a creator to signal intent to create a Zora erc1155 contract or new token on that /// contract by signing a transaction but not paying gas, and have a third party/collector pay the gas /// by executing the transaction. Incentivizes the third party to execute the transaction by offering /// a reward in the form of minted tokens. /// @author @oveddan -contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId, Ownable2StepUpgradeable, ReentrancyGuardUpgradeable { +contract ZoraCreator1155Preminter is Ownable2StepUpgradeable, ReentrancyGuardUpgradeable { IZoraCreator1155Factory factory; IMinter1155 fixedPriceMinter; @@ -31,74 +31,14 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId, Ownable2StepU uint256 constant PERMISSION_BIT_MINTER = 2 ** 2; uint256 constant PERMISSION_BIT_SALES = 2 ** 3; - /// @dev The resulting token id created for a permint. - /// determinstic contract address => token id => created token id - /// if token not created yet, result id will be 0 - mapping(address => mapping(uint32 => uint256)) public premintTokenId; - - error PremintAlreadyExecuted(); error MintNotYetStarted(); error InvalidSignature(); function initialize(IZoraCreator1155Factory _factory) public initializer { - __EIP712_init("Preminter", "0.0.1"); factory = _factory; fixedPriceMinter = _factory.defaultMinters()[0]; } - struct ContractCreationConfig { - // Creator/admin of the created contract. Must match the account that signed the message - address contractAdmin; - // Metadata URI for the created contract - string contractURI; - // Name of the created contract - string contractName; - } - - struct TokenCreationConfig { - // Metadata URI for the created token - string tokenURI; - // Max supply of the created token - uint256 maxSupply; - // Max tokens that can be minted for an address, 0 if unlimited - uint64 maxTokensPerAddress; - // Price per token in eth wei. 0 for a free mint. - uint96 pricePerToken; - // The start time of the mint, 0 for immediate. Prevents signatures from being used until the start time. - uint64 mintStart; - // The duration of the mint, starting from the first mint of this token. 0 for infinite - uint64 mintDuration; - // RoyaltyMintSchedule for created tokens. Every nth token will go to the royalty recipient. - uint32 royaltyMintSchedule; - // RoyaltyBPS for created tokens. The royalty amount in basis points for secondary sales. - uint32 royaltyBPS; - // RoyaltyRecipient for created tokens. The address that will receive the royalty payments. - address royaltyRecipient; - } - - struct PremintConfig { - // The config for the contract to be created - ContractCreationConfig contractConfig; - // The config for the token to be created - TokenCreationConfig tokenConfig; - // Unique id of the token, used to ensure that multiple signatures can't be used to create the same intended token. - // only one signature per token id, scoped to the contract hash can be executed. - uint32 uid; - // Version of this premint, scoped to the uid and contract. Not used for logic in the contract, but used externally to track the newest version - uint32 version; - // If executing this signature results in preventing any signature with this uid from being minted. - bool deleted; - } - - struct PremintStatus { - // If the signature has been executed - bool executed; - // If premint has been executed, the contract address - address contractAddress; - // If premint has been executed, the created token id - uint256 tokenId; - } - event Preminted( address indexed contractAddress, uint256 indexed tokenId, @@ -115,9 +55,6 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId, Ownable2StepU // this could include creating the contract. function premint( PremintConfig calldata premintConfig, - /// @notice Unique id of the token, used to ensure that multiple signatures can't be used to create the same intended token, in the case - /// that a signature is updated for a token, and the old signature is executed, two tokens for the same original intended token could be created. - /// Only one signature per token id, scoped to the contract hash can be executed. bytes calldata signature, uint256 quantityToMint, string calldata mintComment @@ -130,40 +67,26 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId, Ownable2StepU // 6. Make the creator an admin of that token (and remove this contracts admin rights) // 7. Mint x tokens, as configured, to the executor of this transaction. - _validateSignature(premintConfig, signature); - - if (premintConfig.tokenConfig.mintStart != 0 && premintConfig.tokenConfig.mintStart > block.timestamp) { - // if the mint start is in the future, then revert - revert MintNotYetStarted(); - } - - if (premintConfig.deleted) { - // if the signature says to be deleted, then dont execute any further minting logic - return (address(0), 0); - } - - ContractCreationConfig calldata contractConfig = premintConfig.contractConfig; - TokenCreationConfig calldata tokenConfig = premintConfig.tokenConfig; - // get or create the contract with the given params - (IZoraCreator1155 tokenContract, bool isNewContract) = _getOrCreateContract(contractConfig); + (IZoraCreator1155 tokenContract, bool isNewContract) = _getOrCreateContract(premintConfig.contractConfig); contractAddress = address(tokenContract); - // make sure a token hasn't been minted for the premint token uid and contract address - if (premintTokenId[contractAddress][premintConfig.uid] != 0) { - revert PremintAlreadyExecuted(); - } - - // setup the new token, and its sales config - newTokenId = _setupNewTokenAndSale(tokenContract, contractConfig.contractAdmin, tokenConfig); - - premintTokenId[contractAddress][premintConfig.uid] = newTokenId; - - emit Preminted(contractAddress, newTokenId, isNewContract, premintConfig.uid, contractConfig, tokenConfig, msg.sender, quantityToMint); + tokenContract.delegateSetupNewToken(premintConfig, signature); // mint the initial x tokens for this new token id to the executor. address tokenRecipient = msg.sender; tokenContract.mint{value: msg.value}(fixedPriceMinter, newTokenId, quantityToMint, abi.encode(tokenRecipient, mintComment)); + + emit Preminted( + contractAddress, + newTokenId, + isNewContract, + premintConfig.uid, + premintConfig.contractConfig, + premintConfig.tokenConfig, + msg.sender, + quantityToMint + ); } function _getOrCreateContract(ContractCreationConfig calldata contractConfig) private returns (IZoraCreator1155 tokenContract, bool isNewContract) { @@ -198,169 +121,7 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId, Ownable2StepU tokenContract = IZoraCreator1155(newContractAddresss); } - function _setupNewTokenAndSale( - IZoraCreator1155 tokenContract, - address contractAdmin, - TokenCreationConfig calldata tokenConfig - ) private returns (uint256 newTokenId) { - // mint a new token, and get its token id - // this contract has admin rights on that token - - newTokenId = tokenContract.setupNewToken(tokenConfig.tokenURI, tokenConfig.maxSupply); - - // set up the sales strategy - // first, grant the fixed price sale strategy minting capabilities on the token - tokenContract.addPermission(newTokenId, address(fixedPriceMinter), PERMISSION_BIT_MINTER); - - // set the sales config on that token - tokenContract.callSale( - newTokenId, - fixedPriceMinter, - abi.encodeWithSelector( - ZoraCreatorFixedPriceSaleStrategy.setSale.selector, - newTokenId, - _buildNewSalesConfig(contractAdmin, tokenConfig.pricePerToken, tokenConfig.maxTokensPerAddress, tokenConfig.mintDuration) - ) - ); - - // set the royalty config on that token: - tokenContract.updateRoyaltiesForToken( - newTokenId, - ICreatorRoyaltiesControl.RoyaltyConfiguration({ - royaltyBPS: tokenConfig.royaltyBPS, - royaltyRecipient: tokenConfig.royaltyRecipient, - royaltyMintSchedule: tokenConfig.royaltyMintSchedule - }) - ); - - // remove this contract as admin of the newly created token: - tokenContract.removePermission(newTokenId, address(this), PERMISSION_BIT_ADMIN); - } - - function recoverSigner(PremintConfig calldata premintConfig, bytes calldata signature) public view returns (address signatory) { - // first validate the signature - the creator must match the signer of the message - bytes32 digest = premintHashData( - premintConfig, - // here we pass the current contract and chain id, ensuring that the message - // only works for the current chain and contract id - address(this), - block.chainid - ); - - signatory = ECDSAUpgradeable.recover(digest, signature); - } - - /// Gets hash data to sign for a premint. Allows specifying a different chain id and contract address so that the signature - /// can be verified on a different chain. - /// @param premintConfig Premint config to hash - /// @param verifyingContract Contract address that signature is to be verified against - /// @param chainId Chain id that signature is to be verified on - function premintHashData(PremintConfig calldata premintConfig, address verifyingContract, uint256 chainId) public view returns (bytes32) { - bytes32 encoded = _hashPremintConfig(premintConfig); - - // build the struct hash to be signed - // here we pass the chain id, allowing the message to be signed for another chain - return _hashTypedDataV4(encoded, verifyingContract, chainId); - } - - bytes32 constant CONTRACT_AND_TOKEN_DOMAIN = - keccak256( - "Premint(ContractCreationConfig contractConfig,TokenCreationConfig tokenConfig,uint32 uid,uint32 version,bool deleted)ContractCreationConfig(address contractAdmin,string contractURI,string contractName)TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 mintStart,uint64 mintDuration,uint32 royaltyMintSchedule,uint32 royaltyBPS,address royaltyRecipient)" - ); - - function _hashPremintConfig(PremintConfig calldata premintConfig) private pure returns (bytes32) { - return - keccak256( - abi.encode( - CONTRACT_AND_TOKEN_DOMAIN, - _hashContract(premintConfig.contractConfig), - _hashToken(premintConfig.tokenConfig), - premintConfig.uid, - premintConfig.version, - premintConfig.deleted - ) - ); - } - - bytes32 constant TOKEN_DOMAIN = - keccak256( - "TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 mintStart,uint64 mintDuration,uint32 royaltyMintSchedule,uint32 royaltyBPS,address royaltyRecipient)" - ); - - function _hashToken(TokenCreationConfig calldata tokenConfig) private pure returns (bytes32) { - return - keccak256( - abi.encode( - TOKEN_DOMAIN, - _stringHash(tokenConfig.tokenURI), - tokenConfig.maxSupply, - tokenConfig.maxTokensPerAddress, - tokenConfig.pricePerToken, - tokenConfig.mintStart, - tokenConfig.mintDuration, - tokenConfig.royaltyMintSchedule, - tokenConfig.royaltyBPS, - tokenConfig.royaltyRecipient - ) - ); - } - - bytes32 constant CONTRACT_DOMAIN = keccak256("ContractCreationConfig(address contractAdmin,string contractURI,string contractName)"); - - function _hashContract(ContractCreationConfig calldata contractConfig) private pure returns (bytes32) { - return - keccak256( - abi.encode(CONTRACT_DOMAIN, contractConfig.contractAdmin, _stringHash(contractConfig.contractURI), _stringHash(contractConfig.contractName)) - ); - } - - function getPremintedTokenId(ContractCreationConfig calldata contractConfig, uint32 tokenUid) public view returns (uint256) { - address contractAddress = getContractAddress(contractConfig); - - return premintTokenId[contractAddress][tokenUid]; - } - - function premintHasBeenExecuted(ContractCreationConfig calldata contractConfig, uint32 tokenUid) public view returns (bool) { - return getPremintedTokenId(contractConfig, tokenUid) != 0; - } - - /// Validates that the signer of the signature matches the contract admin - /// Checks if the signature is used; if it is, reverts. - /// If it isn't mark that it has been used. - function _validateSignature(PremintConfig calldata premintConfig, bytes calldata signature) private view { - // first validate the signature - the creator must match the signer of the message - // contractAddress = getContractAddress(premintConfig.contractConfig); - address signatory = recoverSigner(premintConfig, signature); - - if (signatory != premintConfig.contractConfig.contractAdmin) { - revert InvalidSignature(); - } - } - function getContractAddress(ContractCreationConfig calldata contractConfig) public view returns (address) { return factory.deterministicContractAddress(address(this), contractConfig.contractURI, contractConfig.contractName, contractConfig.contractAdmin); } - - function _stringHash(string calldata value) private pure returns (bytes32) { - return keccak256(bytes(value)); - } - - function _buildNewSalesConfig( - address creator, - uint96 pricePerToken, - uint64 maxTokensPerAddress, - uint64 duration - ) private view returns (ZoraCreatorFixedPriceSaleStrategy.SalesConfig memory) { - uint64 saleStart = uint64(block.timestamp); - uint64 saleEnd = duration == 0 ? type(uint64).max : saleStart + duration; - - return - ZoraCreatorFixedPriceSaleStrategy.SalesConfig({ - pricePerToken: pricePerToken, - saleStart: saleStart, - saleEnd: saleEnd, - maxTokensPerAddress: maxTokensPerAddress, - fundsRecipient: creator - }); - } } diff --git a/src/utils/PublicMulticall.sol b/src/utils/PublicMulticall.sol index 8fea986fc..3a61bdf09 100644 --- a/src/utils/PublicMulticall.sol +++ b/src/utils/PublicMulticall.sol @@ -15,4 +15,14 @@ abstract contract PublicMulticall { results[i] = Address.functionDelegateCall(address(this), data[i]); } } + + /** + * @notice Receives and executes a batch of function calls on this contract. + */ + function multicallInternal(bytes[] memory data) public virtual returns (bytes[] memory results) { + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + results[i] = Address.functionDelegateCall(address(this), data[i]); + } + } } diff --git a/test/premint/ZoraCreator1155Preminter.t.sol b/test/premint/ZoraCreator1155Preminter.t.sol index 1ab7068cb..a172b4d6e 100644 --- a/test/premint/ZoraCreator1155Preminter.t.sol +++ b/test/premint/ZoraCreator1155Preminter.t.sol @@ -17,6 +17,7 @@ import {Zora1155Factory} from "../../src/proxies/Zora1155Factory.sol"; import {ZoraCreator1155FactoryImpl} from "../../src/factory/ZoraCreator1155FactoryImpl.sol"; import {ZoraCreator1155Preminter} from "../../src/premint/ZoraCreator1155Preminter.sol"; import {IZoraCreator1155} from "../../src/interfaces/IZoraCreator1155.sol"; +import {ZoraCreator1155Attribution, ContractCreationConfig, TokenCreationConfig, PremintConfig} from "../../src/premint/ZoraCreator1155Delegation.sol"; contract ZoraCreator1155PreminterTest is Test { ZoraCreator1155Preminter internal preminter; @@ -32,8 +33,8 @@ contract ZoraCreator1155PreminterTest is Test { uint256 indexed tokenId, bool indexed createdNewContract, uint32 uid, - ZoraCreator1155Preminter.ContractCreationConfig contractConfig, - ZoraCreator1155Preminter.TokenCreationConfig tokenConfig, + ContractCreationConfig contractConfig, + TokenCreationConfig tokenConfig, address minter, uint256 quantityMinted ); @@ -59,13 +60,13 @@ contract ZoraCreator1155PreminterTest is Test { creator = vm.addr(creatorPrivateKey); } - function makeDefaultContractCreationConfig() internal view returns (ZoraCreator1155Preminter.ContractCreationConfig memory) { - return ZoraCreator1155Preminter.ContractCreationConfig({contractAdmin: creator, contractName: "blah", contractURI: "blah.contract"}); + function makeDefaultContractCreationConfig() internal view returns (ContractCreationConfig memory) { + return ContractCreationConfig({contractAdmin: creator, contractName: "blah", contractURI: "blah.contract"}); } - function makeDefaultTokenCreationConfig() internal view returns (ZoraCreator1155Preminter.TokenCreationConfig memory) { + function makeDefaultTokenCreationConfig() internal view returns (TokenCreationConfig memory) { return - ZoraCreator1155Preminter.TokenCreationConfig({ + TokenCreationConfig({ tokenURI: "blah.token", maxSupply: 10, maxTokensPerAddress: 5, @@ -78,9 +79,9 @@ contract ZoraCreator1155PreminterTest is Test { }); } - function makeDefaultPremintConfig() internal view returns (ZoraCreator1155Preminter.PremintConfig memory) { + function makeDefaultPremintConfig() internal view returns (PremintConfig memory) { return - ZoraCreator1155Preminter.PremintConfig({ + PremintConfig({ contractConfig: makeDefaultContractCreationConfig(), tokenConfig: makeDefaultTokenCreationConfig(), uid: 100, @@ -93,7 +94,7 @@ contract ZoraCreator1155PreminterTest is Test { // 1. Make contract creation params // configuration of contract to create - ZoraCreator1155Preminter.PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfig memory premintConfig = makeDefaultPremintConfig(); // how many tokens are minted to the executor uint256 quantityToMint = 4; @@ -101,7 +102,8 @@ contract ZoraCreator1155PreminterTest is Test { string memory comment = "hi"; // 2. Call smart contract to get digest to sign for creation params. - bytes32 digest = preminter.premintHashData(premintConfig, address(preminter), chainId); + bytes32 hashed = ZoraCreator1155Attribution.hashPremintConfig(premintConfig); + bytes32 digest = ZoraCreator1155Attribution.premintHashData(hashed, address(preminter), chainId); // 3. Sign the digest // create a signature with the digest for the params @@ -129,7 +131,7 @@ contract ZoraCreator1155PreminterTest is Test { premintConfig.tokenConfig.tokenURI = "blah2.token"; premintConfig.uid++; - digest = preminter.premintHashData(premintConfig, address(preminter), chainId); + digest = ZoraCreator1155Attribution.premintHashData(ZoraCreator1155Attribution.hashPremintConfig(premintConfig), address(preminter), chainId); signature = _sign(creatorPrivateKey, digest); // premint with new token config and signature @@ -145,7 +147,7 @@ contract ZoraCreator1155PreminterTest is Test { // 1. Make contract creation params // configuration of contract to create - ZoraCreator1155Preminter.PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfig memory premintConfig = makeDefaultPremintConfig(); // how many tokens are minted to the executor uint256 quantityToMint = 4; @@ -180,7 +182,7 @@ contract ZoraCreator1155PreminterTest is Test { } function test_deleted_preventsTokenFromBeingMinted() external { - ZoraCreator1155Preminter.PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfig memory premintConfig = makeDefaultPremintConfig(); premintConfig.deleted = true; uint chainId = block.chainid; @@ -206,7 +208,7 @@ contract ZoraCreator1155PreminterTest is Test { } function test_emitsPremint_whenNewContract() external { - ZoraCreator1155Preminter.PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfig memory premintConfig = makeDefaultPremintConfig(); // how many tokens are minted to the executor uint256 quantityToMint = 4; @@ -246,7 +248,7 @@ contract ZoraCreator1155PreminterTest is Test { } function test_onlyOwner_hasAdminRights_onCreatedToken() public { - ZoraCreator1155Preminter.PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfig memory premintConfig = makeDefaultPremintConfig(); // how many tokens are minted to the executor uint256 quantityToMint = 4; @@ -320,7 +322,7 @@ contract ZoraCreator1155PreminterTest is Test { } function test_premintStatus_getsStatus() external { - ZoraCreator1155Preminter.PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfig memory premintConfig = makeDefaultPremintConfig(); // how many tokens are minted to the executor uint256 quantityToMint = 4; @@ -332,8 +334,8 @@ contract ZoraCreator1155PreminterTest is Test { uint32 firstUid = premintConfig.uid; uint32 secondUid = firstUid + 1; - ZoraCreator1155Preminter.ContractCreationConfig memory firstContractConfig = premintConfig.contractConfig; - ZoraCreator1155Preminter.ContractCreationConfig memory secondContractConfig = ZoraCreator1155Preminter.ContractCreationConfig( + ContractCreationConfig memory firstContractConfig = premintConfig.contractConfig; + ContractCreationConfig memory secondContractConfig = ContractCreationConfig( firstContractConfig.contractAdmin, firstContractConfig.contractURI, string.concat(firstContractConfig.contractName, "4") @@ -380,7 +382,7 @@ contract ZoraCreator1155PreminterTest is Test { } vm.warp(currentTime); - ZoraCreator1155Preminter.PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfig memory premintConfig = makeDefaultPremintConfig(); premintConfig.tokenConfig.mintStart = startDate; uint256 quantityToMint = 4; @@ -411,7 +413,7 @@ contract ZoraCreator1155PreminterTest is Test { } // build a premint with a token that has the given start date and duration - ZoraCreator1155Preminter.PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfig memory premintConfig = makeDefaultPremintConfig(); premintConfig.tokenConfig.mintStart = startDate; premintConfig.tokenConfig.mintDuration = duration; @@ -440,7 +442,7 @@ contract ZoraCreator1155PreminterTest is Test { } function _signAndExecutePremint( - ZoraCreator1155Preminter.PremintConfig memory premintConfig, + PremintConfig memory premintConfig, uint256 privateKey, uint256 chainId, address executor, @@ -454,11 +456,7 @@ contract ZoraCreator1155PreminterTest is Test { return preminter.premint(premintConfig, signature, quantityToMint, comment); } - function _signPremint( - ZoraCreator1155Preminter.PremintConfig memory premintConfig, - uint256 privateKey, - uint256 chainId - ) private view returns (bytes memory) { + function _signPremint(PremintConfig memory premintConfig, uint256 privateKey, uint256 chainId) private view returns (bytes memory) { bytes32 digest = preminter.premintHashData(premintConfig, address(preminter), chainId); // 3. Sign the digest