diff --git a/packages/1155-contracts/src/delegation/EIP712.sol b/packages/1155-contracts/src/delegation/EIP712.sol new file mode 100644 index 000000000..5fede40e8 --- /dev/null +++ b/packages/1155-contracts/src/delegation/EIP712.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; +import {ECDSAUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/ECDSAUpgradeable.sol"; + +library EIP712 { + bytes32 internal 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, bytes32 hashedName, bytes32 hashedVersion) private pure returns (bytes32) { + return _buildDomainSeparator(hashedName, hashedVersion, 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, bytes32 hashedName, bytes32 hashedVersion, address verifyingContract, uint256 chainId) internal pure returns (bytes32) { + return ECDSAUpgradeable.toTypedDataHash(_domainSeparatorV4(chainId, verifyingContract, hashedName, hashedVersion), structHash); + } +} diff --git a/packages/1155-contracts/src/delegation/ZoraCreator1155Attribution.sol b/packages/1155-contracts/src/delegation/ZoraCreator1155Attribution.sol index c7a91a6cf..5f7caf5c4 100644 --- a/packages/1155-contracts/src/delegation/ZoraCreator1155Attribution.sol +++ b/packages/1155-contracts/src/delegation/ZoraCreator1155Attribution.sol @@ -6,6 +6,7 @@ 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"; +import {EIP712} from './EIP712.sol'; struct ContractCreationConfig { // Creator/admin of the created contract. Must match the account that signed the message @@ -16,6 +17,18 @@ struct ContractCreationConfig { string contractName; } +struct PremintConfig { + // 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 TokenCreationConfig { // Metadata URI for the created token string tokenURI; @@ -37,13 +50,12 @@ struct TokenCreationConfig { address royaltyRecipient; // Fixed price minter address address fixedPriceMinter; - // Create referral - address createReferral; } -struct PremintConfig { + +struct PremintConfigV2 { // The config for the token to be created - TokenCreationConfig tokenConfig; + TokenCreationConfigV2 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; @@ -53,94 +65,64 @@ struct PremintConfig { bool deleted; } -/// @title Library for 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. Functions are exposed as external to allow contracts to import this lib and not increase their -/// size. -/// @author @oveddan +struct TokenCreationConfigV2 { + // 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; + // Fixed price minter address + address fixedPriceMinter; + // create referral + address createReferral; +} + library ZoraCreator1155Attribution { - /* start eip712 functionality */ string internal constant NAME = "Preminter"; - string internal constant VERSION = "1"; bytes32 internal constant HASHED_NAME = keccak256(bytes(NAME)); - bytes32 internal constant HASHED_VERSION = keccak256(bytes(VERSION)); - bytes32 internal constant TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + string internal constant VERSION_1 = "1"; + bytes32 internal constant HASHED_VERSION_1 = keccak256(bytes(VERSION_1)); + string internal constant VERSION_2 = "2"; + bytes32 internal constant HASHED_VERSION_2 = keccak256(bytes(VERSION_2)); - /** - * @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, - uint256 chainId - ) internal pure returns (address signatory) { - // first validate the signature - the creator must match the signer of the message - return recoverSignerHashed(hashPremint(premintConfig), signature, erc1155Contract, chainId); - } - - function recoverSignerHashed( - bytes32 hashedPremintConfig, - bytes calldata signature, - address erc1155Contract, - uint256 chainId - ) public pure returns (address signatory) { - // first validate the signature - the creator must match the signer of the message - bytes32 digest = _hashTypedDataV4( - hashedPremintConfig, - // here we pass the current contract and chain id, ensuring that the message - // only works for the current chain and contract id - erc1155Contract, - chainId + bytes32 constant ATTRIBUTION_DOMAIN_V1 = + keccak256( + "CreatorAttribution(TokenCreationConfig tokenConfig,uint32 uid,uint32 version,bool deleted)TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 mintStart,uint64 mintDuration,uint32 royaltyMintSchedule,uint32 royaltyBPS,address royaltyRecipient,address fixedPriceMinter)" ); - 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 erc1155Contract Contract address that signature is to be verified against - /// @param chainId Chain id that signature is to be verified on - function premintHashedTypeDataV4(PremintConfig calldata premintConfig, address erc1155Contract, uint256 chainId) external 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(hashPremint(premintConfig), erc1155Contract, chainId); - } - - bytes32 constant ATTRIBUTION_DOMAIN = + bytes32 constant ATTRIBUTION_DOMAIN_V2 = keccak256( - "CreatorAttribution(TokenCreationConfig tokenConfig,uint32 uid,uint32 version,bool deleted)TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 mintStart,uint64 mintDuration,uint32 royaltyMintSchedule,uint32 royaltyBPS,address royaltyRecipient,address fixedPriceMinter)" + "CreatorAttribution(TokenCreationConfig tokenConfig,uint32 uid,uint32 version,bool deleted)TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 mintStart,uint64 mintDuration,address fixedPriceMinter,address createReferral)" ); - function hashPremint(PremintConfig calldata premintConfig) public pure returns (bytes32) { + function hashPremintV1(PremintConfig calldata premintConfig) public pure returns (bytes32) { return - keccak256(abi.encode(ATTRIBUTION_DOMAIN, _hashToken(premintConfig.tokenConfig), premintConfig.uid, premintConfig.version, premintConfig.deleted)); + keccak256(abi.encode(ATTRIBUTION_DOMAIN_V1, _hashTokenV1(premintConfig.tokenConfig), premintConfig.uid, premintConfig.version, premintConfig.deleted)); } - bytes32 constant TOKEN_DOMAIN = + function hashPremint(PremintConfigV2 calldata premintConfig) public pure returns (bytes32) { + return + keccak256(abi.encode(ATTRIBUTION_DOMAIN_V2, _hashToken(premintConfig.tokenConfig), premintConfig.uid, premintConfig.version, premintConfig.deleted)); + } + + bytes32 constant TOKEN_DOMAIN_V1 = keccak256( "TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 mintStart,uint64 mintDuration,uint32 royaltyMintSchedule,uint32 royaltyBPS,address royaltyRecipient,address fixedPriceMinter)" ); - function _hashToken(TokenCreationConfig calldata tokenConfig) private pure returns (bytes32) { + function _hashTokenV1(TokenCreationConfig calldata tokenConfig) private pure returns (bytes32) { return keccak256( abi.encode( - TOKEN_DOMAIN, + TOKEN_DOMAIN_V1, _stringHash(tokenConfig.tokenURI), tokenConfig.maxSupply, tokenConfig.maxTokensPerAddress, @@ -155,9 +137,105 @@ library ZoraCreator1155Attribution { ); } + bytes32 constant TOKEN_DOMAIN_V2 = + keccak256( + "TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 mintStart,uint64 mintDuration,address fixedPriceMinter,address createReferral)" + ); + + function _hashToken(TokenCreationConfigV2 calldata tokenConfig) private pure returns (bytes32) { + return + keccak256( + abi.encode( + TOKEN_DOMAIN_V1, + _stringHash(tokenConfig.tokenURI), + tokenConfig.maxSupply, + tokenConfig.maxTokensPerAddress, + tokenConfig.pricePerToken, + tokenConfig.mintStart, + tokenConfig.mintDuration, + tokenConfig.fixedPriceMinter, + tokenConfig.createReferral + ) + ); + } + + bytes32 internal constant TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + + function _buildDomainSeparator(bytes32 nameHash, bytes32 versionHash, address verifyingContract, uint256 chainId) private pure returns (bytes32) { + return keccak256(abi.encode(TYPE_HASH, nameHash, versionHash, chainId, verifyingContract)); + } + + /** + * @dev Returns the domain separator for the specified chain. + */ + function _domainSeparatorV4(uint256 chainId, address verifyingContract, bytes32 hashedName, bytes32 hashedVersion) private pure returns (bytes32) { + return _buildDomainSeparator(hashedName, hashedVersion, verifyingContract, chainId); + } + + function hashTypedDataV4(bytes32 structHash, bytes32 hashedName, bytes32 hashedVersion, address verifyingContract, uint256 chainId) internal pure returns (bytes32) { + return ECDSAUpgradeable.toTypedDataHash(_domainSeparatorV4(chainId, verifyingContract, hashedName, hashedVersion), structHash); + } + + /// 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 erc1155Contract Contract address that signature is to be verified against + /// @param chainId Chain id that signature is to be verified on + function premintHashedTypeDataV4(bytes32 structHash, address erc1155Contract, bytes32 signatureVersion, 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(structHash, HASHED_NAME, signatureVersion, erc1155Contract, chainId); + } + + function recoverSignerHashed( + bytes32 hashedPremintConfig, + bytes calldata signature, + address erc1155Contract, + bytes32 signatureVersion, + uint256 chainId + ) public pure returns (address signatory) { + // first validate the signature - the creator must match the signer of the message + bytes32 digest = premintHashedTypeDataV4( + hashedPremintConfig, + // here we pass the current contract and chain id, ensuring that the message + // only works for the current chain and contract id + erc1155Contract, + signatureVersion, + chainId + ); + + (signatory,) = ECDSAUpgradeable.tryRecover(digest, signature); + } + function _stringHash(string calldata value) private pure returns (bytes32) { return keccak256(bytes(value)); } + + /// @notice copied from SharedBaseConstants + uint256 constant CONTRACT_BASE_ID = 0; + /// @dev copied from ZoraCreator1155Impl + uint256 constant PERMISSION_BIT_MINTER = 2 ** 2; + + function isValidSignature( + address originalPremintCreator, + address contractAddress, + bytes32 structHash, + bytes32 hashedVersion, + bytes calldata signature + ) internal view returns (bool isValid, address recoveredSigner) { + recoveredSigner = recoverSignerHashed(structHash, signature, contractAddress, hashedVersion, block.chainid); + + if (recoveredSigner == address(0)) { + return (false, address(0)); + } + + // if contract hasn't been created, signer must be the contract admin on the config + if (contractAddress.code.length == 0) { + isValid = recoveredSigner == originalPremintCreator; + } else { + // if contract has been created, signer must have mint new token permission + isValid = IZoraCreator1155(contractAddress).isAdminOrRole(recoveredSigner, CONTRACT_BASE_ID, PERMISSION_BIT_MINTER); + } + } } /// @notice Utility library to setup tokens created via premint. Functions exposed as external to not increase contract size in calling contract. @@ -168,9 +246,9 @@ library PremintTokenSetup { function makeSetupNewTokenCalls( uint256 newTokenId, address contractAdmin, - TokenCreationConfig calldata tokenConfig + TokenCreationConfigV2 calldata tokenConfig ) external view returns (bytes[] memory calls) { - calls = new bytes[](3); + calls = new bytes[](2); address fixedPriceMinterAddress = tokenConfig.fixedPriceMinter; // build array of the calls to make @@ -191,17 +269,6 @@ library PremintTokenSetup { _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( diff --git a/packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImpl.sol b/packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImpl.sol index 45805a927..d66548141 100644 --- a/packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImpl.sol +++ b/packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImpl.sol @@ -12,21 +12,18 @@ import {SharedBaseConstants} from "../shared/SharedBaseConstants.sol"; import {ZoraCreatorFixedPriceSaleStrategy} from "../minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.sol"; import {IMinter1155} from "../interfaces/IMinter1155.sol"; import {ERC1155DelegationStorageV1} from "../delegation/ERC1155DelegationStorageV1.sol"; -import {PremintConfig, ContractCreationConfig, TokenCreationConfig, ZoraCreator1155Attribution} from "./ZoraCreator1155Attribution.sol"; +import {ZoraCreator1155PremintExecutorImplLib } from './ZoraCreator1155PremintExecutorImplLib.sol'; +import {ZoraCreator1155Attribution, ContractCreationConfig, PremintConfigV2, TokenCreationConfigV2} from "./ZoraCreator1155Attribution.sol"; +import {IZoraCreator1155PremintExecutorImplV1, ZoraCreator1155PremintExecutorImplDeprecated} from './v1/ZoraCreator1155PremintExecutorImplDeprecated.sol'; /// @title Enables creation of and minting tokens on Zora1155 contracts transactions using eip-712 signatures. /// Signature must provided by the contract creator, or an account that's permitted to create new tokens on the contract. /// Mints the first x tokens to the executor of the transaction. /// @author @oveddan -contract ZoraCreator1155PremintExecutorImpl is Ownable2StepUpgradeable, UUPSUpgradeable, IHasContractName, IZoraCreator1155Errors { +contract ZoraCreator1155PremintExecutorImpl is ZoraCreator1155PremintExecutorImplDeprecated, Ownable2StepUpgradeable, UUPSUpgradeable, IHasContractName, IZoraCreator1155Errors { IZoraCreator1155Factory public immutable zora1155Factory; - /// @notice copied from SharedBaseConstants - uint256 constant CONTRACT_BASE_ID = 0; - /// @dev copied from ZoraCreator1155Impl - uint256 constant PERMISSION_BIT_MINTER = 2 ** 2; - - constructor(IZoraCreator1155Factory _factory) { + constructor(IZoraCreator1155Factory _factory) ZoraCreator1155PremintExecutorImplDeprecated(_factory) { zora1155Factory = _factory; } @@ -35,15 +32,16 @@ contract ZoraCreator1155PremintExecutorImpl is Ownable2StepUpgradeable, UUPSUpgr __UUPSUpgradeable_init(); } - event Preminted( + event PremintedV2( address indexed contractAddress, uint256 indexed tokenId, bool indexed createdNewContract, uint32 uid, ContractCreationConfig contractConfig, - TokenCreationConfig tokenConfig, + TokenCreationConfigV2 tokenConfig, address minter, - uint256 quantityMinted + uint256 quantityMinted, + address mintReferral ); /// Creates a new token on the given erc1155 contract on behalf of a creator, and mints x tokens to the executor of this transaction. @@ -58,14 +56,15 @@ contract ZoraCreator1155PremintExecutorImpl is Ownable2StepUpgradeable, UUPSUpgr /// @param mintComment A comment to associate with the mint action function premint( ContractCreationConfig calldata contractConfig, - PremintConfig calldata premintConfig, + PremintConfigV2 calldata premintConfig, bytes calldata signature, uint256 quantityToMint, + address mintReferral, string calldata mintComment - ) public payable returns (uint256 newTokenId) { + ) external payable returns (uint256 newTokenId) { // get or create the contract with the given params // contract address is deterministic. - (IZoraCreator1155 tokenContract, bool isNewContract) = _getOrCreateContract(contractConfig); + (IZoraCreator1155 tokenContract, bool isNewContract) = ZoraCreator1155PremintExecutorImplLib.getOrCreateContract(zora1155Factory, contractConfig); // pass the signature and the premint config to the token contract to create the token. // The token contract will verify the signature and that the signer has permission to create a new token. @@ -75,16 +74,17 @@ contract ZoraCreator1155PremintExecutorImpl is Ownable2StepUpgradeable, UUPSUpgr // if the executor would also like to mint: if (quantityToMint != 0) { // mint the number of specified tokens to the executor - tokenContract.mint{value: msg.value}( + tokenContract.mintWithRewards{value: msg.value}( IMinter1155(premintConfig.tokenConfig.fixedPriceMinter), newTokenId, quantityToMint, - abi.encode(msg.sender, mintComment) + abi.encode(msg.sender, mintComment), + mintReferral ); } // emit Preminted event - emit Preminted( + emit PremintedV2( address(tokenContract), newTokenId, isNewContract, @@ -92,37 +92,19 @@ contract ZoraCreator1155PremintExecutorImpl is Ownable2StepUpgradeable, UUPSUpgr contractConfig, premintConfig.tokenConfig, msg.sender, - quantityToMint + quantityToMint, + mintReferral ); } - function _getOrCreateContract(ContractCreationConfig calldata contractConfig) private returns (IZoraCreator1155 tokenContract, bool isNewContract) { - address contractAddress = getContractAddress(contractConfig); - // first we see if the code is already deployed for the contract - isNewContract = contractAddress.code.length == 0; - - if (isNewContract) { - // if address doesn't exist for hash, create it - tokenContract = _createContract(contractConfig); - } else { - tokenContract = IZoraCreator1155(contractAddress); - } - } - - function _createContract(ContractCreationConfig calldata contractConfig) private returns (IZoraCreator1155 tokenContract) { - // we need to build the setup actions, that must: - bytes[] memory setupActions = new bytes[](0); - - // create the contract via the factory. - address newContractAddresss = zora1155Factory.createContractDeterministic( - contractConfig.contractURI, - contractConfig.contractName, - // default royalty config is empty, since we set it on a token level - ICreatorRoyaltiesControl.RoyaltyConfiguration({royaltyBPS: 0, royaltyRecipient: address(0), royaltyMintSchedule: 0}), - payable(contractConfig.contractAdmin), - setupActions - ); - tokenContract = IZoraCreator1155(newContractAddresss); + function isValidSignature( + address originalPremintCreator, + address contractAddress, + bytes32 hashedPremint, + bytes32 signatureVersion, + bytes calldata signature + ) public view returns (bool isValid, address recoveredSigner) { + return ZoraCreator1155Attribution.isValidSignature(originalPremintCreator, contractAddress, hashedPremint, signatureVersion, signature); } /// Gets the deterministic contract address for the given contract creation config. @@ -130,12 +112,7 @@ contract ZoraCreator1155PremintExecutorImpl is Ownable2StepUpgradeable, UUPSUpgr /// contract admin, and the msg.sender, which is this contract's address. function getContractAddress(ContractCreationConfig calldata contractConfig) public view returns (address) { return - zora1155Factory.deterministicContractAddress(address(this), contractConfig.contractURI, contractConfig.contractName, contractConfig.contractAdmin); - } - - /// Recovers the signer of the given premint config created against the specified zora1155 contract address. - function recoverSigner(PremintConfig calldata premintConfig, address zor1155Address, bytes calldata signature) public view returns (address) { - return ZoraCreator1155Attribution.recoverSigner(premintConfig, signature, zor1155Address, block.chainid); + ZoraCreator1155PremintExecutorImplLib.getContractAddress(zora1155Factory, contractConfig); } /// @notice Utility function to determine if a premint contract has been created for a uid of a premint, and if so, @@ -146,31 +123,7 @@ contract ZoraCreator1155PremintExecutorImpl is Ownable2StepUpgradeable, UUPSUpgr } return (true, ERC1155DelegationStorageV1(contractAddress).delegatedTokenId(uid)); } - - /// @notice Utility function to check if the signature is valid; i.e. the signature can be used to - /// mint a token with the given config. If the contract hasn't been created, then the signer - /// must match the contract admin on the premint config. If it has been created, the signer - /// must have permission to create new tokens on the erc1155 contract. - function isValidSignature( - ContractCreationConfig calldata contractConfig, - PremintConfig calldata premintConfig, - bytes calldata signature - ) public view returns (bool isValid, address contractAddress, address recoveredSigner) { - contractAddress = getContractAddress(contractConfig); - recoveredSigner = recoverSigner(premintConfig, contractAddress, signature); - - if (recoveredSigner == address(0)) { - return (false, contractAddress, address(0)); - } - - // if contract hasn't been created, signer must be the contract admin on the config - if (contractAddress.code.length == 0) { - isValid = recoveredSigner == contractConfig.contractAdmin; - } else { - // if contract has been created, signer must have mint new token permission - isValid = IZoraCreator1155(contractAddress).isAdminOrRole(recoveredSigner, CONTRACT_BASE_ID, PERMISSION_BIT_MINTER); - } - } + // upgrade related functionality diff --git a/packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImplLib.sol b/packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImplLib.sol new file mode 100644 index 000000000..80452e6b4 --- /dev/null +++ b/packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImplLib.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {ContractCreationConfig} from './ZoraCreator1155Attribution.sol'; +import {IZoraCreator1155} from "../interfaces/IZoraCreator1155.sol"; +import {IZoraCreator1155Factory} from "../interfaces/IZoraCreator1155Factory.sol"; +import {ICreatorRoyaltiesControl} from "../interfaces/ICreatorRoyaltiesControl.sol"; + +library ZoraCreator1155PremintExecutorImplLib { + function getOrCreateContract(IZoraCreator1155Factory zora1155Factory, ContractCreationConfig calldata contractConfig) internal returns (IZoraCreator1155 tokenContract, bool isNewContract) { + address contractAddress = getContractAddress(zora1155Factory, contractConfig); + // first we see if the code is already deployed for the contract + isNewContract = contractAddress.code.length == 0; + + if (isNewContract) { + // if address doesn't exist for hash, create it + tokenContract = createContract(zora1155Factory, contractConfig); + } else { + tokenContract = IZoraCreator1155(contractAddress); + } + } + + function createContract(IZoraCreator1155Factory zora1155Factory, ContractCreationConfig calldata contractConfig) internal returns (IZoraCreator1155 tokenContract) { + // we need to build the setup actions, that must: + bytes[] memory setupActions = new bytes[](0); + + // create the contract via the factory. + address newContractAddresss = zora1155Factory.createContractDeterministic( + contractConfig.contractURI, + contractConfig.contractName, + // default royalty config is empty, since we set it on a token level + ICreatorRoyaltiesControl.RoyaltyConfiguration({royaltyBPS: 0, royaltyRecipient: address(0), royaltyMintSchedule: 0}), + payable(contractConfig.contractAdmin), + setupActions + ); + tokenContract = IZoraCreator1155(newContractAddresss); + } + + /// Gets the deterministic contract address for the given contract creation config. + /// Contract address is generated deterministically from a hash based on the contract uri, contract name, + /// contract admin, and the msg.sender, which is this contract's address. + function getContractAddress(IZoraCreator1155Factory zora1155Factory, ContractCreationConfig calldata contractConfig) internal view returns (address) { + return + zora1155Factory.deterministicContractAddress(address(this), contractConfig.contractURI, contractConfig.contractName, contractConfig.contractAdmin); + } +} \ No newline at end of file diff --git a/packages/1155-contracts/src/delegation/v1/ZoraCreator1155PremintExecutorImplDeprecated.sol b/packages/1155-contracts/src/delegation/v1/ZoraCreator1155PremintExecutorImplDeprecated.sol new file mode 100644 index 000000000..7c17fe75c --- /dev/null +++ b/packages/1155-contracts/src/delegation/v1/ZoraCreator1155PremintExecutorImplDeprecated.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {ICreatorRoyaltiesControl} from "../../interfaces/ICreatorRoyaltiesControl.sol"; +import {UUPSUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol"; +import {Ownable2StepUpgradeable} from "../../utils/ownable/Ownable2StepUpgradeable.sol"; +import {IHasContractName} from "../../interfaces/IContractMetadata.sol"; +import {IZoraCreator1155} from "../../interfaces/IZoraCreator1155.sol"; +import {IZoraCreator1155Errors} from "../../interfaces/IZoraCreator1155Errors.sol"; +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 {ZoraCreator1155PremintExecutorImplLib} from '../ZoraCreator1155PremintExecutorImplLib.sol'; +import {ZoraCreator1155Attribution, ContractCreationConfig, PremintConfig, TokenCreationConfig} from "../../delegation/ZoraCreator1155Attribution.sol"; + +interface IZoraCreator1155V1Signatures { + function delegateSetupNewToken( + PremintConfig calldata premintConfig, + bytes calldata signature, + address sender + ) external returns (uint256 newTokenId); +} + +interface IZoraCreator1155PremintExecutorImplV1 { + event Preminted( + address indexed contractAddress, + uint256 indexed tokenId, + bool indexed createdNewContract, + uint32 uid, + ContractCreationConfig contractConfig, + TokenCreationConfig tokenConfig, + address minter, + uint256 quantityMinted + ); + + /// Creates a new token on the given erc1155 contract on behalf of a creator, and mints x tokens to the executor of this transaction. + /// If the erc1155 contract hasn't been created yet, it will be created with the given config within this same transaction. + /// The creator must sign the intent to create the token, and must have mint new token permission on the erc1155 contract, + /// or match the contract admin on the contract creation config if the contract hasn't been created yet. + /// Contract address of the created contract is deterministically generated from the contract config and this contract's address. + /// @param contractConfig Parameters for creating a new contract, if one doesn't exist yet. Used to resolve the deterministic contract address. + /// @param premintConfig Parameters for creating the token, and minting the initial x tokens to the executor. + /// @param signature Signature of the creator of the token, which must match the signer of the premint config, or have permission to create new tokens on the erc1155 contract if it's already been created + /// @param quantityToMint How many tokens to mint to the executor of this transaction once the token is created + /// @param mintComment A comment to associate with the mint action + function premint( + ContractCreationConfig calldata contractConfig, + PremintConfig calldata premintConfig, + bytes calldata signature, + uint256 quantityToMint, + string calldata mintComment + ) external payable returns (uint256 newTokenId); + + /// @notice Deprecated + function isValidSignature( + ContractCreationConfig calldata contractConfig, + PremintConfig calldata premintConfig, + bytes calldata signature + ) external view returns (bool isValid, address contractAddress, address recoveredSigner); + +} + +// legacy interface for v1 of creator attribution +contract ZoraCreator1155PremintExecutorImplDeprecated is IZoraCreator1155PremintExecutorImplV1 { + IZoraCreator1155Factory private immutable zora1155Factory; + + constructor(IZoraCreator1155Factory _factory) { + zora1155Factory = _factory; + } + + /// Creates a new token on the given erc1155 contract on behalf of a creator, and mints x tokens to the executor of this transaction. + /// If the erc1155 contract hasn't been created yet, it will be created with the given config within this same transaction. + /// The creator must sign the intent to create the token, and must have mint new token permission on the erc1155 contract, + /// or match the contract admin on the contract creation config if the contract hasn't been created yet. + /// Contract address of the created contract is deterministically generated from the contract config and this contract's address. + /// @param contractConfig Parameters for creating a new contract, if one doesn't exist yet. Used to resolve the deterministic contract address. + /// @param premintConfig Parameters for creating the token, and minting the initial x tokens to the executor. + /// @param signature Signature of the creator of the token, which must match the signer of the premint config, or have permission to create new tokens on the erc1155 contract if it's already been created + /// @param quantityToMint How many tokens to mint to the executor of this transaction once the token is created + /// @param mintComment A comment to associate with the mint action + function premint( + ContractCreationConfig calldata contractConfig, + PremintConfig calldata premintConfig, + bytes calldata signature, + uint256 quantityToMint, + string calldata mintComment + ) external payable returns (uint256 newTokenId) { + // get or create the contract with the given params + // contract address is deterministic. + (IZoraCreator1155 tokenContract, bool isNewContract) = ZoraCreator1155PremintExecutorImplLib.getOrCreateContract(zora1155Factory, contractConfig); + + // pass the signature and the premint config to the token contract to create the token. + // The token contract will verify the signature and that the signer has permission to create a new token. + // and then create and setup the token using the given token config. + newTokenId = IZoraCreator1155V1Signatures(address(tokenContract)).delegateSetupNewToken(premintConfig, signature, msg.sender); + + // if the executor would also like to mint: + if (quantityToMint != 0) { + // mint the number of specified tokens to the executor + tokenContract.mint{value: msg.value}( + IMinter1155(premintConfig.tokenConfig.fixedPriceMinter), + newTokenId, + quantityToMint, + abi.encode(msg.sender, mintComment) + ); + } + + // emit Preminted event + emit Preminted( + address(tokenContract), + newTokenId, + isNewContract, + premintConfig.uid, + contractConfig, + premintConfig.tokenConfig, + msg.sender, + quantityToMint + ); + } + + /// @notice Deprecated + function isValidSignature( + ContractCreationConfig calldata contractConfig, + PremintConfig calldata premintConfig, + bytes calldata signature + ) public view returns (bool isValid, address contractAddress, address recoveredSigner) { + contractAddress = ZoraCreator1155PremintExecutorImplLib.getContractAddress(zora1155Factory, contractConfig); + + bytes32 hashedPremint = ZoraCreator1155Attribution.hashPremintV1(premintConfig); + + (isValid, recoveredSigner) = ZoraCreator1155Attribution.isValidSignature(contractConfig.contractAdmin, contractAddress, hashedPremint, ZoraCreator1155Attribution.HASHED_VERSION_1, signature); + } +} + diff --git a/packages/1155-contracts/src/deployment/DeploymentTestingUtils.sol b/packages/1155-contracts/src/deployment/DeploymentTestingUtils.sol index e8a7a7997..c477e0335 100644 --- a/packages/1155-contracts/src/deployment/DeploymentTestingUtils.sol +++ b/packages/1155-contracts/src/deployment/DeploymentTestingUtils.sol @@ -7,7 +7,7 @@ import {Zora1155FactoryFixtures} from "../../test/fixtures/Zora1155FactoryFixtur import {Zora1155PremintFixtures} from "../../test/fixtures/Zora1155PremintFixtures.sol"; import {ZoraCreator1155PremintExecutorImpl} from "../delegation/ZoraCreator1155PremintExecutorImpl.sol"; import {ZoraCreator1155FactoryImpl} from "../factory/ZoraCreator1155FactoryImpl.sol"; -import {ZoraCreator1155Attribution, ContractCreationConfig, PremintConfig} from "../delegation/ZoraCreator1155Attribution.sol"; +import {ZoraCreator1155Attribution, ContractCreationConfig, PremintConfigV2} from "../delegation/ZoraCreator1155Attribution.sol"; import {ZoraCreator1155Impl} from "../nft/ZoraCreator1155Impl.sol"; contract DeploymentTestingUtils is Script { @@ -19,7 +19,7 @@ contract DeploymentTestingUtils is Script { IMinter1155 fixedPriceMinter = ZoraCreator1155FactoryImpl(address(preminterAtProxy.zora1155Factory())).fixedPriceMinter(); - PremintConfig memory premintConfig = PremintConfig({ + PremintConfigV2 memory premintConfig = PremintConfigV2({ tokenConfig: Zora1155PremintFixtures.makeDefaultTokenCreationConfig(fixedPriceMinter, creator), uid: 100, version: 0, @@ -30,8 +30,10 @@ contract DeploymentTestingUtils is Script { ContractCreationConfig memory contractConfig = Zora1155PremintFixtures.makeDefaultContractCreationConfig(creator); address deterministicAddress = preminterAtProxy.getContractAddress(contractConfig); + bytes32 signatureVersion = ZoraCreator1155Attribution.HASHED_VERSION_2; + bytes32 structHash = ZoraCreator1155Attribution.hashPremint(premintConfig); // sign the premint - bytes32 digest = ZoraCreator1155Attribution.premintHashedTypeDataV4(premintConfig, deterministicAddress, block.chainid); + bytes32 digest = ZoraCreator1155Attribution.premintHashedTypeDataV4(structHash, deterministicAddress, signatureVersion, block.chainid); (uint8 v, bytes32 r, bytes32 s) = vm.sign(creatorPrivateKey, digest); @@ -40,7 +42,7 @@ contract DeploymentTestingUtils is Script { bytes memory signature = abi.encodePacked(r, s, v); // execute the premint - uint256 tokenId = preminterAtProxy.premint{value: 0.000777 ether}(contractConfig, premintConfig, signature, quantityToMint, ""); + uint256 tokenId = preminterAtProxy.premint{value: 0.000777 ether}(contractConfig, premintConfig, signature, quantityToMint, address(0), ""); require(ZoraCreator1155Impl(deterministicAddress).delegatedTokenId(premintConfig.uid) == tokenId, "token id not created for uid"); } diff --git a/packages/1155-contracts/src/interfaces/IZoraCreator1155.sol b/packages/1155-contracts/src/interfaces/IZoraCreator1155.sol index cf00e8702..71dd5c5c6 100644 --- a/packages/1155-contracts/src/interfaces/IZoraCreator1155.sol +++ b/packages/1155-contracts/src/interfaces/IZoraCreator1155.sol @@ -10,7 +10,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 "../delegation/ZoraCreator1155Attribution.sol"; +import {PremintConfigV2} from "../delegation/ZoraCreator1155Attribution.sol"; /* @@ -71,6 +71,14 @@ interface IZoraCreator1155 is IZoraCreator1155TypesV1, IZoraCreator1155Errors, I /// @param minterArguments calldata for the minter contracts function mint(IMinter1155 minter, uint256 tokenId, uint256 quantity, bytes calldata minterArguments) external payable; + function mintWithRewards( + IMinter1155 minter, + uint256 tokenId, + uint256 quantity, + bytes calldata minterArguments, + address mintReferral + ) external payable; + function adminMint(address recipient, uint256 tokenId, uint256 quantity, bytes memory data) external; function adminMintBatch(address recipient, uint256[] memory tokenIds, uint256[] memory quantities, bytes memory data) external; @@ -82,7 +90,7 @@ interface IZoraCreator1155 is IZoraCreator1155TypesV1, IZoraCreator1155Errors, I /// @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, address sender) external returns (uint256 newTokenId); + function delegateSetupNewToken(PremintConfigV2 calldata premintConfig, bytes calldata signature, address sender) external returns (uint256 newTokenId); function updateTokenURI(uint256 tokenId, string memory _newURI) external; diff --git a/packages/1155-contracts/src/nft/ZoraCreator1155Impl.sol b/packages/1155-contracts/src/nft/ZoraCreator1155Impl.sol index eb2f3222f..d231830e5 100644 --- a/packages/1155-contracts/src/nft/ZoraCreator1155Impl.sol +++ b/packages/1155-contracts/src/nft/ZoraCreator1155Impl.sol @@ -32,7 +32,7 @@ import {TransferHelperUtils} from "../utils/TransferHelperUtils.sol"; import {ZoraCreator1155StorageV1} from "./ZoraCreator1155StorageV1.sol"; import {IZoraCreator1155Errors} from "../interfaces/IZoraCreator1155Errors.sol"; import {ERC1155DelegationStorageV1} from "../delegation/ERC1155DelegationStorageV1.sol"; -import {ZoraCreator1155Attribution, PremintTokenSetup, PremintConfig} from "../delegation/ZoraCreator1155Attribution.sol"; +import {ZoraCreator1155Attribution, PremintTokenSetup, PremintConfigV2} from "../delegation/ZoraCreator1155Attribution.sol"; /// Imagine. Mint. Enjoy. /// @title ZoraCreator1155Impl @@ -763,7 +763,7 @@ contract ZoraCreator1155Impl is /// @param premintConfig configuration of token to be created /// @param signature EIP-712 Signature created on the premintConfig by an account with the PERMISSION_BIT_MINTER role on the contract. function delegateSetupNewToken( - PremintConfig calldata premintConfig, + PremintConfigV2 calldata premintConfig, bytes calldata signature, address sender ) public nonReentrant returns (uint256 newTokenId) { @@ -778,10 +778,10 @@ contract ZoraCreator1155Impl is bytes32 hashedPremintConfig = ZoraCreator1155Attribution.hashPremint(premintConfig); // recover the signer from the data - address creator = ZoraCreator1155Attribution.recoverSignerHashed(hashedPremintConfig, signature, address(this), block.chainid); + address creator = ZoraCreator1155Attribution.recoverSignerHashed(hashedPremintConfig, signature, address(this), ZoraCreator1155Attribution.HASHED_VERSION_2, block.chainid); // this is what attributes this token to have been created by the original creator - emit CreatorAttribution(hashedPremintConfig, ZoraCreator1155Attribution.NAME, ZoraCreator1155Attribution.VERSION, creator, signature); + emit CreatorAttribution(hashedPremintConfig, ZoraCreator1155Attribution.NAME, ZoraCreator1155Attribution.VERSION_2, creator, signature); // require that the signer can create new tokens (is a valid creator) _requireAdminOrRole(creator, CONTRACT_BASE_ID, PERMISSION_BIT_MINTER); @@ -808,7 +808,7 @@ contract ZoraCreator1155Impl is _addPermission(newTokenId, creator, PERMISSION_BIT_ADMIN); } - function validatePremint(PremintConfig calldata premintConfig) private view { + function validatePremint(PremintConfigV2 calldata premintConfig) private view { if (premintConfig.tokenConfig.mintStart != 0 && premintConfig.tokenConfig.mintStart > block.timestamp) { // if the mint start is in the future, then revert revert MintNotYetStarted(); diff --git a/packages/1155-contracts/test/fixtures/Zora1155PremintFixtures.sol b/packages/1155-contracts/test/fixtures/Zora1155PremintFixtures.sol index 4765c5b61..e7d1d8c02 100644 --- a/packages/1155-contracts/test/fixtures/Zora1155PremintFixtures.sol +++ b/packages/1155-contracts/test/fixtures/Zora1155PremintFixtures.sol @@ -10,7 +10,7 @@ import {Zora1155Factory} from "../../src/proxies/Zora1155Factory.sol"; import {ZoraCreator1155FactoryImpl} from "../../src/factory/ZoraCreator1155FactoryImpl.sol"; import {ProtocolRewards} from "@zoralabs/protocol-rewards/src/ProtocolRewards.sol"; import {ProxyShim} from "../../src/utils/ProxyShim.sol"; -import {ContractCreationConfig, TokenCreationConfig, PremintConfig} from "../../src/delegation/ZoraCreator1155Attribution.sol"; +import {ContractCreationConfig, TokenCreationConfigV2} from "../../src/delegation/ZoraCreator1155Attribution.sol"; library Zora1155PremintFixtures { function makeDefaultContractCreationConfig(address contractAdmin) internal pure returns (ContractCreationConfig memory) { @@ -21,19 +21,16 @@ library Zora1155PremintFixtures { return ICreatorRoyaltiesControl.RoyaltyConfiguration({royaltyBPS: 10, royaltyRecipient: royaltyRecipient, royaltyMintSchedule: 100}); } - function makeDefaultTokenCreationConfig(IMinter1155 fixedPriceMinter, address royaltyRecipient) internal pure returns (TokenCreationConfig memory) { + function makeDefaultTokenCreationConfig(IMinter1155 fixedPriceMinter, address royaltyRecipient) internal pure returns (TokenCreationConfigV2 memory) { ICreatorRoyaltiesControl.RoyaltyConfiguration memory royaltyConfig = defaultRoyaltyConfig(royaltyRecipient); return - TokenCreationConfig({ + TokenCreationConfigV2({ tokenURI: "blah.token", maxSupply: 10, maxTokensPerAddress: 5, pricePerToken: 0, mintStart: 0, mintDuration: 0, - royaltyMintSchedule: royaltyConfig.royaltyMintSchedule, - royaltyBPS: royaltyConfig.royaltyBPS, - royaltyRecipient: royaltyConfig.royaltyRecipient, fixedPriceMinter: address(fixedPriceMinter), createReferral: address(0) }); diff --git a/packages/1155-contracts/test/nft/ZoraCreator1155.t.sol b/packages/1155-contracts/test/nft/ZoraCreator1155.t.sol index 847e53639..3687d1312 100644 --- a/packages/1155-contracts/test/nft/ZoraCreator1155.t.sol +++ b/packages/1155-contracts/test/nft/ZoraCreator1155.t.sol @@ -11,7 +11,7 @@ import {ITransferHookReceiver} from "../../src/interfaces/ITransferHookReceiver. import {Zora1155} from "../../src/proxies/Zora1155.sol"; import {ZoraCreatorFixedPriceSaleStrategy} from "../../src/minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.sol"; import {UpgradeGate} from "../../src/upgrades/UpgradeGate.sol"; -import {PremintConfig, TokenCreationConfig} from "../../src/delegation/ZoraCreator1155Attribution.sol"; +import {PremintConfigV2, TokenCreationConfigV2} from "../../src/delegation/ZoraCreator1155Attribution.sol"; import {ZoraCreator1155Attribution} from "../../src/delegation/ZoraCreator1155Attribution.sol"; import {IZoraCreator1155Errors} from "../../src/interfaces/IZoraCreator1155Errors.sol"; @@ -1076,8 +1076,8 @@ contract ZoraCreator1155Test is Test { init(); - PremintConfig memory premintConfig = PremintConfig({ - tokenConfig: TokenCreationConfig({ + PremintConfigV2 memory premintConfig = PremintConfigV2({ + tokenConfig: TokenCreationConfigV2({ // Metadata URI for the created token tokenURI: "", // Max supply of the created token @@ -1090,12 +1090,6 @@ contract ZoraCreator1155Test is Test { mintStart: 0, // The duration of the mint, starting from the first mint of this token. 0 for infinite mintDuration: type(uint64).max - 1, - // RoyaltyMintSchedule for created tokens. Every nth token will go to the royalty recipient. - royaltyMintSchedule: 0, - // RoyaltyBPS for created tokens. The royalty amount in basis points for secondary sales. - royaltyBPS: 0, - // RoyaltyRecipient for created tokens. The address that will receive the royalty payments. - royaltyRecipient: address(0), // Fixed price minter address fixedPriceMinter: address(fixedPriceMinter), // Default create referral @@ -1120,7 +1114,7 @@ contract ZoraCreator1155Test is Test { chainId := chainid() } - (uint8 v, bytes32 r, bytes32 s) = vm.sign(adminKey, ZoraCreator1155Attribution.premintHashedTypeDataV4(premintConfig, address(target), chainId)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(adminKey, ZoraCreator1155Attribution.premintHashedTypeDataV4(ZoraCreator1155Attribution.hashPremint(premintConfig), address(target), ZoraCreator1155Attribution.HASHED_VERSION_1, chainId)); bytes memory signature = abi.encodePacked(r, s, v); diff --git a/packages/1155-contracts/test/premint/Zora1155PremintExecutorProxy.t.sol b/packages/1155-contracts/test/premint/Zora1155PremintExecutorProxy.t.sol index 962c115d8..62588b94e 100644 --- a/packages/1155-contracts/test/premint/Zora1155PremintExecutorProxy.t.sol +++ b/packages/1155-contracts/test/premint/Zora1155PremintExecutorProxy.t.sol @@ -11,7 +11,7 @@ import {ZoraCreator1155PremintExecutorImpl} from "../../src/delegation/ZoraCreat import {Zora1155Factory} from "../../src/proxies/Zora1155Factory.sol"; import {IMinter1155} from "../../src/interfaces/IMinter1155.sol"; import {ProxyShim} from "../../src/utils/ProxyShim.sol"; -import {ZoraCreator1155Attribution, ContractCreationConfig, TokenCreationConfig, PremintConfig} from "../../src/delegation/ZoraCreator1155Attribution.sol"; +import {ZoraCreator1155Attribution, ContractCreationConfig, TokenCreationConfigV2, PremintConfigV2} from "../../src/delegation/ZoraCreator1155Attribution.sol"; import {IOwnable2StepUpgradeable} from "../../src/utils/ownable/IOwnable2StepUpgradeable.sol"; import {IHasContractName} from "../../src/interfaces/IContractMetadata.sol"; @@ -52,7 +52,7 @@ contract Zora1155PremintExecutorProxyTest is Test, IHasContractName { // create premint config IMinter1155 fixedPriceMinter = ZoraCreator1155FactoryImpl(address(factoryProxy)).fixedPriceMinter(); - PremintConfig memory premintConfig = PremintConfig({ + PremintConfigV2 memory premintConfig = PremintConfigV2({ tokenConfig: Zora1155PremintFixtures.makeDefaultTokenCreationConfig(fixedPriceMinter, creator), uid: 100, version: 0, @@ -64,7 +64,8 @@ contract Zora1155PremintExecutorProxyTest is Test, IHasContractName { address deterministicAddress = preminterAtProxy.getContractAddress(contractConfig); // sign the premint - bytes32 digest = ZoraCreator1155Attribution.premintHashedTypeDataV4(premintConfig, deterministicAddress, block.chainid); + bytes32 structHash = ZoraCreator1155Attribution.hashPremint(premintConfig); + bytes32 digest = ZoraCreator1155Attribution.premintHashedTypeDataV4(structHash, deterministicAddress, ZoraCreator1155Attribution.HASHED_VERSION_2, block.chainid); (uint8 v, bytes32 r, bytes32 s) = vm.sign(creatorPrivateKey, digest); @@ -75,7 +76,7 @@ contract Zora1155PremintExecutorProxyTest is Test, IHasContractName { // execute the premint vm.deal(collector, mintFeeAmount); vm.prank(collector); - uint256 tokenId = preminterAtProxy.premint{value: mintFeeAmount}(contractConfig, premintConfig, signature, quantityToMint, ""); + uint256 tokenId = preminterAtProxy.premint{value: mintFeeAmount}(contractConfig, premintConfig, signature, quantityToMint, address(0), ""); assertEq(ZoraCreator1155Impl(deterministicAddress).balanceOf(collector, tokenId), 1); } diff --git a/packages/1155-contracts/test/premint/ZoraCreator1155PremintExecutor.t.sol b/packages/1155-contracts/test/premint/ZoraCreator1155PremintExecutor.t.sol index ee8ba82e8..a3fc58856 100644 --- a/packages/1155-contracts/test/premint/ZoraCreator1155PremintExecutor.t.sol +++ b/packages/1155-contracts/test/premint/ZoraCreator1155PremintExecutor.t.sol @@ -15,10 +15,11 @@ import {ZoraCreatorFixedPriceSaleStrategy} from "../../src/minters/fixed-price/Z import {Zora1155Factory} from "../../src/proxies/Zora1155Factory.sol"; import {ZoraCreator1155FactoryImpl} from "../../src/factory/ZoraCreator1155FactoryImpl.sol"; import {ZoraCreator1155PremintExecutorImpl} from "../../src/delegation/ZoraCreator1155PremintExecutorImpl.sol"; -import {ZoraCreator1155Attribution, ContractCreationConfig, TokenCreationConfig, PremintConfig} from "../../src/delegation/ZoraCreator1155Attribution.sol"; +import {ZoraCreator1155Attribution, ContractCreationConfig, TokenCreationConfigV2, PremintConfigV2} from "../../src/delegation/ZoraCreator1155Attribution.sol"; import {ForkDeploymentConfig, Deployment} from "../../src/deployment/DeploymentConfig.sol"; import {UUPSUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol"; import {ProxyShim} from "../../src/utils/ProxyShim.sol"; +import {ECDSAUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/ECDSAUpgradeable.sol"; contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { uint256 internal constant CONTRACT_BASE_ID = 0; @@ -38,15 +39,16 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { address internal premintExecutor; address internal collector; - event Preminted( + event PremintedV2( address indexed contractAddress, uint256 indexed tokenId, bool indexed createdNewContract, uint32 uid, ContractCreationConfig contractConfig, - TokenCreationConfig tokenConfig, + TokenCreationConfigV2 tokenConfig, address minter, - uint256 quantityMinted + uint256 quantityMinted, + address mintReferral ); function setUp() external { @@ -68,48 +70,42 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { return ContractCreationConfig({contractAdmin: creator, contractName: "blah", contractURI: "blah.contract"}); } - function makeDefaultTokenCreationConfig() internal view returns (TokenCreationConfig memory) { + function makeDefaultTokenCreationConfig() internal view returns (TokenCreationConfigV2 memory) { IMinter1155 fixedPriceMinter = factory.defaultMinters()[0]; return - TokenCreationConfig({ + TokenCreationConfigV2({ tokenURI: "blah.token", maxSupply: 10, maxTokensPerAddress: 5, pricePerToken: 0, mintStart: 0, mintDuration: 0, - royaltyMintSchedule: defaultRoyaltyConfig.royaltyMintSchedule, - royaltyBPS: defaultRoyaltyConfig.royaltyBPS, - royaltyRecipient: defaultRoyaltyConfig.royaltyRecipient, fixedPriceMinter: address(fixedPriceMinter), createReferral: address(0) }); } - function makeTokenCreationConfigWithCreateReferral(address createReferral) internal view returns (TokenCreationConfig memory) { + function makeTokenCreationConfigWithCreateReferral(address createReferral) internal view returns (TokenCreationConfigV2 memory) { IMinter1155 fixedPriceMinter = factory.defaultMinters()[0]; return - TokenCreationConfig({ + TokenCreationConfigV2({ tokenURI: "blah.token", maxSupply: 10, maxTokensPerAddress: 5, pricePerToken: 0, mintStart: 0, mintDuration: 0, - royaltyMintSchedule: defaultRoyaltyConfig.royaltyMintSchedule, - royaltyBPS: defaultRoyaltyConfig.royaltyBPS, - royaltyRecipient: defaultRoyaltyConfig.royaltyRecipient, fixedPriceMinter: address(fixedPriceMinter), createReferral: createReferral }); } - function makeDefaultPremintConfig() internal view returns (PremintConfig memory) { - return PremintConfig({tokenConfig: makeDefaultTokenCreationConfig(), uid: 100, version: 0, deleted: false}); + function makeDefaultPremintConfig() internal view returns (PremintConfigV2 memory) { + return PremintConfigV2({tokenConfig: makeDefaultTokenCreationConfig(), uid: 100, version: 0, deleted: false}); } - function makePremintConfigWithCreateReferral(address createReferral) internal view returns (PremintConfig memory) { - return PremintConfig({tokenConfig: makeTokenCreationConfigWithCreateReferral(createReferral), uid: 100, version: 0, deleted: false}); + function makePremintConfigWithCreateReferral(address createReferral) internal view returns (PremintConfigV2 memory) { + return PremintConfigV2({tokenConfig: makeTokenCreationConfigWithCreateReferral(createReferral), uid: 100, version: 0, deleted: false}); } function test_successfullyMintsTokens() external { @@ -117,7 +113,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { // configuration of contract to create ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); - PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfigV2 memory premintConfig = makeDefaultPremintConfig(); // how many tokens are minted to the executor uint256 quantityToMint = 4; @@ -129,7 +125,8 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { address contractAddress = preminter.getContractAddress(contractConfig); // 2. Call smart contract to get digest to sign for creation params. - bytes32 digest = ZoraCreator1155Attribution.premintHashedTypeDataV4(premintConfig, contractAddress, chainId); + bytes32 structHash = ZoraCreator1155Attribution.hashPremint(premintConfig); + bytes32 digest = ZoraCreator1155Attribution.premintHashedTypeDataV4(structHash, contractAddress, ZoraCreator1155Attribution.HASHED_VERSION_2, chainId); // 3. Sign the digest // create a signature with the digest for the params @@ -141,7 +138,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { // now call the premint function, using the same config that was used to generate the digest, and the signature vm.prank(premintExecutor); - uint256 tokenId = preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, comment); + uint256 tokenId = preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, address(0), comment); // get the contract address from the preminter based on the contract hash id. IZoraCreator1155 created1155Contract = IZoraCreator1155(contractAddress); @@ -154,14 +151,15 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { premintConfig.tokenConfig.tokenURI = "blah2.token"; premintConfig.uid++; - digest = ZoraCreator1155Attribution.premintHashedTypeDataV4(premintConfig, contractAddress, chainId); + structHash = ZoraCreator1155Attribution.hashPremint(premintConfig); + digest = ZoraCreator1155Attribution.premintHashedTypeDataV4(structHash, contractAddress, ZoraCreator1155Attribution.HASHED_VERSION_2, chainId); signature = _sign(creatorPrivateKey, digest); vm.deal(premintExecutor, mintCost); // premint with new token config and signature vm.prank(premintExecutor); - tokenId = preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, comment); + tokenId = preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, address(0), comment); // a new token shoudl have been created, with x tokens minted to the executor, on the same contract address // as before since the contract config didnt change @@ -173,7 +171,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { // configuration of contract to create ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); - PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfigV2 memory premintConfig = makeDefaultPremintConfig(); // how many tokens are minted to the executor uint256 chainId = block.chainid; @@ -184,7 +182,8 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { address contractAddress = preminter.getContractAddress(contractConfig); // 2. Call smart contract to get digest to sign for creation params. - bytes32 digest = ZoraCreator1155Attribution.premintHashedTypeDataV4(premintConfig, contractAddress, chainId); + bytes32 structHash = ZoraCreator1155Attribution.hashPremint(premintConfig); + bytes32 digest = ZoraCreator1155Attribution.premintHashedTypeDataV4(structHash, contractAddress, ZoraCreator1155Attribution.HASHED_VERSION_2, chainId); // 3. Sign the digest // create a signature with the digest for the params @@ -194,7 +193,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { // now call the premint function, using the same config that was used to generate the digest, and the signature vm.prank(premintExecutor); - uint256 tokenId = preminter.premint(contractConfig, premintConfig, signature, quantityToMint, comment); + uint256 tokenId = preminter.premint(contractConfig, premintConfig, signature, quantityToMint, address(0), comment); // get the contract address from the preminter based on the contract hash id. IZoraCreator1155 created1155Contract = IZoraCreator1155(contractAddress); @@ -210,14 +209,15 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { function test_premint_emitsCreatorAttribution_fromErc1155Contract() external { // build a premint ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); - PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfigV2 memory premintConfig = makeDefaultPremintConfig(); // sign and execute premint uint256 chainId = block.chainid; address deterministicAddress = preminter.getContractAddress(contractConfig); - bytes32 structHash = ZoraCreator1155Attribution.premintHashedTypeDataV4(premintConfig, deterministicAddress, chainId); - bytes memory signature = _sign(creatorPrivateKey, structHash); + bytes32 structHash = ZoraCreator1155Attribution.hashPremint(premintConfig); + bytes32 digest = ZoraCreator1155Attribution.premintHashedTypeDataV4(structHash, deterministicAddress, ZoraCreator1155Attribution.HASHED_VERSION_2, chainId); + bytes memory signature = _sign(creatorPrivateKey, digest); uint256 quantityToMint = 4; string memory comment = "hi"; @@ -229,10 +229,10 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { // verify CreatorAttribution was emitted from the erc1155 contract vm.expectEmit(true, false, false, false, deterministicAddress); - emit CreatorAttribution(structHash, ZoraCreator1155Attribution.NAME, ZoraCreator1155Attribution.VERSION, creator, signature); + emit CreatorAttribution(structHash, ZoraCreator1155Attribution.NAME, ZoraCreator1155Attribution.VERSION_2, creator, signature); // create contract and token via premint - preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, comment); + preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, address(0), comment); } /// @notice gets the chains to do fork tests on, by reading environment var FORK_TEST_CHAINS. @@ -251,7 +251,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { // configuration of contract to create ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); - PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfigV2 memory premintConfig = makeDefaultPremintConfig(); // how many tokens are minted to the executor uint256 quantityToMint = 4; @@ -263,7 +263,8 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { address contractAddress = preminter.getContractAddress(contractConfig); // 2. Call smart contract to get digest to sign for creation params. - bytes32 digest = ZoraCreator1155Attribution.premintHashedTypeDataV4(premintConfig, contractAddress, chainId); + bytes32 structHash = ZoraCreator1155Attribution.hashPremint(premintConfig); + bytes32 digest = ZoraCreator1155Attribution.premintHashedTypeDataV4(structHash, contractAddress, ZoraCreator1155Attribution.HASHED_VERSION_2, chainId); // 3. Sign the digest // create a signature with the digest for the params @@ -275,7 +276,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { // now call the premint function, using the same config that was used to generate the digest, and the signature vm.deal(premintExecutor, mintCost); vm.prank(premintExecutor); - uint256 tokenId = preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, comment); + uint256 tokenId = preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, address(0), comment); // get the contract address from the preminter based on the contract hash id. IZoraCreator1155 created1155Contract = IZoraCreator1155(contractAddress); @@ -354,7 +355,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { // configuration of contract to create ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); - PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfigV2 memory premintConfig = makeDefaultPremintConfig(); // how many tokens are minted to the executor uint256 quantityToMint = 2; @@ -377,7 +378,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { vm.startPrank(collector); // premint with new token config and signature, but same uid - it should mint tokens for the first token - nextTokenId = preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, comment); + nextTokenId = preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, address(0), comment); assertEq(nextTokenId, firstTokenId); assertEq(created1155Contract.balanceOf(collector, firstTokenId), quantityToMint); @@ -389,7 +390,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { vm.deal(collector, mintCost); // premint with new token config and signature - it should mint tokens for the first token - nextTokenId = preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, comment); + nextTokenId = preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, address(0), comment); vm.stopPrank(); assertEq(nextTokenId, firstTokenId); @@ -398,7 +399,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { function testCreateTokenPerUid() public { ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); - PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfigV2 memory premintConfig = makeDefaultPremintConfig(); uint256 quantityToMint = 2; uint256 chainId = block.chainid; @@ -417,7 +418,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { vm.startPrank(collector); // premint with new token config and signature, but same uid - it should mint tokens for the first token - uint256 nextTokenId = preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, comment); + uint256 nextTokenId = preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, address(0), comment); assertEq(firstTokenId, 1); assertEq(nextTokenId, 2); @@ -425,7 +426,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { function test_deleted_preventsTokenFromBeingMinted() external { ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); - PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfigV2 memory premintConfig = makeDefaultPremintConfig(); premintConfig.deleted = true; uint chainId = block.chainid; @@ -440,7 +441,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { // now call the premint function, using the same config that was used to generate the digest, and the signature vm.expectRevert(IZoraCreator1155Errors.PremintDeleted.selector); vm.prank(premintExecutor); - uint256 newTokenId = preminter.premint(contractConfig, premintConfig, signature, quantityToMint, comment); + uint256 newTokenId = preminter.premint(contractConfig, premintConfig, signature, quantityToMint, address(0), comment); assertEq(newTokenId, 0, "tokenId"); @@ -450,7 +451,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { function test_emitsPremint_whenNewContract() external { ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); - PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfigV2 memory premintConfig = makeDefaultPremintConfig(); address contractAddress = preminter.getContractAddress(contractConfig); // how many tokens are minted to the executor @@ -472,7 +473,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { bool createdNewContract = true; vm.expectEmit(true, true, true, true); - emit Preminted( + emit PremintedV2( contractAddress, expectedTokenId, createdNewContract, @@ -480,14 +481,15 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { contractConfig, premintConfig.tokenConfig, premintExecutor, - quantityToMint + quantityToMint, + address(0) ); - preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, comment); + preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, address(0), comment); } function test_onlyOwner_hasAdminRights_onCreatedToken() public { ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); - PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfigV2 memory premintConfig = makeDefaultPremintConfig(); // how many tokens are minted to the executor uint256 quantityToMint = 4; @@ -555,7 +557,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { } function test_premintStatus_getsStatus() external { - PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfigV2 memory premintConfig = makeDefaultPremintConfig(); // how many tokens are minted to the executor uint256 quantityToMint = 4; @@ -599,7 +601,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { vm.warp(currentTime); ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); - PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfigV2 memory premintConfig = makeDefaultPremintConfig(); premintConfig.tokenConfig.mintStart = startDate; uint256 quantityToMint = 4; @@ -617,7 +619,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { vm.deal(premintExecutor, mintCost); vm.prank(premintExecutor); - preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, comment); + preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, address(0), comment); } function test_premintCanOnlyBeExecutedUpToDurationFromFirstMint(uint8 startDate, uint8 duration, uint8 timeOfFirstMint, uint8 timeOfSecondMint) external { @@ -634,7 +636,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { // build a premint with a token that has the given start date and duration ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); - PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfigV2 memory premintConfig = makeDefaultPremintConfig(); address contractAddress = preminter.getContractAddress(contractConfig); premintConfig.tokenConfig.mintStart = startDate; @@ -654,7 +656,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { vm.startPrank(premintExecutor); vm.warp(timeOfFirstMint); - uint256 tokenId = preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, comment); + uint256 tokenId = preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, address(0), comment); vm.warp(timeOfSecondMint); @@ -673,7 +675,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { function test_premintStatus_getsIfContractHasBeenCreatedAndTokenIdForPremint() external { // build a premint ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); - PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfigV2 memory premintConfig = makeDefaultPremintConfig(); // get premint status (bool contractCreated, uint256 tokenId) = preminter.premintStatus(preminter.getContractAddress(contractConfig), premintConfig.uid); @@ -700,14 +702,17 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { function test_premint_whenContractCreated_premintCanOnlyBeExecutedByPermissionBitMinter() external { // build a premint ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); - PremintConfig memory premintConfig = makeDefaultPremintConfig(); + PremintConfigV2 memory premintConfig = makeDefaultPremintConfig(); + + address contractAddress = preminter.getContractAddress(contractConfig); // sign and execute premint - bytes memory signature = _signPremint(preminter.getContractAddress(contractConfig), premintConfig, creatorPrivateKey, block.chainid); + bytes memory signature = _signPremint(contractAddress, premintConfig, creatorPrivateKey, block.chainid); - (bool isValidSignature, address contractAddress, ) = preminter.isValidSignature(contractConfig, premintConfig, signature); + (bool isValidSignature, address recoveredSigner) = preminter.isValidSignature(contractConfig.contractAdmin, contractAddress, ZoraCreator1155Attribution.hashPremint(premintConfig), ZoraCreator1155Attribution.HASHED_VERSION_2, signature); - assertTrue(isValidSignature); + assertEq(creator, recoveredSigner, "recovered the wrong signer"); + assertTrue(isValidSignature, "signature should be valid"); _signAndExecutePremint(contractConfig, premintConfig, creatorPrivateKey, block.chainid, premintExecutor, 1, "hi"); @@ -716,16 +721,16 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { // have another creator sign a premint uint256 newCreatorPrivateKey = 0xA11CF; address newCreator = vm.addr(newCreatorPrivateKey); - PremintConfig memory premintConfig2 = premintConfig; + PremintConfigV2 memory premintConfig2 = premintConfig; premintConfig2.uid++; // have new creator sign a premint, isValidSignature should be false, and premint should revert bytes memory newCreatorSignature = _signPremint(contractAddress, premintConfig2, newCreatorPrivateKey, block.chainid); // it should not be considered a valid signature - (isValidSignature, , ) = preminter.isValidSignature(contractConfig, premintConfig2, newCreatorSignature); + (isValidSignature, ) = preminter.isValidSignature(contractConfig.contractAdmin, contractAddress, ZoraCreator1155Attribution.hashPremint(premintConfig2), ZoraCreator1155Attribution.HASHED_VERSION_2, newCreatorSignature); - assertFalse(isValidSignature); + assertFalse(isValidSignature, "signature should not be valid"); uint256 quantityToMint = 1; uint256 mintCost = mintFeeAmount * quantityToMint; @@ -734,28 +739,28 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { // try to mint, it should revert vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155Errors.UserMissingRoleForToken.selector, newCreator, CONTRACT_BASE_ID, PERMISSION_BIT_MINTER)); vm.prank(premintExecutor); - preminter.premint{value: mintCost}(contractConfig, premintConfig2, newCreatorSignature, quantityToMint, "yo"); + preminter.premint{value: mintCost}(contractConfig, premintConfig2, newCreatorSignature, quantityToMint, address(0), "yo"); // now grant the new creator permission to mint vm.prank(creator); IZoraCreator1155(contractAddress).addPermission(CONTRACT_BASE_ID, newCreator, PERMISSION_BIT_MINTER); // should now be considered a valid signature - (isValidSignature, , ) = preminter.isValidSignature(contractConfig, premintConfig2, newCreatorSignature); - assertTrue(isValidSignature); + (isValidSignature, ) = preminter.isValidSignature(contractConfig.contractAdmin, contractAddress, ZoraCreator1155Attribution.hashPremint(premintConfig2), ZoraCreator1155Attribution.HASHED_VERSION_2, newCreatorSignature); + assertTrue(isValidSignature, "valid signature after granted permission"); vm.deal(premintExecutor, mintCost); // try to mint again, should not revert vm.prank(premintExecutor); - preminter.premint{value: mintCost}(contractConfig, premintConfig2, newCreatorSignature, quantityToMint, "yo"); + preminter.premint{value: mintCost}(contractConfig, premintConfig2, newCreatorSignature, quantityToMint, address(0), "yo"); } function testPremintWithCreateReferral() public { address createReferral = makeAddr('createReferral'); ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); - PremintConfig memory premintConfig = makePremintConfigWithCreateReferral(createReferral); + PremintConfigV2 memory premintConfig = makePremintConfigWithCreateReferral(createReferral); uint256 createdTokenId = _signAndExecutePremint(contractConfig, premintConfig, creatorPrivateKey, block.chainid, premintExecutor, 1, "hi"); @@ -768,7 +773,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { function _signAndExecutePremint( ContractCreationConfig memory contractConfig, - PremintConfig memory premintConfig, + PremintConfigV2 memory premintConfig, uint256 privateKey, uint256 chainId, address executor, @@ -782,20 +787,26 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { // now call the premint function, using the same config that was used to generate the digest, and the signature vm.prank(executor); - newTokenId = preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, comment); + newTokenId = preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, address(0), comment); } function _signPremint( address contractAddress, - PremintConfig memory premintConfig, + PremintConfigV2 memory premintConfig, uint256 privateKey, uint256 chainId - ) private pure returns (bytes memory) { - bytes32 digest = ZoraCreator1155Attribution.premintHashedTypeDataV4(premintConfig, contractAddress, chainId); + ) private returns (bytes memory signature) { + bytes32 structHash = ZoraCreator1155Attribution.hashPremint(premintConfig); + bytes32 digest = ZoraCreator1155Attribution.premintHashedTypeDataV4(structHash, contractAddress, ZoraCreator1155Attribution.HASHED_VERSION_2, chainId); + // 3. Sign the digest // create a signature with the digest for the params - return _sign(privateKey, digest); + signature = _sign(privateKey, digest); + + address signatory = ECDSAUpgradeable.recover(digest, signature); + + assertEq(signatory, vm.addr(privateKey)); } function _sign(uint256 privateKey, bytes32 digest) private pure returns (bytes memory) {