From 211d65499c65f9fe2df29129be0a534f86772e7c Mon Sep 17 00:00:00 2001 From: Dan Oved Date: Tue, 13 Jun 2023 14:31:12 -0700 Subject: [PATCH] moved uid out of the token cofnig --- addresses/999.json | 6 ++-- package/preminter.test.ts | 16 ++++++--- package/preminter.ts | 5 ++- script/EstimatePreminterGas.s.sol | 8 ++--- src/premint/ZoraCreator1155Preminter.sol | 39 ++++++++++++--------- test/premint/ZoraCreator1155Preminter.t.sol | 36 ++++++++++--------- 6 files changed, 65 insertions(+), 45 deletions(-) diff --git a/addresses/999.json b/addresses/999.json index 50a50674e..7a9b28ffb 100644 --- a/addresses/999.json +++ b/addresses/999.json @@ -4,8 +4,8 @@ "FACTORY_PROXY": "0x6a357139C1bcDcf0B3AB9bC447932dDdcb956703", "FIXED_PRICE_SALE_STRATEGY": "0xd81351363b7d80b06E4Ec4De7989f0f91e41A846", "MERKLE_MINT_SALE_STRATEGY": "0x2c4457D38A329526063b26a2bB2C31B61553Aa98", - "PREMINTER": "0xAa2c4F9e2EB8c94bcf12771bb09f7B1cc8486D12", + "PREMINTER": "0x02be886A3b2802177181f4734380CB1f4BaC4Bfb", "REDEEM_MINTER_FACTORY": "0x27817bAef1341De9Ad04097Bbba4Ea8dA32c8552", - "timestamp": 1686690340, - "commit": "33d7642" + "timestamp": 1686691999, + "commit": "80ec7f9" } \ No newline at end of file diff --git a/package/preminter.test.ts b/package/preminter.test.ts index 3480afa56..f8c848056 100644 --- a/package/preminter.test.ts +++ b/package/preminter.test.ts @@ -122,14 +122,16 @@ describe("ZoraCreator1155Preminter", () => { royaltyMintSchedule: 30, royaltyBPS: 200, royaltyRecipient: creatorAccount, - uid: 1n, }; + const uid = 105n; + const signedMessage = await walletClient.signTypedData({ ...preminterTypedDataDefinition({ preminterAddress, chainId: foundry.id, contractConfig, + uid, tokenConfig, }), account: creatorAccount, @@ -139,7 +141,7 @@ describe("ZoraCreator1155Preminter", () => { abi: preminterAbi, address: preminterAddress, functionName: "recoverSigner", - args: [contractConfig, tokenConfig, signedMessage], + args: [contractConfig, tokenConfig, uid, signedMessage], }); expect(recoveredAddress).to.equal(creatorAccount); @@ -155,6 +157,8 @@ describe("ZoraCreator1155Preminter", () => { contractName: "My fun NFT", }; + const uid = 1n; + const tokenConfig: TokenCreationConfig = { tokenURI: "ipfs://tokenIpfsId0", maxSupply: 100n, @@ -164,7 +168,6 @@ describe("ZoraCreator1155Preminter", () => { royaltyMintSchedule: 30, royaltyBPS: 200, royaltyRecipient: creatorAccount, - uid: 1n, }; // have creator sign the message to create the contract @@ -175,6 +178,7 @@ describe("ZoraCreator1155Preminter", () => { chainId: foundry.id, contractConfig, tokenConfig, + uid }), // signer account is the creator account: creatorAccount, @@ -200,6 +204,7 @@ describe("ZoraCreator1155Preminter", () => { args: [ contractConfig, tokenConfig, + uid, signedMessage, quantityToMint, comment, @@ -239,13 +244,14 @@ describe("ZoraCreator1155Preminter", () => { // get token balance - should be amount that was created expect(tokenBalance).toBe(quantityToMint); + const uid2 = uid + 1n; + // create a signature to create a second token, // with different ipfs url and price per token const tokenConfig2: TokenCreationConfig = { ...tokenConfig, tokenURI: "ipfs://tokenIpfsId2", pricePerToken: parseEther("0.05"), - uid: 2n, }; const signedMessage2 = await walletClient.signTypedData({ @@ -254,6 +260,7 @@ describe("ZoraCreator1155Preminter", () => { chainId: foundry.id, contractConfig, tokenConfig: tokenConfig2, + uid: uid2 }), account: creatorAccount, }); @@ -272,6 +279,7 @@ describe("ZoraCreator1155Preminter", () => { args: [ contractConfig, tokenConfig2, + uid2, signedMessage2, quantityToMint2, comment, diff --git a/package/preminter.ts b/package/preminter.ts index 820e7cea8..e8fd37e19 100644 --- a/package/preminter.ts +++ b/package/preminter.ts @@ -20,17 +20,20 @@ export const preminterTypedDataDefinition = ({ preminterAddress, contractConfig, tokenConfig, + uid, chainId, }: { preminterAddress: Address; contractConfig: ContractCreationConfig; tokenConfig: TokenCreationConfig; + uid: bigint, chainId: number; }) => { const types = { ContractAndToken: [ { name: "contractConfig", type: "ContractCreationConfig" }, { name: "tokenConfig", type: "TokenCreationConfig" }, + { name: 'uid', type: 'uint256'} ], ContractCreationConfig: [ { name: "contractAdmin", type: "address" }, @@ -46,7 +49,6 @@ export const preminterTypedDataDefinition = ({ { name: "royaltyMintSchedule", type: "uint32" }, { name: "royaltyBPS", type: "uint32" }, { name: "royaltyRecipient", type: "address" }, - { name: "uid", type: "uint256" }, ], }; @@ -61,6 +63,7 @@ export const preminterTypedDataDefinition = ({ message: { contractConfig, tokenConfig, + uid }, primaryType: "ContractAndToken", }; diff --git a/script/EstimatePreminterGas.s.sol b/script/EstimatePreminterGas.s.sol index 9e50a3311..4b01c4b06 100644 --- a/script/EstimatePreminterGas.s.sol +++ b/script/EstimatePreminterGas.s.sol @@ -53,15 +53,15 @@ contract EstimatePreminterGas is ZoraDeployerBase { saleDuration: 365 days, royaltyBPS: 10, royaltyRecipient: deployer, - royaltyMintSchedule: 20, - uid: 1 + royaltyMintSchedule: 20 }); // how many tokens are minted to the executor uint256 quantityToMint = 1; + uint256 uid = 100; uint256 valueToSend = quantityToMint * ZoraCreator1155Impl(address(factory.implementation())).mintFee(); - bytes32 digest = preminter.premintHashData(contractConfig, tokenConfig, chainId()); + bytes32 digest = preminter.premintHashData(contractConfig, tokenConfig, uid, chainId()); uint256 privateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); @@ -75,7 +75,7 @@ contract EstimatePreminterGas is ZoraDeployerBase { // now do an on-chain premint vm.startBroadcast(deployer); - preminter.premint{value: valueToSend}(contractConfig, tokenConfig, signature, quantityToMint, comment); + preminter.premint{value: valueToSend}(contractConfig, tokenConfig, uid, signature, quantityToMint, comment); vm.stopBroadcast(); } diff --git a/src/premint/ZoraCreator1155Preminter.sol b/src/premint/ZoraCreator1155Preminter.sol index 204b07ae5..34b95ae52 100644 --- a/src/premint/ZoraCreator1155Preminter.sol +++ b/src/premint/ZoraCreator1155Preminter.sol @@ -70,10 +70,6 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId { uint32 royaltyBPS; /// @notice RoyaltyRecipient for created tokens. The address that will receive the royalty payments. address royaltyRecipient; - /// @notice Unique id of the token, used to ensure that multiple signatures can't be used to create the same intended token, in the case - /// that a signature is updated for a token, and the old signature is executed, two tokens for the same original intended token could be created. - /// Only one signature per token id, scoped to the contract hash can be executed. - uint256 uid; } event Preminted( @@ -95,6 +91,10 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId { function premint( ContractCreationConfig calldata contractConfig, TokenCreationConfig calldata tokenConfig, + /// @notice Unique id of the token, used to ensure that multiple signatures can't be used to create the same intended token, in the case + /// that a signature is updated for a token, and the old signature is executed, two tokens for the same original intended token could be created. + /// Only one signature per token id, scoped to the contract hash can be executed. + uint256 uid, bytes calldata signature, uint256 quantityToMint, string calldata mintComment @@ -113,12 +113,12 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId { // validate the signature for the current chain id, and make sure it hasn't been used, marking // that it has been used - _validateSignatureAndEnsureNotUsed(contractConfig, tokenConfig, signature); + _validateSignatureAndEnsureNotUsed(contractConfig, tokenConfig, uid, signature); // setup the new token, and its sales config newTokenId = _setupNewTokenAndSale(tokenContract, contractConfig.contractAdmin, tokenConfig); - emit Preminted(contractAddress, newTokenId, isNewContract, contractHash, tokenConfig.uid, contractConfig, tokenConfig, msg.sender, quantityToMint); + emit Preminted(contractAddress, newTokenId, isNewContract, contractHash, uid, contractConfig, tokenConfig, msg.sender, quantityToMint); // mint the initial x tokens for this new token id to the executor. address tokenRecipient = msg.sender; @@ -239,12 +239,14 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId { function recoverSigner( ContractCreationConfig calldata contractConfig, TokenCreationConfig calldata tokenConfig, + uint256 uid, bytes calldata signature ) public view returns (address signatory) { // first validate the signature - the creator must match the signer of the message bytes32 digest = premintHashData( contractConfig, tokenConfig, + uid, // here we pass the current chain id, ensuring that the message // only works for the current chain id block.chainid @@ -256,9 +258,10 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId { function premintHashData( ContractCreationConfig calldata contractConfig, TokenCreationConfig calldata tokenConfig, + uint256 uid, uint256 chainId ) public view returns (bytes32) { - bytes32 encoded = _hashContractAndToken(contractConfig, tokenConfig); + bytes32 encoded = _hashContractAndToken(contractConfig, tokenConfig, uid); // build the struct hash to be signed // here we pass the chain id, allowing the message to be signed for another chain @@ -267,16 +270,20 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId { bytes32 constant CONTRACT_AND_TOKEN_DOMAIN = keccak256( - "ContractAndToken(ContractCreationConfig contractConfig,TokenCreationConfig tokenConfig)ContractCreationConfig(address contractAdmin,string contractURI,string contractName)TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 saleDuration,uint32 royaltyMintSchedule,uint32 royaltyBPS,address royaltyRecipient,uint256 uid)" + "ContractAndToken(ContractCreationConfig contractConfig,TokenCreationConfig tokenConfig,uint256 uid)ContractCreationConfig(address contractAdmin,string contractURI,string contractName)TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 saleDuration,uint32 royaltyMintSchedule,uint32 royaltyBPS,address royaltyRecipient)" ); - function _hashContractAndToken(ContractCreationConfig calldata contractConfig, TokenCreationConfig calldata tokenConfig) private pure returns (bytes32) { - return keccak256(abi.encode(CONTRACT_AND_TOKEN_DOMAIN, _hashContract(contractConfig), _hashToken(tokenConfig))); + function _hashContractAndToken( + ContractCreationConfig calldata contractConfig, + TokenCreationConfig calldata tokenConfig, + uint256 uid + ) private pure returns (bytes32) { + return keccak256(abi.encode(CONTRACT_AND_TOKEN_DOMAIN, _hashContract(contractConfig), _hashToken(tokenConfig), uid)); } bytes32 constant TOKEN_DOMAIN = keccak256( - "TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 saleDuration,uint32 royaltyMintSchedule,uint32 royaltyBPS,address royaltyRecipient,uint256 uid)" + "TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 saleDuration,uint32 royaltyMintSchedule,uint32 royaltyBPS,address royaltyRecipient)" ); function _hashToken(TokenCreationConfig calldata tokenConfig) private pure returns (bytes32) { @@ -291,8 +298,7 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId { tokenConfig.saleDuration, tokenConfig.royaltyMintSchedule, tokenConfig.royaltyBPS, - tokenConfig.royaltyRecipient, - tokenConfig.uid + tokenConfig.royaltyRecipient ) ); } @@ -320,10 +326,11 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId { function _validateSignatureAndEnsureNotUsed( ContractCreationConfig calldata contractConfig, TokenCreationConfig calldata tokenConfig, + uint256 uid, bytes calldata signature ) private returns (uint256 tokenHash) { // first validate the signature - the creator must match the signer of the message - address signatory = recoverSigner(contractConfig, tokenConfig, signature); + address signatory = recoverSigner(contractConfig, tokenConfig, uid, signature); if (signatory != contractConfig.contractAdmin) { revert("Invalid signature"); @@ -331,9 +338,9 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId { // make sure that this signature hasn't been used // token hash includes the contract hash, so we can check uniqueness of contract + token pair - tokenHash = contractAndTokenHash(contractConfig, tokenConfig.uid); + tokenHash = contractAndTokenHash(contractConfig, uid); if (tokenCreated[tokenHash]) { - revert TokenAlreadyCreated(contractConfig.contractAdmin, contractConfig.contractURI, contractConfig.contractName, tokenConfig.uid); + revert TokenAlreadyCreated(contractConfig.contractAdmin, contractConfig.contractURI, contractConfig.contractName, uid); } tokenCreated[tokenHash] = true; diff --git a/test/premint/ZoraCreator1155Preminter.t.sol b/test/premint/ZoraCreator1155Preminter.t.sol index a6cf3c855..3946ff98e 100644 --- a/test/premint/ZoraCreator1155Preminter.t.sol +++ b/test/premint/ZoraCreator1155Preminter.t.sol @@ -74,8 +74,7 @@ contract ZoraCreator1155PreminterTest is Test { saleDuration: 0, royaltyMintSchedule: defaultRoyaltyConfig.royaltyMintSchedule, royaltyBPS: defaultRoyaltyConfig.royaltyBPS, - royaltyRecipient: defaultRoyaltyConfig.royaltyRecipient, - uid: 1 + royaltyRecipient: defaultRoyaltyConfig.royaltyRecipient }); } @@ -91,10 +90,11 @@ contract ZoraCreator1155PreminterTest is Test { // how many tokens are minted to the executor uint256 quantityToMint = 4; uint256 chainId = block.chainid; + uint256 uid = 1; string memory comment = "hi"; // 2. Call smart contract to get digest to sign for creation params. - bytes32 digest = preminter.premintHashData(contractConfig, tokenConfig, chainId); + bytes32 digest = preminter.premintHashData(contractConfig, tokenConfig, uid, chainId); // 3. Sign the digest // create a signature with the digest for the params @@ -105,7 +105,7 @@ contract ZoraCreator1155PreminterTest is 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, tokenConfig, signature, quantityToMint, comment); + (, uint256 tokenId) = preminter.premint(contractConfig, tokenConfig, uid, signature, quantityToMint, comment); // get contract hash, which is unique per contract creation config, and can be used // retreive the address created for a contract @@ -120,13 +120,14 @@ contract ZoraCreator1155PreminterTest is Test { // alter the token creation config, create a new signature with the existing // contract config and new token config tokenConfig.tokenURI = "blah2.token"; - tokenConfig.uid = 2; - digest = preminter.premintHashData(contractConfig, tokenConfig, chainId); + uid++; + + digest = preminter.premintHashData(contractConfig, tokenConfig, uid, chainId); signature = _sign(creatorPrivateKey, digest); // premint with new token config and signature vm.prank(premintExecutor); - (, tokenId) = preminter.premint(contractConfig, tokenConfig, signature, quantityToMint, comment); + (, tokenId) = preminter.premint(contractConfig, tokenConfig, uid, signature, quantityToMint, 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 @@ -145,9 +146,10 @@ contract ZoraCreator1155PreminterTest is Test { // how many tokens are minted to the executor uint256 quantityToMint = 4; uint256 chainId = block.chainid; + uint256 uid = 1; // 2. Call smart contract to get digest to sign for creation params. - bytes32 digest = preminter.premintHashData(contractConfig, tokenConfig, chainId); + bytes32 digest = preminter.premintHashData(contractConfig, tokenConfig, uid, chainId); // 3. Sign the digest // create a signature with the digest for the params @@ -160,11 +162,11 @@ contract ZoraCreator1155PreminterTest is Test { // now call the premint function, using the same config that was used to generate the digest, and the signature vm.startPrank(premintExecutor); - preminter.premint(contractConfig, tokenConfig, signature, quantityToMint, comment); + preminter.premint(contractConfig, tokenConfig, uid, signature, quantityToMint, comment); // create a sig for another token with same uid, it should revert tokenConfig.tokenURI = "blah2.token"; - digest = preminter.premintHashData(contractConfig, tokenConfig, chainId); + digest = preminter.premintHashData(contractConfig, tokenConfig, uid, chainId); signature = _sign(creatorPrivateKey, digest); // premint with new token config and signature - it should revert @@ -174,10 +176,10 @@ contract ZoraCreator1155PreminterTest is Test { contractConfig.contractAdmin, contractConfig.contractURI, contractConfig.contractName, - tokenConfig.uid + uid ) ); - preminter.premint(contractConfig, tokenConfig, signature, quantityToMint, comment); + preminter.premint(contractConfig, tokenConfig, uid, signature, quantityToMint, comment); } function test_emitsPremint_whenNewContract() external { @@ -189,9 +191,10 @@ contract ZoraCreator1155PreminterTest is Test { // how many tokens are minted to the executor uint256 quantityToMint = 4; uint256 chainId = block.chainid; + uint256 uid = 10; // 2. Call smart contract to get digest to sign for creation params. - bytes32 digest = preminter.premintHashData(contractConfig, tokenConfig, chainId); + bytes32 digest = preminter.premintHashData(contractConfig, tokenConfig, uid, chainId); // 3. Sign the digest // create a signature with the digest for the params @@ -206,7 +209,7 @@ contract ZoraCreator1155PreminterTest is Test { // we need the contract address to assert the emitted event, so lets premint, get the contract address, rollback, and premint again uint256 snapshot = vm.snapshot(); - (address contractAddress, uint256 tokenId) = preminter.premint(contractConfig, tokenConfig, signature, quantityToMint, comment); + (address contractAddress, uint256 tokenId) = preminter.premint(contractConfig, tokenConfig, uid, signature, quantityToMint, comment); vm.revertTo(snapshot); // vm.roll(currentBlock + 1); @@ -219,14 +222,13 @@ contract ZoraCreator1155PreminterTest is Test { tokenId, createdNewContract, preminter.contractDataHash(contractConfig), - tokenConfig.uid, + uid, contractConfig, tokenConfig, premintExecutor, quantityToMint ); - // emit ContractCreated(preminter.contractDataHash(contractConfig), contractAddress, contractConfig); - preminter.premint(contractConfig, tokenConfig, signature, quantityToMint, comment); + preminter.premint(contractConfig, tokenConfig, uid, signature, quantityToMint, comment); } function _sign(uint256 privateKey, bytes32 digest) private pure returns (bytes memory) {