diff --git a/packages/1155-contracts/src/delegation/ZoraCreator1155Attribution.sol b/packages/1155-contracts/src/delegation/ZoraCreator1155Attribution.sol index 6f24664b7..55a1b7cc6 100644 --- a/packages/1155-contracts/src/delegation/ZoraCreator1155Attribution.sol +++ b/packages/1155-contracts/src/delegation/ZoraCreator1155Attribution.sol @@ -234,25 +234,17 @@ library ZoraCreator1155Attribution { /// @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 + function isAuthorizedToCreatePremint( + address signer, + address premintContractConfigContractAdmin, + address contractAddress + ) internal view returns (bool authorized) { + // if contract hasn't been created, signer must be the contract admin on the premint config if (contractAddress.code.length == 0) { - isValid = recoveredSigner == originalPremintCreator; + return signer == premintContractConfigContractAdmin; } else { // if contract has been created, signer must have mint new token permission - isValid = IZoraCreator1155(contractAddress).isAdminOrRole(recoveredSigner, CONTRACT_BASE_ID, PERMISSION_BIT_MINTER); + authorized = IZoraCreator1155(contractAddress).isAdminOrRole(signer, CONTRACT_BASE_ID, PERMISSION_BIT_MINTER); } } } diff --git a/packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImpl.sol b/packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImpl.sol index 6f8332340..ec360e993 100644 --- a/packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImpl.sol +++ b/packages/1155-contracts/src/delegation/ZoraCreator1155PremintExecutorImpl.sol @@ -153,7 +153,7 @@ contract ZoraCreator1155PremintExecutorImpl is return (true, ERC1155DelegationStorageV1(contractAddress).delegatedTokenId(uid)); } - // @custom:deprecated use isValidSignatureV1 instead + // @custom:deprecated use isAuthorizedToCreatePremint instead function isValidSignature( ContractCreationConfig calldata contractConfig, PremintConfig calldata premintConfig, @@ -161,57 +161,34 @@ contract ZoraCreator1155PremintExecutorImpl is ) public view returns (bool isValid, address contractAddress, address recoveredSigner) { contractAddress = getContractAddress(contractConfig); - (isValid, recoveredSigner) = isValidSignatureV1(contractConfig.contractAdmin, contractAddress, premintConfig, signature); - } - - /// @notice Recovers the signer of a premint, and checks if the signer is authorized to sign the premint. - /// @dev for use with v1 of premint config, PremintConfig - /// @param premintContractConfigContractAdmin If this contract was created via premint, the original contractConfig.contractAdmin. Otherwise, set to address(0) - /// @param contractAddress The determinstic 1155 contract address the premint is for - /// @param premintConfig The premint config - /// @param signature The signature of the premint - /// @return isValid Whether the signature is valid - /// @return recoveredSigner The signer of the premint - function isValidSignatureV1( - address premintContractConfigContractAdmin, - address contractAddress, - PremintConfig calldata premintConfig, - bytes calldata signature - ) public view returns (bool isValid, address recoveredSigner) { - bytes32 hashedPremint = ZoraCreator1155Attribution.hashPremint(premintConfig); - - (isValid, recoveredSigner) = ZoraCreator1155Attribution.isValidSignature( - premintContractConfigContractAdmin, + recoveredSigner = ZoraCreator1155Attribution.recoverSignerHashed( + ZoraCreator1155Attribution.hashPremint(premintConfig), + signature, contractAddress, - hashedPremint, ZoraCreator1155Attribution.HASHED_VERSION_1, - signature + block.chainid ); + + if (recoveredSigner == address(0)) { + return (false, address(0), recoveredSigner); + } + + isValid = isAuthorizedToCreatePremint(recoveredSigner, contractConfig.contractAdmin, contractAddress); } - /// @notice Recovers the signer of a premint, and checks if the signer is authorized to sign the premint. - /// @dev for use with v2 of premint config, PremintConfig + /// @notice Checks if the signer of a premint is authorized to sign a premint for a given contract. If the contract hasn't been created yet, + /// then the signer is authorized if the signer's address matches contractConfig.contractAdmin. Otherwise, the signer must have the PERMISSION_BIT_MINTER + /// role on the contract + /// @param signer The signer of the premint /// @param premintContractConfigContractAdmin If this contract was created via premint, the original contractConfig.contractAdmin. Otherwise, set to address(0) /// @param contractAddress The determinstic 1155 contract address the premint is for - /// @param premintConfig The premint config - /// @param signature The signature of the premint - /// @return isValid Whether the signature is valid - /// @return recoveredSigner The signer of the premint - function isValidSignatureV2( + /// @return isAuthorized Whether the signer is authorized + function isAuthorizedToCreatePremint( + address signer, address premintContractConfigContractAdmin, - address contractAddress, - PremintConfigV2 calldata premintConfig, - bytes calldata signature - ) public view returns (bool isValid, address recoveredSigner) { - bytes32 hashedPremint = ZoraCreator1155Attribution.hashPremint(premintConfig); - - (isValid, recoveredSigner) = ZoraCreator1155Attribution.isValidSignature( - premintContractConfigContractAdmin, - contractAddress, - hashedPremint, - ZoraCreator1155Attribution.HASHED_VERSION_2, - signature - ); + address contractAddress + ) public view returns (bool isAuthorized) { + return ZoraCreator1155Attribution.isAuthorizedToCreatePremint(signer, premintContractConfigContractAdmin, contractAddress); } /// @notice Returns the versions of the premint signature that the contract supports diff --git a/packages/1155-contracts/src/interfaces/IZoraCreator1155PremintExecutor.sol b/packages/1155-contracts/src/interfaces/IZoraCreator1155PremintExecutor.sol index 8c4c92ef8..967166cfc 100644 --- a/packages/1155-contracts/src/interfaces/IZoraCreator1155PremintExecutor.sol +++ b/packages/1155-contracts/src/interfaces/IZoraCreator1155PremintExecutor.sol @@ -27,6 +27,12 @@ interface ILegacyZoraCreator1155PremintExecutor { uint256 quantityToMint, string calldata mintComment ) external payable returns (uint256 newTokenId); + + function isAuthorizedToCreatePremint( + address signer, + address premintContractConfigContractAdmin, + address contractAddress + ) external view returns (bool isAuthorized); } interface IZoraCreator1155PremintExecutorV1 { @@ -37,13 +43,6 @@ interface IZoraCreator1155PremintExecutorV1 { uint256 quantityToMint, IZoraCreator1155PremintExecutor.MintArguments calldata mintArguments ) external payable returns (IZoraCreator1155PremintExecutor.PremintResult memory); - - function isValidSignatureV1( - address originalContractAdmin, - address contractAddress, - PremintConfig calldata premintConfig, - bytes calldata signature - ) external view returns (bool isValid, address recoveredSigner); } interface IZoraCreator1155PremintExecutorV2 { @@ -54,13 +53,6 @@ interface IZoraCreator1155PremintExecutorV2 { uint256 quantityToMint, IZoraCreator1155PremintExecutor.MintArguments calldata mintArguments ) external payable returns (IZoraCreator1155PremintExecutor.PremintResult memory); - - function isValidSignatureV2( - address originalContractAdmin, - address contractAddress, - PremintConfigV2 calldata premintConfig, - bytes calldata signature - ) external view returns (bool isValid, address recoveredSigner); } interface IZoraCreator1155PremintExecutor is diff --git a/packages/1155-contracts/test/premint/ZoraCreator1155PremintExecutor.t.sol b/packages/1155-contracts/test/premint/ZoraCreator1155PremintExecutor.t.sol index 11e15b0fa..9ff66fc4e 100644 --- a/packages/1155-contracts/test/premint/ZoraCreator1155PremintExecutor.t.sol +++ b/packages/1155-contracts/test/premint/ZoraCreator1155PremintExecutor.t.sol @@ -132,7 +132,7 @@ contract ZoraCreator1155PreminterTest is Test { assertTrue(isValid); // now check using new method - (isValid, ) = preminter.isValidSignatureV1(contractConfig.contractAdmin, contractAddress, premintConfig, signature); + isValid = preminter.isAuthorizedToCreatePremint(creator, contractConfig.contractAdmin, contractAddress); assertTrue(isValid); // now call the premint function, using the same config that was used to generate the digest, and the signature @@ -667,18 +667,13 @@ contract ZoraCreator1155PreminterTest is Test { address contractAddress = preminter.getContractAddress(contractConfig); - // sign and execute premint - bytes memory signature = _signPremint(contractAddress, premintConfig, creatorPrivateKey, block.chainid); - - (bool isValidSignature, address recoveredSigner) = preminter.isValidSignatureV2( - contractConfig.contractAdmin, - contractAddress, - premintConfig, - signature - ); + bool isValidSignature = preminter.isAuthorizedToCreatePremint({ + signer: creator, + premintContractConfigContractAdmin: contractConfig.contractAdmin, + contractAddress: contractAddress + }); - assertEq(creator, recoveredSigner, "recovered the wrong signer"); - assertTrue(isValidSignature, "signature should be valid"); + assertTrue(isValidSignature, "creator should be allowed to create premint before contract created"); _signAndExecutePremint(contractConfig, premintConfig, creatorPrivateKey, block.chainid, premintExecutor, 1, "hi"); @@ -694,9 +689,13 @@ contract ZoraCreator1155PreminterTest is Test { bytes memory newCreatorSignature = _signPremint(contractAddress, premintConfig2, newCreatorPrivateKey, block.chainid); // it should not be considered a valid signature - (isValidSignature, ) = preminter.isValidSignatureV2(contractConfig.contractAdmin, contractAddress, premintConfig2, newCreatorSignature); + isValidSignature = preminter.isAuthorizedToCreatePremint({ + signer: newCreator, + premintContractConfigContractAdmin: contractConfig.contractAdmin, + contractAddress: contractAddress + }); - assertFalse(isValidSignature, "signature should not be valid"); + assertFalse(isValidSignature, "alternative creator should not be allowed to create a premint"); uint256 quantityToMint = 1; uint256 mintCost = mintFeeAmount * quantityToMint; @@ -712,7 +711,11 @@ contract ZoraCreator1155PreminterTest is Test { IZoraCreator1155(contractAddress).addPermission(CONTRACT_BASE_ID, newCreator, PERMISSION_BIT_MINTER); // should now be considered a valid signature - (isValidSignature, ) = preminter.isValidSignatureV2(contractConfig.contractAdmin, contractAddress, premintConfig2, newCreatorSignature); + isValidSignature = preminter.isAuthorizedToCreatePremint({ + signer: newCreator, + premintContractConfigContractAdmin: contractConfig.contractAdmin, + contractAddress: contractAddress + }); assertTrue(isValidSignature, "valid signature after granted permission"); vm.deal(premintExecutor, mintCost); diff --git a/packages/premint-sdk/src/preminter.test.ts b/packages/premint-sdk/src/preminter.test.ts index 3b13fca39..d40b956e5 100644 --- a/packages/premint-sdk/src/preminter.test.ts +++ b/packages/premint-sdk/src/preminter.test.ts @@ -24,6 +24,7 @@ import { ContractCreationConfig, PremintConfig, TokenCreationConfig, + isValidSignatureV1, preminterTypedDataDefinition, } from "./preminter"; @@ -191,14 +192,17 @@ describe("ZoraCreator1155Preminter", () => { }); // recover and verify address is correct - const [,,recoveredAddress] = await publicClient.readContract({ - abi: preminterAbi, - address: preminterAddress, - functionName: "isValidSignature", - args: [contractConfig, premintConfig, signedMessage], + const { recoveredAddress, isAuthorized } = await isValidSignatureV1({ + contractAddress, + chainId: anvilChainId, + originalContractAdmin: contractConfig.contractAdmin, + premintConfig, + publicClient, + signature: signedMessage }); expect(recoveredAddress).to.equal(creatorAccount); + expect(isAuthorized).toBe(true); }, 20 * 1000, diff --git a/packages/premint-sdk/src/preminter.ts b/packages/premint-sdk/src/preminter.ts index dd9a93b0a..c0263a943 100644 --- a/packages/premint-sdk/src/preminter.ts +++ b/packages/premint-sdk/src/preminter.ts @@ -1,7 +1,7 @@ import { Address } from "abitype"; import { ExtractAbiFunction, AbiParametersToPrimitiveTypes } from "abitype"; -import { zoraCreator1155PremintExecutorImplABI as preminterAbi } from "@zoralabs/protocol-deployments"; -import { TypedDataDefinition } from "viem"; +import { zoraCreator1155PremintExecutorImplABI as preminterAbi, zoraCreator1155PremintExecutorImplAddress } from "@zoralabs/protocol-deployments"; +import { TypedDataDefinition, recoverTypedDataAddress, Hex, PublicClient } from "viem"; type PremintInputs = ExtractAbiFunction< typeof preminterAbi, @@ -14,6 +14,7 @@ export type ContractCreationConfig = PreminterHashDataTypes[0]; export type PremintConfig = PreminterHashDataTypes[1]; export type TokenCreationConfig = PremintConfig["tokenConfig"]; + // Convenience method to create the structured typed data // needed to sign for a premint contract and token export const preminterTypedDataDefinition = ({ @@ -72,3 +73,63 @@ export const preminterTypedDataDefinition = ({ return result; }; + +export async function isValidSignatureV1({ + contractAddress, + originalContractAdmin, + premintConfig, + signature, + chainId, + publicClient +}: { + contractAddress: Address, + originalContractAdmin: Address, + premintConfig: PremintConfig, + signature: Hex, + chainId: number, + publicClient: PublicClient + }): Promise<{ + isAuthorized: boolean; + recoveredAddress?: Address; + }> { + const typedData = preminterTypedDataDefinition({ + verifyingContract: contractAddress, + premintConfig, + chainId, + }); + + // recover the address from the signature + let recoveredAddress: Address; + + try { + recoveredAddress = await recoverTypedDataAddress({ + ...typedData, + signature, + }); + } catch (error) { + console.error(error); + + return { + isAuthorized: false, + } + } + + // premint executor is same address on all chains + const premintExecutorAddress = zoraCreator1155PremintExecutorImplAddress[999]; + + const isAuthorized = await publicClient.readContract({ + abi: preminterAbi, + address: premintExecutorAddress, + functionName: 'isAuthorizedToCreatePremint', + args: [ + recoveredAddress, + originalContractAdmin, + contractAddress + ] + }); + + return { + isAuthorized, + recoveredAddress + }; +}