From 1863881871c8fe3ebd609e96792b6299170eb334 Mon Sep 17 00:00:00 2001 From: iain nash Date: Fri, 20 Oct 2023 14:39:33 -0400 Subject: [PATCH] add premint --- packages/1155-contracts/.env.anvil | 2 - packages/1155-contracts/package.json | 50 +--- packages/premint-sdk/README.md | 0 packages/premint-sdk/package.json | 14 +- packages/premint-sdk/src/preminter.test.ts | 268 ++++++++++++--------- packages/premint-sdk/tsconfig.json | 25 ++ packages/premint-sdk/tsup.config.js | 2 +- 7 files changed, 194 insertions(+), 167 deletions(-) delete mode 100644 packages/1155-contracts/.env.anvil create mode 100644 packages/premint-sdk/README.md create mode 100644 packages/premint-sdk/tsconfig.json diff --git a/packages/1155-contracts/.env.anvil b/packages/1155-contracts/.env.anvil deleted file mode 100644 index 761c2b1ad..000000000 --- a/packages/1155-contracts/.env.anvil +++ /dev/null @@ -1,2 +0,0 @@ -FORK_RPC_URL="https://testnet.rpc.zora.co/" -FORK_BLOCK_NUMBER=1433428 \ No newline at end of file diff --git a/packages/1155-contracts/package.json b/packages/1155-contracts/package.json index ac3a587ba..affdea9d1 100644 --- a/packages/1155-contracts/package.json +++ b/packages/1155-contracts/package.json @@ -6,28 +6,6 @@ "license": "MIT", "main": "./dist/index.js", "types": "./dist/package/index.d.ts", - "exports": { - "./premint-api": { - "require": "./dist/premint-api.cjs", - "import": "./dist/premint-api.js", - "types": "./dist/package/premint-api.d.ts" - }, - ".": { - "require": "./dist/index.cjs", - "import": "./dist/index.js", - "types": "./dist/package/index.d.ts" - } - }, - "typesVersions": { - "*": { - ".": [ - "./dist/package/index.d.ts" - ], - "./premint-api": [ - "./dist/package/premint-api.d.ts" - ] - } - }, "type": "module", "scripts": { "test": "forge test", @@ -49,9 +27,7 @@ "bundle-configs": "node script/bundle-chainConfigs.mjs && yarn prettier", "wagmi": "wagmi generate", "storage-inspect:check": "./script/storage-check.sh check ZoraCreator1155Impl ZoraCreator1155FactoryImpl", - "storage-inspect:generate": "./script/storage-check.sh generate ZoraCreator1155Impl ZoraCreator1155FactoryImpl", - "js-test:watch": "vitest dev", - "anvil": "source .env.anvil && anvil --fork-url $FORK_RPC_URL --fork-block-number $FORK_BLOCK_NUMBER --chain-id 31337" + "storage-inspect:generate": "./script/storage-check.sh generate ZoraCreator1155Impl ZoraCreator1155FactoryImpl" }, "files": [ "dist/", @@ -65,29 +41,25 @@ ], "dependencies": { "@openzeppelin/contracts": "4.9.2", - "@wagmi/cli": "^1.0.1", "@zoralabs/openzeppelin-contracts-upgradeable": "4.8.4", "@zoralabs/protocol-rewards": "*", - "abitype": "^0.8.7", "ds-test": "https://github.com/dapphub/ds-test#cd98eff28324bfac652e63a239a60632a761790b", - "es-main": "^1.2.0", "forge-std": "https://github.com/foundry-rs/forge-std#705263c95892a906d7af65f0f73ce8a4a0c80b80", - "glob": "^10.2.2", - "prettier": "^2.8.8", - "prettier-plugin-solidity": "^1.1.3", "solady": "^0.0.123", - "solmate": "^6.1.0", - "tsup": "^7.2.0", - "tsx": "^3.13.0", - "typescript": "^5.0.4", - "viem": "^1.16.2", - "vite": "^4.1.4", - "vitest": "~0.30.1" + "solmate": "^6.1.0" }, "devDependencies": { + "@wagmi/cli": "^1.0.1", + "tsx": "^3.13.0", + "glob": "^10.2.2", + "prettier": "^2.8.8", + "es-main": "^1.2.0", "@turnkey/api-key-stamper": "^0.1.1", + "prettier-plugin-solidity": "^1.1.3", "@turnkey/http": "^1.2.0", "@turnkey/viem": "^0.2.4", - "@types/node": "^20.1.2" + "@types/node": "^20.1.2", + "tsup": "^7.2.0", + "typescript": "^5.0.4" } } diff --git a/packages/premint-sdk/README.md b/packages/premint-sdk/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/premint-sdk/package.json b/packages/premint-sdk/package.json index 8b3c764ec..5b90d27c0 100644 --- a/packages/premint-sdk/package.json +++ b/packages/premint-sdk/package.json @@ -1,16 +1,20 @@ { - "name": "premint-sdk", - "version": "1.0.0", - "main": "index.js", + "name": "@zoralabs/premint-sdk", + "version": "0.0.1", "repository": "https://github.com/ourzora/zora-protocol", "license": "MIT", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "type": "module", "scripts": { "build": "tsup", "prepack": "yarn build", - "test": "vitest src" + "test": "vitest src", + "anvil": "source .env.anvil && anvil --fork-url $FORK_RPC_URL --fork-block-number $FORK_BLOCK_NUMBER --chain-id 31337" }, "dependencies": { - "@zoralabs/zora-1155-contracts": "*" + "@zoralabs/zora-1155-contracts": "*", + "abitype": "^0.8.7" }, "devDependencies": { "typescript": "^5.2.2", diff --git a/packages/premint-sdk/src/preminter.test.ts b/packages/premint-sdk/src/preminter.test.ts index ff4fe775d..22f5ae1a1 100644 --- a/packages/premint-sdk/src/preminter.test.ts +++ b/packages/premint-sdk/src/preminter.test.ts @@ -3,8 +3,13 @@ import { http, createWalletClient, createPublicClient, + keccak256, + Hex, + concat, + recoverAddress, + hashDomain, } from "viem"; -import { foundry, zoraTestnet } from "viem/chains"; +import { foundry, zora } from "viem/chains"; import { describe, it, beforeEach, expect } from "vitest"; import { parseEther } from "viem"; import { @@ -14,12 +19,7 @@ import { zoraCreator1155FactoryImplAddress, zoraCreator1155FactoryImplConfig, } from "@zoralabs/zora-1155-contracts"; -import ZoraCreator1155Attribution from "@zoralabs/zora-1155-contracts/out/ZoraCreator1155Attribution.sol/ZoraCreator1155Attribution.json"; -import zoraCreator1155PremintExecutor from "@zoralabs/zora-1155-contracts/out/ZoraCreator1155PremintExecutorImpl.sol/ZoraCreator1155PremintExecutorImpl.json"; -import zoraCreator1155Impl from "@zoralabs/zora-1155-contracts/out/ZoraCreator1155Impl.sol/ZoraCreator1155Impl.json"; -import zoraCreator1155FactoryImpl from "@zoralabs/zora-1155-contracts/out/ZoraCreator1155FactoryImpl.sol/ZoraCreator1155FactoryImpl.json"; -import zoraCreatorFixedPriceSaleStrategy from "@zoralabs/zora-1155-contracts/out/ZoraCreatorFixedPriceSaleStrategy.sol/ZoraCreatorFixedPriceSaleStrategy.json"; -import protocolRewards from "@zoralabs/zora-1155-contracts/out/ProtocolRewards.sol/ProtocolRewards.json"; + import { ContractCreationConfig, PremintConfig, @@ -50,15 +50,9 @@ const publicClient = createPublicClient({ type Address = `0x${string}`; -const zeroAddress: Address = "0x0000000000000000000000000000000000000000"; - // JSON-RPC Account -const [ - deployerAccount, - creatorAccount, - collectorAccount, - mintFeeRecipientAccount, -] = (await walletClient.getAddresses()) as [Address, Address, Address, Address]; +const [deployerAccount, creatorAccount, collectorAccount] = + (await walletClient.getAddresses()) as [Address, Address, Address, Address]; type TestContext = { preminterAddress: `0x${string}`; @@ -68,81 +62,6 @@ type TestContext = { fixedPriceMinterAddress: Address; }; -const deployContractAndGetAddress = async ( - args: Parameters[0] -) => { - const hash = await walletClient.deployContract(args); - return ( - await publicClient.waitForTransactionReceipt({ - hash, - }) - ).contractAddress!; -}; - -export const deployFactoryProxy = async () => { - console.log("deploying protocol rewards"); - const protocolRewardsAddress = await deployContractAndGetAddress({ - abi: protocolRewards.abi, - bytecode: protocolRewards.bytecode.object as `0x${string}`, - account: deployerAccount, - args: [], - }); - - console.log("deploying attribution lib"); - const attributionAddress = await deployContractAndGetAddress({ - abi: ZoraCreator1155Attribution.abi, - bytecode: ZoraCreator1155Attribution.bytecode.object as `0x${string}`, - account: deployerAccount, - }); - - console.log("attribution address is ", attributionAddress); - - console.log("deploying 1155"); - const zora1155Address = await deployContractAndGetAddress({ - abi: zoraCreator1155Impl.abi, - bytecode: zoraCreator1155Impl.bytecode.object as `0x${string}`, - account: deployerAccount, - args: [0n, mintFeeRecipientAccount, zeroAddress, protocolRewardsAddress], - }); - - console.log("deploying fixed priced minter"); - const fixedPriceMinterAddress = await deployContractAndGetAddress({ - abi: zoraCreatorFixedPriceSaleStrategy.abi, - bytecode: zoraCreatorFixedPriceSaleStrategy.bytecode - .object as `0x${string}`, - account: deployerAccount, - }); - - console.log("deploying factory impl"); - const factoryImplAddress = await deployContractAndGetAddress({ - abi: zoraCreator1155FactoryImpl.abi, - bytecode: zoraCreator1155FactoryImpl.bytecode.object as `0x${string}`, - account: deployerAccount, - args: [zora1155Address, zeroAddress, fixedPriceMinterAddress, zeroAddress], - }); - - const factoryProxyAddress = factoryImplAddress!; - - return { factoryProxyAddress, zora1155Address, fixedPriceMinterAddress }; -}; - -export const deployPreminterContract = async (factoryProxyAddress: Address) => { - const deployPreminterHash = await walletClient.deployContract({ - abi: zoraCreator1155PremintExecutor.abi, - bytecode: zoraCreator1155PremintExecutor.bytecode.object as `0x${string}`, - account: deployerAccount, - args: [factoryProxyAddress], - }); - - const receipt = await publicClient.waitForTransactionReceipt({ - hash: deployPreminterHash, - }); - - const preminterAddress = receipt.contractAddress!; - - return { preminterAddress, factoryProxyAddress }; -}; - // create token and contract creation config: const defaultContractConfig = ({ contractAdmin, @@ -155,7 +74,7 @@ const defaultContractConfig = ({ }); const defaultTokenConfig = ( - fixedPriceMinterAddress: Address + fixedPriceMinterAddress: Address, ): TokenCreationConfig => ({ tokenURI: "ipfs://tokenIpfsId0", maxSupply: 100n, @@ -176,8 +95,6 @@ const defaultPremintConfig = (fixedPriceMinter: Address): PremintConfig => ({ version: 0, }); -const useForkContract = true; - describe("ZoraCreator1155Preminter", () => { beforeEach(async (ctx) => { // deploy signature minter contract @@ -186,36 +103,22 @@ describe("ZoraCreator1155Preminter", () => { value: parseEther("10"), }); - ctx.forkedChainId = zoraTestnet.id; + ctx.forkedChainId = zora.id; ctx.anvilChainId = foundry.id; - let preminterAddress: Address; - - if (useForkContract) { - const factoryProxyAddress = - zoraCreator1155FactoryImplAddress[ctx.forkedChainId]; - ctx.fixedPriceMinterAddress = await publicClient.readContract({ - abi: zoraCreator1155FactoryImplConfig.abi, - address: zoraCreator1155FactoryImplAddress[ctx.forkedChainId], - functionName: "fixedPriceMinter", - }); - ctx.preminterAddress = zoraCreator1155PremintExecutorAddress[999]; - const deployed = await deployPreminterContract(factoryProxyAddress); - preminterAddress = deployed.preminterAddress; - } else { - const factoryProxyAddress = (await deployFactoryProxy()) - .factoryProxyAddress; - const deployed = await deployPreminterContract(factoryProxyAddress); - preminterAddress = deployed.preminterAddress; - } - + ctx.fixedPriceMinterAddress = await publicClient.readContract({ + abi: zoraCreator1155FactoryImplConfig.abi, + address: zoraCreator1155FactoryImplAddress[ctx.forkedChainId], + functionName: "fixedPriceMinter", + }); ctx.zoraMintFee = parseEther("0.000777"); - ctx.preminterAddress = preminterAddress; + ctx.preminterAddress = + zoraCreator1155PremintExecutorAddress[ctx.forkedChainId]; }, 20 * 1000); // skip for now - we need to make this work on zora testnet chain too - it.skip( + it( "can sign on the forked premint contract", async ({ fixedPriceMinterAddress, forkedChainId }) => { const premintConfig = defaultPremintConfig(fixedPriceMinterAddress); @@ -251,7 +154,7 @@ describe("ZoraCreator1155Preminter", () => { contractAddress, }); }, - 20 * 1000 + 20 * 1000, ); it( "can sign and recover a signature", @@ -294,7 +197,7 @@ describe("ZoraCreator1155Preminter", () => { expect(recoveredAddress).to.equal(creatorAccount); }, - 20 * 1000 + 20 * 1000, ); it( "can sign and mint multiple tokens", @@ -449,7 +352,7 @@ describe("ZoraCreator1155Preminter", () => { expect( (await publicClient.waitForTransactionReceipt({ hash: mintHash2 })) - .status + .status, ).toBe("success"); // now premint status for the second mint, it should be minted @@ -473,6 +376,131 @@ describe("ZoraCreator1155Preminter", () => { expect(tokenBalance2).toBe(quantityToMint2); }, // 10 second timeout - 40 * 1000 + 40 * 1000, ); + + it("can decode the CreatorAttribution event", async ({ + zoraMintFee, + anvilChainId, + preminterAddress: preminterAddress, + fixedPriceMinterAddress, + }) => { + const premintConfig = defaultPremintConfig(fixedPriceMinterAddress); + const contractConfig = defaultContractConfig({ + contractAdmin: creatorAccount, + }); + + // lets make it a random number to not break the existing tests that expect fresh data + premintConfig.uid = Math.round(Math.random() * 1000000); + + let contractAddress = await publicClient.readContract({ + abi: preminterAbi, + address: preminterAddress, + functionName: "getContractAddress", + args: [contractConfig], + }); + + // have creator sign the message to create the contract + // and the token + const signedMessage = await walletClient.signTypedData({ + ...preminterTypedDataDefinition({ + verifyingContract: contractAddress, + // we need to sign here for the anvil chain, cause thats where it is run on + chainId: anvilChainId, + premintConfig, + }), + account: creatorAccount, + }); + + const quantityToMint = 2n; + + const valueToSend = + (zoraMintFee + premintConfig.tokenConfig.pricePerToken) * quantityToMint; + + const comment = "I love this!"; + + await testClient.setBalance({ + address: collectorAccount, + value: parseEther("10"), + }); + + // now have the collector execute the first signed message; + // it should create the contract, the token, + // and min the quantity to mint tokens to the collector + // the signature along with contract + token creation + // parameters are required to call this function + const mintHash = await walletClient.writeContract({ + abi: preminterAbi, + functionName: "premint", + account: collectorAccount, + address: preminterAddress, + args: [ + contractConfig, + premintConfig, + signedMessage, + quantityToMint, + comment, + ], + value: valueToSend, + }); + + // ensure it succeeded + const receipt = await publicClient.waitForTransactionReceipt({ + hash: mintHash, + }); + + expect(receipt.status).toBe("success"); + + // get the CreatorAttribution event from the erc1155 contract: + const topics = await publicClient.getContractEvents({ + abi: zoraCreator1155ImplABI, + address: contractAddress, + eventName: "CreatorAttribution", + }); + + expect(topics.length).toBe(1); + + const creatorAttributionEvent = topics[0]!; + + const { creator, domainName, signature, structHash, version } = + creatorAttributionEvent.args; + + const chainId = anvilChainId; + + // hash the eip712 domain based on the parameters emitted from the event: + const hashedDomain = hashDomain({ + domain: { + chainId, + name: domainName, + verifyingContract: contractAddress, + version, + }, + types: { + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { + name: "chainId", + type: "uint256", + }, + { + name: "verifyingContract", + type: "address", + }, + ], + }, + }); + + // re-build the eip-712 typed data hash, consisting of the hashed domain and the structHash emitted from the event: + const parts: Hex[] = ["0x1901", hashedDomain, structHash!]; + + const hashedTypedData = keccak256(concat(parts)); + + const recoveredSigner = await recoverAddress({ + hash: hashedTypedData, + signature: signature!, + }); + + expect(recoveredSigner).toBe(creator); + }); }); diff --git a/packages/premint-sdk/tsconfig.json b/packages/premint-sdk/tsconfig.json new file mode 100644 index 000000000..3da7aa253 --- /dev/null +++ b/packages/premint-sdk/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "allowJs": true, + "baseUrl": ".", + "downlevelIteration": true, + "esModuleInterop": true, + "isolatedModules": true, + "lib": ["es2021", "DOM"], + "module": "esnext", + "moduleResolution": "node", + "noImplicitAny": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "strictNullChecks": true, + "target": "es2021", + "types": ["node"], + "outDir": "dist" + }, + "exclude": ["node_modules/**", "dist/**"], + "include": ["src/**/*.ts"] +} diff --git a/packages/premint-sdk/tsup.config.js b/packages/premint-sdk/tsup.config.js index 32ad526c4..f47dfbef8 100644 --- a/packages/premint-sdk/tsup.config.js +++ b/packages/premint-sdk/tsup.config.js @@ -1,7 +1,7 @@ import { defineConfig } from "tsup"; export default defineConfig({ - entry: ["package/index.ts", "package/premint-api.ts"], + entry: ["src/index.ts"], sourcemap: true, clean: true, dts: false,