From 6d90ee011fe329f660fc4d8915e5d582b94a3aa7 Mon Sep 17 00:00:00 2001 From: Dan Oved Date: Mon, 16 Oct 2023 08:53:37 -0700 Subject: [PATCH] wip on v1/v2 of create referral fix --- .../1155-contracts/package/preminter.test.ts | 54 ++-- packages/1155-contracts/package/preminter.ts | 102 +++++++- .../delegation/ZoraCreator1155Attribution.sol | 237 ++++++++++++------ .../ZoraCreator1155PremintExecutorImpl.sol | 227 +++++++++++------ .../ZoraCreator1155PremintExecutorImplLib.sol | 61 +++++ .../src/deployment/DeploymentTestingUtils.sol | 19 +- .../src/interfaces/IZoraCreator1155.sol | 6 +- .../src/nft/ZoraCreator1155Impl.sol | 16 +- .../test/fixtures/Zora1155PremintFixtures.sol | 17 +- .../test/nft/ZoraCreator1155.t.sol | 22 +- .../Zora1155PremintExecutorProxy.t.sol | 23 +- .../ZoraCreator1155PremintExecutor.t.sol | 198 ++++++++------- 12 files changed, 672 insertions(+), 310 deletions(-) create mode 100644 packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImplLib.sol diff --git a/packages/1155-contracts/package/preminter.test.ts b/packages/1155-contracts/package/preminter.test.ts index 79c94b6d8..807954619 100644 --- a/packages/1155-contracts/package/preminter.test.ts +++ b/packages/1155-contracts/package/preminter.test.ts @@ -22,9 +22,10 @@ import { import { ContractCreationConfig, - PremintConfig, - TokenCreationConfig, - preminterTypedDataDefinition, + PremintConfigV2, + TokenCreationConfigV2, + encodeMintArguments, + preminterTypedDataDefinitionV2, } from "./preminter"; const walletClient = createWalletClient({ @@ -51,8 +52,19 @@ const publicClient = createPublicClient({ type Address = `0x${string}`; // JSON-RPC Account -const [deployerAccount, creatorAccount, collectorAccount] = - (await walletClient.getAddresses()) as [Address, Address, Address, Address]; +const [ + deployerAccount, + creatorAccount, + collectorAccount, + createReferralAccount, + mintReferral, +] = (await walletClient.getAddresses()) as [ + Address, + Address, + Address, + Address, + Address +]; type TestContext = { preminterAddress: `0x${string}`; @@ -75,20 +87,20 @@ const defaultContractConfig = ({ const defaultTokenConfig = ( fixedPriceMinterAddress: Address -): TokenCreationConfig => ({ +): TokenCreationConfigV2 => ({ tokenURI: "ipfs://tokenIpfsId0", maxSupply: 100n, maxTokensPerAddress: 10n, pricePerToken: 0n, mintStart: 0n, mintDuration: 100n, - royaltyMintSchedule: 30, - royaltyBPS: 200, - royaltyRecipient: creatorAccount, fixedPriceMinter: fixedPriceMinterAddress, + royaltyBPS: 10, + royaltyRecipient: creatorAccount, + createReferral: createReferralAccount, }); -const defaultPremintConfig = (fixedPriceMinter: Address): PremintConfig => ({ +const defaultPremintConfig = (fixedPriceMinter: Address): PremintConfigV2 => ({ tokenConfig: defaultTokenConfig(fixedPriceMinter), deleted: false, uid: 105, @@ -138,7 +150,7 @@ describe("ZoraCreator1155Preminter", () => { }); const signedMessage = await walletClient.signTypedData({ - ...preminterTypedDataDefinition({ + ...preminterTypedDataDefinitionV2({ verifyingContract: contractAddress, chainId: 999, premintConfig, @@ -177,7 +189,7 @@ describe("ZoraCreator1155Preminter", () => { // sign message containing contract and token creation config and uid const signedMessage = await walletClient.signTypedData({ - ...preminterTypedDataDefinition({ + ...preminterTypedDataDefinitionV2({ verifyingContract: contractAddress, // we need to sign here for the anvil chain, cause thats where it is run on chainId: anvilChainId, @@ -187,11 +199,11 @@ describe("ZoraCreator1155Preminter", () => { }); // recover and verify address is correct - const recoveredAddress = await publicClient.readContract({ + const [, , recoveredAddress] = await publicClient.readContract({ abi: preminterAbi, address: preminterAddress, - functionName: "recoverSigner", - args: [premintConfig, contractAddress, signedMessage], + functionName: "isValidSignature", + args: [contractConfig, premintConfig, signedMessage], }); expect(recoveredAddress).to.equal(creatorAccount); @@ -226,7 +238,7 @@ describe("ZoraCreator1155Preminter", () => { // have creator sign the message to create the contract // and the token const signedMessage = await walletClient.signTypedData({ - ...preminterTypedDataDefinition({ + ...preminterTypedDataDefinitionV2({ verifyingContract: contractAddress, // we need to sign here for the anvil chain, cause thats where it is run on chainId: anvilChainId, @@ -274,7 +286,7 @@ describe("ZoraCreator1155Preminter", () => { premintConfig, signedMessage, quantityToMint, - comment, + encodeMintArguments({ mintComment: comment }), ], value: valueToSend, }); @@ -319,7 +331,7 @@ describe("ZoraCreator1155Preminter", () => { // sign the message to create the second token const signedMessage2 = await walletClient.signTypedData({ - ...preminterTypedDataDefinition({ + ...preminterTypedDataDefinitionV2({ verifyingContract: contractAddress, chainId: foundry.id, premintConfig: premintConfig2, @@ -345,7 +357,7 @@ describe("ZoraCreator1155Preminter", () => { premintConfig2, signedMessage2, quantityToMint2, - comment, + encodeMintArguments({ mintComment: comment }), ], value: valueToSend2, }); @@ -403,7 +415,7 @@ describe("ZoraCreator1155Preminter", () => { // have creator sign the message to create the contract // and the token const signedMessage = await walletClient.signTypedData({ - ...preminterTypedDataDefinition({ + ...preminterTypedDataDefinitionV2({ verifyingContract: contractAddress, // we need to sign here for the anvil chain, cause thats where it is run on chainId: anvilChainId, @@ -439,7 +451,7 @@ describe("ZoraCreator1155Preminter", () => { premintConfig, signedMessage, quantityToMint, - comment, + encodeMintArguments({ mintComment: comment }), ], value: valueToSend, }); diff --git a/packages/1155-contracts/package/preminter.ts b/packages/1155-contracts/package/preminter.ts index 488fda2a4..e9759a062 100644 --- a/packages/1155-contracts/package/preminter.ts +++ b/packages/1155-contracts/package/preminter.ts @@ -1,7 +1,7 @@ import { Address } from "abitype"; import { ExtractAbiFunction, AbiParametersToPrimitiveTypes } from "abitype"; import { zoraCreator1155PremintExecutorImplABI as preminterAbi } from "./wagmiGenerated"; -import { TypedDataDefinition } from "viem"; +import { TypedDataDefinition, encodeAbiParameters } from "viem"; type PremintInputs = ExtractAbiFunction< typeof preminterAbi, @@ -11,18 +11,90 @@ type PremintInputs = ExtractAbiFunction< type PreminterHashDataTypes = AbiParametersToPrimitiveTypes; export type ContractCreationConfig = PreminterHashDataTypes[0]; -export type PremintConfig = PreminterHashDataTypes[1]; -export type TokenCreationConfig = PremintConfig["tokenConfig"]; +export type PremintConfigs = PreminterHashDataTypes[1]; + +export type TokenCreationConfigV1 = PremintConfigV2["tokenConfig"]; +export type PremintConfigV2 = Extract< + PremintConfigs, + { + tokenConfig: { + createReferral: string; + }; + } +>; +export type PremintConfigV1 = Exclude; +export type TokenCreationConfigV2 = PremintConfigV2["tokenConfig"]; + +const premintV2Types = { + CreatorAttribution: [ + { name: "tokenConfig", type: "TokenCreationConfig" }, + // unique id scoped to the contract and token to create. + // ensure that a signature can be replaced, as long as the replacement + // has the same uid, and a newer version. + { name: "uid", type: "uint32" }, + { name: "version", type: "uint32" }, + // if this update should result in the signature being deleted. + { name: "deleted", type: "bool" }, + ], + TokenCreationConfig: [ + { name: "tokenURI", type: "string" }, + { name: "maxSupply", type: "uint256" }, + { name: "maxTokensPerAddress", type: "uint64" }, + { name: "pricePerToken", type: "uint96" }, + { name: "mintStart", type: "uint64" }, + { name: "mintDuration", type: "uint64" }, + { name: "royaltyBPS", type: "uint32" }, + { name: "royaltyRecipient", type: "address" }, + { name: "fixedPriceMinter", type: "address" }, + { name: "createReferral", type: "address" }, + ], +}; + +// Convenience method to create the structured typed data +// needed to sign for a premint contract and token +export const preminterTypedDataDefinitionV2 = ({ + verifyingContract, + premintConfig, + chainId, +}: { + verifyingContract: Address; + premintConfig: PremintConfigV2; + chainId: number; +}) => { + const { tokenConfig, uid, version, deleted } = premintConfig; + + const result: TypedDataDefinition< + typeof premintV2Types, + "CreatorAttribution" + > = { + domain: { + chainId, + name: "Preminter", + version: "2", + verifyingContract: verifyingContract, + }, + types: premintV2Types, + message: { + tokenConfig, + uid, + version, + deleted, + }, + primaryType: "CreatorAttribution", + }; + + return result; +}; // Convenience method to create the structured typed data // needed to sign for a premint contract and token -export const preminterTypedDataDefinition = ({ +export const preminterTypedDataDefinitionV1 = ({ verifyingContract, premintConfig, chainId, }: { verifyingContract: Address; - premintConfig: PremintConfig; + premintConfig: PremintConfigV1; chainId: number; }) => { const { tokenConfig, uid, version, deleted } = premintConfig; @@ -68,7 +140,23 @@ export const preminterTypedDataDefinition = ({ primaryType: "CreatorAttribution", }; - // console.log({ result, deleted }); - return result; }; + +const zeroAddress: Address = "0x0000000000000000000000000000000000000000"; + +export const encodeMintArguments = ({ + mintComment = "", + mintReferral = zeroAddress, +}: { + mintComment?: string; + mintReferral?: Address; +}) => { + return encodeAbiParameters( + [ + { name: "mintReferral", type: "address" }, + { name: "mintComment", type: "string" }, + ], + [mintReferral, mintComment] + ); +}; diff --git a/packages/1155-contracts/src/delegation/ZoraCreator1155Attribution.sol b/packages/1155-contracts/src/delegation/ZoraCreator1155Attribution.sol index c7a91a6cf..cfdaeebb1 100644 --- a/packages/1155-contracts/src/delegation/ZoraCreator1155Attribution.sol +++ b/packages/1155-contracts/src/delegation/ZoraCreator1155Attribution.sol @@ -16,6 +16,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 +49,11 @@ 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,85 +63,62 @@ 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; + // 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; + // 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,uint32 royaltyBPS,address royaltyRecipient,address fixedPriceMinter,address createReferral)" ); function hashPremint(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, _hashToken(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)" ); @@ -140,7 +127,7 @@ library ZoraCreator1155Attribution { return keccak256( abi.encode( - TOKEN_DOMAIN, + TOKEN_DOMAIN_V1, _stringHash(tokenConfig.tokenURI), tokenConfig.maxSupply, tokenConfig.maxTokensPerAddress, @@ -155,9 +142,113 @@ library ZoraCreator1155Attribution { ); } + bytes32 constant TOKEN_DOMAIN_V2 = + keccak256( + "TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 mintStart,uint64 mintDuration,uint32 royaltyBPS,address royaltyRecipient,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.royaltyBPS, + tokenConfig.royaltyRecipient, + 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,7 +259,7 @@ library PremintTokenSetup { function makeSetupNewTokenCalls( uint256 newTokenId, address contractAdmin, - TokenCreationConfig calldata tokenConfig + TokenCreationConfigV2 calldata tokenConfig ) external view returns (bytes[] memory calls) { calls = new bytes[](3); @@ -199,7 +290,7 @@ library PremintTokenSetup { ICreatorRoyaltiesControl.RoyaltyConfiguration({ royaltyBPS: tokenConfig.royaltyBPS, royaltyRecipient: tokenConfig.royaltyRecipient, - royaltyMintSchedule: tokenConfig.royaltyMintSchedule + royaltyMintSchedule: 0 }) ); } diff --git a/packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImpl.sol b/packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImpl.sol index 45805a927..e441afeb2 100644 --- a/packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImpl.sol +++ b/packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImpl.sol @@ -12,20 +12,50 @@ 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, PremintConfig, PremintConfigV2, TokenCreationConfig, TokenCreationConfigV2} from "./ZoraCreator1155Attribution.sol"; + +interface IZoraCreator1155PremintV1Signatures { + function delegateSetupNewToken(PremintConfig calldata premintConfig, bytes calldata signature, address sender) external returns (uint256 newTokenId); +} + +// interface for legacy v1 of premint executor methods +// maintained in order to not break existing calls +// to legacy api when this api is upgraded +interface ILegacyZoraCreator1155PremintExecutor { + event Preminted( + address indexed contractAddress, + uint256 indexed tokenId, + bool indexed createdNewContract, + uint32 uid, + ContractCreationConfig contractConfig, + TokenCreationConfig tokenConfig, + address minter, + uint256 quantityMinted + ); + + function premint( + ContractCreationConfig calldata contractConfig, + PremintConfig calldata premintConfig, + bytes calldata signature, + uint256 quantityToMint, + string calldata mintComment + ) external payable returns (uint256 newTokenId); +} /// @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 + ILegacyZoraCreator1155PremintExecutor, + 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) { zora1155Factory = _factory; } @@ -35,15 +65,14 @@ 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, address minter, - uint256 quantityMinted + uint256 quantityMinted, + bytes mintArgumets ); /// Creates a new token on the given erc1155 contract on behalf of a creator, and mints x tokens to the executor of this transaction. @@ -55,87 +84,94 @@ contract ZoraCreator1155PremintExecutorImpl is Ownable2StepUpgradeable, UUPSUpgr /// @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 + /// @param mintArguments Abi encoded additional mint arguments: including mintComment and mintReferral function premint( ContractCreationConfig calldata contractConfig, - PremintConfig calldata premintConfig, + PremintConfigV2 calldata premintConfig, bytes calldata signature, uint256 quantityToMint, - string calldata mintComment - ) public payable returns (uint256 newTokenId) { + bytes calldata mintArguments + ) 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. // and then create and setup the token using the given token config. newTokenId = 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) - ); - } + _performMint(tokenContract, premintConfig.tokenConfig.fixedPriceMinter, newTokenId, quantityToMint, mintArguments); // emit Preminted event - emit Preminted( - address(tokenContract), - newTokenId, - isNewContract, - premintConfig.uid, - contractConfig, - premintConfig.tokenConfig, - msg.sender, - quantityToMint - ); + emit PremintedV2(address(tokenContract), newTokenId, isNewContract, premintConfig.uid, msg.sender, quantityToMint, mintArguments); } - 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; + /// 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 mintArguments Abi encoded additional mint arguments: including mintComment and mintReferral + function premint( + ContractCreationConfig calldata contractConfig, + PremintConfig calldata premintConfig, + bytes calldata signature, + uint256 quantityToMint, + bytes memory mintArguments + ) public 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); - if (isNewContract) { - // if address doesn't exist for hash, create it - tokenContract = _createContract(contractConfig); - } else { - tokenContract = IZoraCreator1155(contractAddress); - } + // assume contract has legacy interface expecting v1 signatures; call it. + newTokenId = IZoraCreator1155PremintV1Signatures(address(tokenContract)).delegateSetupNewToken(premintConfig, signature, msg.sender); + + _performMint(tokenContract, premintConfig.tokenConfig.fixedPriceMinter, newTokenId, quantityToMint, mintArguments); + + // emit Preminted event + emit PremintedV2(address(tokenContract), newTokenId, isNewContract, premintConfig.uid, msg.sender, quantityToMint, mintArguments); } - 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 _performMint( + IZoraCreator1155 tokenContract, + address fixedPriceMinter, + uint256 tokenId, + uint256 quantityToMint, + bytes memory mintArguments + ) internal { + (address mintReferral, string memory mintComment) = ZoraCreator1155PremintExecutorImplLib.decodeMintArguments(mintArguments); + + if (quantityToMint != 0) + // mint the number of specified tokens to the executor + tokenContract.mintWithRewards{value: msg.value}( + IMinter1155(fixedPriceMinter), + tokenId, + quantityToMint, + abi.encode(msg.sender, mintComment), + mintReferral + ); + } + + 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. /// 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(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); + return 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, @@ -147,29 +183,40 @@ 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)); - } + bytes32 hashedPremint = ZoraCreator1155Attribution.hashPremint(premintConfig); - // 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); - } + (isValid, recoveredSigner) = isValidSignature( + contractConfig.contractAdmin, + contractAddress, + hashedPremint, + ZoraCreator1155Attribution.HASHED_VERSION_1, + signature + ); + } + + function isValidSignature( + ContractCreationConfig calldata contractConfig, + PremintConfigV2 calldata premintConfig, + bytes calldata signature + ) public view returns (bool isValid, address contractAddress, address recoveredSigner) { + contractAddress = getContractAddress(contractConfig); + + bytes32 hashedPremint = ZoraCreator1155Attribution.hashPremint(premintConfig); + + (isValid, recoveredSigner) = isValidSignature( + contractConfig.contractAdmin, + contractAddress, + hashedPremint, + ZoraCreator1155Attribution.HASHED_VERSION_2, + signature + ); } // upgrade related functionality @@ -194,4 +241,20 @@ contract ZoraCreator1155PremintExecutorImpl is Ownable2StepUpgradeable, UUPSUpgr function _equals(string memory a, string memory b) internal pure returns (bool) { return (keccak256(bytes(a)) == keccak256(bytes(b))); } + + // Deprecated functions: + + /// @notice Deprecated + function premint( + ContractCreationConfig calldata contractConfig, + PremintConfig calldata premintConfig, + bytes calldata signature, + uint256 quantityToMint, + string calldata mintComment + ) external payable returns (uint256 newTokenId) { + // encode legacy mint arguments to call current function: + bytes memory mintArguments = ZoraCreator1155PremintExecutorImplLib.encodeMintArguments(address(0), mintComment); + + return premint(contractConfig, premintConfig, signature, quantityToMint, mintArguments); + } } diff --git a/packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImplLib.sol b/packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImplLib.sol new file mode 100644 index 000000000..6ac32a540 --- /dev/null +++ b/packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImplLib.sol @@ -0,0 +1,61 @@ +// 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"; +import {IMinter1155} from "../interfaces/IMinter1155.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); + } + + function encodeMintArguments(address mintReferral, string memory mintComment) internal pure returns (bytes memory) { + return abi.encode(mintReferral, mintComment); + } + + function decodeMintArguments(bytes memory mintArguments) internal pure returns (address mintReferral, string memory mintComment) { + return abi.decode(mintArguments, (address, string)); + } +} diff --git a/packages/1155-contracts/src/deployment/DeploymentTestingUtils.sol b/packages/1155-contracts/src/deployment/DeploymentTestingUtils.sol index e8a7a7997..bc4180b48 100644 --- a/packages/1155-contracts/src/deployment/DeploymentTestingUtils.sol +++ b/packages/1155-contracts/src/deployment/DeploymentTestingUtils.sol @@ -7,8 +7,9 @@ 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"; +import {ZoraCreator1155PremintExecutorImplLib} from "../delegation/ZoraCreator1155PremintExecutorImplLib.sol"; contract DeploymentTestingUtils is Script { function signAndExecutePremint(address premintExecutorProxyAddress) internal { @@ -19,8 +20,8 @@ contract DeploymentTestingUtils is Script { IMinter1155 fixedPriceMinter = ZoraCreator1155FactoryImpl(address(preminterAtProxy.zora1155Factory())).fixedPriceMinter(); - PremintConfig memory premintConfig = PremintConfig({ - tokenConfig: Zora1155PremintFixtures.makeDefaultTokenCreationConfig(fixedPriceMinter, creator), + PremintConfigV2 memory premintConfig = PremintConfigV2({ + tokenConfig: Zora1155PremintFixtures.makeDefaultTokenCreationConfig(fixedPriceMinter), uid: 100, version: 0, deleted: false @@ -30,8 +31,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 +43,13 @@ 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, + ZoraCreator1155PremintExecutorImplLib.encodeMintArguments(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..0a6b2958e 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,8 @@ 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 +84,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 8c03f487b..c606709e2 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 @@ -752,7 +752,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) { @@ -767,10 +767,16 @@ 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); @@ -797,7 +803,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 dbb152aa3..a29524642 100644 --- a/packages/1155-contracts/test/fixtures/Zora1155PremintFixtures.sol +++ b/packages/1155-contracts/test/fixtures/Zora1155PremintFixtures.sol @@ -5,36 +5,29 @@ import {ZoraCreator1155Impl} from "../../src/nft/ZoraCreator1155Impl.sol"; import {ZoraCreatorFixedPriceSaleStrategy} from "../../src/minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.sol"; import {IZoraCreator1155Errors} from "../../src/interfaces/IZoraCreator1155Errors.sol"; import {IMinter1155} from "../../src/interfaces/IMinter1155.sol"; -import {ICreatorRoyaltiesControl} from "../../src/interfaces/ICreatorRoyaltiesControl.sol"; 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) { return ContractCreationConfig({contractAdmin: contractAdmin, contractName: "blah", contractURI: "blah.contract"}); } - function defaultRoyaltyConfig(address royaltyRecipient) internal pure returns (ICreatorRoyaltiesControl.RoyaltyConfiguration memory) { - return ICreatorRoyaltiesControl.RoyaltyConfiguration({royaltyBPS: 10, royaltyRecipient: royaltyRecipient, royaltyMintSchedule: 0}); - } - - function makeDefaultTokenCreationConfig(IMinter1155 fixedPriceMinter, address royaltyRecipient) internal pure returns (TokenCreationConfig memory) { - ICreatorRoyaltiesControl.RoyaltyConfiguration memory royaltyConfig = defaultRoyaltyConfig(royaltyRecipient); + function makeDefaultTokenCreationConfig(IMinter1155 fixedPriceMinter) internal pure returns (TokenCreationConfigV2 memory) { 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), + royaltyRecipient: address(0), + royaltyBPS: 0, createReferral: address(0) }); } diff --git a/packages/1155-contracts/test/nft/ZoraCreator1155.t.sol b/packages/1155-contracts/test/nft/ZoraCreator1155.t.sol index 274ed33ec..10b85911c 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"; @@ -1051,8 +1051,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 @@ -1065,12 +1065,8 @@ 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. + royaltyRecipient: admin, 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 @@ -1095,7 +1091,15 @@ 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_2, + 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..9a9fa1931 100644 --- a/packages/1155-contracts/test/premint/Zora1155PremintExecutorProxy.t.sol +++ b/packages/1155-contracts/test/premint/Zora1155PremintExecutorProxy.t.sol @@ -11,9 +11,10 @@ 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"; +import {ZoraCreator1155PremintExecutorImplLib} from "../../src/delegation/ZoraCreator1155PremintExecutorImplLib.sol"; contract Zora1155PremintExecutorProxyTest is Test, IHasContractName { address internal owner; @@ -52,8 +53,8 @@ contract Zora1155PremintExecutorProxyTest is Test, IHasContractName { // create premint config IMinter1155 fixedPriceMinter = ZoraCreator1155FactoryImpl(address(factoryProxy)).fixedPriceMinter(); - PremintConfig memory premintConfig = PremintConfig({ - tokenConfig: Zora1155PremintFixtures.makeDefaultTokenCreationConfig(fixedPriceMinter, creator), + PremintConfigV2 memory premintConfig = PremintConfigV2({ + tokenConfig: Zora1155PremintFixtures.makeDefaultTokenCreationConfig(fixedPriceMinter), uid: 100, version: 0, deleted: false @@ -64,7 +65,13 @@ 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 +82,13 @@ 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, + ZoraCreator1155PremintExecutorImplLib.encodeMintArguments(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 51981dd9c..22712ae59 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 {ZoraCreator1155PremintExecutorImplLib} from "../../src/delegation/ZoraCreator1155PremintExecutorImplLib.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( + bytes defaultMintArguments; + + event PremintedV2( address indexed contractAddress, uint256 indexed tokenId, bool indexed createdNewContract, uint32 uid, - ContractCreationConfig contractConfig, - TokenCreationConfig tokenConfig, address minter, - uint256 quantityMinted + uint256 quantityMinted, + bytes mintArgumets ); function setUp() external { @@ -62,54 +64,54 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { factory = ZoraCreator1155FactoryImpl(address(factoryProxy)); preminter = new ZoraCreator1155PremintExecutorImpl(factory); + + defaultMintArguments = ZoraCreator1155PremintExecutorImplLib.encodeMintArguments(address(0), ""); } function makeDefaultContractCreationConfig() internal view returns (ContractCreationConfig memory) { 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), + royaltyRecipient: creator, + royaltyBPS: 10, 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), + royaltyRecipient: creator, + royaltyBPS: 10, 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,19 +119,19 @@ 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; uint256 chainId = block.chainid; - string memory comment = "hi"; // get contract hash, which is unique per contract creation config, and can be used // retreive the address created for a contract 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 +143,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, defaultMintArguments); // get the contract address from the preminter based on the contract hash id. IZoraCreator1155 created1155Contract = IZoraCreator1155(contractAddress); @@ -154,14 +156,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, defaultMintArguments); // 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,18 +176,18 @@ 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; - string memory comment = "hi"; // get contract hash, which is unique per contract creation config, and can be used // retreive the address created for a contract 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 +197,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, defaultMintArguments); // get the contract address from the preminter based on the contract hash id. IZoraCreator1155 created1155Contract = IZoraCreator1155(contractAddress); @@ -210,17 +213,22 @@ 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"; uint256 mintCost = mintFeeAmount * quantityToMint; // this account will be used to execute the premint, and should result in a contract being created vm.deal(collector, mintCost); @@ -229,10 +237,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, defaultMintArguments); } /// @notice gets the chains to do fork tests on, by reading environment var FORK_TEST_CHAINS. @@ -251,19 +259,19 @@ 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; uint256 chainId = block.chainid; - string memory comment = "hi"; console.log("loading preminter"); 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 +283,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, defaultMintArguments); // get the contract address from the preminter based on the contract hash id. IZoraCreator1155 created1155Contract = IZoraCreator1155(contractAddress); @@ -320,7 +328,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; @@ -343,7 +351,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, defaultMintArguments); assertEq(nextTokenId, firstTokenId); assertEq(created1155Contract.balanceOf(collector, firstTokenId), quantityToMint); @@ -355,7 +363,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, defaultMintArguments); vm.stopPrank(); assertEq(nextTokenId, firstTokenId); @@ -364,7 +372,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; @@ -383,7 +391,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, defaultMintArguments); assertEq(firstTokenId, 1); assertEq(nextTokenId, 2); @@ -391,12 +399,11 @@ 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; uint256 quantityToMint = 2; - string memory comment = "I love it"; address contractAddress = preminter.getContractAddress(contractConfig); @@ -406,7 +413,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, defaultMintArguments); assertEq(newTokenId, 0, "tokenId"); @@ -416,7 +423,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 @@ -428,32 +435,23 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { uint256 expectedTokenId = 1; - string memory comment = "I love it"; - uint256 mintCost = mintFeeAmount * quantityToMint; // this account will be used to execute the premint, and should result in a contract being created vm.deal(premintExecutor, mintCost); + bytes memory mintArguments = defaultMintArguments; + vm.startPrank(premintExecutor); bool createdNewContract = true; vm.expectEmit(true, true, true, true); - emit Preminted( - contractAddress, - expectedTokenId, - createdNewContract, - premintConfig.uid, - contractConfig, - premintConfig.tokenConfig, - premintExecutor, - quantityToMint - ); - preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, comment); + emit PremintedV2(contractAddress, expectedTokenId, createdNewContract, premintConfig.uid, premintExecutor, quantityToMint, mintArguments); + preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, mintArguments); } 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; @@ -521,7 +519,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; @@ -565,12 +563,11 @@ 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; uint256 chainId = block.chainid; - string memory comment = "I love it"; // get signature for the premint: bytes memory signature = _signPremint(preminter.getContractAddress(contractConfig), premintConfig, creatorPrivateKey, chainId); @@ -583,7 +580,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, defaultMintArguments); } function test_premintCanOnlyBeExecutedUpToDurationFromFirstMint(uint8 startDate, uint8 duration, uint8 timeOfFirstMint, uint8 timeOfSecondMint) external { @@ -600,7 +597,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; @@ -620,7 +617,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, defaultMintArguments); vm.warp(timeOfSecondMint); @@ -639,7 +636,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); @@ -666,14 +663,23 @@ 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"); @@ -682,16 +688,22 @@ 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; @@ -700,28 +712,34 @@ 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, defaultMintArguments); // 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, defaultMintArguments); } function testPremintWithCreateReferral() public { - address createReferral = makeAddr('createReferral'); + 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"); @@ -734,7 +752,7 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { function _signAndExecutePremint( ContractCreationConfig memory contractConfig, - PremintConfig memory premintConfig, + PremintConfigV2 memory premintConfig, uint256 privateKey, uint256 chainId, address executor, @@ -743,25 +761,27 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { ) private returns (uint256 newTokenId) { bytes memory signature = _signPremint(preminter.getContractAddress(contractConfig), premintConfig, privateKey, chainId); + bytes memory mintArguments = ZoraCreator1155PremintExecutorImplLib.encodeMintArguments(address(0), comment); + uint256 mintCost = mintFeeAmount * quantityToMint; vm.deal(executor, mintCost); // 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, mintArguments); } 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 pure 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); } function _sign(uint256 privateKey, bytes32 digest) private pure returns (bytes memory) {