diff --git a/.changeset/happy-socks-melt.md b/.changeset/happy-socks-melt.md new file mode 100644 index 000000000..e13532db9 --- /dev/null +++ b/.changeset/happy-socks-melt.md @@ -0,0 +1,5 @@ +--- +"@zoralabs/zora-1155-contracts": minor +--- + +Adds first minter rewards to zora 1155 contracts. \ No newline at end of file diff --git a/.changeset/long-avocados-visit.md b/.changeset/long-avocados-visit.md new file mode 100644 index 000000000..581c4e430 --- /dev/null +++ b/.changeset/long-avocados-visit.md @@ -0,0 +1,5 @@ +--- +"@zoralabs/zora-1155-contracts": minor +--- + +Added deterministic contract creation from the Zora1155 factory diff --git a/.changeset/mighty-kiwis-compete.md b/.changeset/mighty-kiwis-compete.md new file mode 100644 index 000000000..36c077f2c --- /dev/null +++ b/.changeset/mighty-kiwis-compete.md @@ -0,0 +1,5 @@ +--- +"@zoralabs/zora-1155-contracts": patch +--- + +Export preminter from package.json \ No newline at end of file diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 000000000..5699514cb --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,14 @@ +{ + "mode": "pre", + "tag": "gasless", + "initialVersions": { + "@zoralabs/zora-1155-contracts": "1.4.0" + }, + "changesets": [ + "happy-socks-melt", + "long-avocados-visit", + "mighty-kiwis-compete", + "spotty-horses-battle", + "twelve-comics-sniff" + ] +} diff --git a/.changeset/spotty-horses-battle.md b/.changeset/spotty-horses-battle.md new file mode 100644 index 000000000..e018133bf --- /dev/null +++ b/.changeset/spotty-horses-battle.md @@ -0,0 +1,5 @@ +--- +"@zoralabs/zora-1155-contracts": patch +--- + +Deprecate ZoraCreatorRedeemMinterStrategy at v1.0.1, a newer version will soon be released diff --git a/.changeset/twelve-comics-sniff.md b/.changeset/twelve-comics-sniff.md new file mode 100644 index 000000000..e251c6a53 --- /dev/null +++ b/.changeset/twelve-comics-sniff.md @@ -0,0 +1,5 @@ +--- +"@zoralabs/zora-1155-contracts": minor +--- + +Added the PremintExecutor contract, and updated erc1155 to support delegated minting \ No newline at end of file diff --git a/.env.anvil b/.env.anvil new file mode 100644 index 000000000..0c2078aa6 --- /dev/null +++ b/.env.anvil @@ -0,0 +1,2 @@ +FORK_RPC_URL="https://testnet.rpc.zora.co/" +FORK_BLOCK_NUMBER=916572 \ No newline at end of file diff --git a/.github/workflows/changesets-prerelease.yml b/.github/workflows/changesets-prerelease.yml new file mode 100644 index 000000000..1fad4a385 --- /dev/null +++ b/.github/workflows/changesets-prerelease.yml @@ -0,0 +1,50 @@ +name: Release + +on: + push: + branches: + - "*prerelease*" + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + + - name: Check pre.json existence + id: check_pre + uses: andstor/file-existence-action@v1 + with: + files: ".changeset/pre.json" + + - name: Ensure pre.json exists + if: steps.check_pre.outputs.files_exists != 'true' + run: echo "pre.json does not exist, enter prerelease mode with 'yarn changeset pre enter {prereleaseName}'"; exit 1 + + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: "yarn" + + - name: Install project dependencies + run: yarn + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Create Release Pull Request or Publish to npm + id: changesets + uses: changesets/action@v1 + with: + # This expects you to have a script called release which does a build for your packages and calls changeset publish + publish: yarn release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 18da216af..879b2facd 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -38,6 +38,12 @@ jobs: - name: Setup LCOV uses: hrishikesh-kadam/setup-lcov@v1 + - name: Filter files to ignore + run: | + lcov --rc lcov_branch_coverage=1 \ + --remove lcov.info \ + --output-file lcov.info "*node_modules*" "*test*" "*script*" "*DeploymentConfig*" "*Redeem*" + - name: Report code coverage uses: zgosalvez/github-actions-report-lcov@v2 with: diff --git a/.gitignore b/.gitignore index 169f7460a..77f4ae5b6 100644 --- a/.gitignore +++ b/.gitignore @@ -32,8 +32,10 @@ lcov.info dist/ .env -.env* !.env.example package/wagmiGenerated.ts -package/chainConfigs.ts \ No newline at end of file +package/chainConfigs.ts + +# not currently using pnpm +pnpm-lock.yaml \ No newline at end of file diff --git a/.storage-layout b/.storage-layout index b35d8519c..c8795d895 100644 --- a/.storage-layout +++ b/.storage-layout @@ -33,6 +33,8 @@ | permissions | mapping(uint256 => mapping(address => uint256)) | 510 | 0 | 32 | src/nft/ZoraCreator1155Impl.sol:ZoraCreator1155Impl | | __gap | uint256[50] | 511 | 0 | 1600 | src/nft/ZoraCreator1155Impl.sol:ZoraCreator1155Impl | | createReferrals | mapping(uint256 => address) | 561 | 0 | 32 | src/nft/ZoraCreator1155Impl.sol:ZoraCreator1155Impl | +| firstMinters | mapping(uint256 => address) | 562 | 0 | 32 | src/nft/ZoraCreator1155Impl.sol:ZoraCreator1155Impl | +| delegatedTokenId | mapping(uint32 => uint256) | 563 | 0 | 32 | src/nft/ZoraCreator1155Impl.sol:ZoraCreator1155Impl | ======================= ➡ ZoraCreator1155FactoryImpl diff --git a/CHANGELOG.md b/CHANGELOG.md index 748733569..1ebf279a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # @zoralabs/zora-1155-contracts +## 1.5.0-gasless.1 + +### Patch Changes + +- 4a67eee: Export preminter from package.json + +## 1.5.0-gasless.0 + +### Minor Changes + +- bb8069e: Adds first minter rewards to zora 1155 contracts. +- f3c86d8: Added deterministic contract creation from the Zora1155 factory +- 91db21c: Added the PremintExecutor contract, and updated erc1155 to support delegated minting + +### Patch Changes + +- bb8069e: Deprecate ZoraCreatorRedeemMinterStrategy at v1.0.1, a newer version will soon be released + ## 1.4.0 ### Minor Changes diff --git a/addresses/999.json b/addresses/999.json index 5906865f6..f2f9d9404 100644 --- a/addresses/999.json +++ b/addresses/999.json @@ -1,11 +1,12 @@ { - "CONTRACT_1155_IMPL": "0x91C1863eD54809c45b53bb6090eb437036c792C4", + "CONTRACT_1155_IMPL": "0x324bA85dAE97331D8256D96D7e45BdC4688f665C", "CONTRACT_1155_IMPL_VERSION": "1.4.0", - "FACTORY_IMPL": "0xdF4A315443Ce2c11e6657D6A98B3a7143DE7B268", - "FACTORY_PROXY": "0x6a357139C1bcDcf0B3AB9bC447932dDdcb956703", + "FACTORY_IMPL": "0xd360c892c69A70F900352d142e78A9C0a59544c5", + "FACTORY_PROXY": "0x415015A8d73582DF01d83CFa16D11b6c7aD8AF24", "FIXED_PRICE_SALE_STRATEGY": "0xd81351363b7d80b06E4Ec4De7989f0f91e41A846", "MERKLE_MINT_SALE_STRATEGY": "0x2c4457D38A329526063b26a2bB2C31B61553Aa98", + "PREMINTER": "0xcb81DbF6DB4526fe2Cdf59Bd8a2Ef627899B9Ef2", "REDEEM_MINTER_FACTORY": "0x27817bAef1341De9Ad04097Bbba4Ea8dA32c8552", - "timestamp": 1688509842, - "commit": "f60dd5f" -} + "timestamp": 1694716506, + "commit": "b197446" +} \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 83304a006..d5d3af477 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,7 +2,7 @@ fs_permissions = [{access = "read", path = "./addresses"}, {access = "read", path = "./chainConfigs"}, {access = "read", path = "./package.json"}] libs = ['_imagine', 'node_modules', 'script'] optimizer = true -optimizer_runs = 3000 +optimizer_runs = 250 out = 'out' solc_version = '0.8.17' src = 'src' @@ -10,7 +10,7 @@ via_ir = true [profile.optimized] optimizer = true -optimizer_runs = 3000 +optimizer_runs = 250 out = 'out' script = 'src' solc_version = '0.8.17' diff --git a/package.json b/package.json index 18ad20489..7777ee3e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@zoralabs/zora-1155-contracts", - "version": "1.4.0", + "version": "1.5.0-gasless.1", "repository": "git@github.com:ourzora/creator-contracts.git", "author": "Iain ", "license": "MIT", @@ -14,7 +14,7 @@ "prettier": "prettier --write 'src/**/*.sol' 'test/**/*.sol' 'package/**/*.ts' 'wagmi.config.ts'", "coverage": "forge coverage --report lcov", "write-gas-report": "forge test --gas-report > gasreport.ansi", - "prepack": "node script/copy-deployed-contracts.mjs && yarn wagmi && yarn bundle-configs && yarn build", + "prepack": "yarn wagmi && yarn bundle-configs && yarn build", "update-new-deployment-addresses": "node script/copy-deployed-contracts.mjs deploy", "build": "tsup", "bundle-configs": "node script/bundle-chainConfigs.mjs && yarn prettier", @@ -22,9 +22,9 @@ "publish-packages": "yarn prepack && changeset publish", "storage-inspect:check": "./script/storage-check.sh check ZoraCreator1155Impl ZoraCreator1155FactoryImpl", "storage-inspect:generate": "./script/storage-check.sh generate ZoraCreator1155Impl ZoraCreator1155FactoryImpl", - "release": "yarn run prepack && changeset publish", "js-test:watch": "vitest dev", - "anvil": "anvil --fork-url https://rpc.zora.energy --fork-block-number 2550000 --chain-id 31337" + "anvil": "source .env.anvil && anvil --fork-url $FORK_RPC_URL --fork-block-number $FORK_BLOCK_NUMBER --chain-id 31337", + "release": "yarn run prepack && changeset publish" }, "files": [ "dist/", @@ -36,9 +36,10 @@ "dependencies": { "@openzeppelin/contracts": "4.9.2", "@zoralabs/openzeppelin-contracts-upgradeable": "4.8.4", - "@zoralabs/protocol-rewards": "1.1.1", + "@zoralabs/protocol-rewards": "1.1.2", "ds-test": "https://github.com/dapphub/ds-test#cd98eff28324bfac652e63a239a60632a761790b", - "forge-std": "https://github.com/foundry-rs/forge-std#cd7d533f9a0ee0ec02ad81e0a8f262bc4203c653" + "forge-std": "https://github.com/foundry-rs/forge-std#705263c95892a906d7af65f0f73ce8a4a0c80b80", + "solmate": "^6.1.0" }, "devDependencies": { "@changesets/cli": "^2.26.1", diff --git a/package/index.ts b/package/index.ts index 061b80576..29807da63 100644 --- a/package/index.ts +++ b/package/index.ts @@ -2,4 +2,5 @@ // built at build time. They are not checked in to git. // The can be generated by running `yarn prepack` in the root export * from "./wagmiGenerated"; +export * as preminter from "./preminter"; export { chainConfigs } from "./chainConfigs"; diff --git a/package/preminter.test.ts b/package/preminter.test.ts new file mode 100644 index 000000000..100e46386 --- /dev/null +++ b/package/preminter.test.ts @@ -0,0 +1,477 @@ +import { + createTestClient, + http, + createWalletClient, + createPublicClient, +} from "viem"; +import { foundry, zoraTestnet } from "viem/chains"; +import { describe, it, beforeEach, expect } from "vitest"; +import { parseEther } from "viem"; +import { + zoraCreator1155PremintExecutorABI as preminterAbi, + zoraCreator1155PremintExecutorAddress, + zoraCreator1155ImplABI, + zoraCreator1155FactoryImplAddress, + zoraCreator1155FactoryImplConfig, +} from "./wagmiGenerated"; +import ZoraCreator1155Attribution from "../out/ZoraCreator1155Attribution.sol/ZoraCreator1155Attribution.json"; +import zoraCreator1155PremintExecutor from "../out/ZoraCreator1155PremintExecutor.sol/ZoraCreator1155PremintExecutor.json"; +import zoraCreator1155Impl from "../out/ZoraCreator1155Impl.sol/ZoraCreator1155Impl.json"; +import zoraCreator1155FactoryImpl from "../out/ZoraCreator1155FactoryImpl.sol/ZoraCreator1155FactoryImpl.json"; +import zoraCreatorFixedPriceSaleStrategy from "../out/ZoraCreatorFixedPriceSaleStrategy.sol/ZoraCreatorFixedPriceSaleStrategy.json"; +import protocolRewards from "../out/ProtocolRewards.sol/ProtocolRewards.json"; +import { + ContractCreationConfig, + PremintConfig, + TokenCreationConfig, + preminterTypedDataDefinition, +} from "./preminter"; + +const walletClient = createWalletClient({ + chain: foundry, + transport: http(), +}); + +export const walletClientWithAccount = createWalletClient({ + chain: foundry, + transport: http(), +}); + +const testClient = createTestClient({ + chain: foundry, + mode: "anvil", + transport: http(), +}); + +const publicClient = createPublicClient({ + chain: foundry, + transport: http(), +}); + +type Address = `0x${string}`; + +const zeroAddress: Address = "0x0000000000000000000000000000000000000000"; + +// JSON-RPC Account +const [ + deployerAccount, + creatorAccount, + collectorAccount, + mintFeeRecipientAccount, +] = (await walletClient.getAddresses()) as [Address, Address, Address, Address]; + +type TestContext = { + preminterAddress: `0x${string}`; + forkedChainId: keyof typeof zoraCreator1155FactoryImplAddress; + anvilChainId: number; + zoraMintFee: bigint; + 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, +}: { + contractAdmin: Address; +}): ContractCreationConfig => ({ + contractAdmin, + contractURI: "ipfs://asdfasdfasdf", + contractName: "My fun NFT", +}); + +const defaultTokenConfig = ( + fixedPriceMinterAddress: Address +): TokenCreationConfig => ({ + tokenURI: "ipfs://tokenIpfsId0", + maxSupply: 100n, + maxTokensPerAddress: 10n, + pricePerToken: 0n, + mintStart: 0n, + mintDuration: 100n, + royaltyMintSchedule: 30, + royaltyBPS: 200, + royaltyRecipient: creatorAccount, + fixedPriceMinter: fixedPriceMinterAddress, +}); + +const defaultPremintConfig = (fixedPriceMinter: Address): PremintConfig => ({ + tokenConfig: defaultTokenConfig(fixedPriceMinter), + deleted: false, + uid: 105, + version: 0, +}); + +const useForkContract = true; + +describe("ZoraCreator1155Preminter", () => { + beforeEach(async (ctx) => { + // deploy signature minter contract + await testClient.setBalance({ + address: deployerAccount, + value: parseEther("10"), + }); + + ctx.forkedChainId = zoraTestnet.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", + }); + const deployed = await deployPreminterContract(factoryProxyAddress); + preminterAddress = deployed.preminterAddress; + } else { + const factoryProxyAddress = (await deployFactoryProxy()) + .factoryProxyAddress; + const deployed = await deployPreminterContract(factoryProxyAddress); + preminterAddress = deployed.preminterAddress; + } + + ctx.zoraMintFee = parseEther("0.000777"); + + ctx.preminterAddress = preminterAddress; + }, 20 * 1000); + + // skip for now - we need to make this work on zora testnet chain too + it.skip( + "can sign on the forked premint contract", + async ({ fixedPriceMinterAddress, forkedChainId }) => { + const premintConfig = defaultPremintConfig(fixedPriceMinterAddress); + const contractConfig = defaultContractConfig({ + contractAdmin: creatorAccount, + }); + + const preminterAddress = zoraCreator1155PremintExecutorAddress[ + forkedChainId as keyof typeof zoraCreator1155PremintExecutorAddress + ] as Address; + + const contractAddress = await publicClient.readContract({ + abi: preminterAbi, + address: preminterAddress, + functionName: "getContractAddress", + args: [contractConfig], + }); + + const signedMessage = await walletClient.signTypedData({ + ...preminterTypedDataDefinition({ + verifyingContract: contractAddress, + chainId: 999, + premintConfig, + }), + account: creatorAccount, + }); + + console.log({ + creatorAccount, + signedMessage, + contractConfig, + premintConfig, + contractAddress, + }); + }, + 20 * 1000 + ); + it( + "can sign and recover a signature", + async ({ + preminterAddress: preminterAddress, + anvilChainId, + fixedPriceMinterAddress, + }) => { + const premintConfig = defaultPremintConfig(fixedPriceMinterAddress); + const contractConfig = defaultContractConfig({ + contractAdmin: creatorAccount, + }); + + const contractAddress = await publicClient.readContract({ + abi: preminterAbi, + address: preminterAddress, + functionName: "getContractAddress", + args: [contractConfig], + }); + + // sign message containing contract and token creation config and uid + 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, + }); + + // recover and verify address is correct + const recoveredAddress = await publicClient.readContract({ + abi: preminterAbi, + address: preminterAddress, + functionName: "recoverSigner", + args: [premintConfig, contractAddress, signedMessage], + }); + + expect(recoveredAddress).to.equal(creatorAccount); + }, + + 20 * 1000 + ); + it( + "can sign and mint multiple tokens", + async ({ + zoraMintFee, + anvilChainId, + preminterAddress: preminterAddress, + fixedPriceMinterAddress, + }) => { + // setup contract and token creation parameters + 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"), + }); + + // get the premint status - it should not be minted + let [contractCreated, tokenId] = await publicClient.readContract({ + abi: preminterAbi, + address: preminterAddress, + functionName: "premintStatus", + args: [contractAddress, premintConfig.uid], + }); + + expect(contractCreated).toBe(false); + expect(tokenId).toBe(0n); + + // 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"); + + // fetch the premint token id + [contractCreated, tokenId] = await publicClient.readContract({ + abi: preminterAbi, + address: preminterAddress, + functionName: "premintStatus", + args: [contractAddress, premintConfig.uid], + }); + + expect(tokenId).not.toBe(0n); + + // now use what was created, to get the balance from the created contract + const tokenBalance = await publicClient.readContract({ + abi: zoraCreator1155ImplABI, + address: contractAddress, + functionName: "balanceOf", + args: [collectorAccount, tokenId], + }); + + // get token balance - should be amount that was created + expect(tokenBalance).toBe(quantityToMint); + + const premintConfig2 = { + ...premintConfig, + uid: premintConfig.uid + 1, + tokenConfig: { + ...premintConfig.tokenConfig, + tokenURI: "ipfs://tokenIpfsId2", + pricePerToken: parseEther("0.05"), + }, + }; + + // sign the message to create the second token + const signedMessage2 = await walletClient.signTypedData({ + ...preminterTypedDataDefinition({ + verifyingContract: contractAddress, + chainId: foundry.id, + premintConfig: premintConfig2, + }), + account: creatorAccount, + }); + + const quantityToMint2 = 4n; + + const valueToSend2 = + (zoraMintFee + premintConfig2.tokenConfig.pricePerToken) * + quantityToMint2; + + // now have the collector execute the second signed message. + // it should create a new token against the existing contract + const mintHash2 = await walletClient.writeContract({ + abi: preminterAbi, + functionName: "premint", + account: collectorAccount, + address: preminterAddress, + args: [ + contractConfig, + premintConfig2, + signedMessage2, + quantityToMint2, + comment, + ], + value: valueToSend2, + }); + + expect( + (await publicClient.waitForTransactionReceipt({ hash: mintHash2 })) + .status + ).toBe("success"); + + // now premint status for the second mint, it should be minted + [, tokenId] = await publicClient.readContract({ + abi: preminterAbi, + address: preminterAddress, + functionName: "premintStatus", + args: [contractAddress, premintConfig2.uid], + }); + + expect(tokenId).not.toBe(0n); + + // get balance of second token + const tokenBalance2 = await publicClient.readContract({ + abi: zoraCreator1155ImplABI, + address: contractAddress, + functionName: "balanceOf", + args: [collectorAccount, tokenId], + }); + + expect(tokenBalance2).toBe(quantityToMint2); + }, + // 10 second timeout + 40 * 1000 + ); +}); diff --git a/package/preminter.ts b/package/preminter.ts new file mode 100644 index 000000000..6b6f4e12c --- /dev/null +++ b/package/preminter.ts @@ -0,0 +1,74 @@ +import { Address } from "abitype"; +import { ExtractAbiFunction, AbiParametersToPrimitiveTypes } from "abitype"; +import { zoraCreator1155PremintExecutorABI as preminterAbi } from "./wagmiGenerated"; +import { TypedDataDefinition } from "viem"; + +type PremintInputs = ExtractAbiFunction< + typeof preminterAbi, + "premint" +>["inputs"]; + +type PreminterHashDataTypes = AbiParametersToPrimitiveTypes; + +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 = ({ + verifyingContract, + premintConfig, + chainId, +}: { + verifyingContract: Address; + premintConfig: PremintConfig; + chainId: number; +}) => { + const { tokenConfig, uid, version, deleted } = premintConfig; + const types = { + 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: "royaltyMintSchedule", type: "uint32" }, + { name: "royaltyBPS", type: "uint32" }, + { name: "royaltyRecipient", type: "address" }, + { name: "fixedPriceMinter", type: "address" }, + ], + }; + + const result: TypedDataDefinition = { + domain: { + chainId, + name: "Preminter", + version: "1", + verifyingContract: verifyingContract, + }, + types, + message: { + tokenConfig, + uid, + version, + deleted, + }, + primaryType: "CreatorAttribution", + }; + + // console.log({ result, deleted }); + + return result; +}; diff --git a/remappings.txt b/remappings.txt index 740c53f29..76854be3f 100644 --- a/remappings.txt +++ b/remappings.txt @@ -3,4 +3,5 @@ forge-std/=node_modules/forge-std/src/ @zoralabs/openzeppelin-contracts-upgradeable/=node_modules/@zoralabs/openzeppelin-contracts-upgradeable/ @zoralabs/protocol-rewards/src/=node_modules/@zoralabs/protocol-rewards/src/ @openzeppelin/contracts/=node_modules/@openzeppelin/contracts/ -_imagine=_imagine/ \ No newline at end of file +_imagine=_imagine/ +solemate/=/node_modules/solemate/src/ \ No newline at end of file diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index b302012b5..1cbf10167 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -12,9 +12,7 @@ import {Zora1155Factory} from "../src/proxies/Zora1155Factory.sol"; import {ZoraCreator1155Impl} from "../src/nft/ZoraCreator1155Impl.sol"; import {ICreatorRoyaltiesControl} from "../src/interfaces/ICreatorRoyaltiesControl.sol"; import {IZoraCreator1155Factory} from "../src/interfaces/IZoraCreator1155Factory.sol"; -import {IMinter1155} from "../src/interfaces/IMinter1155.sol"; import {IZoraCreator1155} from "../src/interfaces/IZoraCreator1155.sol"; -import {ProxyShim} from "../src/utils/ProxyShim.sol"; import {ZoraCreatorFixedPriceSaleStrategy} from "../src/minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.sol"; import {ZoraCreatorMerkleMinterStrategy} from "../src/minters/merkle/ZoraCreatorMerkleMinterStrategy.sol"; import {ZoraCreatorRedeemMinterFactory} from "../src/minters/redeem/ZoraCreatorRedeemMinterFactory.sol"; @@ -41,33 +39,9 @@ contract DeployScript is ZoraDeployerBase { deployment.merkleMintSaleStrategy = address(merkleMinter); deployment.redeemMinterFactory = address(redeemMinterFactory); - address factoryShimAddress = address(new ProxyShim(deployer)); - Zora1155Factory factoryProxy = new Zora1155Factory(factoryShimAddress, ""); + deployNew1155AndFactoryProxy(deployment, deployer); - deployment.factoryProxy = address(factoryProxy); - - ZoraCreator1155Impl creatorImpl = - new ZoraCreator1155Impl(chainConfig.mintFeeAmount, chainConfig.mintFeeRecipient, address(factoryProxy), chainConfig.protocolRewards); - - deployment.contract1155Impl = address(creatorImpl); - - ZoraCreator1155FactoryImpl factoryImpl = new ZoraCreator1155FactoryImpl({ - _implementation: creatorImpl, - _merkleMinter: merkleMinter, - _redeemMinterFactory: redeemMinterFactory, - _fixedPriceMinter: fixedPricedMinter - }); - - deployment.factoryImpl = address(factoryImpl); - - // Upgrade to "real" factory address - ZoraCreator1155FactoryImpl(address(factoryProxy)).upgradeTo(address(factoryImpl)); - ZoraCreator1155FactoryImpl(address(factoryProxy)).initialize(chainConfig.factoryOwner); - - console2.log("Factory Proxy", address(factoryProxy)); - console2.log("Implementation Address", address(creatorImpl)); - - deployTestContractForVerification(address(factoryProxy), chainConfig.factoryOwner); + deployTestContractForVerification(deployment.factoryProxy, chainConfig.factoryOwner); return getDeploymentJSON(deployment); } diff --git a/script/DeployNewPreminterAndFactoryProxy.s.sol b/script/DeployNewPreminterAndFactoryProxy.s.sol new file mode 100644 index 000000000..e6fcd46d5 --- /dev/null +++ b/script/DeployNewPreminterAndFactoryProxy.s.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "forge-std/Script.sol"; +import "forge-std/console2.sol"; + +import {ZoraDeployerBase} from "./ZoraDeployerBase.sol"; +import {ChainConfig, Deployment} from "../src/deployment/DeploymentConfig.sol"; + +contract DeployNewPreminterAndFactoryProxy is ZoraDeployerBase { + function run() public returns (string memory) { + Deployment memory deployment = getDeployment(); + + uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + address deployer = vm.envAddress("DEPLOYER"); + + vm.startBroadcast(deployerPrivateKey); + + deployNew1155AndFactoryProxy(deployment, deployer); + + deployNewPreminterProxy(deployment); + + vm.stopBroadcast(); + + return getDeploymentJSON(deployment); + } +} diff --git a/script/TestCreateDeterministic.sol b/script/TestCreateDeterministic.sol new file mode 100644 index 000000000..28ae44a4d --- /dev/null +++ b/script/TestCreateDeterministic.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "forge-std/Script.sol"; +import "forge-std/console2.sol"; + +import {ZoraDeployerBase} from "./ZoraDeployerBase.sol"; +import {ProtocolRewards} from "@zoralabs/protocol-rewards/src/ProtocolRewards.sol"; +import {ChainConfig, Deployment} from "../src/deployment/DeploymentConfig.sol"; + +import {ZoraCreator1155FactoryImpl} from "../src/factory/ZoraCreator1155FactoryImpl.sol"; +import {Zora1155Factory} from "../src/proxies/Zora1155Factory.sol"; +import {ZoraCreator1155Impl} from "../src/nft/ZoraCreator1155Impl.sol"; +import {ICreatorRoyaltiesControl} from "../src/interfaces/ICreatorRoyaltiesControl.sol"; +import {IZoraCreator1155Factory} from "../src/interfaces/IZoraCreator1155Factory.sol"; +import {IMinter1155} from "../src/interfaces/IMinter1155.sol"; +import {IZoraCreator1155} from "../src/interfaces/IZoraCreator1155.sol"; +import {ProxyShim} from "../src/utils/ProxyShim.sol"; +import {ZoraCreatorFixedPriceSaleStrategy} from "../src/minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.sol"; +import {ZoraCreatorMerkleMinterStrategy} from "../src/minters/merkle/ZoraCreatorMerkleMinterStrategy.sol"; +import {ZoraCreatorRedeemMinterFactory} from "../src/minters/redeem/ZoraCreatorRedeemMinterFactory.sol"; + +contract DeployScript is ZoraDeployerBase { + function run() public { + // ChainConfig memory chainConfig = getChainConfig(); + + // console2.log("zoraFeeAmount", chainConfig.mintFeeAmount); + // console2.log("zoraFeeRecipient", chainConfig.mintFeeRecipient); + // console2.log("factoryOwner", chainConfig.factoryOwner); + + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + address deployer = vm.addr(deployerPrivateKey); + + ICreatorRoyaltiesControl.RoyaltyConfiguration memory royaltyConfig = ICreatorRoyaltiesControl.RoyaltyConfiguration({ + royaltyBPS: 10, + royaltyRecipient: vm.addr(5), + royaltyMintSchedule: 100 + }); + bytes[] memory initSetup = new bytes[](1); + initSetup[0] = abi.encodeWithSelector(IZoraCreator1155.setupNewToken.selector, "ipfs://asdfadsf", 100); + string memory uri = "ipfs://asdfadsf"; + string memory nameA = "nameA"; + + vm.startBroadcast(deployerPrivateKey); + + ZoraCreator1155Impl zoraCreator1155Impl = new ZoraCreator1155Impl(0, address(0), address(0), address(new ProtocolRewards())); + // get above constructor args encoded for verification later: + ZoraCreator1155FactoryImpl factory = new ZoraCreator1155FactoryImpl( + zoraCreator1155Impl, + IMinter1155(address(1)), + IMinter1155(address(2)), + IMinter1155(address(3)) + ); + + address factoryOwner = deployer; + + // 1. create the proxy, pointing it to the factory implentation and setting the owner + ZoraCreator1155FactoryImpl proxy = ZoraCreator1155FactoryImpl( + payable(new Zora1155Factory(address(factory), abi.encodeWithSelector(ZoraCreator1155FactoryImpl.initialize.selector, factoryOwner))) + ); + + address createdErc1155 = proxy.createContractDeterministic(uri, nameA, royaltyConfig, payable(deployer), initSetup); + + console.log("deployed erc1155 at", createdErc1155); + console.log("constructor args", string(abi.encode(0, address(0), address(0)))); + vm.stopBroadcast(); + } +} diff --git a/script/UpgradePreminter.s.sol b/script/UpgradePreminter.s.sol new file mode 100644 index 000000000..60438548a --- /dev/null +++ b/script/UpgradePreminter.s.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "forge-std/Script.sol"; +import "forge-std/console2.sol"; + +import {ZoraDeployerBase} from "./ZoraDeployerBase.sol"; +import {ChainConfig, Deployment} from "../src/deployment/DeploymentConfig.sol"; +import {UUPSUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol"; + +contract UpgradePreminter is ZoraDeployerBase { + function run() public returns (string memory, bytes memory upgradeCalldata, address upgradeTarget) { + Deployment memory deployment = getDeployment(); + + uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + + vm.startBroadcast(deployerPrivateKey); + + address preminterImplementation = deployNewPreminterImplementation(deployment); + + vm.stopBroadcast(); + + upgradeCalldata = abi.encodeWithSelector(UUPSUpgradeable.upgradeTo.selector, preminterImplementation); + + upgradeTarget = deployment.preminter; + + console2.log("Upgrade PremintExecutor target and implementatin:", upgradeTarget, preminterImplementation); + console2.log("To upgrade, use this calldata:"); + console2.logBytes(upgradeCalldata); + + return (getDeploymentJSON(deployment), upgradeCalldata, upgradeTarget); + } +} diff --git a/script/ZoraDeployerBase.sol b/script/ZoraDeployerBase.sol index fcc4530b8..909d65d9c 100644 --- a/script/ZoraDeployerBase.sol +++ b/script/ZoraDeployerBase.sol @@ -6,8 +6,14 @@ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {IZoraCreator1155Factory} from "../src/interfaces/IZoraCreator1155Factory.sol"; import {ZoraCreator1155Impl} from "../src/nft/ZoraCreator1155Impl.sol"; +import {Zora1155Factory} from "../src/proxies/Zora1155Factory.sol"; import {ICreatorRoyaltiesControl} from "../src/interfaces/ICreatorRoyaltiesControl.sol"; -import {ScriptDeploymentConfig, Deployment} from "../src/deployment/DeploymentConfig.sol"; +import {ScriptDeploymentConfig, Deployment, ChainConfig} from "../src/deployment/DeploymentConfig.sol"; +import {ProxyShim} from "../src/utils/ProxyShim.sol"; +import {ZoraCreator1155FactoryImpl} from "../src/factory/ZoraCreator1155FactoryImpl.sol"; +import {Zora1155PremintExecutorProxy} from "../src/proxies/Zora1155PremintExecutorProxy.sol"; +import {ZoraCreator1155PremintExecutor} from "../src/premint/ZoraCreator1155PremintExecutor.sol"; +import {IMinter1155} from "../src/interfaces/IMinter1155.sol"; /// @notice Deployment drops for base where abstract contract ZoraDeployerBase is ScriptDeploymentConfig, Script { @@ -27,10 +33,70 @@ abstract contract ZoraDeployerBase is ScriptDeploymentConfig, Script { vm.serializeString(deploymentJsonKey, CONTRACT_1155_IMPL_VERSION, deployment.contract1155ImplVersion); vm.serializeAddress(deploymentJsonKey, CONTRACT_1155_IMPL, deployment.contract1155Impl); vm.serializeAddress(deploymentJsonKey, FACTORY_IMPL, deployment.factoryImpl); + vm.serializeAddress(deploymentJsonKey, PREMINTER, deployment.preminter); deploymentJson = vm.serializeAddress(deploymentJsonKey, FACTORY_PROXY, deployment.factoryProxy); console2.log(deploymentJson); } + function deployNew1155AndFactoryImpl(Deployment memory deployment, Zora1155Factory factoryProxy) internal { + ChainConfig memory chainConfig = getChainConfig(); + + ZoraCreator1155Impl creatorImpl = new ZoraCreator1155Impl( + chainConfig.mintFeeAmount, + chainConfig.mintFeeRecipient, + address(factoryProxy), + chainConfig.protocolRewards + ); + + console2.log("Implementation Address", address(creatorImpl)); + + deployment.contract1155Impl = address(creatorImpl); + + ZoraCreator1155FactoryImpl factoryImpl = new ZoraCreator1155FactoryImpl({ + _implementation: creatorImpl, + _merkleMinter: IMinter1155(deployment.merkleMintSaleStrategy), + _redeemMinterFactory: IMinter1155(deployment.redeemMinterFactory), + _fixedPriceMinter: IMinter1155(deployment.fixedPriceSaleStrategy) + }); + + deployment.factoryImpl = address(factoryImpl); + } + + function deployNew1155AndFactoryProxy(Deployment memory deployment, address deployer) internal { + address factoryShimAddress = address(new ProxyShim(deployer)); + Zora1155Factory factoryProxy = new Zora1155Factory(factoryShimAddress, ""); + + deployment.factoryProxy = address(factoryProxy); + + // deploy new 1155 and factory impl, and udpdate deployment config with it + deployNew1155AndFactoryImpl(deployment, factoryProxy); + + ZoraCreator1155FactoryImpl(address(factoryProxy)).upgradeTo(deployment.factoryImpl); + ZoraCreator1155FactoryImpl(address(factoryProxy)).initialize(getChainConfig().factoryOwner); + + console2.log("Factory Proxy", address(factoryProxy)); + } + + function deployNewPreminterImplementation(Deployment memory deployment) internal returns (address) { + // create preminter implementation + ZoraCreator1155PremintExecutor preminterImplementation = new ZoraCreator1155PremintExecutor(ZoraCreator1155FactoryImpl(deployment.factoryProxy)); + + return address(preminterImplementation); + } + + function deployNewPreminterProxy(Deployment memory deployment) internal { + address preminterImplementation = deployNewPreminterImplementation(deployment); + + // build the proxy + Zora1155PremintExecutorProxy proxy = new Zora1155PremintExecutorProxy(preminterImplementation, ""); + + deployment.preminter = address(proxy); + + // access the executor implementation via the proxy, and initialize the admin + ZoraCreator1155PremintExecutor preminterAtProxy = ZoraCreator1155PremintExecutor(address(proxy)); + preminterAtProxy.initialize(getChainConfig().factoryOwner); + } + /// @notice Deploy a test contract for etherscan auto-verification /// @param factoryProxy Factory address to use /// @param admin Admin owner address to use @@ -45,11 +111,7 @@ abstract contract ZoraDeployerBase is ScriptDeploymentConfig, Script { IZoraCreator1155Factory(factoryProxy).createContract( "ipfs://bafybeicgolwqpozsc7iwgytavete56a2nnytzix2nb2rxefdvbtwwtnnoe/metadata", unicode"🪄", - ICreatorRoyaltiesControl.RoyaltyConfiguration({ - royaltyBPS: 0, - royaltyRecipient: address(0), - royaltyMintSchedule: 0 - }), + ICreatorRoyaltiesControl.RoyaltyConfiguration({royaltyBPS: 0, royaltyRecipient: address(0), royaltyMintSchedule: 0}), payable(admin), initUpdate ) diff --git a/script/copy-deployed-contracts.mjs b/script/copy-deployed-contracts.mjs index 8aed2c7d1..8f6faabc4 100644 --- a/script/copy-deployed-contracts.mjs +++ b/script/copy-deployed-contracts.mjs @@ -2,11 +2,9 @@ import { writeFile, readFile } from "fs/promises"; import esMain from "es-main"; import { glob } from "glob"; -async function copyEnvironmentRunFiles(isDeploy) { +async function copyEnvironmentRunFiles(scriptName) { const latestFiles = await glob( - isDeploy - ? "broadcast/Deploy.s.sol/*/run-latest.json" - : "broadcast/Upgrade.s.sol/*/run-latest.json" + `broadcast/${scriptName}/*/run-latest.json` ); for (const file of latestFiles) { @@ -41,9 +39,13 @@ async function copyEnvironmentRunFiles(isDeploy) { if (esMain(import.meta)) { const command = process.argv[2]; - let deploy = false; - if (command === "deploy") { - deploy = true; + let scriptName = 'Deploy.s.sol'; + + if (command === "upgrade"){ + scriptName = 'Upgrade.s.sol'; + } else if (command === 'deploy-premint') { + scriptName = 'DeployNewPreminterAndFactoryProxy.s.sol' } - await copyEnvironmentRunFiles(deploy); + + await copyEnvironmentRunFiles(scriptName); } diff --git a/src/deployment/DeploymentConfig.sol b/src/deployment/DeploymentConfig.sol index 77fccac89..348aae27d 100644 --- a/src/deployment/DeploymentConfig.sol +++ b/src/deployment/DeploymentConfig.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.17; import "forge-std/Test.sol"; -import {MintFeeManager} from "../../src/fee/MintFeeManager.sol"; +import {CommonBase} from "forge-std/Base.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; /// @notice Chain configuration for constants set manually during deploy. Does not get written to after deploys. @@ -33,6 +33,8 @@ struct Deployment { address factoryImpl; /// @notice Factory proxy contract that creates zora drops style NFT contracts address factoryProxy; + /// @notice Preminter contract address + address preminter; } abstract contract DeploymentConfig is CommonBase { @@ -58,6 +60,7 @@ abstract contract DeploymentConfig is CommonBase { string constant CONTRACT_1155_IMPL_VERSION = "CONTRACT_1155_IMPL_VERSION"; string constant FACTORY_IMPL = "FACTORY_IMPL"; string constant FACTORY_PROXY = "FACTORY_PROXY"; + string constant PREMINTER = "PREMINTER"; /// @notice Return a prefixed key for reading with a ".". /// @param key key to prefix @@ -76,17 +79,28 @@ abstract contract DeploymentConfig is CommonBase { chainConfig.protocolRewards = json.readAddress(getKeyPrefix(PROTOCOL_REWARDS)); } + function readAddressOrDefaultToZero(string memory json, string memory key) internal view returns (address addr) { + string memory keyPrefix = getKeyPrefix(key); + + if (vm.keyExists(json, keyPrefix)) { + addr = json.readAddress(keyPrefix); + } else { + addr = address(0); + } + } + /// @notice Get the deployment configuration struct from the JSON configuration file /// @return deployment deployment configuration structure function getDeployment() internal view returns (Deployment memory deployment) { string memory json = vm.readFile(string.concat("addresses/", Strings.toString(chainId()), ".json")); - deployment.fixedPriceSaleStrategy = json.readAddress(getKeyPrefix(FIXED_PRICE_SALE_STRATEGY)); - deployment.merkleMintSaleStrategy = json.readAddress(getKeyPrefix(MERKLE_MINT_SALE_STRATEGY)); - deployment.redeemMinterFactory = json.readAddress(getKeyPrefix(REDEEM_MINTER_FACTORY)); - deployment.contract1155Impl = json.readAddress(getKeyPrefix(CONTRACT_1155_IMPL)); + deployment.fixedPriceSaleStrategy = readAddressOrDefaultToZero(json, FIXED_PRICE_SALE_STRATEGY); + deployment.merkleMintSaleStrategy = readAddressOrDefaultToZero(json, MERKLE_MINT_SALE_STRATEGY); + deployment.redeemMinterFactory = readAddressOrDefaultToZero(json, REDEEM_MINTER_FACTORY); + deployment.contract1155Impl = readAddressOrDefaultToZero(json, CONTRACT_1155_IMPL); deployment.contract1155ImplVersion = json.readString(getKeyPrefix(CONTRACT_1155_IMPL_VERSION)); - deployment.factoryImpl = json.readAddress(getKeyPrefix(FACTORY_IMPL)); - deployment.factoryProxy = json.readAddress(getKeyPrefix(FACTORY_PROXY)); + deployment.factoryImpl = readAddressOrDefaultToZero(json, FACTORY_IMPL); + deployment.factoryProxy = readAddressOrDefaultToZero(json, FACTORY_PROXY); + deployment.preminter = readAddressOrDefaultToZero(json, PREMINTER); } } diff --git a/src/factory/ZoraCreator1155FactoryImpl.sol b/src/factory/ZoraCreator1155FactoryImpl.sol index ba1bdd552..1f48a75b0 100644 --- a/src/factory/ZoraCreator1155FactoryImpl.sol +++ b/src/factory/ZoraCreator1155FactoryImpl.sol @@ -12,6 +12,8 @@ import {IContractMetadata} from "../interfaces/IContractMetadata.sol"; import {Ownable2StepUpgradeable} from "../utils/ownable/Ownable2StepUpgradeable.sol"; import {FactoryManagedUpgradeGate} from "../upgrades/FactoryManagedUpgradeGate.sol"; import {Zora1155} from "../proxies/Zora1155.sol"; +import {Create2Upgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/utils/Create2Upgradeable.sol"; +import {CREATE3} from "solmate/src/utils/CREATE3.sol"; import {ContractVersionBase} from "../version/ContractVersionBase.sol"; @@ -66,14 +68,56 @@ contract ZoraCreator1155FactoryImpl is IZoraCreator1155Factory, ContractVersionB /// @param defaultAdmin The default admin for the contract /// @param setupActions The actions to perform on the new contract upon initialization function createContract( - string memory newContractURI, + string calldata newContractURI, string calldata name, ICreatorRoyaltiesControl.RoyaltyConfiguration memory defaultRoyaltyConfiguration, address payable defaultAdmin, bytes[] calldata setupActions ) external returns (address) { - address newContract = address(new Zora1155(address(implementation))); + Zora1155 newContract = new Zora1155(address(implementation)); + _initializeContract(Zora1155(newContract), newContractURI, name, defaultRoyaltyConfiguration, defaultAdmin, setupActions); + + return address(newContract); + } + + function createContractDeterministic( + string calldata newContractURI, + string calldata name, + ICreatorRoyaltiesControl.RoyaltyConfiguration calldata defaultRoyaltyConfiguration, + address payable defaultAdmin, + bytes[] calldata setupActions + ) external returns (address) { + bytes32 digest = _hashContract(msg.sender, newContractURI, name, defaultAdmin); + + address createdContract = CREATE3.deploy(digest, abi.encodePacked(type(Zora1155).creationCode, abi.encode(implementation)), 0); + + Zora1155 newContract = Zora1155(payable(createdContract)); + + _initializeContract(newContract, newContractURI, name, defaultRoyaltyConfiguration, defaultAdmin, setupActions); + + return address(newContract); + } + + function deterministicContractAddress( + address msgSender, + string calldata newContractURI, + string calldata name, + address contractAdmin + ) external view returns (address) { + bytes32 digest = _hashContract(msgSender, newContractURI, name, contractAdmin); + + return CREATE3.getDeployed(digest); + } + + function _initializeContract( + Zora1155 newContract, + string calldata newContractURI, + string calldata name, + ICreatorRoyaltiesControl.RoyaltyConfiguration memory defaultRoyaltyConfiguration, + address payable defaultAdmin, + bytes[] calldata setupActions + ) private { emit SetupNewContract({ newContract: address(newContract), creator: msg.sender, @@ -83,9 +127,15 @@ contract ZoraCreator1155FactoryImpl is IZoraCreator1155Factory, ContractVersionB defaultRoyaltyConfiguration: defaultRoyaltyConfiguration }); - IZoraCreator1155Initializer(newContract).initialize(name, newContractURI, defaultRoyaltyConfiguration, defaultAdmin, setupActions); + IZoraCreator1155Initializer(address(newContract)).initialize(name, newContractURI, defaultRoyaltyConfiguration, defaultAdmin, setupActions); + } + + function _hashContract(address msgSender, string calldata newContractURI, string calldata name, address contractAdmin) private pure returns (bytes32) { + return keccak256(abi.encode(msgSender, contractAdmin, _stringHash(newContractURI), _stringHash(name))); + } - return address(newContract); + function _stringHash(string calldata value) private pure returns (bytes32) { + return keccak256(bytes(value)); } /// /// diff --git a/src/fee/MintFeeManager.sol b/src/fee/MintFeeManager.sol deleted file mode 100644 index e4ded27e9..000000000 --- a/src/fee/MintFeeManager.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import {TransferHelperUtils} from "../utils/TransferHelperUtils.sol"; -import {IMintFeeManager} from "../interfaces/IMintFeeManager.sol"; - -/// @title MintFeeManager -/// @notice Manages mint fees for an 1155 contract -contract MintFeeManager is IMintFeeManager { - uint256 public immutable mintFee; - address public immutable mintFeeRecipient; - - constructor(uint256 _mintFee, address _mintFeeRecipient) { - // Set fixed finders fee - if (_mintFee >= 0.1 ether) { - revert MintFeeCannotBeMoreThanZeroPointOneETH(_mintFee); - } - if (_mintFeeRecipient == address(0) && _mintFee > 0) { - revert CannotSetMintFeeToZeroAddress(); - } - mintFeeRecipient = _mintFeeRecipient; - mintFee = _mintFee; - } - - /// @notice Sends the mint fee to the mint fee recipient and returns the amount of ETH remaining that can be used in this transaction - /// @param _quantity The amount of toknens being minted - function _handleFeeAndGetValueSent(uint256 _quantity) internal returns (uint256 ethValueSent) { - ethValueSent = msg.value; - - // Handle mint fee - if (mintFeeRecipient != address(0)) { - uint256 totalFee = mintFee * _quantity; - ethValueSent -= totalFee; - if (!TransferHelperUtils.safeSendETH(mintFeeRecipient, totalFee, TransferHelperUtils.FUNDS_SEND_LOW_GAS_LIMIT)) { - revert CannotSendMintFee(mintFeeRecipient, totalFee); - } - } - } -} diff --git a/src/interfaces/IContractMetadata.sol b/src/interfaces/IContractMetadata.sol index 10dccd9dd..faddf69ae 100644 --- a/src/interfaces/IContractMetadata.sol +++ b/src/interfaces/IContractMetadata.sol @@ -1,10 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -interface IContractMetadata { +interface IHasContractName { /// @notice Contract name returns the pretty contract name function contractName() external returns (string memory); +} +interface IContractMetadata is IHasContractName { /// @notice Contract URI returns the uri for more information about the given contract function contractURI() external returns (string memory); } diff --git a/src/interfaces/IZoraCreator1155.sol b/src/interfaces/IZoraCreator1155.sol index 7ffd1d490..cdc8dd11d 100644 --- a/src/interfaces/IZoraCreator1155.sol +++ b/src/interfaces/IZoraCreator1155.sol @@ -4,11 +4,13 @@ pragma solidity 0.8.17; import {IERC165Upgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/interfaces/IERC165Upgradeable.sol"; import {IERC1155MetadataURIUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/interfaces/IERC1155MetadataURIUpgradeable.sol"; import {IZoraCreator1155TypesV1} from "../nft/IZoraCreator1155TypesV1.sol"; +import {IZoraCreator1155Errors} from "./IZoraCreator1155Errors.sol"; import {IRenderer1155} from "../interfaces/IRenderer1155.sol"; 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 "../premint/ZoraCreator1155Attribution.sol"; /* @@ -34,7 +36,7 @@ import {ICreatorRoyaltiesControl} from "../interfaces/ICreatorRoyaltiesControl.s /// @notice Main interface for the ZoraCreator1155 contract /// @author @iainnash / @tbtstl -interface IZoraCreator1155 is IZoraCreator1155TypesV1, IVersionedContract, IOwnable, IERC1155MetadataURIUpgradeable { +interface IZoraCreator1155 is IZoraCreator1155TypesV1, IZoraCreator1155Errors, IVersionedContract, IOwnable, IERC1155MetadataURIUpgradeable { function PERMISSION_BIT_ADMIN() external returns (uint256); function PERMISSION_BIT_MINTER() external returns (uint256); @@ -59,31 +61,7 @@ interface IZoraCreator1155 is IZoraCreator1155TypesV1, IVersionedContract, IOwna event ContractRendererUpdated(IRenderer1155 renderer); event ContractMetadataUpdated(address indexed updater, string uri, string name); event Purchased(address indexed sender, address indexed minter, uint256 indexed tokenId, uint256 quantity, uint256 value); - - error TokenIdMismatch(uint256 expected, uint256 actual); - error UserMissingRoleForToken(address user, uint256 tokenId, uint256 role); - - error Config_TransferHookNotSupported(address proposedAddress); - - error Mint_InsolventSaleTransfer(); - error Mint_ValueTransferFail(); - error Mint_TokenIDMintNotAllowed(); - error Mint_UnknownCommand(); - - error Burn_NotOwnerOrApproved(address operator, address user); - - error NewOwnerNeedsToBeAdmin(); - - error Sale_CannotCallNonSalesContract(address targetContract); - - error CallFailed(bytes reason); - error Renderer_NotValidRendererContract(); - - error ETHWithdrawFailed(address recipient, uint256 amount); - error FundsWithdrawInsolvent(uint256 amount, uint256 contractValue); - error ProtocolRewardsWithdrawFailed(address caller, address recipient, uint256 amount); - - error CannotMintMoreTokens(uint256 tokenId, uint256 quantity, uint256 totalMinted, uint256 maxSupply); + event CreatorAttribution(bytes32 structHash, string domainName, string version, address creator, bytes signature); /// @notice Only allow minting one token id at time /// @dev Mint contract function that calls the underlying sales function for commands @@ -104,6 +82,10 @@ interface IZoraCreator1155 is IZoraCreator1155TypesV1, IVersionedContract, IOwna /// @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) external returns (uint256 newTokenId); + + function delegatedTokenId(uint32 uid) external view returns (uint256 tokenId); + function updateTokenURI(uint256 tokenId, string memory _newURI) external; function updateContractMetadata(string memory _newURI, string memory _newName) external; diff --git a/src/interfaces/IZoraCreator1155Errors.sol b/src/interfaces/IZoraCreator1155Errors.sol new file mode 100644 index 000000000..19709c7ec --- /dev/null +++ b/src/interfaces/IZoraCreator1155Errors.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +interface IZoraCreator1155Errors { + error TokenIdMismatch(uint256 expected, uint256 actual); + error UserMissingRoleForToken(address user, uint256 tokenId, uint256 role); + + error Config_TransferHookNotSupported(address proposedAddress); + + error Mint_InsolventSaleTransfer(); + error Mint_ValueTransferFail(); + error Mint_TokenIDMintNotAllowed(); + error Mint_UnknownCommand(); + + error Burn_NotOwnerOrApproved(address operator, address user); + + error NewOwnerNeedsToBeAdmin(); + + error Sale_CannotCallNonSalesContract(address targetContract); + + error CallFailed(bytes reason); + error Renderer_NotValidRendererContract(); + + error ETHWithdrawFailed(address recipient, uint256 amount); + error FundsWithdrawInsolvent(uint256 amount, uint256 contractValue); + error ProtocolRewardsWithdrawFailed(address caller, address recipient, uint256 amount); + + error CannotMintMoreTokens(uint256 tokenId, uint256 quantity, uint256 totalMinted, uint256 maxSupply); +} diff --git a/src/interfaces/IZoraCreator1155Factory.sol b/src/interfaces/IZoraCreator1155Factory.sol index 19cdfa5f5..b72a2b469 100644 --- a/src/interfaces/IZoraCreator1155Factory.sol +++ b/src/interfaces/IZoraCreator1155Factory.sol @@ -29,6 +29,22 @@ interface IZoraCreator1155Factory is IVersionedContract { bytes[] calldata setupActions ) external returns (address); + /// @notice creates the contract, using a deterministic address based on the name, contract uri, and defaultAdmin + function createContractDeterministic( + string calldata contractURI, + string calldata name, + ICreatorRoyaltiesControl.RoyaltyConfiguration calldata defaultRoyaltyConfiguration, + address payable defaultAdmin, + bytes[] calldata setupActions + ) external returns (address); + + function deterministicContractAddress( + address msgSender, + string calldata newContractURI, + string calldata name, + address contractAdmin + ) external view returns (address); + function defaultMinters() external returns (IMinter1155[] memory minters); function initialize(address _owner) external; diff --git a/src/nft/ZoraCreator1155Impl.sol b/src/nft/ZoraCreator1155Impl.sol index d9638473b..bf62db80c 100644 --- a/src/nft/ZoraCreator1155Impl.sol +++ b/src/nft/ZoraCreator1155Impl.sol @@ -26,11 +26,11 @@ import {ITransferHookReceiver} from "../interfaces/ITransferHookReceiver.sol"; import {IFactoryManagedUpgradeGate} from "../interfaces/IFactoryManagedUpgradeGate.sol"; import {IZoraCreator1155} from "../interfaces/IZoraCreator1155.sol"; import {LegacyNamingControl} from "../legacy-naming/LegacyNamingControl.sol"; -import {MintFeeManager} from "../fee/MintFeeManager.sol"; import {PublicMulticall} from "../utils/PublicMulticall.sol"; import {SharedBaseConstants} from "../shared/SharedBaseConstants.sol"; import {TransferHelperUtils} from "../utils/TransferHelperUtils.sol"; import {ZoraCreator1155StorageV1} from "./ZoraCreator1155StorageV1.sol"; +import {ZoraCreator1155Attribution, PremintTokenSetup, PremintConfig} from "../premint/ZoraCreator1155Attribution.sol"; /// Imagine. Mint. Enjoy. /// @title ZoraCreator1155Impl @@ -43,7 +43,6 @@ contract ZoraCreator1155Impl is ReentrancyGuardUpgradeable, PublicMulticall, ERC1155Upgradeable, - MintFeeManager, UUPSUpgradeable, CreatorRendererControl, LegacyNamingControl, @@ -68,11 +67,11 @@ contract ZoraCreator1155Impl is IFactoryManagedUpgradeGate internal immutable factory; constructor( - uint256 _mintFeeAmount, + uint256, // TODO remove address _mintFeeRecipient, address _factory, address _protocolRewards - ) MintFeeManager(_mintFeeAmount, _mintFeeRecipient) ERC1155Rewards(_protocolRewards, _mintFeeRecipient) initializer { + ) ERC1155Rewards(_protocolRewards, _mintFeeRecipient) initializer { factory = IFactoryManagedUpgradeGate(_factory); } @@ -412,8 +411,18 @@ contract ZoraCreator1155Impl is // Require admin from the minter to mint _requireAdminOrRole(address(minter), tokenId, PERMISSION_BIT_MINTER); + // Get the token's first minter + address firstMinter = _handleFirstMinter(tokenId, minterArguments); + // Get value sent and handle mint fee - uint256 ethValueSent = _handleFeeAndGetValueSent(quantity); + uint256 ethValueSent = _handleRewardsAndGetValueSent( + msg.value, + quantity, + getCreatorRewardRecipient(), + createReferrals[tokenId], + address(0), + firstMinter + ); // Execute commands returned from minter _executeCommands(minter.requestMint(msg.sender, tokenId, quantity, ethValueSent, minterArguments).commands, ethValueSent, tokenId); @@ -421,13 +430,7 @@ contract ZoraCreator1155Impl is emit Purchased(msg.sender, address(minter), tokenId, quantity, msg.value); } - /// @notice Get the creator reward recipient address - /// @dev The creator is not enforced to set a funds recipient address, so in that case the reward would be claimable by creator's contract - function getCreatorRewardRecipient() public view returns (address payable) { - return config.fundsRecipient != address(0) ? config.fundsRecipient : payable(address(this)); - } - - /// @notice Mint tokens and payout rewards given a minter contract, minter arguments, a finder, and a origin + /// @notice Mint tokens and payout rewards given a minter contract, minter arguments, and a mint referral /// @param minter The minter contract to use /// @param tokenId The token ID to mint /// @param quantity The quantity of tokens to mint @@ -443,8 +446,18 @@ contract ZoraCreator1155Impl is // Require admin from the minter to mint _requireAdminOrRole(address(minter), tokenId, PERMISSION_BIT_MINTER); + // Get the token's first minter + address firstMinter = _handleFirstMinter(tokenId, minterArguments); + // Get value sent and handle mint rewards - uint256 ethValueSent = _handleRewardsAndGetValueSent(msg.value, quantity, getCreatorRewardRecipient(), createReferrals[tokenId], mintReferral); + uint256 ethValueSent = _handleRewardsAndGetValueSent( + msg.value, + quantity, + getCreatorRewardRecipient(), + createReferrals[tokenId], + mintReferral, + firstMinter + ); // Execute commands returned from minter _executeCommands(minter.requestMint(msg.sender, tokenId, quantity, ethValueSent, minterArguments).commands, ethValueSent, tokenId); @@ -452,6 +465,29 @@ contract ZoraCreator1155Impl is emit Purchased(msg.sender, address(minter), tokenId, quantity, msg.value); } + /// @dev Get and/or set the first minter a token + function _handleFirstMinter(uint256 tokenId, bytes calldata data) internal returns (address) { + // If this is the first mint for the token: + if (firstMinters[tokenId] == address(0)) { + // Decode the address of the reward recipient + // Assume the first argument is an address + address rewardRecipient = abi.decode(data, (address)); + + // Store the address to lookup for future mints + firstMinters[tokenId] = rewardRecipient; + + return rewardRecipient; + } + + return firstMinters[tokenId]; + } + + /// @notice Get the creator reward recipient address + /// @dev The creator is not enforced to set a funds recipient address, so in that case the reward would be claimable by creator's contract + function getCreatorRewardRecipient() public view returns (address payable) { + return config.fundsRecipient != address(0) ? config.fundsRecipient : payable(address(this)); + } + /// @notice Set a metadata renderer for a token /// @param tokenId The token ID to set the renderer for /// @param renderer The renderer to set @@ -722,4 +758,43 @@ contract ZoraCreator1155Impl is revert(); } } + + /* start eip712 functionality */ + mapping(uint32 => uint256) public delegatedTokenId; + + function delegateSetupNewToken(PremintConfig calldata premintConfig, bytes calldata signature) public nonReentrant returns (uint256 newTokenId) { + // if a token has already been created for a premint config with this uid: + if (delegatedTokenId[premintConfig.uid] != 0) { + // return its token id + return delegatedTokenId[premintConfig.uid]; + } + + bytes32 hashedPremintConfig = ZoraCreator1155Attribution.validateAndHashPremint(premintConfig); + + // recover the signer from the data + address creator = ZoraCreator1155Attribution.recoverSignerHashed(hashedPremintConfig, signature, address(this), 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); + + // require that the signer can create new tokens (is a valid creator) + _requireAdminOrRole(creator, CONTRACT_BASE_ID, PERMISSION_BIT_MINTER); + + // create the new token; msg sender will have PERMISSION_BIT_ADMIN on the new token + newTokenId = _setupNewTokenAndPermission(premintConfig.tokenConfig.tokenURI, premintConfig.tokenConfig.maxSupply, msg.sender, PERMISSION_BIT_ADMIN); + + delegatedTokenId[premintConfig.uid] = newTokenId; + + // invoke setup actions for new token, to save contract size, first get them from an external lib + bytes[] memory tokenSetupActions = PremintTokenSetup.makeSetupNewTokenCalls(newTokenId, creator, premintConfig.tokenConfig); + + // then invoke them, calling account should be original msg.sender, which has admin on the new token + _multicallInternal(tokenSetupActions); + + // remove the token creator as admin of the newly created token: + _removePermission(newTokenId, msg.sender, PERMISSION_BIT_ADMIN); + + // grant the token creator as admin of the newly created token + _addPermission(newTokenId, creator, PERMISSION_BIT_ADMIN); + } } diff --git a/src/premint/README.md b/src/premint/README.md new file mode 100644 index 000000000..6b65baff7 --- /dev/null +++ b/src/premint/README.md @@ -0,0 +1,59 @@ +# Preminter + +## Design + +A Preminter contract validates signatures and executes actions to 1. deploy contracts, 2. create tokens, 3. setup created token parameters +4. mint tokens to the executor of the transaction as a reward. + +## Design + +- General goal: Create a contract (”SignedExecutor”) that validates signatures and executes actions to 1. deploy contracts, 2. create tokens, 3. setup created token parameters 4. mint tokens to the executor of the transaction as a reward +- A creator can create multiple tokens without needing to pay any gas. Each token creation intent is bundled into a signature, which can be executed later by any account. The signature for each token are unordered; they can be executed in any order, and the order they are executed on will determine their token id. +## Contracts + +`Preminter`: Executes commands on the 1155 contract factory, and created 1155 contracts + +Constraints: + * **Contract creation params must be unique** - the combination of creator + metadata uri + name must be unique. The Preminter can only create a single contract for each combination of creator, metadat uri, and name. There must be some sort of validation in the create flow that ensures a contract has not been created with those parameters. + * **For each contract, token parameters must be unique.** The combination of parameters for the token to be created, including metadata uri, max supply, duration, etc **must be unique within each contract.** i.e. a contract cannot have two tokens with the same parameters. This is because we use this combination to ensure that a signature to create the token can only be executed once. An alternative design is to require a unique nonce to be appended to the parameters, which would ensure uniqueness; this would need to be provided by the backend. + +Functions: + * `premint`: takes an [EIP712 signature](https://eips.ethereum.org/EIPS/eip-712) created by a creator, contract and token creation params, and creates a contract if the contract doesn’t exist and creates a new token, or creates a new token on an existing contract if it exists. It then mints a specified quantity of tokens to the executor as a reward. These parameters are the same both if a new contract is created or a token is created on an existing contract. The signature must have been previously created from a hash built from all of the input parameters; the hash can be generated using `premintHashData`. **Each signature can only be executed against once**; this is enforced through uniqueness of the contract creation params, the token creation params, and quantity to mint. + * inputs: + * `contractCreationConfig` + * `contractAdmin` - creator/admin of the contract. **Must match the address of the account that signed the signature** + * `contractURI` - metadata uri of the contract + * `defaultRoyaltyConfiguration` - contract royalty config + * `tokenCreationConfig` + * `tokenURI` - metadata uri of the token to be created + * `tokenMaxSupply` - max supply of the token to be created + * `saleDuration` - how long this token should be on sale for, from the time of the first mint. If 0, duration is infinite + * `maxTokensPerAddress` - max tokens an address can mint + * `pricePerToken` - cost to mint each token + * `uid` - unique id of the token scoped within the contract. Ensures that multiple signatures for a token cannot be executed thus creating two tokens. + * `signature` - signature signed message containing all of the above parameters + * `quantityToMint` - how many of the initial tokens to mint to the executor + +## Functional flow: + +### Diagrams + +Creating a new contract + token: + +![Preminter creation flow](../../uml/generated/gasslessCreate-creation-sequence.svg) +![Preminter creation flow](../../uml/generated/gasslessCreate-creation-activity.svg) + +Collecting: + +![Preminter collection flow](../../uml/generated/gasslessCreate-collecting-sequence.svg) +![Preminter collection flow](../../uml/generated/gasslessCreate-collecting-activity.svg) + +* In the front-end a creator creates a signature for contract and token creation. The signature is created off-chain by the creator's account on a hash of the above said parameters. It there are additional tokens to be created, signatures are created for each token to be created. There must be some validation that a signature with the same parameters has not already been created (see constraints above). This can be done by checking against the uniqueness of the created signature. +* Once the creator has signed the message, a backend service (another db or blockchain) must store these signatures which can be retreived later by a collector. This backend must store both the contract + token creation parameters and the signature. +* A collector lands on a page that loads the signature and contract creation params based on the bytes32 signature. The contract + token creation parameters and signature are loaded from the backend service or a subgraph which loads the previously stored signature. +* The collector account executs the function `premint`, passing the corresponding signature and contract creation params. If the contract has not been created, it is created. A new token is created on that contract, and `quantityToMint` tokens are minted to the executor. + +## Additional caveats + +* The `Preminter` contract is granted the role `PERMISSION_BIT_MINTER` on the 1155 contract, allowing it to create new tokens. +* There are some issues where marketplaces show tx.origin of a transaction as the contract creator, which in this case would show the collector as the contract creator. \ No newline at end of file diff --git a/src/premint/ZoraCreator1155Attribution.sol b/src/premint/ZoraCreator1155Attribution.sol new file mode 100644 index 000000000..4c7f5cc2e --- /dev/null +++ b/src/premint/ZoraCreator1155Attribution.sol @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {IMinter1155} from "../interfaces/IMinter1155.sol"; +import {IZoraCreator1155} from "../interfaces/IZoraCreator1155.sol"; +import {ICreatorRoyaltiesControl} from "../interfaces/ICreatorRoyaltiesControl.sol"; +import {ECDSAUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/ECDSAUpgradeable.sol"; +import {ZoraCreatorFixedPriceSaleStrategy} from "../minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.sol"; + +struct ContractCreationConfig { + // Creator/admin of the created contract. Must match the account that signed the message + address contractAdmin; + // Metadata URI for the created contract + string contractURI; + // Name of the created contract + string contractName; +} + +struct TokenCreationConfig { + // 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; + // RoyaltyMintSchedule for created tokens. Every nth token will go to the royalty recipient. + uint32 royaltyMintSchedule; + // 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; +} + +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; +} + +/// @title 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. Incentivizes the third party to execute the transaction by offering +/// a reward in the form of minted tokens. +/// @author @oveddan +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)"); + + /** + * @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 + ); + + 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 = + 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)" + ); + + function hashPremint(PremintConfig calldata premintConfig) public pure returns (bytes32) { + return + keccak256(abi.encode(ATTRIBUTION_DOMAIN, _hashToken(premintConfig.tokenConfig), premintConfig.uid, premintConfig.version, premintConfig.deleted)); + } + + bytes32 constant TOKEN_DOMAIN = + keccak256( + "TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 mintStart,uint64 mintDuration,uint32 royaltyMintSchedule,uint32 royaltyBPS,address royaltyRecipient,address fixedPriceMinter)" + ); + + function _hashToken(TokenCreationConfig calldata tokenConfig) private pure returns (bytes32) { + return + keccak256( + abi.encode( + TOKEN_DOMAIN, + _stringHash(tokenConfig.tokenURI), + tokenConfig.maxSupply, + tokenConfig.maxTokensPerAddress, + tokenConfig.pricePerToken, + tokenConfig.mintStart, + tokenConfig.mintDuration, + tokenConfig.royaltyMintSchedule, + tokenConfig.royaltyBPS, + tokenConfig.royaltyRecipient, + tokenConfig.fixedPriceMinter + ) + ); + } + + function _stringHash(string calldata value) private pure returns (bytes32) { + return keccak256(bytes(value)); + } + + // todo: move to its own contract + error MintNotYetStarted(); + error PremintDeleted(); + + function validateAndHashPremint(PremintConfig calldata premintConfig) external view returns (bytes32) { + if (premintConfig.tokenConfig.mintStart != 0 && premintConfig.tokenConfig.mintStart > block.timestamp) { + // if the mint start is in the future, then revert + revert MintNotYetStarted(); + } + if (premintConfig.deleted) { + // if the signature says to be deleted, then dont execute any further minting logic; + // return 0 + revert PremintDeleted(); + } + + return hashPremint(premintConfig); + } +} + +// todo: make it consistent. +library PremintTokenSetup { + uint256 constant PERMISSION_BIT_MINTER = 2 ** 2; + + function makeSetupNewTokenCalls( + uint256 newTokenId, + address contractAdmin, + TokenCreationConfig calldata tokenConfig + ) external view returns (bytes[] memory calls) { + calls = new bytes[](3); + + address fixedPriceMinterAddress = tokenConfig.fixedPriceMinter; + // build array of the calls to make + // get setup actions and invoke them + // set up the sales strategy + // first, grant the fixed price sale strategy minting capabilities on the token + // tokenContract.addPermission(newTokenId, address(fixedPriceMinter), PERMISSION_BIT_MINTER); + calls[0] = abi.encodeWithSelector(IZoraCreator1155.addPermission.selector, newTokenId, fixedPriceMinterAddress, PERMISSION_BIT_MINTER); + + // set the sales config on that token + calls[1] = abi.encodeWithSelector( + IZoraCreator1155.callSale.selector, + newTokenId, + IMinter1155(fixedPriceMinterAddress), + abi.encodeWithSelector( + ZoraCreatorFixedPriceSaleStrategy.setSale.selector, + newTokenId, + _buildNewSalesConfig(contractAdmin, tokenConfig.pricePerToken, tokenConfig.maxTokensPerAddress, tokenConfig.mintDuration) + ) + ); + + // set the royalty config on that token: + calls[2] = abi.encodeWithSelector( + IZoraCreator1155.updateRoyaltiesForToken.selector, + newTokenId, + ICreatorRoyaltiesControl.RoyaltyConfiguration({ + royaltyBPS: tokenConfig.royaltyBPS, + royaltyRecipient: tokenConfig.royaltyRecipient, + royaltyMintSchedule: tokenConfig.royaltyMintSchedule + }) + ); + } + + function _buildNewSalesConfig( + address creator, + uint96 pricePerToken, + uint64 maxTokensPerAddress, + uint64 duration + ) private view returns (ZoraCreatorFixedPriceSaleStrategy.SalesConfig memory) { + uint64 saleStart = uint64(block.timestamp); + uint64 saleEnd = duration == 0 ? type(uint64).max : saleStart + duration; + + return + ZoraCreatorFixedPriceSaleStrategy.SalesConfig({ + pricePerToken: pricePerToken, + saleStart: saleStart, + saleEnd: saleEnd, + maxTokensPerAddress: maxTokensPerAddress, + fundsRecipient: creator + }); + } +} diff --git a/src/premint/ZoraCreator1155PremintExecutor.sol b/src/premint/ZoraCreator1155PremintExecutor.sol new file mode 100644 index 000000000..710ec62bc --- /dev/null +++ b/src/premint/ZoraCreator1155PremintExecutor.sol @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {ICreatorRoyaltiesControl} from "../interfaces/ICreatorRoyaltiesControl.sol"; +import {UUPSUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol"; +import {Ownable2StepUpgradeable} from "../utils/ownable/Ownable2StepUpgradeable.sol"; +import {IHasContractName} from "../interfaces/IContractMetadata.sol"; +import {IZoraCreator1155} from "../interfaces/IZoraCreator1155.sol"; +import {IZoraCreator1155Errors} from "../interfaces/IZoraCreator1155Errors.sol"; +import {IZoraCreator1155Factory} from "../interfaces/IZoraCreator1155Factory.sol"; +import {SharedBaseConstants} from "../shared/SharedBaseConstants.sol"; +import {ZoraCreatorFixedPriceSaleStrategy} from "../minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.sol"; +import {IMinter1155} from "../interfaces/IMinter1155.sol"; +import {PremintConfig, ContractCreationConfig, TokenCreationConfig, ZoraCreator1155Attribution} from "./ZoraCreator1155Attribution.sol"; + +/// @title Enables creation of and minting tokens on Zora1155 contracts transactions using eip-712 signatures. +/// Signature must provided by the contract creator, or an account that's permitted to create new tokens on the contract. +/// Mints the first x tokens to the executor of the transaction. +/// @author @oveddan +contract ZoraCreator1155PremintExecutor is 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; + + error MintNotYetStarted(); + error InvalidSignature(); + + constructor(IZoraCreator1155Factory _factory) { + zora1155Factory = _factory; + } + + function initialize(address _initialOwner) public initializer { + __Ownable_init(_initialOwner); + __UUPSUpgradeable_init(); + } + + event Preminted( + address indexed contractAddress, + uint256 indexed tokenId, + bool indexed createdNewContract, + uint32 uid, + ContractCreationConfig contractConfig, + TokenCreationConfig tokenConfig, + address minter, + uint256 quantityMinted + ); + + /// Creates a new token on the given erc1155 contract on behalf of a creator, and mints x tokens to the executor of this transaction. + /// If the erc1155 contract hasn't been created yet, it will be created with the given config within this same transaction. + /// The creator must sign the intent to create the token, and must have mint new token permission on the erc1155 contract, + /// or match the contract admin on the contract creation config if the contract hasn't been created yet. + /// Contract address of the created contract is deterministically generated from the contract config and this contract's address. + /// @param contractConfig Parameters for creating a new contract, if one doesn't exist yet. Used to resolve the deterministic contract address. + /// @param premintConfig Parameters for creating the token, and minting the initial x tokens to the executor. + /// @param signature Signature of the creator of the token, which must match the signer of the premint config, or have permission to create new tokens on the erc1155 contract if it's already been created + /// @param quantityToMint How many tokens to mint to the executor of this transaction once the token is created + /// @param mintComment A comment to associate with the mint action + function premint( + ContractCreationConfig calldata contractConfig, + PremintConfig calldata premintConfig, + bytes calldata signature, + uint256 quantityToMint, + string calldata mintComment + ) public payable returns (uint256 newTokenId) { + // get or create the contract with the given params + // contract address is deterministic. + (IZoraCreator1155 tokenContract, bool isNewContract) = _getOrCreateContract(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); + + tokenContract.mint{value: msg.value}( + IMinter1155(premintConfig.tokenConfig.fixedPriceMinter), + newTokenId, + quantityToMint, + abi.encode(msg.sender, mintComment) + ); + + // emit Preminted event + emit Preminted( + address(tokenContract), + newTokenId, + isNewContract, + premintConfig.uid, + contractConfig, + premintConfig.tokenConfig, + msg.sender, + quantityToMint + ); + } + + function _getOrCreateContract(ContractCreationConfig calldata contractConfig) private returns (IZoraCreator1155 tokenContract, bool isNewContract) { + address contractAddress = getContractAddress(contractConfig); + // first we see if the code is already deployed for the contract + isNewContract = contractAddress.code.length == 0; + + if (isNewContract) { + // if address doesnt exist for hash, createi t + tokenContract = _createContract(contractConfig); + } else { + tokenContract = IZoraCreator1155(contractAddress); + } + } + + function _createContract(ContractCreationConfig calldata contractConfig) private returns (IZoraCreator1155 tokenContract) { + // we need to build the setup actions, that must: + bytes[] memory setupActions = new bytes[](0); + + // create the contract via the factory. + address newContractAddresss = zora1155Factory.createContractDeterministic( + contractConfig.contractURI, + contractConfig.contractName, + // default royalty config is empty, since we set it on a token level + ICreatorRoyaltiesControl.RoyaltyConfiguration({royaltyBPS: 0, royaltyRecipient: address(0), royaltyMintSchedule: 0}), + payable(contractConfig.contractAdmin), + setupActions + ); + tokenContract = IZoraCreator1155(newContractAddresss); + } + + /// Gets the deterministic contract address for the given contract creation config. + /// Contract address is generated deterministically from a hash based onthe 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); + } + + /// @notice Utility function to determine if a premint contract has been created for a uid of a premint, and if so, + /// What is the token id that was created for the uid. + function premintStatus(address contractAddress, uint32 uid) public view returns (bool contractCreated, uint256 tokenIdForPremint) { + if (contractAddress.code.length == 0) { + return (false, 0); + } + return (true, IZoraCreator1155(contractAddress).delegatedTokenId(uid)); + } + + /// @notice Utility function to check if the signature is valid; i.e. the signature can be used to + /// mint a token with the given config. If the contract hasn't been created, then the signer + /// must match the contract admin on the premint config. If it has been created, the signer + /// must have permission to create new tokens on the erc1155 contract. + function isValidSignature( + ContractCreationConfig calldata contractConfig, + PremintConfig calldata premintConfig, + bytes calldata signature + ) public view returns (bool isValid, address contractAddress, address recoveredSigner) { + contractAddress = getContractAddress(contractConfig); + recoveredSigner = recoverSigner(premintConfig, contractAddress, signature); + + if (recoveredSigner == address(0)) { + return (false, contractAddress, address(0)); + } + + // if contract hasn't been created, signer must be the contract admin on the config + if (contractAddress.code.length == 0) { + isValid = recoveredSigner == contractConfig.contractAdmin; + } else { + // if contract has been created, signer must have mint new token permission + isValid = IZoraCreator1155(contractAddress).isAdminOrRole(recoveredSigner, CONTRACT_BASE_ID, PERMISSION_BIT_MINTER); + } + } + + // upgrade related functionality + + /// @notice The name of the contract for upgrade purposes + function contractName() external pure returns (string memory) { + return "ZORA 1155 Premint Executor"; + } + + // upgrade functionality + error UpgradeToMismatchedContractName(string expected, string actual); + + /// @notice Ensures the caller is authorized to upgrade the contract + /// @dev This function is called in `upgradeTo` & `upgradeToAndCall` + /// @param _newImpl The new implementation address + function _authorizeUpgrade(address _newImpl) internal override onlyOwner { + if (!_equals(IHasContractName(_newImpl).contractName(), this.contractName())) { + revert UpgradeToMismatchedContractName(this.contractName(), IHasContractName(_newImpl).contractName()); + } + } + + function _equals(string memory a, string memory b) internal pure returns (bool) { + return (keccak256(bytes(a)) == keccak256(bytes(b))); + } +} diff --git a/src/proxies/Zora1155PremintExecutorProxy.sol b/src/proxies/Zora1155PremintExecutorProxy.sol new file mode 100644 index 000000000..01bf0c0bc --- /dev/null +++ b/src/proxies/Zora1155PremintExecutorProxy.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {Enjoy} from "_imagine/mint/Enjoy.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +/* + + + ░░░░░░░░░░░░░░ + ░░▒▒░░░░░░░░░░░░░░░░░░░░ + ░░▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░ + ░░▒▒▒▒░░░░░░░░░░░░░░ ░░░░░░░░ + ░▓▓▒▒▒▒░░░░░░░░░░░░ ░░░░░░░ + ░▓▓▓▒▒▒▒░░░░░░░░░░░░ ░░░░░░░░ + ░▓▓▓▒▒▒▒░░░░░░░░░░░░░░ ░░░░░░░░░░ + ░▓▓▓▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░ + ░▓▓▓▓▓▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░ + ░▓▓▓▓▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░ + ░░▓▓▓▓▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ + ░░▓▓▓▓▓▓▒▒▒▒▒▒▒▒░░░░░░░░░▒▒▒▒▒░░ + ░░▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░ + ░░▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░ + + OURS TRULY, + + + */ + +/// Imagine. Mint. Enjoy. +/// @notice Imagine. Mint. Enjoy. +/// @author @oveddan +contract Zora1155PremintExecutorProxy is Enjoy, ERC1967Proxy { + constructor(address _logic, bytes memory _data) ERC1967Proxy(_logic, _data) {} +} diff --git a/src/utils/PublicMulticall.sol b/src/utils/PublicMulticall.sol index 8fea986fc..2c03e71a8 100644 --- a/src/utils/PublicMulticall.sol +++ b/src/utils/PublicMulticall.sol @@ -15,4 +15,14 @@ abstract contract PublicMulticall { results[i] = Address.functionDelegateCall(address(this), data[i]); } } + + /** + * @notice Receives and executes a batch of function calls on this contract. + */ + function _multicallInternal(bytes[] memory data) internal virtual returns (bytes[] memory results) { + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + results[i] = Address.functionDelegateCall(address(this), data[i]); + } + } } diff --git a/test/factory/ZoraCreator1155Factory.t.sol b/test/factory/ZoraCreator1155Factory.t.sol index aea59306b..03d8c0292 100644 --- a/test/factory/ZoraCreator1155Factory.t.sol +++ b/test/factory/ZoraCreator1155Factory.t.sol @@ -8,19 +8,37 @@ import {ZoraCreator1155Impl} from "../../src/nft/ZoraCreator1155Impl.sol"; import {Zora1155Factory} from "../../src/proxies/Zora1155Factory.sol"; import {IZoraCreator1155Factory} from "../../src/interfaces/IZoraCreator1155Factory.sol"; import {IZoraCreator1155} from "../../src/interfaces/IZoraCreator1155.sol"; +import {IZoraCreator1155Errors} from "../../src/interfaces/IZoraCreator1155Errors.sol"; import {IMinter1155} from "../../src/interfaces/IMinter1155.sol"; import {ICreatorRoyaltiesControl} from "../../src/interfaces/ICreatorRoyaltiesControl.sol"; +import {Zora1155} from "../../src/proxies/Zora1155.sol"; import {MockContractMetadata} from "../mock/MockContractMetadata.sol"; +import {ProxyShim} from "../../src/utils/ProxyShim.sol"; contract ZoraCreator1155FactoryTest is Test { - ZoraCreator1155FactoryImpl internal factory; address internal zora; + uint256 internal mintFeeAmount; + + ZoraCreator1155FactoryImpl internal factoryImpl; + ZoraCreator1155FactoryImpl internal factory; function setUp() external { zora = makeAddr("zora"); + mintFeeAmount = 0.000777 ether; + + address factoryShimAddress = address(new ProxyShim(zora)); + Zora1155Factory factoryProxy = new Zora1155Factory(factoryShimAddress, ""); + ProtocolRewards protocolRewards = new ProtocolRewards(); - ZoraCreator1155Impl zoraCreator1155Impl = new ZoraCreator1155Impl(0, zora, address(0), address(protocolRewards)); - factory = new ZoraCreator1155FactoryImpl(zoraCreator1155Impl, IMinter1155(address(1)), IMinter1155(address(2)), IMinter1155(address(3))); + ZoraCreator1155Impl zoraCreator1155Impl = new ZoraCreator1155Impl(mintFeeAmount, zora, address(factoryProxy), address(protocolRewards)); + + factoryImpl = new ZoraCreator1155FactoryImpl(zoraCreator1155Impl, IMinter1155(address(1)), IMinter1155(address(2)), IMinter1155(address(3))); + factory = ZoraCreator1155FactoryImpl(address(factoryProxy)); + + vm.startPrank(zora); + factory.upgradeTo(address(factoryImpl)); + factory.initialize(zora); + vm.stopPrank(); } function test_contractVersion() external { @@ -38,7 +56,7 @@ contract ZoraCreator1155FactoryTest is Test { function test_initialize(address initialOwner) external { vm.assume(initialOwner != address(0)); address payable proxyAddress = payable( - address(new Zora1155Factory(address(factory), abi.encodeWithSelector(ZoraCreator1155FactoryImpl.initialize.selector, initialOwner))) + address(new Zora1155Factory(address(factoryImpl), abi.encodeWithSelector(ZoraCreator1155FactoryImpl.initialize.selector, initialOwner))) ); ZoraCreator1155FactoryImpl proxy = ZoraCreator1155FactoryImpl(proxyAddress); assertEq(proxy.owner(), initialOwner); @@ -103,7 +121,7 @@ contract ZoraCreator1155FactoryTest is Test { ); address payable proxyAddress = payable( - address(new Zora1155Factory(address(factory), abi.encodeWithSelector(ZoraCreator1155FactoryImpl.initialize.selector, initialOwner))) + address(new Zora1155Factory(address(factoryImpl), abi.encodeWithSelector(ZoraCreator1155FactoryImpl.initialize.selector, initialOwner))) ); ZoraCreator1155FactoryImpl proxy = ZoraCreator1155FactoryImpl(proxyAddress); vm.prank(initialOwner); @@ -117,11 +135,127 @@ contract ZoraCreator1155FactoryTest is Test { MockContractMetadata mockContractMetadata = new MockContractMetadata("ipfs://asdfadsf", "name"); address payable proxyAddress = payable( - address(new Zora1155Factory(address(factory), abi.encodeWithSelector(ZoraCreator1155FactoryImpl.initialize.selector, initialOwner))) + address(new Zora1155Factory(address(factoryImpl), abi.encodeWithSelector(ZoraCreator1155FactoryImpl.initialize.selector, initialOwner))) ); ZoraCreator1155FactoryImpl proxy = ZoraCreator1155FactoryImpl(proxyAddress); vm.prank(initialOwner); vm.expectRevert(abi.encodeWithSignature("UpgradeToMismatchedContractName(string,string)", "ZORA 1155 Contract Factory", "name")); proxy.upgradeTo(address(mockContractMetadata)); } + + function test_createContractDeterministic_createsContractAtSameAddressForNameAndUri( + string calldata nameA, + string calldata uri, + address contractAdmin, + // this number will determine how transactions the factory makes before + // creating the deterministic contract. it should not affect the address + uint16 numberOfCallsBeforeCreation + ) external { + vm.assume(contractAdmin != address(0)); + vm.assume(numberOfCallsBeforeCreation < 5); + + address contractCreator = vm.addr(1); + + // we can know ahead of time the expected address + address expectedContractAddress = factory.deterministicContractAddress(contractCreator, uri, nameA, contractAdmin); + + // create parameters for contract creation + ICreatorRoyaltiesControl.RoyaltyConfiguration memory royaltyConfig = ICreatorRoyaltiesControl.RoyaltyConfiguration({ + royaltyBPS: 10, + royaltyRecipient: vm.addr(5), + royaltyMintSchedule: 100 + }); + bytes[] memory initSetup = new bytes[](1); + initSetup[0] = abi.encodeWithSelector(IZoraCreator1155.setupNewToken.selector, "ipfs://asdfadsf", 100); + + // create x number of contracts via the factory, this should affect the nonce. + for (uint256 i = 0; i < numberOfCallsBeforeCreation; i++) { + factory.createContract("ipfs://someOtherUri", "someOtherName", royaltyConfig, payable(vm.addr(3)), initSetup); + } + + // now create deterministically, address should match expected address + vm.prank(contractCreator); + address createdAddress = factory.createContractDeterministic(uri, nameA, royaltyConfig, payable(contractAdmin), new bytes[](0)); + + assertEq(createdAddress, expectedContractAddress); + } + + function test_createContractDeterministic_whenContractUpgraded_stillHasSameAddress() external { + string memory uri = "ipfs://asdfadsf"; + string memory nameA = "nameA"; + address contractAdmin = vm.addr(1); + // account that creates the contract (not necessarily the owner/admin) + address contractCreator = vm.addr(2); + + // 1. get the deterministic address of the contract before its created, from the existing factory proxy + address expectedContractAddress = factory.deterministicContractAddress(contractCreator, uri, nameA, contractAdmin); + + // 2. update the erc1155 implementation: + // * create a new version of the erc1155 implementation + // * create a new factory that points to that new erc1155 implementation, + // * upgrade the proxy to point to the new factory + uint256 newMintFeeAmount = 0.000888 ether; + IZoraCreator1155 newZoraCreator = new ZoraCreator1155Impl(newMintFeeAmount, zora, address(factory), address(new ProtocolRewards())); + + ZoraCreator1155FactoryImpl newFactoryImpl = new ZoraCreator1155FactoryImpl( + newZoraCreator, + IMinter1155(address(0)), + IMinter1155(address(0)), + IMinter1155(address(0)) + ); + + vm.prank(zora); + factory.upgradeTo(address(newFactoryImpl)); + + // sanity check - make sure that the proxy erc1155 implementation is pointing to the new implementation + assertEq(address(factory.implementation()), address(newZoraCreator)); + + // 3. Create a contract with a deterministic address, it should match the address from before the upgrade + ICreatorRoyaltiesControl.RoyaltyConfiguration memory royaltyConfig = ICreatorRoyaltiesControl.RoyaltyConfiguration({ + royaltyBPS: 10, + royaltyRecipient: vm.addr(5), + royaltyMintSchedule: 100 + }); + bytes[] memory initSetup = new bytes[](1); + initSetup[0] = abi.encodeWithSelector(IZoraCreator1155.setupNewToken.selector, "ipfs://asdfadsf", 100); + + // now create deterministically, address should match expected address + vm.prank(contractCreator); + address createdAddress = factory.createContractDeterministic(uri, nameA, royaltyConfig, payable(contractAdmin), initSetup); + + assertEq(createdAddress, expectedContractAddress); + } + + function test_createContractDeterministic_createdContractcontractCanBeUpgraded() external { + string memory uri = "ipfs://asdfadsf"; + string memory nameA = "nameA"; + address contractAdmin = vm.addr(1); + + // 1. Have the factory the contract deterministically + ICreatorRoyaltiesControl.RoyaltyConfiguration memory royaltyConfig = ICreatorRoyaltiesControl.RoyaltyConfiguration({ + royaltyBPS: 10, + royaltyRecipient: vm.addr(5), + royaltyMintSchedule: 100 + }); + bytes[] memory initSetup = new bytes[](1); + initSetup[0] = abi.encodeWithSelector(IZoraCreator1155.setupNewToken.selector, "ipfs://asdfadsf", 100); + + // now create deterministically, address should match expected address + address createdAddress = factory.createContractDeterministic(uri, nameA, royaltyConfig, payable(contractAdmin), initSetup); + + ZoraCreator1155Impl creatorProxy = ZoraCreator1155Impl(createdAddress); + + // 2. upgrade the created contract by creating a new contract and upgrading the existing one to point to it. + uint256 newMintFeeAmount = 0.000888 ether; + IZoraCreator1155 newZoraCreator = new ZoraCreator1155Impl(newMintFeeAmount, zora, address(0), address(new ProtocolRewards())); + + address[] memory baseImpls = new address[](1); + baseImpls[0] = address(factory.implementation()); + + vm.prank(zora); + factory.registerUpgradePath(baseImpls, address(newZoraCreator)); + + vm.prank(creatorProxy.owner()); + creatorProxy.upgradeTo(address(newZoraCreator)); + } } diff --git a/test/factory/ZoraCreator1155Factory_Fork.t.sol b/test/factory/ZoraCreator1155Factory_Fork.t.sol index 894b55c6d..82a083834 100644 --- a/test/factory/ZoraCreator1155Factory_Fork.t.sol +++ b/test/factory/ZoraCreator1155Factory_Fork.t.sol @@ -3,18 +3,24 @@ pragma solidity 0.8.17; import "forge-std/Test.sol"; import {IZoraCreator1155Factory} from "../../src/interfaces/IZoraCreator1155Factory.sol"; +import {IZoraCreator1155Errors} from "../../src/interfaces/IZoraCreator1155Errors.sol"; import {IZoraCreator1155} from "../../src/interfaces/IZoraCreator1155.sol"; +import {ZoraCreator1155Impl} from "../../src/nft/ZoraCreator1155Impl.sol"; import {IMinter1155} from "../../src/interfaces/IMinter1155.sol"; import {IOwnable} from "../../src/interfaces/IOwnable.sol"; import {ICreatorRoyaltiesControl} from "../../src/interfaces/ICreatorRoyaltiesControl.sol"; import {MockContractMetadata} from "../mock/MockContractMetadata.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {ZoraCreatorFixedPriceSaleStrategy} from "../../src/minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.sol"; -import {MintFeeManager} from "../../src/fee/MintFeeManager.sol"; - import {ForkDeploymentConfig} from "../../src/deployment/DeploymentConfig.sol"; contract ZoraCreator1155FactoryForkTest is ForkDeploymentConfig, Test { + uint96 constant tokenPrice = 1 ether; + uint256 constant quantityToMint = 3; + uint256 constant tokenMaxSupply = 100; + uint32 constant royaltyMintSchedule = 10; + uint32 constant royaltyBPS = 100; + address collector; address creator; @@ -38,7 +44,6 @@ contract ZoraCreator1155FactoryForkTest is ForkDeploymentConfig, Test { function _setupToken(IZoraCreator1155 target, IMinter1155 fixedPrice, uint96 tokenPrice) private returns (uint256 tokenId) { string memory tokenURI = "ipfs://token"; - uint256 tokenMaxSupply = 100; tokenId = target.setupNewToken(tokenURI, tokenMaxSupply); @@ -114,20 +119,12 @@ contract ZoraCreator1155FactoryForkTest is ForkDeploymentConfig, Test { IZoraCreator1155 target = _createErc1155Contract(factory); // ** 2. Setup a new token with the fixed price sales strategy and the token price ** - uint96 tokenPrice = 1 ether; uint256 tokenId = _setupToken(target, fixedPrice, tokenPrice); // ** 3. Mint on that contract ** - - // get the mint fee from the contract - uint256 mintFee = MintFeeManager(address(target)).mintFee(); - - // make sure the mint fee amount matches the configured mint fee amount - assertEq(mintFee, getChainConfig().mintFeeAmount, chainName); + uint256 mintFee = getChainConfig().mintFeeAmount; // mint 3 tokens - - uint256 quantityToMint = 3; uint256 valueToSend = quantityToMint * (tokenPrice + mintFee); // mint the token diff --git a/test/fee/MintFeeManager.t.sol b/test/fee/MintFeeManager.t.sol deleted file mode 100644 index 12c903e5d..000000000 --- a/test/fee/MintFeeManager.t.sol +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import "forge-std/Test.sol"; -import {ProtocolRewards} from "@zoralabs/protocol-rewards/src/ProtocolRewards.sol"; -import {ZoraCreator1155Impl} from "../../src/nft/ZoraCreator1155Impl.sol"; -import {Zora1155} from "../../src/proxies/Zora1155.sol"; -import {IZoraCreator1155} from "../../src/interfaces/IZoraCreator1155.sol"; -import {IZoraCreator1155TypesV1} from "../../src/nft/IZoraCreator1155TypesV1.sol"; -import {ICreatorRoyaltiesControl} from "../../src/interfaces/ICreatorRoyaltiesControl.sol"; -import {IZoraCreator1155Factory} from "../../src/interfaces/IZoraCreator1155Factory.sol"; -import {IMintFeeManager} from "../../src/interfaces/IMintFeeManager.sol"; -import {SimpleMinter} from "../mock/SimpleMinter.sol"; - -contract MintFeeManagerTest is Test { - ZoraCreator1155Impl internal zoraCreator1155Impl; - ZoraCreator1155Impl internal target; - ProtocolRewards internal protocolRewards; - address payable internal admin; - address internal recipient; - uint256 internal adminRole; - uint256 internal minterRole; - uint256 internal fundsManagerRole; - - function setUp() external { - protocolRewards = new ProtocolRewards(); - admin = payable(vm.addr(0x1)); - recipient = vm.addr(0x2); - } - - function _emptyInitData() internal pure returns (bytes[] memory response) { - response = new bytes[](0); - } - - function test_mintFeeSent(uint32 mintFee, uint256 quantity) external { - vm.assume(quantity < 100); - vm.assume(mintFee < 0.1 ether); - uint256 mintPrice = mintFee * quantity; - zoraCreator1155Impl = new ZoraCreator1155Impl(mintFee, recipient, address(0), address(protocolRewards)); - target = ZoraCreator1155Impl(address(new Zora1155(address(zoraCreator1155Impl)))); - adminRole = target.PERMISSION_BIT_ADMIN(); - target.initialize("test", "test", ICreatorRoyaltiesControl.RoyaltyConfiguration(0, 0, address(0)), admin, _emptyInitData()); - - vm.prank(admin); - uint256 tokenId = target.setupNewToken("test", quantity); - - address minter = address(new SimpleMinter()); - vm.prank(admin); - target.addPermission(tokenId, minter, adminRole); - - vm.deal(admin, mintPrice); - vm.prank(admin); - target.mint{value: mintPrice}(SimpleMinter(payable(minter)), tokenId, quantity, abi.encode(recipient)); - - vm.prank(admin); - target.withdraw(); - - // Mint fee is not paid if the recipient is address(0) - assertEq(recipient.balance, recipient == address(0) ? 0 : mintPrice); - assertEq(admin.balance, recipient == address(0) ? mintPrice : mintPrice - (mintFee * quantity)); - } - - function test_mintFeeSent_revertCannotSendMintFee(uint32 mintFee, uint256 quantity) external { - vm.assume(quantity < 100); - - uint256 mintPrice = mintFee * quantity; - - // Use this mock contract as a recipient so we can reject ETH payments. - SimpleMinter _recip = new SimpleMinter(); - _recip.setReceiveETH(false); - address _recipient = address(_recip); - - zoraCreator1155Impl = new ZoraCreator1155Impl(mintFee, _recipient, address(0), address(protocolRewards)); - target = ZoraCreator1155Impl(address(new Zora1155(address(zoraCreator1155Impl)))); - adminRole = target.PERMISSION_BIT_ADMIN(); - target.initialize("test", "test", ICreatorRoyaltiesControl.RoyaltyConfiguration(0, 0, address(0)), admin, _emptyInitData()); - - vm.prank(admin); - uint256 tokenId = target.setupNewToken("test", quantity); - - address minter = address(new SimpleMinter()); - vm.prank(admin); - target.addPermission(tokenId, minter, adminRole); - - vm.deal(admin, mintPrice); - vm.expectRevert(abi.encodeWithSelector(IMintFeeManager.CannotSendMintFee.selector, _recipient, mintPrice)); - vm.prank(admin); - target.mint{value: mintPrice}(SimpleMinter(payable(minter)), tokenId, quantity, abi.encode(recipient)); - } -} diff --git a/test/fixtures/Zora1155FactoryFixtures.sol b/test/fixtures/Zora1155FactoryFixtures.sol new file mode 100644 index 000000000..83fbf19d9 --- /dev/null +++ b/test/fixtures/Zora1155FactoryFixtures.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {ZoraCreator1155Impl} from "../../src/nft/ZoraCreator1155Impl.sol"; +import {ZoraCreatorFixedPriceSaleStrategy} from "../../src/minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.sol"; +import {IZoraCreator1155Errors} from "../../src/interfaces/IZoraCreator1155Errors.sol"; +import {IZoraCreator1155} from "../../src/interfaces/IZoraCreator1155.sol"; +import {IMinter1155} from "../../src/interfaces/IMinter1155.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"; + +library Zora1155FactoryFixtures { + function setupZora1155Impl(uint256 mintFeeAmount, address zora, Zora1155Factory factoryProxy) internal returns (ZoraCreator1155Impl) { + ProtocolRewards rewards = new ProtocolRewards(); + return new ZoraCreator1155Impl(mintFeeAmount, zora, address(factoryProxy), address(rewards)); + } + + function upgradeFactoryProxyToUse1155( + Zora1155Factory factoryProxy, + IZoraCreator1155 zoraCreator1155Impl, + IMinter1155 fixedPriceMinter, + address admin + ) internal returns (ZoraCreator1155FactoryImpl factoryImpl) { + factoryImpl = new ZoraCreator1155FactoryImpl(zoraCreator1155Impl, IMinter1155(address(1)), fixedPriceMinter, IMinter1155(address(3))); + + ZoraCreator1155FactoryImpl factoryAtProxy = ZoraCreator1155FactoryImpl(address(factoryProxy)); + + factoryAtProxy.upgradeTo(address(factoryImpl)); + factoryAtProxy.initialize(admin); + } + + function setupFactoryProxy(address deployer) internal returns (Zora1155Factory factoryProxy) { + address factoryShimAddress = address(new ProxyShim(deployer)); + factoryProxy = new Zora1155Factory(factoryShimAddress, ""); + } + + function setup1155AndFactoryProxy( + uint256 mintFeeAmount, + address zora, + address deployer + ) internal returns (ZoraCreator1155Impl zoraCreator1155Impl, IMinter1155 fixedPriceMinter, Zora1155Factory factoryProxy) { + factoryProxy = setupFactoryProxy(deployer); + fixedPriceMinter = new ZoraCreatorFixedPriceSaleStrategy(); + zoraCreator1155Impl = setupZora1155Impl(mintFeeAmount, zora, factoryProxy); + upgradeFactoryProxyToUse1155(factoryProxy, zoraCreator1155Impl, fixedPriceMinter, deployer); + } +} diff --git a/test/fixtures/Zora1155PremintFixtures.sol b/test/fixtures/Zora1155PremintFixtures.sol new file mode 100644 index 000000000..274615a1d --- /dev/null +++ b/test/fixtures/Zora1155PremintFixtures.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +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/premint/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: 100}); + } + + function makeDefaultTokenCreationConfig(IMinter1155 fixedPriceMinter, address royaltyRecipient) internal pure returns (TokenCreationConfig memory) { + ICreatorRoyaltiesControl.RoyaltyConfiguration memory royaltyConfig = defaultRoyaltyConfig(royaltyRecipient); + return + TokenCreationConfig({ + 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) + }); + } +} diff --git a/test/minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.t.sol b/test/minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.t.sol index caa87907b..3e0dfee30 100644 --- a/test/minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.t.sol +++ b/test/minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.t.sol @@ -5,7 +5,7 @@ import "forge-std/Test.sol"; import {ProtocolRewards} from "@zoralabs/protocol-rewards/src/ProtocolRewards.sol"; import {ZoraCreator1155Impl} from "../../../src/nft/ZoraCreator1155Impl.sol"; import {Zora1155} from "../../../src/proxies/Zora1155.sol"; -import {IZoraCreator1155} from "../../../src/interfaces/IZoraCreator1155.sol"; +import {IZoraCreator1155Errors} from "../../../src/interfaces/IZoraCreator1155Errors.sol"; import {IMinter1155} from "../../../src/interfaces/IMinter1155.sol"; import {ICreatorRoyaltiesControl} from "../../../src/interfaces/ICreatorRoyaltiesControl.sol"; import {IZoraCreator1155Factory} from "../../../src/interfaces/IZoraCreator1155Factory.sol"; @@ -17,12 +17,17 @@ contract ZoraCreatorFixedPriceSaleStrategyTest is Test { ZoraCreatorFixedPriceSaleStrategy internal fixedPrice; address payable internal admin = payable(address(0x999)); address internal zora; + address internal tokenRecipient; + address internal fundsRecipient; event SaleSet(address indexed mediaContract, uint256 indexed tokenId, ZoraCreatorFixedPriceSaleStrategy.SalesConfig salesConfig); event MintComment(address indexed sender, address indexed tokenContract, uint256 indexed tokenId, uint256 quantity, string comment); function setUp() external { zora = makeAddr("zora"); + tokenRecipient = makeAddr("tokenRecipient"); + fundsRecipient = makeAddr("fundsRecipient"); + bytes[] memory emptyData = new bytes[](0); ProtocolRewards protocolRewards = new ProtocolRewards(); ZoraCreator1155Impl targetImpl = new ZoraCreator1155Impl(0, zora, address(0), address(protocolRewards)); @@ -73,11 +78,14 @@ contract ZoraCreatorFixedPriceSaleStrategyTest is Test { ); vm.stopPrank(); - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); + uint256 numTokens = 10; + uint256 totalReward = target.computeTotalReward(numTokens); + uint256 totalValue = (1 ether * numTokens) + totalReward; + + vm.deal(tokenRecipient, totalValue); vm.startPrank(tokenRecipient); - target.mint{value: 10 ether}(fixedPrice, newTokenId, 10, abi.encode(tokenRecipient, "")); + target.mint{value: totalValue}(fixedPrice, newTokenId, 10, abi.encode(tokenRecipient, "")); assertEq(target.balanceOf(tokenRecipient, newTokenId), 10); assertEq(address(target).balance, 10 ether); @@ -118,11 +126,14 @@ contract ZoraCreatorFixedPriceSaleStrategyTest is Test { ); vm.stopPrank(); - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); + uint256 numTokens = 10; + uint256 totalReward = target.computeTotalReward(numTokens); + uint256 totalValue = (1 ether * numTokens) + totalReward; + + vm.deal(tokenRecipient, totalValue); vm.startPrank(tokenRecipient); - target.mint{value: 10 ether}(fixedPrice, newTokenId, 10, abi.encode(tokenRecipient)); + target.mint{value: totalValue}(fixedPrice, newTokenId, 10, abi.encode(tokenRecipient)); assertEq(target.balanceOf(tokenRecipient, newTokenId), 10); assertEq(address(target).balance, 10 ether); @@ -163,13 +174,16 @@ contract ZoraCreatorFixedPriceSaleStrategyTest is Test { ); vm.stopPrank(); - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); + uint256 numTokens = 10; + uint256 totalReward = target.computeTotalReward(numTokens); + uint256 totalValue = (1 ether * numTokens) + totalReward; + + vm.deal(tokenRecipient, totalValue); vm.startPrank(tokenRecipient); vm.expectEmit(true, true, true, true); emit MintComment(tokenRecipient, address(target), newTokenId, 10, "test comment"); - target.mint{value: 10 ether}(fixedPrice, newTokenId, 10, abi.encode(tokenRecipient, "test comment")); + target.mint{value: totalValue}(fixedPrice, newTokenId, 10, abi.encode(tokenRecipient, "test comment")); assertEq(target.balanceOf(tokenRecipient, newTokenId), 10); assertEq(address(target).balance, 10 ether); @@ -198,7 +212,6 @@ contract ZoraCreatorFixedPriceSaleStrategyTest is Test { ); vm.stopPrank(); - address tokenRecipient = address(322); vm.deal(tokenRecipient, 20 ether); vm.expectRevert(abi.encodeWithSignature("SaleHasNotStarted()")); @@ -229,7 +242,6 @@ contract ZoraCreatorFixedPriceSaleStrategyTest is Test { ); vm.stopPrank(); - address tokenRecipient = address(322); vm.deal(tokenRecipient, 20 ether); vm.expectRevert(abi.encodeWithSignature("SaleEnded()")); @@ -260,12 +272,15 @@ contract ZoraCreatorFixedPriceSaleStrategyTest is Test { ); vm.stopPrank(); - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); + uint256 numTokens = 6; + uint256 totalReward = target.computeTotalReward(numTokens); + uint256 totalValue = (1 ether * numTokens) + totalReward; + + vm.deal(tokenRecipient, totalValue); vm.prank(tokenRecipient); vm.expectRevert(abi.encodeWithSelector(ILimitedMintPerAddress.UserExceedsMintLimit.selector, tokenRecipient, 5, 6)); - target.mint{value: 6 ether}(fixedPrice, newTokenId, 6, abi.encode(tokenRecipient, "")); + target.mint{value: totalValue}(fixedPrice, newTokenId, numTokens, abi.encode(tokenRecipient, "")); } function testFail_setupMint() external { @@ -289,7 +304,6 @@ contract ZoraCreatorFixedPriceSaleStrategyTest is Test { ); vm.stopPrank(); - address tokenRecipient = address(322); vm.deal(tokenRecipient, 20 ether); vm.startPrank(tokenRecipient); @@ -324,19 +338,19 @@ contract ZoraCreatorFixedPriceSaleStrategyTest is Test { ); vm.stopPrank(); - address tokenRecipient = address(322); vm.deal(tokenRecipient, 20 ether); vm.startPrank(tokenRecipient); - vm.expectRevert(abi.encodeWithSignature("WrongValueSent()")); - target.mint{value: 0.9 ether}(fixedPrice, newTokenId, 1, abi.encode(tokenRecipient, "")); - vm.expectRevert(abi.encodeWithSignature("WrongValueSent()")); - target.mint{value: 1.1 ether}(fixedPrice, newTokenId, 1, abi.encode(tokenRecipient, "")); - target.mint{value: 1 ether}(fixedPrice, newTokenId, 1, abi.encode(tokenRecipient, "")); + + target.mint{value: 1.000777 ether}(fixedPrice, newTokenId, 1, abi.encode(tokenRecipient, "")); + vm.stopPrank(); } function test_FundsRecipient() external { + uint96 pricePerToken = 1 ether; + uint256 numTokens = 10; + vm.startPrank(admin); uint256 newTokenId = target.setupNewToken("https://zora.co/testing/token.json", 10); target.addPermission(newTokenId, address(fixedPrice), target.PERMISSION_BIT_MINTER()); @@ -347,22 +361,25 @@ contract ZoraCreatorFixedPriceSaleStrategyTest is Test { ZoraCreatorFixedPriceSaleStrategy.setSale.selector, newTokenId, ZoraCreatorFixedPriceSaleStrategy.SalesConfig({ - pricePerToken: 1 ether, + pricePerToken: pricePerToken, saleStart: 0, saleEnd: type(uint64).max, maxTokensPerAddress: 0, - fundsRecipient: address(1) + fundsRecipient: fundsRecipient }) ) ); vm.stopPrank(); - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); + uint256 totalReward = target.computeTotalReward(numTokens); + uint256 totalValue = (pricePerToken * numTokens) + totalReward; + + vm.deal(tokenRecipient, totalValue); + vm.prank(tokenRecipient); - target.mint{value: 10 ether}(fixedPrice, newTokenId, 10, abi.encode(tokenRecipient, "")); + target.mint{value: totalValue}(fixedPrice, newTokenId, numTokens, abi.encode(tokenRecipient, "")); - assertEq(address(1).balance, 10 ether); + assertEq(fundsRecipient.balance, 10 ether); } function test_MintedPerRecipientGetter() external { @@ -386,10 +403,13 @@ contract ZoraCreatorFixedPriceSaleStrategyTest is Test { ); vm.stopPrank(); - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); + uint256 numTokens = 10; + uint256 totalReward = target.computeTotalReward(numTokens); + + vm.deal(tokenRecipient, totalReward); + vm.prank(tokenRecipient); - target.mint{value: 0 ether}(fixedPrice, newTokenId, 10, abi.encode(tokenRecipient, "")); + target.mint{value: totalReward}(fixedPrice, newTokenId, 10, abi.encode(tokenRecipient, "")); assertEq(fixedPrice.getMintedPerWallet(address(target), newTokenId, tokenRecipient), 10); } diff --git a/test/minters/merkle/ZoraCreatorMerkleMinterStrategy.t.sol b/test/minters/merkle/ZoraCreatorMerkleMinterStrategy.t.sol index 60c4c7781..972553200 100644 --- a/test/minters/merkle/ZoraCreatorMerkleMinterStrategy.t.sol +++ b/test/minters/merkle/ZoraCreatorMerkleMinterStrategy.t.sol @@ -5,7 +5,7 @@ import "forge-std/Test.sol"; import {ProtocolRewards} from "@zoralabs/protocol-rewards/src/ProtocolRewards.sol"; import {ZoraCreator1155Impl} from "../../../src/nft/ZoraCreator1155Impl.sol"; import {Zora1155} from "../../../src/proxies/Zora1155.sol"; -import {IZoraCreator1155} from "../../../src/interfaces/IZoraCreator1155.sol"; +import {IZoraCreator1155Errors} from "../../../src/interfaces/IZoraCreator1155Errors.sol"; import {IRenderer1155} from "../../../src/interfaces/IRenderer1155.sol"; import {ICreatorRoyaltiesControl} from "../../../src/interfaces/ICreatorRoyaltiesControl.sol"; import {ILimitedMintPerAddress} from "../../../src/interfaces/ILimitedMintPerAddress.sol"; @@ -18,11 +18,13 @@ contract ZoraCreatorMerkleMinterStrategyTest is Test { ZoraCreatorMerkleMinterStrategy internal merkleMinter; address payable internal admin = payable(address(0x999)); address internal zora; + address internal mintTo; event SaleSet(address indexed sender, uint256 indexed tokenId, ZoraCreatorMerkleMinterStrategy.MerkleSaleSettings merkleSaleSettings); function setUp() external { zora = makeAddr("zora"); + mintTo = address(1); bytes[] memory emptyData = new bytes[](0); protocolRewards = new ProtocolRewards(); ZoraCreator1155Impl targetImpl = new ZoraCreator1155Impl(0, zora, address(0), address(protocolRewards)); @@ -71,16 +73,19 @@ contract ZoraCreatorMerkleMinterStrategyTest is Test { ); vm.stopPrank(); - address mintTo = address(0x0000000000000000000000000000000000000001); - vm.deal(mintTo, 20 ether); + uint256 totalSale = 10 ether; + uint256 totalReward = target.computeTotalReward(10); + uint256 totalValue = totalSale + totalReward; + + vm.deal(mintTo, totalValue); uint256 maxQuantity = 10; - uint256 pricePerToken = 1000000000000000000; + uint256 pricePerToken = 1 ether; bytes32[] memory merkleProof = new bytes32[](1); merkleProof[0] = bytes32(0x71013e6ce1f439aaa91aa706ddd0769517fbaa4d72a936af4a7c75d29b1ca862); vm.startPrank(mintTo); - target.mint{value: 10 ether}(merkleMinter, newTokenId, 10, abi.encode(mintTo, maxQuantity, pricePerToken, merkleProof)); + target.mint{value: totalValue}(merkleMinter, newTokenId, 10, abi.encode(mintTo, maxQuantity, pricePerToken, merkleProof)); assertEq(target.balanceOf(mintTo, newTokenId), 10); assertEq(address(target).balance, 10 ether); @@ -109,17 +114,20 @@ contract ZoraCreatorMerkleMinterStrategyTest is Test { ); vm.stopPrank(); - address mintTo = address(0x0000000000000000000000000000000000000001); - vm.deal(mintTo, 20 ether); + uint256 totalSale = 10 ether; + uint256 totalReward = target.computeTotalReward(10); + uint256 totalValue = totalSale + totalReward; + + vm.deal(mintTo, totalValue); uint256 maxQuantity = 10; - uint256 pricePerToken = 1000000000000000000; + uint256 pricePerToken = 1 ether; bytes32[] memory merkleProof = new bytes32[](1); merkleProof[0] = bytes32(0x71013e6ce1f439aaa91aa706ddd0769517fbaa4d72a936af4a7c75d29b1ca862); vm.prank(mintTo); vm.expectRevert(abi.encodeWithSignature("SaleHasNotStarted()")); - target.mint{value: 10 ether}(merkleMinter, newTokenId, 10, abi.encode(mintTo, maxQuantity, pricePerToken, merkleProof)); + target.mint{value: totalValue}(merkleMinter, newTokenId, 10, abi.encode(mintTo, maxQuantity, pricePerToken, merkleProof)); } function test_PreSaleEnd() external { @@ -144,7 +152,6 @@ contract ZoraCreatorMerkleMinterStrategyTest is Test { ); vm.stopPrank(); - address mintTo = address(0x0000000000000000000000000000000000000001); vm.deal(mintTo, 20 ether); uint256 maxQuantity = 10; @@ -184,8 +191,11 @@ contract ZoraCreatorMerkleMinterStrategyTest is Test { ); vm.stopPrank(); - address mintTo = address(0x0000000000000000000000000000000000000001); - vm.deal(mintTo, 20 ether); + uint256 totalSale = 10 ether; + uint256 totalReward = target.computeTotalReward(10); + uint256 totalValue = totalSale + totalReward; + + vm.deal(mintTo, totalValue); uint256 maxQuantity = 10; uint256 pricePerToken = 1000000000000000000; @@ -193,7 +203,7 @@ contract ZoraCreatorMerkleMinterStrategyTest is Test { merkleProof[0] = bytes32(0x71013e6ce1f439aaa91aa706ddd0769517fbaa4d72a936af4a7c75d29b1ca862); vm.prank(mintTo); - target.mint{value: 10 ether}(merkleMinter, newTokenId, 10, abi.encode(mintTo, maxQuantity, pricePerToken, merkleProof)); + target.mint{value: totalValue}(merkleMinter, newTokenId, 10, abi.encode(mintTo, maxQuantity, pricePerToken, merkleProof)); assertEq(address(1234).balance, 10 ether); } @@ -225,7 +235,6 @@ contract ZoraCreatorMerkleMinterStrategyTest is Test { ); vm.stopPrank(); - address mintTo = address(0x0000000000000000000000000000000000000001); vm.deal(mintTo, 20 ether); uint256 maxQuantity = 10; @@ -265,8 +274,11 @@ contract ZoraCreatorMerkleMinterStrategyTest is Test { ); vm.stopPrank(); - address mintTo = address(0x0000000000000000000000000000000000000001); - vm.deal(mintTo, 20 ether); + uint256 totalSale = 10 ether; + uint256 totalReward = target.computeTotalReward(10); + uint256 totalValue = totalSale + totalReward; + + vm.deal(mintTo, totalValue); uint256 maxQuantity = 10; uint256 pricePerToken = 1000000000000000000; @@ -274,15 +286,17 @@ contract ZoraCreatorMerkleMinterStrategyTest is Test { merkleProof[0] = bytes32(0x71013e6ce1f439aaa91aa706ddd0769517fbaa4d72a936af4a7c75d29b1ca862); vm.startPrank(mintTo); - target.mint{value: 10 ether}(merkleMinter, newTokenId, 10, abi.encode(mintTo, maxQuantity, pricePerToken, merkleProof)); + target.mint{value: totalValue}(merkleMinter, newTokenId, 10, abi.encode(mintTo, maxQuantity, pricePerToken, merkleProof)); + + vm.deal(mintTo, 1.000777 ether); + vm.expectRevert(abi.encodeWithSelector(ILimitedMintPerAddress.UserExceedsMintLimit.selector, mintTo, 10, 11)); - target.mint{value: 1 ether}(merkleMinter, newTokenId, 1, abi.encode(mintTo, maxQuantity, pricePerToken, merkleProof)); + target.mint{value: 1.000777 ether}(merkleMinter, newTokenId, 1, abi.encode(mintTo, maxQuantity, pricePerToken, merkleProof)); vm.stopPrank(); } - function test_PricePerToken(uint256 ethToSend) external { - vm.assume(ethToSend != 10 ether); + function test_PricePerToken() external { vm.startPrank(admin); uint256 newTokenId = target.setupNewToken("https://zora.co/testing/token.json", 10); target.addPermission(newTokenId, address(merkleMinter), target.PERMISSION_BIT_MINTER()); @@ -309,17 +323,20 @@ contract ZoraCreatorMerkleMinterStrategyTest is Test { ); vm.stopPrank(); - address mintTo = address(0x0000000000000000000000000000000000000001); - vm.deal(mintTo, ethToSend); + uint256 totalSale = 10 ether; + uint256 totalReward = target.computeTotalReward(10); + uint256 totalValue = totalSale + totalReward; + + vm.deal(mintTo, totalValue); uint256 maxQuantity = 10; - uint256 pricePerToken = 1000000000000000000; + uint256 pricePerToken = 1 ether; bytes32[] memory merkleProof = new bytes32[](1); merkleProof[0] = bytes32(0x71013e6ce1f439aaa91aa706ddd0769517fbaa4d72a936af4a7c75d29b1ca862); vm.startPrank(mintTo); vm.expectRevert(abi.encodeWithSignature("WrongValueSent()")); - target.mint{value: ethToSend}(merkleMinter, newTokenId, 10, abi.encode(mintTo, maxQuantity, pricePerToken, merkleProof)); + target.mint{value: totalValue - 1}(merkleMinter, newTokenId, 10, abi.encode(mintTo, maxQuantity, pricePerToken, merkleProof)); } function test_ResetSale() external { diff --git a/test/minters/redeem/ZoraCreatorRedeemMinterFactory.t.sol b/test/minters/redeem/ZoraCreatorRedeemMinterFactory.t.sol index 92a9f26fb..8b563fdc4 100644 --- a/test/minters/redeem/ZoraCreatorRedeemMinterFactory.t.sol +++ b/test/minters/redeem/ZoraCreatorRedeemMinterFactory.t.sol @@ -9,71 +9,72 @@ import {ProtocolRewards} from "@zoralabs/protocol-rewards/src/ProtocolRewards.so import {ZoraCreator1155Impl} from "../../../src/nft/ZoraCreator1155Impl.sol"; import {Zora1155} from "../../../src/proxies/Zora1155.sol"; import {IMinter1155} from "../../../src/interfaces/IMinter1155.sol"; -import {IZoraCreator1155} from "../../../src/interfaces/IZoraCreator1155.sol"; +import {IZoraCreator1155Errors} from "../../../src/interfaces/IZoraCreator1155Errors.sol"; import {IRenderer1155} from "../../../src/interfaces/IRenderer1155.sol"; import {ICreatorRoyaltiesControl} from "../../../src/interfaces/ICreatorRoyaltiesControl.sol"; import {IZoraCreator1155Factory} from "../../../src/interfaces/IZoraCreator1155Factory.sol"; import {ZoraCreatorRedeemMinterStrategy} from "../../../src/minters/redeem/ZoraCreatorRedeemMinterStrategy.sol"; import {ZoraCreatorRedeemMinterFactory} from "../../../src/minters/redeem/ZoraCreatorRedeemMinterFactory.sol"; -contract ZoraCreatorRedeemMinterFactoryTest is Test { - ProtocolRewards internal protocolRewards; - ZoraCreator1155Impl internal target; - ZoraCreatorRedeemMinterFactory internal minterFactory; - address payable internal tokenAdmin = payable(address(0x999)); - address payable internal factoryAdmin = payable(address(0x888)); - address internal zora; +/// @notice Contract versions after v1.4.0 will not support the current burn to redeem minter +// contract ZoraCreatorRedeemMinterFactoryTest is Test { +// ProtocolRewards internal protocolRewards; +// ZoraCreator1155Impl internal target; +// ZoraCreatorRedeemMinterFactory internal minterFactory; +// address payable internal tokenAdmin = payable(address(0x999)); +// address payable internal factoryAdmin = payable(address(0x888)); +// address internal zora; - event RedeemMinterDeployed(address indexed creatorContract, address indexed minterContract); +// event RedeemMinterDeployed(address indexed creatorContract, address indexed minterContract); - function setUp() public { - zora = makeAddr("zora"); - bytes[] memory emptyData = new bytes[](0); - protocolRewards = new ProtocolRewards(); - ZoraCreator1155Impl targetImpl = new ZoraCreator1155Impl(0, zora, address(0), address(protocolRewards)); - Zora1155 proxy = new Zora1155(address(targetImpl)); - target = ZoraCreator1155Impl(address(proxy)); - target.initialize("test", "test", ICreatorRoyaltiesControl.RoyaltyConfiguration(0, 0, address(0)), tokenAdmin, emptyData); +// function setUp() public { +// zora = makeAddr("zora"); +// bytes[] memory emptyData = new bytes[](0); +// protocolRewards = new ProtocolRewards(); +// ZoraCreator1155Impl targetImpl = new ZoraCreator1155Impl(0, zora, address(0), address(protocolRewards)); +// Zora1155 proxy = new Zora1155(address(targetImpl)); +// target = ZoraCreator1155Impl(address(proxy)); +// target.initialize("test", "test", ICreatorRoyaltiesControl.RoyaltyConfiguration(0, 0, address(0)), tokenAdmin, emptyData); - minterFactory = new ZoraCreatorRedeemMinterFactory(); - } +// minterFactory = new ZoraCreatorRedeemMinterFactory(); +// } - function test_contractVersion() public { - assertEq(minterFactory.contractVersion(), "1.0.1"); - } +// function test_contractVersion() public { +// assertEq(minterFactory.contractVersion(), "1.0.1"); +// } - function test_createMinterIfNoneExists() public { - vm.startPrank(tokenAdmin); - target.addPermission(0, address(minterFactory), target.PERMISSION_BIT_MINTER()); - address predictedAddress = minterFactory.predictMinterAddress(address(target)); - vm.expectEmit(false, false, false, false); - emit RedeemMinterDeployed(address(target), predictedAddress); - target.callSale(0, minterFactory, abi.encodeWithSelector(ZoraCreatorRedeemMinterFactory.createMinterIfNoneExists.selector)); - vm.stopPrank(); +// function test_createMinterIfNoneExists() public { +// vm.startPrank(tokenAdmin); +// target.addPermission(0, address(minterFactory), target.PERMISSION_BIT_MINTER()); +// address predictedAddress = minterFactory.predictMinterAddress(address(target)); +// vm.expectEmit(false, false, false, false); +// emit RedeemMinterDeployed(address(target), predictedAddress); +// target.callSale(0, minterFactory, abi.encodeWithSelector(ZoraCreatorRedeemMinterFactory.createMinterIfNoneExists.selector)); +// vm.stopPrank(); - ZoraCreatorRedeemMinterStrategy minter = ZoraCreatorRedeemMinterStrategy(predictedAddress); - assertTrue(address(minter).code.length > 0); - } +// ZoraCreatorRedeemMinterStrategy minter = ZoraCreatorRedeemMinterStrategy(predictedAddress); +// assertTrue(address(minter).code.length > 0); +// } - function test_createMinterRequiresIZoraCreator1155Caller() public { - ERC1155PresetMinterPauser randomToken = new ERC1155PresetMinterPauser("https://uri.com"); +// function test_createMinterRequiresIZoraCreator1155Caller() public { +// ERC1155PresetMinterPauser randomToken = new ERC1155PresetMinterPauser("https://uri.com"); - vm.expectRevert(abi.encodeWithSignature("CallerNotZoraCreator1155()")); - vm.prank(address(randomToken)); - minterFactory.createMinterIfNoneExists(); - } +// vm.expectRevert(abi.encodeWithSignature("CallerNotZoraCreator1155()")); +// vm.prank(address(randomToken)); +// minterFactory.createMinterIfNoneExists(); +// } - function test_getDeployedMinterForCreatorContract() public { - vm.prank(address(target)); - minterFactory.createMinterIfNoneExists(); - address minterAddress = minterFactory.predictMinterAddress(address(target)); +// function test_getDeployedMinterForCreatorContract() public { +// vm.prank(address(target)); +// minterFactory.createMinterIfNoneExists(); +// address minterAddress = minterFactory.predictMinterAddress(address(target)); - assertEq(minterAddress, minterFactory.getDeployedRedeemMinterForCreatorContract(address(target))); - } +// assertEq(minterAddress, minterFactory.getDeployedRedeemMinterForCreatorContract(address(target))); +// } - function test_supportsInterface() public { - assertTrue(minterFactory.supportsInterface(0x01ffc9a7)); // ERC165 - assertTrue(minterFactory.supportsInterface(type(IMinter1155).interfaceId)); - assertTrue(!minterFactory.supportsInterface(0x6467a6fc)); // old IMinter1155 - } -} +// function test_supportsInterface() public { +// assertTrue(minterFactory.supportsInterface(0x01ffc9a7)); // ERC165 +// assertTrue(minterFactory.supportsInterface(type(IMinter1155).interfaceId)); +// assertTrue(!minterFactory.supportsInterface(0x6467a6fc)); // old IMinter1155 +// } +// } diff --git a/test/minters/redeem/ZoraCreatorRedeemMinterStrategy.t.sol b/test/minters/redeem/ZoraCreatorRedeemMinterStrategy.t.sol index 3be42ba5b..63f24ed94 100644 --- a/test/minters/redeem/ZoraCreatorRedeemMinterStrategy.t.sol +++ b/test/minters/redeem/ZoraCreatorRedeemMinterStrategy.t.sol @@ -8,1491 +8,1492 @@ import {ERC1155PresetMinterPauser} from "@openzeppelin/contracts/token/ERC1155/p import {ProtocolRewards} from "@zoralabs/protocol-rewards/src/ProtocolRewards.sol"; import {ZoraCreator1155Impl} from "../../../src/nft/ZoraCreator1155Impl.sol"; import {Zora1155} from "../../../src/proxies/Zora1155.sol"; -import {IZoraCreator1155} from "../../../src/interfaces/IZoraCreator1155.sol"; +import {IZoraCreator1155Errors} from "../../../src/interfaces/IZoraCreator1155Errors.sol"; import {IRenderer1155} from "../../../src/interfaces/IRenderer1155.sol"; import {ICreatorRoyaltiesControl} from "../../../src/interfaces/ICreatorRoyaltiesControl.sol"; import {IZoraCreator1155Factory} from "../../../src/interfaces/IZoraCreator1155Factory.sol"; import {ZoraCreatorRedeemMinterStrategy} from "../../../src/minters/redeem/ZoraCreatorRedeemMinterStrategy.sol"; -contract ZoraCreatorRedeemMinterStrategyTest is Test { - ProtocolRewards internal protocolRewards; - ZoraCreator1155Impl internal target; - ZoraCreatorRedeemMinterStrategy internal redeemMinter; - address payable internal admin = payable(address(0x999)); - uint256 internal newTokenId; - address internal zora; - - event RedeemSet(address indexed target, bytes32 indexed redeemsInstructionsHash, ZoraCreatorRedeemMinterStrategy.RedeemInstructions data); - event RedeemProcessed(address indexed target, bytes32 indexed redeemsInstructionsHash, address sender, uint256[][] tokenIds, uint256[][] amounts); - event RedeemsCleared(address indexed target, bytes32[] indexed redeemInstructionsHashes); - - function setUp() external { - zora = makeAddr("zora"); - bytes[] memory emptyData = new bytes[](0); - protocolRewards = new ProtocolRewards(); - ZoraCreator1155Impl targetImpl = new ZoraCreator1155Impl(0, zora, address(0), address(protocolRewards)); - Zora1155 proxy = new Zora1155(address(targetImpl)); - target = ZoraCreator1155Impl(address(proxy)); - target.initialize("test", "test", ICreatorRoyaltiesControl.RoyaltyConfiguration(0, 0, address(0)), admin, emptyData); - redeemMinter = new ZoraCreatorRedeemMinterStrategy(); - redeemMinter.initialize(address(target)); - vm.startPrank(admin); - newTokenId = target.setupNewToken("https://zora.co/testing/token.json", 10); - target.addPermission(newTokenId, address(redeemMinter), target.PERMISSION_BIT_MINTER()); - vm.stopPrank(); - } - - function test_ContractURI() external { - assertEq(redeemMinter.contractURI(), "https://github.com/ourzora/zora-1155-contracts/"); - } - - function test_ContractName() external { - assertEq(redeemMinter.contractName(), "Redeem Minter Sale Strategy"); - } - - function test_Version() external { - assertEq(redeemMinter.contractVersion(), "1.0.1"); - } - - function test_OnlyDropContractCanCallWriteFunctions() external { - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions; - vm.startPrank(address(admin)); - - vm.expectRevert(abi.encodeWithSignature("CallerNotCreatorContract()")); - redeemMinter.setRedeem(redeemInstructions); - - bytes32[] memory hashes = new bytes32[](0); - vm.expectRevert(abi.encodeWithSignature("CallerNotCreatorContract()")); - redeemMinter.clearRedeem(hashes); - - vm.expectRevert(abi.encodeWithSignature("CallerNotCreatorContract()")); - redeemMinter.requestMint(address(0), 0, 0, 0, bytes("")); - - vm.expectRevert(abi.encodeWithSignature("CallerNotCreatorContract()")); - redeemMinter.resetSale(0); - - vm.stopPrank(); - } - - ///////// SET REDEEM ///////// - - function test_SetRedeem() external { - vm.startPrank(admin); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC20PresetMinterPauser burnToken = new ERC20PresetMinterPauser("Random Token", "RAND"); - burnToken.mint(address(tokenRecipient), 1000); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, - amount: 500, - tokenIdStart: 0, - tokenIdEnd: 0, - tokenContract: address(burnToken), - transferRecipient: address(0), - burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - vm.stopPrank(); - - vm.expectEmit(true, true, false, true); - emit RedeemSet(address(target), keccak256(abi.encode(redeemInstructions)), redeemInstructions); - vm.startPrank(address(target)); - redeemMinter.setRedeem(redeemInstructions); - vm.expectRevert(abi.encodeWithSignature("RedeemInstructionAlreadySet()")); - redeemMinter.setRedeem(redeemInstructions); - vm.stopPrank(); - - assertTrue(redeemMinter.redeemInstructionsHashIsAllowed(keccak256(abi.encode(redeemInstructions)))); - } - - function test_SetRedeemInstructionValidation() external { - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC20PresetMinterPauser burnToken = new ERC20PresetMinterPauser("Random Token", "RAND"); - burnToken.mint(address(tokenRecipient), 1000); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.NULL, - amount: 500, - tokenIdStart: 0, - tokenIdEnd: 0, - tokenContract: address(burnToken), - transferRecipient: address(0), - burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - vm.startPrank(address(target)); - - // InvalidTokenIdsForTokenType: ERC20 w/ nonzero tokenId start or end - redeemInstructions.instructions[0].tokenType = ZoraCreatorRedeemMinterStrategy.TokenType.ERC20; - redeemInstructions.instructions[0].tokenIdStart = 1; - redeemInstructions.instructions[0].tokenIdEnd = 0; - vm.expectRevert(abi.encodeWithSignature("InvalidTokenIdsForTokenType()")); - redeemMinter.setRedeem(redeemInstructions); - redeemInstructions.instructions[0].tokenIdStart = 0; - redeemInstructions.instructions[0].tokenIdEnd = 1; - vm.expectRevert(abi.encodeWithSignature("InvalidTokenIdsForTokenType()")); - redeemMinter.setRedeem(redeemInstructions); - - // InvalidTokenIdsForTokenType: non ERC20 w/ tokenID start > end - redeemInstructions.instructions[0].tokenType = ZoraCreatorRedeemMinterStrategy.TokenType.ERC721; - redeemInstructions.instructions[0].tokenIdStart = 4; - redeemInstructions.instructions[0].tokenIdEnd = 2; - vm.expectRevert(abi.encodeWithSignature("InvalidTokenIdsForTokenType()")); - redeemMinter.setRedeem(redeemInstructions); - redeemInstructions.mintToken.tokenType = ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155; - vm.expectRevert(abi.encodeWithSignature("InvalidTokenIdsForTokenType()")); - redeemMinter.setRedeem(redeemInstructions); - redeemInstructions.instructions[0].tokenIdStart = 0; - redeemInstructions.instructions[0].tokenIdEnd = 0; - - // InvalidTokenType: tokenType is NULL - redeemInstructions.instructions[0].tokenType = ZoraCreatorRedeemMinterStrategy.TokenType.NULL; - vm.expectRevert(abi.encodeWithSignature("InvalidTokenType()")); - redeemMinter.setRedeem(redeemInstructions); - redeemInstructions.instructions[0].tokenType = ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155; - - // MustBurnOrTransfer: both transferRecipient and burnFunction are 0 - redeemInstructions.instructions[0].transferRecipient = address(0); - redeemInstructions.instructions[0].burnFunction = bytes4(0); - vm.expectRevert(abi.encodeWithSignature("MustBurnOrTransfer()")); - redeemMinter.setRedeem(redeemInstructions); - - // MustBurnOrTransfer: both transferRecipient and burnFunction are non-zero - redeemInstructions.instructions[0].transferRecipient = address(1); - redeemInstructions.instructions[0].burnFunction = bytes4(keccak256(bytes("burnFrom(address,uint256)"))); - vm.expectRevert(abi.encodeWithSignature("MustBurnOrTransfer()")); - redeemMinter.setRedeem(redeemInstructions); - redeemInstructions.instructions[0].transferRecipient = address(0); - - // IncorrectMintAmount - redeemInstructions.instructions[0].amount = 0; - vm.expectRevert(abi.encodeWithSignature("IncorrectMintAmount()")); - redeemMinter.setRedeem(redeemInstructions); - redeemInstructions.instructions[0].amount = 500; - - // InvalidSaleEndOrStart: start > end - redeemInstructions.saleStart = 1; - redeemInstructions.saleEnd = 0; - vm.expectRevert(abi.encodeWithSignature("InvalidSaleEndOrStart()")); - redeemMinter.setRedeem(redeemInstructions); - redeemInstructions.saleStart = 0; - redeemInstructions.saleEnd = type(uint64).max; - - // InvalidSaleEndOrStart: block.timestamp > end - redeemInstructions.saleStart = 0; - redeemInstructions.saleEnd = 1 days; - vm.warp(2 days); - vm.expectRevert(abi.encodeWithSignature("InvalidSaleEndOrStart()")); - redeemMinter.setRedeem(redeemInstructions); - redeemInstructions.saleEnd = type(uint64).max; - - // EmptyRedeemInstructions(); - redeemInstructions.instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](0); - vm.expectRevert(abi.encodeWithSignature("EmptyRedeemInstructions()")); - redeemMinter.setRedeem(redeemInstructions); - redeemInstructions.instructions = instructions; - - // MintTokenContractMustBeCreatorContract - redeemInstructions.mintToken.tokenContract = address(0); - vm.expectRevert(abi.encodeWithSignature("MintTokenContractMustBeCreatorContract()")); - redeemMinter.setRedeem(redeemInstructions); - redeemInstructions.mintToken.tokenContract = address(target); - - // MintTokenTypeMustBeERC1155: - redeemInstructions.mintToken.tokenType = ZoraCreatorRedeemMinterStrategy.TokenType.ERC721; - vm.expectRevert(abi.encodeWithSignature("MintTokenTypeMustBeERC1155()")); - redeemMinter.setRedeem(redeemInstructions); - } - - ///////// REQUEST MINT ///////// - - function test_MintFlowERC20() external { - vm.startPrank(admin); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC20PresetMinterPauser burnToken = new ERC20PresetMinterPauser("Random Token", "RAND"); - burnToken.mint(address(tokenRecipient), 1000); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, - amount: 500, - tokenIdStart: 0, - tokenIdEnd: 0, - tokenContract: address(burnToken), - transferRecipient: address(0), - burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - vm.startPrank(tokenRecipient); - burnToken.approve(address(redeemMinter), 500); - uint256[][] memory tokenIds = new uint256[][](1); - tokenIds[0] = new uint256[](0); - uint256[][] memory amounts = new uint256[][](1); - amounts[0] = new uint256[](1); - amounts[0][0] = 500; - vm.expectEmit(true, true, false, false); - emit RedeemProcessed(address(target), keccak256(abi.encode(redeemInstructions)), tokenRecipient, tokenIds, amounts); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - assertEq(address(target).balance, 1 ether); - assertEq(target.balanceOf(tokenRecipient, newTokenId), 10); - assertEq(burnToken.balanceOf(tokenRecipient), 500); - vm.stopPrank(); - } - - function test_MintFlowERC1155() external { - vm.startPrank(admin); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC1155PresetMinterPauser burnToken = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); - burnToken.mint(address(tokenRecipient), 1, 1000, ""); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155, - amount: 500, - tokenIdStart: 1, - tokenIdEnd: 1, - tokenContract: address(burnToken), - transferRecipient: address(0), - burnFunction: bytes4(keccak256(bytes("burnBatch(address,uint256[],uint256[])"))) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - vm.startPrank(tokenRecipient); - burnToken.setApprovalForAll(address(redeemMinter), true); - uint256[][] memory tokenIds = new uint256[][](1); - tokenIds[0] = new uint256[](1); - tokenIds[0][0] = 1; - uint256[][] memory amounts = new uint256[][](1); - amounts[0] = new uint256[](1); - amounts[0][0] = 500; - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - assertEq(address(target).balance, 1 ether); - assertEq(target.balanceOf(tokenRecipient, newTokenId), 10); - assertEq(burnToken.balanceOf(tokenRecipient, 1), 500); - vm.stopPrank(); - } - - function test_MintFlowERC721() external { - vm.startPrank(admin); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC721PresetMinterPauserAutoId burnToken = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); - burnToken.mint(address(tokenRecipient)); - burnToken.mint(address(tokenRecipient)); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC721, - amount: 2, - tokenIdStart: 0, - tokenIdEnd: 1, - tokenContract: address(burnToken), - transferRecipient: address(0), - burnFunction: bytes4(keccak256(bytes("burn(uint256)"))) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - vm.startPrank(tokenRecipient); - burnToken.setApprovalForAll(address(redeemMinter), true); - uint256[][] memory tokenIds = new uint256[][](1); - tokenIds[0] = new uint256[](2); - tokenIds[0][0] = 0; - tokenIds[0][1] = 1; - uint256[][] memory amounts = new uint256[][](1); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - assertEq(address(target).balance, 1 ether); - assertEq(target.balanceOf(tokenRecipient, newTokenId), 10); - vm.expectRevert(); - burnToken.ownerOf(0); - vm.expectRevert(); - burnToken.ownerOf(1); - vm.stopPrank(); - } - - function test_MintFlowMultiple() external { - vm.startPrank(admin); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC20PresetMinterPauser burnTokenERC20 = new ERC20PresetMinterPauser("Random Token", "RAND"); - burnTokenERC20.mint(address(tokenRecipient), 1000); - ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); - burnTokenERC1155.mint(address(tokenRecipient), 1, 1000, ""); - ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); - burnTokenERC721.mint(address(tokenRecipient)); - burnTokenERC721.mint(address(tokenRecipient)); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](3); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, - amount: 500, - tokenIdStart: 0, - tokenIdEnd: 0, - tokenContract: address(burnTokenERC20), - transferRecipient: address(0), - burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) - }); - instructions[1] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155, - amount: 500, - tokenIdStart: 1, - tokenIdEnd: 1, - tokenContract: address(burnTokenERC1155), - transferRecipient: address(0), - burnFunction: bytes4(keccak256(bytes("burnBatch(address,uint256[],uint256[])"))) - }); - instructions[2] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC721, - amount: 2, - tokenIdStart: 0, - tokenIdEnd: 1, - tokenContract: address(burnTokenERC721), - transferRecipient: address(0), - burnFunction: bytes4(keccak256(bytes("burn(uint256)"))) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - vm.startPrank(tokenRecipient); - burnTokenERC20.approve(address(redeemMinter), 500); - burnTokenERC1155.setApprovalForAll(address(redeemMinter), true); - burnTokenERC721.setApprovalForAll(address(redeemMinter), true); - - uint256[][] memory tokenIds = new uint256[][](3); - uint256[][] memory amounts = new uint256[][](3); - - amounts[0] = new uint256[](1); - amounts[0][0] = 500; - - tokenIds[1] = new uint256[](1); - tokenIds[1][0] = 1; - amounts[1] = new uint256[](1); - amounts[1][0] = 500; - - tokenIds[2] = new uint256[](2); - tokenIds[2][0] = 0; - tokenIds[2][1] = 1; - - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - assertEq(address(target).balance, 1 ether); - assertEq(target.balanceOf(tokenRecipient, newTokenId), 10); - assertEq(burnTokenERC20.balanceOf(tokenRecipient), 500); - assertEq(burnTokenERC1155.balanceOf(tokenRecipient, 1), 500); - assertEq(burnTokenERC721.balanceOf(address(tokenRecipient)), 0); - vm.stopPrank(); - } - - function test_MintFlowMultipleWithTransferInsteadOfBurn() external { - vm.startPrank(admin); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - address redeemTokenRecipient = address(323); - - ERC20PresetMinterPauser burnTokenERC20 = new ERC20PresetMinterPauser("Random Token", "RAND"); - burnTokenERC20.mint(address(tokenRecipient), 1000); - ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); - burnTokenERC1155.mint(address(tokenRecipient), 1, 1000, ""); - ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); - burnTokenERC721.mint(address(tokenRecipient)); - burnTokenERC721.mint(address(tokenRecipient)); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](3); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, - amount: 500, - tokenIdStart: 0, - tokenIdEnd: 0, - tokenContract: address(burnTokenERC20), - transferRecipient: redeemTokenRecipient, - burnFunction: 0 - }); - instructions[1] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155, - amount: 500, - tokenIdStart: 1, - tokenIdEnd: 1, - tokenContract: address(burnTokenERC1155), - transferRecipient: redeemTokenRecipient, - burnFunction: 0 - }); - instructions[2] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC721, - amount: 2, - tokenIdStart: 0, - tokenIdEnd: 1, - tokenContract: address(burnTokenERC721), - transferRecipient: redeemTokenRecipient, - burnFunction: 0 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - vm.startPrank(tokenRecipient); - burnTokenERC20.approve(address(redeemMinter), 500); - burnTokenERC1155.setApprovalForAll(address(redeemMinter), true); - burnTokenERC721.setApprovalForAll(address(redeemMinter), true); - - uint256[][] memory tokenIds = new uint256[][](3); - uint256[][] memory amounts = new uint256[][](3); - - amounts[0] = new uint256[](1); - amounts[0][0] = 500; - - tokenIds[1] = new uint256[](1); - tokenIds[1][0] = 1; - amounts[1] = new uint256[](1); - amounts[1][0] = 500; - - tokenIds[2] = new uint256[](2); - tokenIds[2][0] = 0; - tokenIds[2][1] = 1; - - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - assertEq(address(target).balance, 1 ether); - assertEq(target.balanceOf(tokenRecipient, newTokenId), 10); - assertEq(burnTokenERC20.balanceOf(tokenRecipient), 500); - assertEq(burnTokenERC20.balanceOf(redeemTokenRecipient), 500); - assertEq(burnTokenERC1155.balanceOf(tokenRecipient, 1), 500); - assertEq(burnTokenERC1155.balanceOf(redeemTokenRecipient, 1), 500); - assertEq(burnTokenERC721.balanceOf(address(tokenRecipient)), 0); - assertEq(burnTokenERC721.balanceOf(redeemTokenRecipient), 2); - - vm.stopPrank(); - } - - function test_MintFlowTokenIdRangesForERC1155AndERC721() external { - vm.startPrank(admin); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - address redeemTokenRecipient = address(323); - - ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); - burnTokenERC1155.mint(address(tokenRecipient), 7, 100, ""); - burnTokenERC1155.mint(address(tokenRecipient), 8, 100, ""); - burnTokenERC1155.mint(address(tokenRecipient), 9, 100, ""); - ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); - burnTokenERC721.mint(address(tokenRecipient)); - burnTokenERC721.mint(address(tokenRecipient)); - burnTokenERC721.mint(address(tokenRecipient)); - burnTokenERC721.mint(address(tokenRecipient)); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](2); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155, - amount: 300, - tokenIdStart: 7, - tokenIdEnd: 9, - tokenContract: address(burnTokenERC1155), - transferRecipient: redeemTokenRecipient, - burnFunction: 0 - }); - instructions[1] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC721, - amount: 3, - tokenIdStart: 1, - tokenIdEnd: 3, - tokenContract: address(burnTokenERC721), - transferRecipient: redeemTokenRecipient, - burnFunction: 0 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - vm.startPrank(tokenRecipient); - burnTokenERC1155.setApprovalForAll(address(redeemMinter), true); - burnTokenERC721.setApprovalForAll(address(redeemMinter), true); - - uint256[][] memory tokenIds = new uint256[][](2); - uint256[][] memory amounts = new uint256[][](2); - - tokenIds[0] = new uint256[](3); - tokenIds[0][0] = 7; - tokenIds[0][1] = 8; - tokenIds[0][2] = 9; - amounts[0] = new uint256[](3); - amounts[0][0] = 100; - amounts[0][1] = 100; - amounts[0][2] = 100; - - tokenIds[1] = new uint256[](3); - tokenIds[1][0] = 1; - tokenIds[1][1] = 2; - tokenIds[1][2] = 3; - - // detour: tokenId out of range - tokenIds[0][0] = 6; - vm.expectRevert(abi.encodeWithSignature("TokenIdOutOfRange()")); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - tokenIds[0][0] = 10; - vm.expectRevert(abi.encodeWithSignature("TokenIdOutOfRange()")); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - tokenIds[0][0] = 7; - tokenIds[1][0] = 0; - vm.expectRevert(abi.encodeWithSignature("TokenIdOutOfRange()")); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - tokenIds[1][0] = 4; - vm.expectRevert(abi.encodeWithSignature("TokenIdOutOfRange()")); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - tokenIds[1][0] = 1; - - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - assertEq(address(target).balance, 1 ether); - assertEq(target.balanceOf(tokenRecipient, newTokenId), 10); - assertEq(burnTokenERC1155.balanceOf(redeemTokenRecipient, 7), 100); - assertEq(burnTokenERC1155.balanceOf(redeemTokenRecipient, 8), 100); - assertEq(burnTokenERC1155.balanceOf(redeemTokenRecipient, 9), 100); - assertEq(burnTokenERC721.balanceOf(redeemTokenRecipient), 3); - - vm.stopPrank(); - } - - function test_MintFlowRedeemStart() external { - vm.startPrank(admin); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC20PresetMinterPauser burnToken = new ERC20PresetMinterPauser("Random Token", "RAND"); - burnToken.mint(address(tokenRecipient), 1000); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, - amount: 500, - tokenIdStart: 0, - tokenIdEnd: 0, - tokenContract: address(burnToken), - transferRecipient: address(0), - burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: uint64(block.timestamp + 1 days), - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - vm.startPrank(tokenRecipient); - burnToken.approve(address(redeemMinter), 500); - uint256[][] memory tokenIds = new uint256[][](1); - tokenIds[0] = new uint256[](0); - uint256[][] memory amounts = new uint256[][](1); - amounts[0] = new uint256[](1); - amounts[0][0] = 500; - vm.expectRevert(abi.encodeWithSignature("SaleHasNotStarted()")); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 1, abi.encode(redeemInstructions, tokenIds, amounts)); - vm.stopPrank(); - } - - function test_MintFlowRedeemEnd() external { - vm.startPrank(admin); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC20PresetMinterPauser burnToken = new ERC20PresetMinterPauser("Random Token", "RAND"); - burnToken.mint(address(tokenRecipient), 1000); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, - amount: 500, - tokenIdStart: 0, - tokenIdEnd: 0, - tokenContract: address(burnToken), - transferRecipient: address(0), - burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: uint64(1 days), - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - vm.warp(2 days); - - vm.startPrank(tokenRecipient); - burnToken.approve(address(redeemMinter), 1000); - uint256[] memory tokenIds = new uint256[](0); - uint256[][] memory amounts = new uint256[][](1); - amounts[0] = new uint256[](1); - amounts[0][0] = 500; - vm.expectRevert(abi.encodeWithSignature("SaleEnded()")); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 1, abi.encode(redeemInstructions, tokenIds, amounts)); - vm.stopPrank(); - } - - function test_MintFlowEthValue() external { - vm.startPrank(admin); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC20PresetMinterPauser burnToken = new ERC20PresetMinterPauser("Random Token", "RAND"); - burnToken.mint(address(tokenRecipient), 1000); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, - amount: 500, - tokenIdStart: 0, - tokenIdEnd: 0, - tokenContract: address(burnToken), - transferRecipient: address(0), - burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - vm.startPrank(tokenRecipient); - burnToken.approve(address(redeemMinter), 500); - uint256[][] memory tokenIds = new uint256[][](1); - tokenIds[0] = new uint256[](0); - uint256[][] memory amounts = new uint256[][](1); - amounts[0] = new uint256[](1); - amounts[0][0] = 500; - vm.expectRevert(abi.encodeWithSignature("WrongValueSent()")); - target.mint{value: 0.9 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - vm.expectRevert(abi.encodeWithSignature("WrongValueSent()")); - target.mint{value: 1.1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - vm.stopPrank(); - } - - function test_MintFlowFundsRecipient() external { - vm.startPrank(admin); - - address fundsRecipient = address(239); - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC20PresetMinterPauser burnToken = new ERC20PresetMinterPauser("Random Token", "RAND"); - burnToken.mint(address(tokenRecipient), 1000); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, - amount: 500, - tokenIdStart: 0, - tokenIdEnd: 0, - tokenContract: address(burnToken), - transferRecipient: address(0), - burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: fundsRecipient - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - vm.startPrank(tokenRecipient); - burnToken.approve(address(redeemMinter), 500); - uint256[][] memory tokenIds = new uint256[][](1); - tokenIds[0] = new uint256[](0); - uint256[][] memory amounts = new uint256[][](1); - amounts[0] = new uint256[](1); - amounts[0][0] = 500; - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - assertEq(fundsRecipient.balance, 1 ether); - vm.stopPrank(); - } - - function test_MintFlowAmount() external { - vm.startPrank(admin); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC20PresetMinterPauser burnToken = new ERC20PresetMinterPauser("Random Token", "RAND"); - burnToken.mint(address(tokenRecipient), 1000); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, - amount: 500, - tokenIdStart: 0, - tokenIdEnd: 0, - tokenContract: address(burnToken), - transferRecipient: address(0), - burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - vm.startPrank(tokenRecipient); - burnToken.approve(address(redeemMinter), 500); - uint256[][] memory tokenIds = new uint256[][](1); - tokenIds[0] = new uint256[](0); - uint256[][] memory amounts = new uint256[][](1); - amounts[0] = new uint256[](1); - amounts[0][0] = 500; - vm.expectRevert(abi.encodeWithSignature("IncorrectMintAmount()")); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 11, abi.encode(redeemInstructions, tokenIds, amounts)); - vm.stopPrank(); - } - - function test_MintFlowIncorrectNumberOfTokenIds() external { - vm.startPrank(admin); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC1155PresetMinterPauser burnToken = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); - burnToken.mint(address(tokenRecipient), 1, 1000, ""); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155, - amount: 500, - tokenIdStart: 1, - tokenIdEnd: 1, - tokenContract: address(burnToken), - transferRecipient: address(0), - burnFunction: bytes4(keccak256(bytes("burnBatch(address,uint256[],uint256[])"))) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - // instructions length != tokenIds length - vm.startPrank(tokenRecipient); - burnToken.setApprovalForAll(address(redeemMinter), true); - uint256[][] memory tokenIds = new uint256[][](2); - uint256[][] memory amounts = new uint256[][](1); - amounts[0] = new uint256[](1); - amounts[0][0] = 500; - vm.expectRevert(abi.encodeWithSignature("IncorrectNumberOfTokenIds()")); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - tokenIds = new uint256[][](1); - tokenIds[0] = new uint256[](1); - tokenIds[0][0] = 1; - - // ERC1155: amounts length != tokenIds length - amounts = new uint256[][](2); - vm.expectRevert(abi.encodeWithSignature("IncorrectNumberOfTokenIds()")); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - } - - function test_MintFlowIncorrectBurnOrTransferAmount() external { - vm.startPrank(admin); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - address redeemTokenRecipient = address(323); - - ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); - burnTokenERC1155.mint(address(tokenRecipient), 1, 500, ""); - ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); - burnTokenERC721.mint(address(tokenRecipient)); - burnTokenERC721.mint(address(tokenRecipient)); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](2); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155, - amount: 300, - tokenIdStart: 1, - tokenIdEnd: 1, - tokenContract: address(burnTokenERC1155), - transferRecipient: redeemTokenRecipient, - burnFunction: 0 - }); - instructions[1] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC721, - amount: 3, - tokenIdStart: 0, - tokenIdEnd: 2, - tokenContract: address(burnTokenERC721), - transferRecipient: redeemTokenRecipient, - burnFunction: 0 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - vm.startPrank(tokenRecipient); - burnTokenERC1155.setApprovalForAll(address(redeemMinter), true); - burnTokenERC721.setApprovalForAll(address(redeemMinter), true); - - uint256[][] memory tokenIds = new uint256[][](2); - uint256[][] memory amounts = new uint256[][](2); - - // this would be correct - tokenIds[0] = new uint256[](1); - tokenIds[0][0] = 1; - amounts[0] = new uint256[](1); - amounts[0][0] = 500; - tokenIds[1] = new uint256[](2); - tokenIds[1][0] = 0; - tokenIds[1][1] = 1; - - // ERC721: tokenids length != instruction amount - tokenIds[1] = new uint256[](1); - vm.expectRevert(abi.encodeWithSignature("IncorrectBurnOrTransferAmount()")); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - tokenIds[1] = new uint256[](3); - vm.expectRevert(abi.encodeWithSignature("IncorrectBurnOrTransferAmount()")); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - tokenIds[1] = new uint256[](2); - tokenIds[1][0] = 0; - tokenIds[1][1] = 1; - - // ERC1155: sum of amounts != instruction amount - amounts[0][0] = 499; - vm.expectRevert(abi.encodeWithSignature("IncorrectBurnOrTransferAmount()")); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - amounts[0][0] = 501; - vm.expectRevert(abi.encodeWithSignature("IncorrectBurnOrTransferAmount()")); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - - vm.stopPrank(); - } - - function test_MintFlowSenderNotTokenOwnerBurn20() external { - vm.startPrank(admin); - - address actualTokenOwner = address(92834); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC20PresetMinterPauser burnTokenERC20 = new ERC20PresetMinterPauser("Random Token", "RAND"); - burnTokenERC20.mint(address(actualTokenOwner), 1000); - ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); - burnTokenERC1155.mint(address(actualTokenOwner), 1, 1000, ""); - ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); - burnTokenERC721.mint(address(actualTokenOwner)); - burnTokenERC721.mint(address(actualTokenOwner)); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, - amount: 500, - tokenIdStart: 0, - tokenIdEnd: 0, - tokenContract: address(burnTokenERC20), - transferRecipient: address(0), - burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - vm.startPrank(tokenRecipient); - burnTokenERC20.approve(address(redeemMinter), 500); - - uint256[][] memory tokenIds = new uint256[][](1); - uint256[][] memory amounts = new uint256[][](1); - amounts[0] = new uint256[](1); - amounts[0][0] = 500; - - vm.expectRevert(abi.encodeWithSignature("BurnFailed()")); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - vm.stopPrank(); - } - - function test_MintFlowSenderNotTokenOwnerBurn1155() external { - vm.startPrank(admin); - - address actualTokenOwner = address(92834); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC20PresetMinterPauser burnTokenERC20 = new ERC20PresetMinterPauser("Random Token", "RAND"); - burnTokenERC20.mint(address(actualTokenOwner), 1000); - ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); - burnTokenERC1155.mint(address(actualTokenOwner), 1, 1000, ""); - ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); - burnTokenERC721.mint(address(actualTokenOwner)); - burnTokenERC721.mint(address(actualTokenOwner)); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155, - amount: 500, - tokenIdStart: 1, - tokenIdEnd: 1, - tokenContract: address(burnTokenERC1155), - transferRecipient: address(0), - burnFunction: bytes4(keccak256(bytes("burnBatch(address,uint256[],uint256[])"))) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - vm.startPrank(tokenRecipient); - burnTokenERC1155.setApprovalForAll(address(redeemMinter), true); - - uint256[][] memory tokenIds = new uint256[][](1); - tokenIds[0] = new uint256[](1); - tokenIds[0][0] = 1; - uint256[][] memory amounts = new uint256[][](1); - amounts[0] = new uint256[](1); - amounts[0][0] = 500; - - vm.expectRevert(abi.encodeWithSignature("BurnFailed()")); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - vm.stopPrank(); - } - - function test_MintFlowSenderNotTokenOwnerBurn721() external { - vm.startPrank(admin); - - address actualTokenOwner = address(92834); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC20PresetMinterPauser burnTokenERC20 = new ERC20PresetMinterPauser("Random Token", "RAND"); - burnTokenERC20.mint(address(actualTokenOwner), 1000); - ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); - burnTokenERC1155.mint(address(actualTokenOwner), 1, 1000, ""); - ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); - burnTokenERC721.mint(address(actualTokenOwner)); - burnTokenERC721.mint(address(actualTokenOwner)); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC721, - amount: 2, - tokenIdStart: 0, - tokenIdEnd: 1, - tokenContract: address(burnTokenERC721), - transferRecipient: address(0), - burnFunction: bytes4(keccak256(bytes("burn(uint256)"))) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - vm.prank(actualTokenOwner); - burnTokenERC721.setApprovalForAll(address(redeemMinter), true); - vm.startPrank(tokenRecipient); - burnTokenERC721.setApprovalForAll(address(redeemMinter), true); - - uint256[][] memory tokenIds = new uint256[][](1); - tokenIds[0] = new uint256[](2); - tokenIds[0][0] = 0; - tokenIds[0][1] = 1; - uint256[][] memory amounts; - - vm.expectRevert(abi.encodeWithSignature("SenderIsNotTokenOwner()")); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - vm.stopPrank(); - } - - function test_MintFlowSenderNotTokenOwnerTransfer20() external { - vm.startPrank(admin); - - address actualTokenOwner = address(92834); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC20PresetMinterPauser burnTokenERC20 = new ERC20PresetMinterPauser("Random Token", "RAND"); - burnTokenERC20.mint(address(actualTokenOwner), 1000); - ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); - burnTokenERC1155.mint(address(actualTokenOwner), 1, 1000, ""); - ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); - burnTokenERC721.mint(address(actualTokenOwner)); - burnTokenERC721.mint(address(actualTokenOwner)); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, - amount: 500, - tokenIdStart: 0, - tokenIdEnd: 0, - tokenContract: address(burnTokenERC20), - transferRecipient: address(1), - burnFunction: bytes4(0) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - vm.startPrank(tokenRecipient); - burnTokenERC20.approve(address(redeemMinter), 500); - - uint256[][] memory tokenIds = new uint256[][](1); - uint256[][] memory amounts = new uint256[][](1); - amounts[0] = new uint256[](1); - amounts[0][0] = 500; - - vm.expectRevert("ERC20: transfer amount exceeds balance"); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - vm.stopPrank(); - } - - function test_MintFlowSenderNotTokenOwnerTransfer1155() external { - vm.startPrank(admin); - - address actualTokenOwner = address(92834); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC20PresetMinterPauser burnTokenERC20 = new ERC20PresetMinterPauser("Random Token", "RAND"); - burnTokenERC20.mint(address(actualTokenOwner), 1000); - ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); - burnTokenERC1155.mint(address(actualTokenOwner), 1, 1000, ""); - ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); - burnTokenERC721.mint(address(actualTokenOwner)); - burnTokenERC721.mint(address(actualTokenOwner)); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155, - amount: 500, - tokenIdStart: 1, - tokenIdEnd: 1, - tokenContract: address(burnTokenERC1155), - transferRecipient: address(1), - burnFunction: bytes4(0) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - vm.startPrank(tokenRecipient); - burnTokenERC1155.setApprovalForAll(address(redeemMinter), true); - - uint256[][] memory tokenIds = new uint256[][](1); - tokenIds[0] = new uint256[](1); - tokenIds[0][0] = 1; - uint256[][] memory amounts = new uint256[][](1); - amounts[0] = new uint256[](1); - amounts[0][0] = 500; - - vm.expectRevert("ERC1155: insufficient balance for transfer"); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - vm.stopPrank(); - } - - function test_MintFlowSenderNotTokenOwnerTransfer721() external { - vm.startPrank(admin); - - address actualTokenOwner = address(92834); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC20PresetMinterPauser burnTokenERC20 = new ERC20PresetMinterPauser("Random Token", "RAND"); - burnTokenERC20.mint(address(actualTokenOwner), 1000); - ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); - burnTokenERC1155.mint(address(actualTokenOwner), 1, 1000, ""); - ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); - burnTokenERC721.mint(address(actualTokenOwner)); - burnTokenERC721.mint(address(actualTokenOwner)); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC721, - amount: 2, - tokenIdStart: 0, - tokenIdEnd: 1, - tokenContract: address(burnTokenERC721), - transferRecipient: address(1), - burnFunction: bytes4(0) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - vm.stopPrank(); - - vm.startPrank(tokenRecipient); - burnTokenERC721.setApprovalForAll(address(redeemMinter), true); - - uint256[][] memory tokenIds = new uint256[][](1); - tokenIds[0] = new uint256[](2); - tokenIds[0][0] = 0; - tokenIds[0][1] = 1; - uint256[][] memory amounts; - - vm.expectRevert("ERC721: caller is not token owner or approved"); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - vm.stopPrank(); - } - - ///////// RESET AND CLEAR ///////// - - function test_ResetSaleAlwaysReverts() external { - vm.prank(address(target)); - vm.expectRevert(abi.encodeWithSignature("MustCallClearRedeem()")); - redeemMinter.resetSale(uint256(1)); - } - - function test_ClearRedeem() external { - vm.startPrank(admin); - - address tokenRecipient = address(322); - vm.deal(tokenRecipient, 20 ether); - - ERC721PresetMinterPauserAutoId burnToken = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); - burnToken.mint(address(tokenRecipient)); - burnToken.mint(address(tokenRecipient)); - - ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ - tokenContract: address(target), - tokenId: newTokenId, - amount: 10, - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); - instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ - tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC721, - amount: 2, - tokenIdStart: 0, - tokenIdEnd: 1, - tokenContract: address(burnToken), - transferRecipient: address(0), - burnFunction: bytes4(keccak256(bytes("burn(uint256)"))) - }); - ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ - mintToken: mintToken, - instructions: instructions, - saleStart: 0, - saleEnd: type(uint64).max, - ethAmount: 1 ether, - ethRecipient: address(0) - }); - - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); - bytes32[] memory hashes = new bytes32[](1); - hashes[0] = keccak256(abi.encode(redeemInstructions)); - vm.expectEmit(true, false, false, true); - emit RedeemsCleared(address(target), hashes); - target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.clearRedeem.selector, hashes)); - vm.stopPrank(); - - vm.startPrank(tokenRecipient); - burnToken.setApprovalForAll(address(redeemMinter), true); - uint256[][] memory tokenIds = new uint256[][](1); - tokenIds[0] = new uint256[](2); - tokenIds[0][0] = 0; - tokenIds[0][1] = 1; - uint256[][] memory amounts = new uint256[][](1); - vm.expectRevert(abi.encodeWithSignature("RedeemInstructionNotAllowed()")); - target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); - } -} +/// @notice Contract versions after v1.4.0 will not support the current burn to redeem minter +// contract ZoraCreatorRedeemMinterStrategyTest is Test { +// ProtocolRewards internal protocolRewards; +// ZoraCreator1155Impl internal target; +// ZoraCreatorRedeemMinterStrategy internal redeemMinter; +// address payable internal admin = payable(address(0x999)); +// uint256 internal newTokenId; +// address internal zora; + +// event RedeemSet(address indexed target, bytes32 indexed redeemsInstructionsHash, ZoraCreatorRedeemMinterStrategy.RedeemInstructions data); +// event RedeemProcessed(address indexed target, bytes32 indexed redeemsInstructionsHash, address sender, uint256[][] tokenIds, uint256[][] amounts); +// event RedeemsCleared(address indexed target, bytes32[] indexed redeemInstructionsHashes); + +// function setUp() external { +// zora = makeAddr("zora"); +// bytes[] memory emptyData = new bytes[](0); +// protocolRewards = new ProtocolRewards(); +// ZoraCreator1155Impl targetImpl = new ZoraCreator1155Impl(0, zora, address(0), address(protocolRewards)); +// Zora1155 proxy = new Zora1155(address(targetImpl)); +// target = ZoraCreator1155Impl(address(proxy)); +// target.initialize("test", "test", ICreatorRoyaltiesControl.RoyaltyConfiguration(0, 0, address(0)), admin, emptyData); +// redeemMinter = new ZoraCreatorRedeemMinterStrategy(); +// redeemMinter.initialize(address(target)); +// vm.startPrank(admin); +// newTokenId = target.setupNewToken("https://zora.co/testing/token.json", 10); +// target.addPermission(newTokenId, address(redeemMinter), target.PERMISSION_BIT_MINTER()); +// vm.stopPrank(); +// } + +// function test_ContractURI() external { +// assertEq(redeemMinter.contractURI(), "https://github.com/ourzora/zora-1155-contracts/"); +// } + +// function test_ContractName() external { +// assertEq(redeemMinter.contractName(), "Redeem Minter Sale Strategy"); +// } + +// function test_Version() external { +// assertEq(redeemMinter.contractVersion(), "1.0.1"); +// } + +// function test_OnlyDropContractCanCallWriteFunctions() external { +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions; +// vm.startPrank(address(admin)); + +// vm.expectRevert(abi.encodeWithSignature("CallerNotCreatorContract()")); +// redeemMinter.setRedeem(redeemInstructions); + +// bytes32[] memory hashes = new bytes32[](0); +// vm.expectRevert(abi.encodeWithSignature("CallerNotCreatorContract()")); +// redeemMinter.clearRedeem(hashes); + +// vm.expectRevert(abi.encodeWithSignature("CallerNotCreatorContract()")); +// redeemMinter.requestMint(address(0), 0, 0, 0, bytes("")); + +// vm.expectRevert(abi.encodeWithSignature("CallerNotCreatorContract()")); +// redeemMinter.resetSale(0); + +// vm.stopPrank(); +// } + +// ///////// SET REDEEM ///////// + +// function test_SetRedeem() external { +// vm.startPrank(admin); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC20PresetMinterPauser burnToken = new ERC20PresetMinterPauser("Random Token", "RAND"); +// burnToken.mint(address(tokenRecipient), 1000); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, +// amount: 500, +// tokenIdStart: 0, +// tokenIdEnd: 0, +// tokenContract: address(burnToken), +// transferRecipient: address(0), +// burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); +// vm.stopPrank(); + +// vm.expectEmit(true, true, false, true); +// emit RedeemSet(address(target), keccak256(abi.encode(redeemInstructions)), redeemInstructions); +// vm.startPrank(address(target)); +// redeemMinter.setRedeem(redeemInstructions); +// vm.expectRevert(abi.encodeWithSignature("RedeemInstructionAlreadySet()")); +// redeemMinter.setRedeem(redeemInstructions); +// vm.stopPrank(); + +// assertTrue(redeemMinter.redeemInstructionsHashIsAllowed(keccak256(abi.encode(redeemInstructions)))); +// } + +// function test_SetRedeemInstructionValidation() external { +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC20PresetMinterPauser burnToken = new ERC20PresetMinterPauser("Random Token", "RAND"); +// burnToken.mint(address(tokenRecipient), 1000); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.NULL, +// amount: 500, +// tokenIdStart: 0, +// tokenIdEnd: 0, +// tokenContract: address(burnToken), +// transferRecipient: address(0), +// burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// vm.startPrank(address(target)); + +// // InvalidTokenIdsForTokenType: ERC20 w/ nonzero tokenId start or end +// redeemInstructions.instructions[0].tokenType = ZoraCreatorRedeemMinterStrategy.TokenType.ERC20; +// redeemInstructions.instructions[0].tokenIdStart = 1; +// redeemInstructions.instructions[0].tokenIdEnd = 0; +// vm.expectRevert(abi.encodeWithSignature("InvalidTokenIdsForTokenType()")); +// redeemMinter.setRedeem(redeemInstructions); +// redeemInstructions.instructions[0].tokenIdStart = 0; +// redeemInstructions.instructions[0].tokenIdEnd = 1; +// vm.expectRevert(abi.encodeWithSignature("InvalidTokenIdsForTokenType()")); +// redeemMinter.setRedeem(redeemInstructions); + +// // InvalidTokenIdsForTokenType: non ERC20 w/ tokenID start > end +// redeemInstructions.instructions[0].tokenType = ZoraCreatorRedeemMinterStrategy.TokenType.ERC721; +// redeemInstructions.instructions[0].tokenIdStart = 4; +// redeemInstructions.instructions[0].tokenIdEnd = 2; +// vm.expectRevert(abi.encodeWithSignature("InvalidTokenIdsForTokenType()")); +// redeemMinter.setRedeem(redeemInstructions); +// redeemInstructions.mintToken.tokenType = ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155; +// vm.expectRevert(abi.encodeWithSignature("InvalidTokenIdsForTokenType()")); +// redeemMinter.setRedeem(redeemInstructions); +// redeemInstructions.instructions[0].tokenIdStart = 0; +// redeemInstructions.instructions[0].tokenIdEnd = 0; + +// // InvalidTokenType: tokenType is NULL +// redeemInstructions.instructions[0].tokenType = ZoraCreatorRedeemMinterStrategy.TokenType.NULL; +// vm.expectRevert(abi.encodeWithSignature("InvalidTokenType()")); +// redeemMinter.setRedeem(redeemInstructions); +// redeemInstructions.instructions[0].tokenType = ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155; + +// // MustBurnOrTransfer: both transferRecipient and burnFunction are 0 +// redeemInstructions.instructions[0].transferRecipient = address(0); +// redeemInstructions.instructions[0].burnFunction = bytes4(0); +// vm.expectRevert(abi.encodeWithSignature("MustBurnOrTransfer()")); +// redeemMinter.setRedeem(redeemInstructions); + +// // MustBurnOrTransfer: both transferRecipient and burnFunction are non-zero +// redeemInstructions.instructions[0].transferRecipient = address(1); +// redeemInstructions.instructions[0].burnFunction = bytes4(keccak256(bytes("burnFrom(address,uint256)"))); +// vm.expectRevert(abi.encodeWithSignature("MustBurnOrTransfer()")); +// redeemMinter.setRedeem(redeemInstructions); +// redeemInstructions.instructions[0].transferRecipient = address(0); + +// // IncorrectMintAmount +// redeemInstructions.instructions[0].amount = 0; +// vm.expectRevert(abi.encodeWithSignature("IncorrectMintAmount()")); +// redeemMinter.setRedeem(redeemInstructions); +// redeemInstructions.instructions[0].amount = 500; + +// // InvalidSaleEndOrStart: start > end +// redeemInstructions.saleStart = 1; +// redeemInstructions.saleEnd = 0; +// vm.expectRevert(abi.encodeWithSignature("InvalidSaleEndOrStart()")); +// redeemMinter.setRedeem(redeemInstructions); +// redeemInstructions.saleStart = 0; +// redeemInstructions.saleEnd = type(uint64).max; + +// // InvalidSaleEndOrStart: block.timestamp > end +// redeemInstructions.saleStart = 0; +// redeemInstructions.saleEnd = 1 days; +// vm.warp(2 days); +// vm.expectRevert(abi.encodeWithSignature("InvalidSaleEndOrStart()")); +// redeemMinter.setRedeem(redeemInstructions); +// redeemInstructions.saleEnd = type(uint64).max; + +// // EmptyRedeemInstructions(); +// redeemInstructions.instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](0); +// vm.expectRevert(abi.encodeWithSignature("EmptyRedeemInstructions()")); +// redeemMinter.setRedeem(redeemInstructions); +// redeemInstructions.instructions = instructions; + +// // MintTokenContractMustBeCreatorContract +// redeemInstructions.mintToken.tokenContract = address(0); +// vm.expectRevert(abi.encodeWithSignature("MintTokenContractMustBeCreatorContract()")); +// redeemMinter.setRedeem(redeemInstructions); +// redeemInstructions.mintToken.tokenContract = address(target); + +// // MintTokenTypeMustBeERC1155: +// redeemInstructions.mintToken.tokenType = ZoraCreatorRedeemMinterStrategy.TokenType.ERC721; +// vm.expectRevert(abi.encodeWithSignature("MintTokenTypeMustBeERC1155()")); +// redeemMinter.setRedeem(redeemInstructions); +// } + +// ///////// REQUEST MINT ///////// + +// function test_MintFlowERC20() external { +// vm.startPrank(admin); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC20PresetMinterPauser burnToken = new ERC20PresetMinterPauser("Random Token", "RAND"); +// burnToken.mint(address(tokenRecipient), 1000); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, +// amount: 500, +// tokenIdStart: 0, +// tokenIdEnd: 0, +// tokenContract: address(burnToken), +// transferRecipient: address(0), +// burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// vm.startPrank(tokenRecipient); +// burnToken.approve(address(redeemMinter), 500); +// uint256[][] memory tokenIds = new uint256[][](1); +// tokenIds[0] = new uint256[](0); +// uint256[][] memory amounts = new uint256[][](1); +// amounts[0] = new uint256[](1); +// amounts[0][0] = 500; +// vm.expectEmit(true, true, false, false); +// emit RedeemProcessed(address(target), keccak256(abi.encode(redeemInstructions)), tokenRecipient, tokenIds, amounts); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// assertEq(address(target).balance, 1 ether); +// assertEq(target.balanceOf(tokenRecipient, newTokenId), 10); +// assertEq(burnToken.balanceOf(tokenRecipient), 500); +// vm.stopPrank(); +// } + +// function test_MintFlowERC1155() external { +// vm.startPrank(admin); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC1155PresetMinterPauser burnToken = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); +// burnToken.mint(address(tokenRecipient), 1, 1000, ""); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155, +// amount: 500, +// tokenIdStart: 1, +// tokenIdEnd: 1, +// tokenContract: address(burnToken), +// transferRecipient: address(0), +// burnFunction: bytes4(keccak256(bytes("burnBatch(address,uint256[],uint256[])"))) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// vm.startPrank(tokenRecipient); +// burnToken.setApprovalForAll(address(redeemMinter), true); +// uint256[][] memory tokenIds = new uint256[][](1); +// tokenIds[0] = new uint256[](1); +// tokenIds[0][0] = 1; +// uint256[][] memory amounts = new uint256[][](1); +// amounts[0] = new uint256[](1); +// amounts[0][0] = 500; +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// assertEq(address(target).balance, 1 ether); +// assertEq(target.balanceOf(tokenRecipient, newTokenId), 10); +// assertEq(burnToken.balanceOf(tokenRecipient, 1), 500); +// vm.stopPrank(); +// } + +// function test_MintFlowERC721() external { +// vm.startPrank(admin); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC721PresetMinterPauserAutoId burnToken = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); +// burnToken.mint(address(tokenRecipient)); +// burnToken.mint(address(tokenRecipient)); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC721, +// amount: 2, +// tokenIdStart: 0, +// tokenIdEnd: 1, +// tokenContract: address(burnToken), +// transferRecipient: address(0), +// burnFunction: bytes4(keccak256(bytes("burn(uint256)"))) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// vm.startPrank(tokenRecipient); +// burnToken.setApprovalForAll(address(redeemMinter), true); +// uint256[][] memory tokenIds = new uint256[][](1); +// tokenIds[0] = new uint256[](2); +// tokenIds[0][0] = 0; +// tokenIds[0][1] = 1; +// uint256[][] memory amounts = new uint256[][](1); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// assertEq(address(target).balance, 1 ether); +// assertEq(target.balanceOf(tokenRecipient, newTokenId), 10); +// vm.expectRevert(); +// burnToken.ownerOf(0); +// vm.expectRevert(); +// burnToken.ownerOf(1); +// vm.stopPrank(); +// } + +// function test_MintFlowMultiple() external { +// vm.startPrank(admin); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC20PresetMinterPauser burnTokenERC20 = new ERC20PresetMinterPauser("Random Token", "RAND"); +// burnTokenERC20.mint(address(tokenRecipient), 1000); +// ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); +// burnTokenERC1155.mint(address(tokenRecipient), 1, 1000, ""); +// ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); +// burnTokenERC721.mint(address(tokenRecipient)); +// burnTokenERC721.mint(address(tokenRecipient)); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](3); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, +// amount: 500, +// tokenIdStart: 0, +// tokenIdEnd: 0, +// tokenContract: address(burnTokenERC20), +// transferRecipient: address(0), +// burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) +// }); +// instructions[1] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155, +// amount: 500, +// tokenIdStart: 1, +// tokenIdEnd: 1, +// tokenContract: address(burnTokenERC1155), +// transferRecipient: address(0), +// burnFunction: bytes4(keccak256(bytes("burnBatch(address,uint256[],uint256[])"))) +// }); +// instructions[2] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC721, +// amount: 2, +// tokenIdStart: 0, +// tokenIdEnd: 1, +// tokenContract: address(burnTokenERC721), +// transferRecipient: address(0), +// burnFunction: bytes4(keccak256(bytes("burn(uint256)"))) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// vm.startPrank(tokenRecipient); +// burnTokenERC20.approve(address(redeemMinter), 500); +// burnTokenERC1155.setApprovalForAll(address(redeemMinter), true); +// burnTokenERC721.setApprovalForAll(address(redeemMinter), true); + +// uint256[][] memory tokenIds = new uint256[][](3); +// uint256[][] memory amounts = new uint256[][](3); + +// amounts[0] = new uint256[](1); +// amounts[0][0] = 500; + +// tokenIds[1] = new uint256[](1); +// tokenIds[1][0] = 1; +// amounts[1] = new uint256[](1); +// amounts[1][0] = 500; + +// tokenIds[2] = new uint256[](2); +// tokenIds[2][0] = 0; +// tokenIds[2][1] = 1; + +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// assertEq(address(target).balance, 1 ether); +// assertEq(target.balanceOf(tokenRecipient, newTokenId), 10); +// assertEq(burnTokenERC20.balanceOf(tokenRecipient), 500); +// assertEq(burnTokenERC1155.balanceOf(tokenRecipient, 1), 500); +// assertEq(burnTokenERC721.balanceOf(address(tokenRecipient)), 0); +// vm.stopPrank(); +// } + +// function test_MintFlowMultipleWithTransferInsteadOfBurn() external { +// vm.startPrank(admin); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); +// address redeemTokenRecipient = address(323); + +// ERC20PresetMinterPauser burnTokenERC20 = new ERC20PresetMinterPauser("Random Token", "RAND"); +// burnTokenERC20.mint(address(tokenRecipient), 1000); +// ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); +// burnTokenERC1155.mint(address(tokenRecipient), 1, 1000, ""); +// ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); +// burnTokenERC721.mint(address(tokenRecipient)); +// burnTokenERC721.mint(address(tokenRecipient)); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](3); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, +// amount: 500, +// tokenIdStart: 0, +// tokenIdEnd: 0, +// tokenContract: address(burnTokenERC20), +// transferRecipient: redeemTokenRecipient, +// burnFunction: 0 +// }); +// instructions[1] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155, +// amount: 500, +// tokenIdStart: 1, +// tokenIdEnd: 1, +// tokenContract: address(burnTokenERC1155), +// transferRecipient: redeemTokenRecipient, +// burnFunction: 0 +// }); +// instructions[2] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC721, +// amount: 2, +// tokenIdStart: 0, +// tokenIdEnd: 1, +// tokenContract: address(burnTokenERC721), +// transferRecipient: redeemTokenRecipient, +// burnFunction: 0 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// vm.startPrank(tokenRecipient); +// burnTokenERC20.approve(address(redeemMinter), 500); +// burnTokenERC1155.setApprovalForAll(address(redeemMinter), true); +// burnTokenERC721.setApprovalForAll(address(redeemMinter), true); + +// uint256[][] memory tokenIds = new uint256[][](3); +// uint256[][] memory amounts = new uint256[][](3); + +// amounts[0] = new uint256[](1); +// amounts[0][0] = 500; + +// tokenIds[1] = new uint256[](1); +// tokenIds[1][0] = 1; +// amounts[1] = new uint256[](1); +// amounts[1][0] = 500; + +// tokenIds[2] = new uint256[](2); +// tokenIds[2][0] = 0; +// tokenIds[2][1] = 1; + +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// assertEq(address(target).balance, 1 ether); +// assertEq(target.balanceOf(tokenRecipient, newTokenId), 10); +// assertEq(burnTokenERC20.balanceOf(tokenRecipient), 500); +// assertEq(burnTokenERC20.balanceOf(redeemTokenRecipient), 500); +// assertEq(burnTokenERC1155.balanceOf(tokenRecipient, 1), 500); +// assertEq(burnTokenERC1155.balanceOf(redeemTokenRecipient, 1), 500); +// assertEq(burnTokenERC721.balanceOf(address(tokenRecipient)), 0); +// assertEq(burnTokenERC721.balanceOf(redeemTokenRecipient), 2); + +// vm.stopPrank(); +// } + +// function test_MintFlowTokenIdRangesForERC1155AndERC721() external { +// vm.startPrank(admin); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); +// address redeemTokenRecipient = address(323); + +// ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); +// burnTokenERC1155.mint(address(tokenRecipient), 7, 100, ""); +// burnTokenERC1155.mint(address(tokenRecipient), 8, 100, ""); +// burnTokenERC1155.mint(address(tokenRecipient), 9, 100, ""); +// ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); +// burnTokenERC721.mint(address(tokenRecipient)); +// burnTokenERC721.mint(address(tokenRecipient)); +// burnTokenERC721.mint(address(tokenRecipient)); +// burnTokenERC721.mint(address(tokenRecipient)); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](2); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155, +// amount: 300, +// tokenIdStart: 7, +// tokenIdEnd: 9, +// tokenContract: address(burnTokenERC1155), +// transferRecipient: redeemTokenRecipient, +// burnFunction: 0 +// }); +// instructions[1] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC721, +// amount: 3, +// tokenIdStart: 1, +// tokenIdEnd: 3, +// tokenContract: address(burnTokenERC721), +// transferRecipient: redeemTokenRecipient, +// burnFunction: 0 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// vm.startPrank(tokenRecipient); +// burnTokenERC1155.setApprovalForAll(address(redeemMinter), true); +// burnTokenERC721.setApprovalForAll(address(redeemMinter), true); + +// uint256[][] memory tokenIds = new uint256[][](2); +// uint256[][] memory amounts = new uint256[][](2); + +// tokenIds[0] = new uint256[](3); +// tokenIds[0][0] = 7; +// tokenIds[0][1] = 8; +// tokenIds[0][2] = 9; +// amounts[0] = new uint256[](3); +// amounts[0][0] = 100; +// amounts[0][1] = 100; +// amounts[0][2] = 100; + +// tokenIds[1] = new uint256[](3); +// tokenIds[1][0] = 1; +// tokenIds[1][1] = 2; +// tokenIds[1][2] = 3; + +// // detour: tokenId out of range +// tokenIds[0][0] = 6; +// vm.expectRevert(abi.encodeWithSignature("TokenIdOutOfRange()")); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// tokenIds[0][0] = 10; +// vm.expectRevert(abi.encodeWithSignature("TokenIdOutOfRange()")); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// tokenIds[0][0] = 7; +// tokenIds[1][0] = 0; +// vm.expectRevert(abi.encodeWithSignature("TokenIdOutOfRange()")); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// tokenIds[1][0] = 4; +// vm.expectRevert(abi.encodeWithSignature("TokenIdOutOfRange()")); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// tokenIds[1][0] = 1; + +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// assertEq(address(target).balance, 1 ether); +// assertEq(target.balanceOf(tokenRecipient, newTokenId), 10); +// assertEq(burnTokenERC1155.balanceOf(redeemTokenRecipient, 7), 100); +// assertEq(burnTokenERC1155.balanceOf(redeemTokenRecipient, 8), 100); +// assertEq(burnTokenERC1155.balanceOf(redeemTokenRecipient, 9), 100); +// assertEq(burnTokenERC721.balanceOf(redeemTokenRecipient), 3); + +// vm.stopPrank(); +// } + +// function test_MintFlowRedeemStart() external { +// vm.startPrank(admin); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC20PresetMinterPauser burnToken = new ERC20PresetMinterPauser("Random Token", "RAND"); +// burnToken.mint(address(tokenRecipient), 1000); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, +// amount: 500, +// tokenIdStart: 0, +// tokenIdEnd: 0, +// tokenContract: address(burnToken), +// transferRecipient: address(0), +// burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: uint64(block.timestamp + 1 days), +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// vm.startPrank(tokenRecipient); +// burnToken.approve(address(redeemMinter), 500); +// uint256[][] memory tokenIds = new uint256[][](1); +// tokenIds[0] = new uint256[](0); +// uint256[][] memory amounts = new uint256[][](1); +// amounts[0] = new uint256[](1); +// amounts[0][0] = 500; +// vm.expectRevert(abi.encodeWithSignature("SaleHasNotStarted()")); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 1, abi.encode(redeemInstructions, tokenIds, amounts)); +// vm.stopPrank(); +// } + +// function test_MintFlowRedeemEnd() external { +// vm.startPrank(admin); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC20PresetMinterPauser burnToken = new ERC20PresetMinterPauser("Random Token", "RAND"); +// burnToken.mint(address(tokenRecipient), 1000); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, +// amount: 500, +// tokenIdStart: 0, +// tokenIdEnd: 0, +// tokenContract: address(burnToken), +// transferRecipient: address(0), +// burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: uint64(1 days), +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// vm.warp(2 days); + +// vm.startPrank(tokenRecipient); +// burnToken.approve(address(redeemMinter), 1000); +// uint256[] memory tokenIds = new uint256[](0); +// uint256[][] memory amounts = new uint256[][](1); +// amounts[0] = new uint256[](1); +// amounts[0][0] = 500; +// vm.expectRevert(abi.encodeWithSignature("SaleEnded()")); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 1, abi.encode(redeemInstructions, tokenIds, amounts)); +// vm.stopPrank(); +// } + +// function test_MintFlowEthValue() external { +// vm.startPrank(admin); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC20PresetMinterPauser burnToken = new ERC20PresetMinterPauser("Random Token", "RAND"); +// burnToken.mint(address(tokenRecipient), 1000); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, +// amount: 500, +// tokenIdStart: 0, +// tokenIdEnd: 0, +// tokenContract: address(burnToken), +// transferRecipient: address(0), +// burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// vm.startPrank(tokenRecipient); +// burnToken.approve(address(redeemMinter), 500); +// uint256[][] memory tokenIds = new uint256[][](1); +// tokenIds[0] = new uint256[](0); +// uint256[][] memory amounts = new uint256[][](1); +// amounts[0] = new uint256[](1); +// amounts[0][0] = 500; +// vm.expectRevert(abi.encodeWithSignature("INVALID_ETH_AMOUNT()")); +// target.mint{value: 0.9 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// vm.expectRevert(abi.encodeWithSignature("INVALID_ETH_AMOUNT()")); +// target.mint{value: 1.1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// vm.stopPrank(); +// } + +// function test_MintFlowFundsRecipient() external { +// vm.startPrank(admin); + +// address fundsRecipient = address(239); +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC20PresetMinterPauser burnToken = new ERC20PresetMinterPauser("Random Token", "RAND"); +// burnToken.mint(address(tokenRecipient), 1000); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, +// amount: 500, +// tokenIdStart: 0, +// tokenIdEnd: 0, +// tokenContract: address(burnToken), +// transferRecipient: address(0), +// burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: fundsRecipient +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// vm.startPrank(tokenRecipient); +// burnToken.approve(address(redeemMinter), 500); +// uint256[][] memory tokenIds = new uint256[][](1); +// tokenIds[0] = new uint256[](0); +// uint256[][] memory amounts = new uint256[][](1); +// amounts[0] = new uint256[](1); +// amounts[0][0] = 500; +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// assertEq(fundsRecipient.balance, 1 ether); +// vm.stopPrank(); +// } + +// function test_MintFlowAmount() external { +// vm.startPrank(admin); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC20PresetMinterPauser burnToken = new ERC20PresetMinterPauser("Random Token", "RAND"); +// burnToken.mint(address(tokenRecipient), 1000); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, +// amount: 500, +// tokenIdStart: 0, +// tokenIdEnd: 0, +// tokenContract: address(burnToken), +// transferRecipient: address(0), +// burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// vm.startPrank(tokenRecipient); +// burnToken.approve(address(redeemMinter), 500); +// uint256[][] memory tokenIds = new uint256[][](1); +// tokenIds[0] = new uint256[](0); +// uint256[][] memory amounts = new uint256[][](1); +// amounts[0] = new uint256[](1); +// amounts[0][0] = 500; +// vm.expectRevert(abi.encodeWithSignature("IncorrectMintAmount()")); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 11, abi.encode(redeemInstructions, tokenIds, amounts)); +// vm.stopPrank(); +// } + +// function test_MintFlowIncorrectNumberOfTokenIds() external { +// vm.startPrank(admin); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC1155PresetMinterPauser burnToken = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); +// burnToken.mint(address(tokenRecipient), 1, 1000, ""); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155, +// amount: 500, +// tokenIdStart: 1, +// tokenIdEnd: 1, +// tokenContract: address(burnToken), +// transferRecipient: address(0), +// burnFunction: bytes4(keccak256(bytes("burnBatch(address,uint256[],uint256[])"))) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// // instructions length != tokenIds length +// vm.startPrank(tokenRecipient); +// burnToken.setApprovalForAll(address(redeemMinter), true); +// uint256[][] memory tokenIds = new uint256[][](2); +// uint256[][] memory amounts = new uint256[][](1); +// amounts[0] = new uint256[](1); +// amounts[0][0] = 500; +// vm.expectRevert(abi.encodeWithSignature("IncorrectNumberOfTokenIds()")); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// tokenIds = new uint256[][](1); +// tokenIds[0] = new uint256[](1); +// tokenIds[0][0] = 1; + +// // ERC1155: amounts length != tokenIds length +// amounts = new uint256[][](2); +// vm.expectRevert(abi.encodeWithSignature("IncorrectNumberOfTokenIds()")); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// } + +// function test_MintFlowIncorrectBurnOrTransferAmount() external { +// vm.startPrank(admin); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); +// address redeemTokenRecipient = address(323); + +// ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); +// burnTokenERC1155.mint(address(tokenRecipient), 1, 500, ""); +// ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); +// burnTokenERC721.mint(address(tokenRecipient)); +// burnTokenERC721.mint(address(tokenRecipient)); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](2); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155, +// amount: 300, +// tokenIdStart: 1, +// tokenIdEnd: 1, +// tokenContract: address(burnTokenERC1155), +// transferRecipient: redeemTokenRecipient, +// burnFunction: 0 +// }); +// instructions[1] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC721, +// amount: 3, +// tokenIdStart: 0, +// tokenIdEnd: 2, +// tokenContract: address(burnTokenERC721), +// transferRecipient: redeemTokenRecipient, +// burnFunction: 0 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// vm.startPrank(tokenRecipient); +// burnTokenERC1155.setApprovalForAll(address(redeemMinter), true); +// burnTokenERC721.setApprovalForAll(address(redeemMinter), true); + +// uint256[][] memory tokenIds = new uint256[][](2); +// uint256[][] memory amounts = new uint256[][](2); + +// // this would be correct +// tokenIds[0] = new uint256[](1); +// tokenIds[0][0] = 1; +// amounts[0] = new uint256[](1); +// amounts[0][0] = 500; +// tokenIds[1] = new uint256[](2); +// tokenIds[1][0] = 0; +// tokenIds[1][1] = 1; + +// // ERC721: tokenids length != instruction amount +// tokenIds[1] = new uint256[](1); +// vm.expectRevert(abi.encodeWithSignature("IncorrectBurnOrTransferAmount()")); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// tokenIds[1] = new uint256[](3); +// vm.expectRevert(abi.encodeWithSignature("IncorrectBurnOrTransferAmount()")); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// tokenIds[1] = new uint256[](2); +// tokenIds[1][0] = 0; +// tokenIds[1][1] = 1; + +// // ERC1155: sum of amounts != instruction amount +// amounts[0][0] = 499; +// vm.expectRevert(abi.encodeWithSignature("IncorrectBurnOrTransferAmount()")); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// amounts[0][0] = 501; +// vm.expectRevert(abi.encodeWithSignature("IncorrectBurnOrTransferAmount()")); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); + +// vm.stopPrank(); +// } + +// function test_MintFlowSenderNotTokenOwnerBurn20() external { +// vm.startPrank(admin); + +// address actualTokenOwner = address(92834); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC20PresetMinterPauser burnTokenERC20 = new ERC20PresetMinterPauser("Random Token", "RAND"); +// burnTokenERC20.mint(address(actualTokenOwner), 1000); +// ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); +// burnTokenERC1155.mint(address(actualTokenOwner), 1, 1000, ""); +// ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); +// burnTokenERC721.mint(address(actualTokenOwner)); +// burnTokenERC721.mint(address(actualTokenOwner)); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, +// amount: 500, +// tokenIdStart: 0, +// tokenIdEnd: 0, +// tokenContract: address(burnTokenERC20), +// transferRecipient: address(0), +// burnFunction: bytes4(keccak256(bytes("burnFrom(address,uint256)"))) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// vm.startPrank(tokenRecipient); +// burnTokenERC20.approve(address(redeemMinter), 500); + +// uint256[][] memory tokenIds = new uint256[][](1); +// uint256[][] memory amounts = new uint256[][](1); +// amounts[0] = new uint256[](1); +// amounts[0][0] = 500; + +// vm.expectRevert(abi.encodeWithSignature("BurnFailed()")); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// vm.stopPrank(); +// } + +// function test_MintFlowSenderNotTokenOwnerBurn1155() external { +// vm.startPrank(admin); + +// address actualTokenOwner = address(92834); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC20PresetMinterPauser burnTokenERC20 = new ERC20PresetMinterPauser("Random Token", "RAND"); +// burnTokenERC20.mint(address(actualTokenOwner), 1000); +// ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); +// burnTokenERC1155.mint(address(actualTokenOwner), 1, 1000, ""); +// ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); +// burnTokenERC721.mint(address(actualTokenOwner)); +// burnTokenERC721.mint(address(actualTokenOwner)); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); + +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155, +// amount: 500, +// tokenIdStart: 1, +// tokenIdEnd: 1, +// tokenContract: address(burnTokenERC1155), +// transferRecipient: address(0), +// burnFunction: bytes4(keccak256(bytes("burnBatch(address,uint256[],uint256[])"))) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// vm.startPrank(tokenRecipient); +// burnTokenERC1155.setApprovalForAll(address(redeemMinter), true); + +// uint256[][] memory tokenIds = new uint256[][](1); +// tokenIds[0] = new uint256[](1); +// tokenIds[0][0] = 1; +// uint256[][] memory amounts = new uint256[][](1); +// amounts[0] = new uint256[](1); +// amounts[0][0] = 500; + +// vm.expectRevert(abi.encodeWithSignature("BurnFailed()")); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// vm.stopPrank(); +// } + +// function test_MintFlowSenderNotTokenOwnerBurn721() external { +// vm.startPrank(admin); + +// address actualTokenOwner = address(92834); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC20PresetMinterPauser burnTokenERC20 = new ERC20PresetMinterPauser("Random Token", "RAND"); +// burnTokenERC20.mint(address(actualTokenOwner), 1000); +// ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); +// burnTokenERC1155.mint(address(actualTokenOwner), 1, 1000, ""); +// ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); +// burnTokenERC721.mint(address(actualTokenOwner)); +// burnTokenERC721.mint(address(actualTokenOwner)); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); + +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC721, +// amount: 2, +// tokenIdStart: 0, +// tokenIdEnd: 1, +// tokenContract: address(burnTokenERC721), +// transferRecipient: address(0), +// burnFunction: bytes4(keccak256(bytes("burn(uint256)"))) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// vm.prank(actualTokenOwner); +// burnTokenERC721.setApprovalForAll(address(redeemMinter), true); +// vm.startPrank(tokenRecipient); +// burnTokenERC721.setApprovalForAll(address(redeemMinter), true); + +// uint256[][] memory tokenIds = new uint256[][](1); +// tokenIds[0] = new uint256[](2); +// tokenIds[0][0] = 0; +// tokenIds[0][1] = 1; +// uint256[][] memory amounts; + +// vm.expectRevert(abi.encodeWithSignature("SenderIsNotTokenOwner()")); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// vm.stopPrank(); +// } + +// function test_MintFlowSenderNotTokenOwnerTransfer20() external { +// vm.startPrank(admin); + +// address actualTokenOwner = address(92834); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC20PresetMinterPauser burnTokenERC20 = new ERC20PresetMinterPauser("Random Token", "RAND"); +// burnTokenERC20.mint(address(actualTokenOwner), 1000); +// ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); +// burnTokenERC1155.mint(address(actualTokenOwner), 1, 1000, ""); +// ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); +// burnTokenERC721.mint(address(actualTokenOwner)); +// burnTokenERC721.mint(address(actualTokenOwner)); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); + +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC20, +// amount: 500, +// tokenIdStart: 0, +// tokenIdEnd: 0, +// tokenContract: address(burnTokenERC20), +// transferRecipient: address(1), +// burnFunction: bytes4(0) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// vm.startPrank(tokenRecipient); +// burnTokenERC20.approve(address(redeemMinter), 500); + +// uint256[][] memory tokenIds = new uint256[][](1); +// uint256[][] memory amounts = new uint256[][](1); +// amounts[0] = new uint256[](1); +// amounts[0][0] = 500; + +// vm.expectRevert("ERC20: transfer amount exceeds balance"); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// vm.stopPrank(); +// } + +// function test_MintFlowSenderNotTokenOwnerTransfer1155() external { +// vm.startPrank(admin); + +// address actualTokenOwner = address(92834); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC20PresetMinterPauser burnTokenERC20 = new ERC20PresetMinterPauser("Random Token", "RAND"); +// burnTokenERC20.mint(address(actualTokenOwner), 1000); +// ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); +// burnTokenERC1155.mint(address(actualTokenOwner), 1, 1000, ""); +// ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); +// burnTokenERC721.mint(address(actualTokenOwner)); +// burnTokenERC721.mint(address(actualTokenOwner)); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); + +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155, +// amount: 500, +// tokenIdStart: 1, +// tokenIdEnd: 1, +// tokenContract: address(burnTokenERC1155), +// transferRecipient: address(1), +// burnFunction: bytes4(0) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// vm.startPrank(tokenRecipient); +// burnTokenERC1155.setApprovalForAll(address(redeemMinter), true); + +// uint256[][] memory tokenIds = new uint256[][](1); +// tokenIds[0] = new uint256[](1); +// tokenIds[0][0] = 1; +// uint256[][] memory amounts = new uint256[][](1); +// amounts[0] = new uint256[](1); +// amounts[0][0] = 500; + +// vm.expectRevert("ERC1155: insufficient balance for transfer"); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// vm.stopPrank(); +// } + +// function test_MintFlowSenderNotTokenOwnerTransfer721() external { +// vm.startPrank(admin); + +// address actualTokenOwner = address(92834); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC20PresetMinterPauser burnTokenERC20 = new ERC20PresetMinterPauser("Random Token", "RAND"); +// burnTokenERC20.mint(address(actualTokenOwner), 1000); +// ERC1155PresetMinterPauser burnTokenERC1155 = new ERC1155PresetMinterPauser("https://zora.co/testing/token.json"); +// burnTokenERC1155.mint(address(actualTokenOwner), 1, 1000, ""); +// ERC721PresetMinterPauserAutoId burnTokenERC721 = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); +// burnTokenERC721.mint(address(actualTokenOwner)); +// burnTokenERC721.mint(address(actualTokenOwner)); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); + +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC721, +// amount: 2, +// tokenIdStart: 0, +// tokenIdEnd: 1, +// tokenContract: address(burnTokenERC721), +// transferRecipient: address(1), +// burnFunction: bytes4(0) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// vm.stopPrank(); + +// vm.startPrank(tokenRecipient); +// burnTokenERC721.setApprovalForAll(address(redeemMinter), true); + +// uint256[][] memory tokenIds = new uint256[][](1); +// tokenIds[0] = new uint256[](2); +// tokenIds[0][0] = 0; +// tokenIds[0][1] = 1; +// uint256[][] memory amounts; + +// vm.expectRevert("ERC721: caller is not token owner or approved"); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// vm.stopPrank(); +// } + +// ///////// RESET AND CLEAR ///////// + +// function test_ResetSaleAlwaysReverts() external { +// vm.prank(address(target)); +// vm.expectRevert(abi.encodeWithSignature("MustCallClearRedeem()")); +// redeemMinter.resetSale(uint256(1)); +// } + +// function test_ClearRedeem() external { +// vm.startPrank(admin); + +// address tokenRecipient = address(322); +// vm.deal(tokenRecipient, 20 ether); + +// ERC721PresetMinterPauserAutoId burnToken = new ERC721PresetMinterPauserAutoId("Test token", "TEST", "https://zora.co/testing/token.json"); +// burnToken.mint(address(tokenRecipient)); +// burnToken.mint(address(tokenRecipient)); + +// ZoraCreatorRedeemMinterStrategy.MintToken memory mintToken = ZoraCreatorRedeemMinterStrategy.MintToken({ +// tokenContract: address(target), +// tokenId: newTokenId, +// amount: 10, +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC1155 +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstruction[] memory instructions = new ZoraCreatorRedeemMinterStrategy.RedeemInstruction[](1); +// instructions[0] = ZoraCreatorRedeemMinterStrategy.RedeemInstruction({ +// tokenType: ZoraCreatorRedeemMinterStrategy.TokenType.ERC721, +// amount: 2, +// tokenIdStart: 0, +// tokenIdEnd: 1, +// tokenContract: address(burnToken), +// transferRecipient: address(0), +// burnFunction: bytes4(keccak256(bytes("burn(uint256)"))) +// }); +// ZoraCreatorRedeemMinterStrategy.RedeemInstructions memory redeemInstructions = ZoraCreatorRedeemMinterStrategy.RedeemInstructions({ +// mintToken: mintToken, +// instructions: instructions, +// saleStart: 0, +// saleEnd: type(uint64).max, +// ethAmount: 1 ether, +// ethRecipient: address(0) +// }); + +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.setRedeem.selector, redeemInstructions)); +// bytes32[] memory hashes = new bytes32[](1); +// hashes[0] = keccak256(abi.encode(redeemInstructions)); +// vm.expectEmit(true, false, false, true); +// emit RedeemsCleared(address(target), hashes); +// target.callSale(newTokenId, redeemMinter, abi.encodeWithSelector(ZoraCreatorRedeemMinterStrategy.clearRedeem.selector, hashes)); +// vm.stopPrank(); + +// vm.startPrank(tokenRecipient); +// burnToken.setApprovalForAll(address(redeemMinter), true); +// uint256[][] memory tokenIds = new uint256[][](1); +// tokenIds[0] = new uint256[](2); +// tokenIds[0][0] = 0; +// tokenIds[0][1] = 1; +// uint256[][] memory amounts = new uint256[][](1); +// vm.expectRevert(abi.encodeWithSignature("RedeemInstructionNotAllowed()")); +// target.mint{value: 1 ether}(redeemMinter, newTokenId, 10, abi.encode(redeemInstructions, tokenIds, amounts)); +// } +// } diff --git a/test/nft/ZoraCreator1155.t.sol b/test/nft/ZoraCreator1155.t.sol index 26f2538d6..b59aee2a7 100644 --- a/test/nft/ZoraCreator1155.t.sol +++ b/test/nft/ZoraCreator1155.t.sol @@ -10,6 +10,7 @@ import {ZoraCreator1155Impl} from "../../src/nft/ZoraCreator1155Impl.sol"; import {Zora1155} from "../../src/proxies/Zora1155.sol"; import {ZoraCreatorFixedPriceSaleStrategy} from "../../src/minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.sol"; +import {IZoraCreator1155Errors} from "../../src/interfaces/IZoraCreator1155Errors.sol"; import {IZoraCreator1155} from "../../src/interfaces/IZoraCreator1155.sol"; import {IRenderer1155} from "../../src/interfaces/IRenderer1155.sol"; import {IZoraCreator1155TypesV1} from "../../src/nft/IZoraCreator1155TypesV1.sol"; @@ -215,7 +216,7 @@ contract ZoraCreator1155Test is Test { function test_setupNewToken_revertOnlyAdminOrRole() external { init(); - vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155.UserMissingRoleForToken.selector, address(this), 0, target.PERMISSION_BIT_MINTER())); + vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155Errors.UserMissingRoleForToken.selector, address(this), 0, target.PERMISSION_BIT_MINTER())); target.setupNewToken("test", 1); } @@ -260,7 +261,7 @@ contract ZoraCreator1155Test is Test { function test_setTokenMetadataRenderer_revertOnlyAdminOrRole() external { init(); - vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155.UserMissingRoleForToken.selector, address(this), 0, target.PERMISSION_BIT_METADATA())); + vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155Errors.UserMissingRoleForToken.selector, address(this), 0, target.PERMISSION_BIT_METADATA())); target.setTokenMetadataRenderer(0, IRenderer1155(address(0))); } @@ -281,7 +282,7 @@ contract ZoraCreator1155Test is Test { vm.assume(tokenId != 0); init(); - vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155.UserMissingRoleForToken.selector, recipient, tokenId, adminRole)); + vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155Errors.UserMissingRoleForToken.selector, recipient, tokenId, adminRole)); vm.prank(recipient); target.addPermission(tokenId, recipient, adminRole); } @@ -332,7 +333,7 @@ contract ZoraCreator1155Test is Test { vm.assume(tokenId != 0); init(); - vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155.UserMissingRoleForToken.selector, recipient, tokenId, adminRole)); + vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155Errors.UserMissingRoleForToken.selector, recipient, tokenId, adminRole)); vm.prank(recipient); target.removePermission(tokenId, address(0), adminRole); } @@ -413,7 +414,9 @@ contract ZoraCreator1155Test is Test { vm.prank(admin); uint256 tokenId = target.setupNewToken("test", 1000); - vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155.UserMissingRoleForToken.selector, address(this), tokenId, target.PERMISSION_BIT_MINTER())); + vm.expectRevert( + abi.encodeWithSelector(IZoraCreator1155Errors.UserMissingRoleForToken.selector, address(this), tokenId, target.PERMISSION_BIT_MINTER()) + ); target.adminMint(address(0), tokenId, 0, ""); } @@ -424,7 +427,7 @@ contract ZoraCreator1155Test is Test { vm.prank(admin); uint256 tokenId = target.setupNewToken("test", quantity - 1); - vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155.CannotMintMoreTokens.selector, tokenId, quantity, 0, quantity - 1)); + vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155Errors.CannotMintMoreTokens.selector, tokenId, quantity, 0, quantity - 1)); vm.prank(admin); target.adminMint(recipient, tokenId, quantity, ""); } @@ -540,7 +543,9 @@ contract ZoraCreator1155Test is Test { tokenIds[0] = tokenId; quantities[0] = 0; - vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155.UserMissingRoleForToken.selector, address(this), tokenId, target.PERMISSION_BIT_MINTER())); + vm.expectRevert( + abi.encodeWithSelector(IZoraCreator1155Errors.UserMissingRoleForToken.selector, address(this), tokenId, target.PERMISSION_BIT_MINTER()) + ); target.adminMintBatch(address(0), tokenIds, quantities, ""); } @@ -556,7 +561,7 @@ contract ZoraCreator1155Test is Test { tokenIds[0] = tokenId; quantities[0] = quantity; - vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155.CannotMintMoreTokens.selector, tokenId, quantity, 0, quantity - 1)); + vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155Errors.CannotMintMoreTokens.selector, tokenId, quantity, 0, quantity - 1)); vm.prank(admin); target.adminMintBatch(recipient, tokenIds, quantities, ""); } @@ -578,18 +583,21 @@ contract ZoraCreator1155Test is Test { } function test_mint(uint256 quantity) external { + vm.assume(quantity > 0 && quantity < type(uint200).max); + init(); vm.prank(admin); uint256 tokenId = target.setupNewToken("test", quantity); vm.prank(admin); - target.addPermission(tokenId, address(simpleMinter), adminRole); + target.addPermission(tokenId, address(simpleMinter), minterRole); + + uint256 totalReward = target.computeTotalReward(quantity); + vm.deal(admin, totalReward); vm.prank(admin); - vm.expectEmit(true, true, true, true); - emit Purchased(admin, address(simpleMinter), tokenId, quantity, 0); - target.mint(simpleMinter, tokenId, quantity, abi.encode(recipient)); + target.mint{value: totalReward}(simpleMinter, tokenId, quantity, abi.encode(recipient)); IZoraCreator1155TypesV1.TokenData memory tokenData = target.getTokenInfo(tokenId); assertEq(tokenData.totalMinted, quantity); @@ -602,22 +610,26 @@ contract ZoraCreator1155Test is Test { vm.prank(admin); uint256 tokenId = target.setupNewToken("test", 1000); - vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155.UserMissingRoleForToken.selector, address(0), tokenId, target.PERMISSION_BIT_MINTER())); + vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155Errors.UserMissingRoleForToken.selector, address(0), tokenId, target.PERMISSION_BIT_MINTER())); target.mint(SimpleMinter(payable(address(0))), tokenId, 0, ""); } function test_mint_revertCannotMintMoreTokens() external { init(); - vm.prank(admin); + uint256 totalReward = target.computeTotalReward(1001); + vm.deal(admin, totalReward); + + vm.startPrank(admin); + uint256 tokenId = target.setupNewToken("test", 1000); - vm.prank(admin); target.addPermission(tokenId, address(simpleMinter), adminRole); - vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155.CannotMintMoreTokens.selector, tokenId, 1001, 0, 1000)); - vm.prank(admin); - target.mint(simpleMinter, tokenId, 1001, abi.encode(recipient)); + vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155Errors.CannotMintMoreTokens.selector, tokenId, 1001, 0, 1000)); + target.mint{value: totalReward}(simpleMinter, tokenId, 1001, abi.encode(recipient)); + + vm.stopPrank(); } function test_FreeMintRewards(uint256 quantity) public { @@ -641,7 +653,8 @@ contract ZoraCreator1155Test is Test { (, , address fundsRecipient, , , ) = target.config(); - assertEq(protocolRewards.balanceOf(fundsRecipient), settings.creatorReward + settings.firstMinterReward); + assertEq(protocolRewards.balanceOf(recipient), settings.firstMinterReward); + assertEq(protocolRewards.balanceOf(fundsRecipient), settings.creatorReward); assertEq(protocolRewards.balanceOf(zora), settings.zoraReward + settings.mintReferralReward + settings.createReferralReward); } @@ -666,7 +679,8 @@ contract ZoraCreator1155Test is Test { (, , address fundsRecipient, , , ) = target.config(); - assertEq(protocolRewards.balanceOf(fundsRecipient), settings.creatorReward + settings.firstMinterReward); + assertEq(protocolRewards.balanceOf(recipient), settings.firstMinterReward); + assertEq(protocolRewards.balanceOf(fundsRecipient), settings.creatorReward); assertEq(protocolRewards.balanceOf(createReferral), settings.createReferralReward); assertEq(protocolRewards.balanceOf(zora), settings.zoraReward + settings.mintReferralReward); } @@ -692,7 +706,8 @@ contract ZoraCreator1155Test is Test { (, , address fundsRecipient, , , ) = target.config(); - assertEq(protocolRewards.balanceOf(fundsRecipient), settings.creatorReward + settings.firstMinterReward); + assertEq(protocolRewards.balanceOf(recipient), settings.firstMinterReward); + assertEq(protocolRewards.balanceOf(fundsRecipient), settings.creatorReward); assertEq(protocolRewards.balanceOf(mintReferral), settings.mintReferralReward); assertEq(protocolRewards.balanceOf(zora), settings.zoraReward + settings.createReferralReward); } @@ -718,7 +733,8 @@ contract ZoraCreator1155Test is Test { (, , address fundsRecipient, , , ) = target.config(); - assertEq(protocolRewards.balanceOf(fundsRecipient), settings.creatorReward + settings.firstMinterReward); + assertEq(protocolRewards.balanceOf(recipient), settings.firstMinterReward); + assertEq(protocolRewards.balanceOf(fundsRecipient), settings.creatorReward); assertEq(protocolRewards.balanceOf(createReferral), settings.createReferralReward); assertEq(protocolRewards.balanceOf(mintReferral), settings.mintReferralReward); assertEq(protocolRewards.balanceOf(zora), settings.zoraReward); @@ -779,11 +795,9 @@ contract ZoraCreator1155Test is Test { vm.prank(collector); target.mintWithRewards{value: totalValue}(fixedPriceMinter, tokenId, quantity, abi.encode(recipient), address(0)); - (, , address fundsRecipient, , , ) = target.config(); - assertEq(address(target).balance, totalSale); - assertEq(protocolRewards.balanceOf(fundsRecipient), settings.firstMinterReward); + assertEq(protocolRewards.balanceOf(recipient), settings.firstMinterReward); assertEq(protocolRewards.balanceOf(zora), settings.zoraReward + settings.mintReferralReward + settings.createReferralReward); } @@ -826,12 +840,10 @@ contract ZoraCreator1155Test is Test { vm.prank(collector); target.mintWithRewards{value: totalValue}(fixedPriceMinter, tokenId, quantity, abi.encode(recipient), mintReferral); - (, , address fundsRecipient, , , ) = target.config(); - assertEq(address(target).balance, totalSale); assertEq(protocolRewards.balanceOf(mintReferral), settings.mintReferralReward); - assertEq(protocolRewards.balanceOf(fundsRecipient), settings.firstMinterReward); + assertEq(protocolRewards.balanceOf(recipient), settings.firstMinterReward); assertEq(protocolRewards.balanceOf(zora), settings.zoraReward + settings.createReferralReward); } @@ -874,10 +886,8 @@ contract ZoraCreator1155Test is Test { vm.prank(collector); target.mintWithRewards{value: totalValue}(fixedPriceMinter, tokenId, quantity, abi.encode(recipient), address(0)); - (, , address fundsRecipient, , , ) = target.config(); - assertEq(address(target).balance, totalSale); - assertEq(protocolRewards.balanceOf(fundsRecipient), settings.firstMinterReward); + assertEq(protocolRewards.balanceOf(recipient), settings.firstMinterReward); assertEq(protocolRewards.balanceOf(createReferral), settings.createReferralReward); assertEq(protocolRewards.balanceOf(zora), settings.zoraReward + settings.mintReferralReward); } @@ -921,10 +931,8 @@ contract ZoraCreator1155Test is Test { vm.prank(collector); target.mintWithRewards{value: totalValue}(fixedPriceMinter, tokenId, quantity, abi.encode(recipient), mintReferral); - (, , address fundsRecipient, , , ) = target.config(); - assertEq(address(target).balance, totalSale); - assertEq(protocolRewards.balanceOf(fundsRecipient), settings.firstMinterReward); + assertEq(protocolRewards.balanceOf(recipient), settings.firstMinterReward); assertEq(protocolRewards.balanceOf(mintReferral), settings.mintReferralReward); assertEq(protocolRewards.balanceOf(createReferral), settings.createReferralReward); assertEq(protocolRewards.balanceOf(zora), settings.zoraReward); @@ -963,6 +971,66 @@ contract ZoraCreator1155Test is Test { target.mintWithRewards(fixedPriceMinter, tokenId, quantity, abi.encode(recipient), address(0)); } + function test_FirstMinterRewardReceivedOnConsecutiveMints(uint256 quantity) public { + vm.assume(quantity > 0 && quantity < type(uint200).max); + + init(); + + vm.prank(admin); + uint256 tokenId = target.setupNewToken("test", quantity * 2); + + vm.prank(admin); + target.addPermission(tokenId, address(simpleMinter), adminRole); + + RewardsSettings memory settings = target.computeFreeMintRewards(quantity); + + uint256 totalReward = target.computeTotalReward(quantity); + vm.deal(collector, totalReward); + + address firstMinter = makeAddr("firstMinter"); + + vm.prank(collector); + target.mintWithRewards{value: totalReward}(simpleMinter, tokenId, quantity, abi.encode(firstMinter), address(0)); + + assertEq(protocolRewards.balanceOf(firstMinter), settings.firstMinterReward); + + address collector2 = makeAddr("collector2"); + vm.deal(collector2, totalReward); + + vm.prank(collector2); + target.mintWithRewards{value: totalReward}(simpleMinter, tokenId, quantity, abi.encode(collector2), address(0)); + + assertEq(protocolRewards.balanceOf(firstMinter), settings.firstMinterReward * 2); + } + + function test_AssumeFirstMinterRecipientIsAddress(uint256 quantity) public { + vm.assume(quantity > 0 && quantity < type(uint200).max); + + init(); + + vm.prank(admin); + uint256 tokenId = target.setupNewToken("test", quantity); + + vm.prank(admin); + target.addPermission(tokenId, address(simpleMinter), adminRole); + + RewardsSettings memory settings = target.computeFreeMintRewards(quantity); + + uint256 totalReward = target.computeTotalReward(quantity); + vm.deal(collector, totalReward); + + uint256 rewardRecipient = 1234; + + vm.prank(collector); + target.mintWithRewards{value: totalReward}(simpleMinter, tokenId, quantity, abi.encode(rewardRecipient), address(0)); + + (, , address fundsRecipient, , , ) = target.config(); + + assertEq(protocolRewards.balanceOf(address(uint160(rewardRecipient))), settings.firstMinterReward); + assertEq(protocolRewards.balanceOf(fundsRecipient), settings.creatorReward); + assertEq(protocolRewards.balanceOf(zora), settings.zoraReward + settings.mintReferralReward + settings.createReferralReward); + } + function testRevert_WrongValueForSale(uint256 quantity, uint256 salePrice) public { vm.assume(quantity > 0 && quantity < 1_000_000); vm.assume(salePrice > 0 && salePrice < 10 ether); @@ -1008,7 +1076,7 @@ contract ZoraCreator1155Test is Test { target.callSale(tokenId, simpleMinter, abi.encodeWithSignature("setNum(uint256)", 1)); assertEq(simpleMinter.num(), 1); - vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155.CallFailed.selector, "")); + vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155Errors.CallFailed.selector, "")); target.callSale(tokenId, simpleMinter, abi.encodeWithSignature("setNum(uint256)", 0)); vm.stopPrank(); @@ -1030,7 +1098,7 @@ contract ZoraCreator1155Test is Test { target.callRenderer(tokenId, abi.encodeWithSelector(SimpleRenderer.setup.selector, "callRender successful")); assertEq(target.uri(tokenId), "callRender successful"); - vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155.CallFailed.selector, "")); + vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155Errors.CallFailed.selector, "")); target.callRenderer(tokenId, abi.encodeWithSelector(SimpleRenderer.setup.selector, "")); vm.stopPrank(); @@ -1085,7 +1153,7 @@ contract ZoraCreator1155Test is Test { uint256 tokenId = target.setupNewToken("", 1); target.setTokenMetadataRenderer(tokenId, renderer); - vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155.CallFailed.selector, "")); + vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155Errors.CallFailed.selector, "")); target.callRenderer(tokenId, "0xfoobar"); } @@ -1115,8 +1183,11 @@ contract ZoraCreator1155Test is Test { vm.prank(admin); target.addPermission(tokenId, address(simpleMinter), adminRole); + uint256 totalReward = target.computeTotalReward(5); + vm.deal(admin, totalReward); + vm.prank(admin); - target.mint(simpleMinter, tokenId, 5, abi.encode(recipient)); + target.mint{value: totalReward}(simpleMinter, tokenId, 5, abi.encode(recipient)); uint256[] memory burnBatchIds = new uint256[](1); uint256[] memory burnBatchValues = new uint256[](1); @@ -1136,8 +1207,11 @@ contract ZoraCreator1155Test is Test { vm.prank(admin); target.addPermission(tokenId, address(simpleMinter), adminRole); + uint256 totalReward = target.computeTotalReward(5); + vm.deal(admin, totalReward); + vm.prank(admin); - target.mint(simpleMinter, tokenId, 5, abi.encode(recipient)); + target.mint{value: totalReward}(simpleMinter, tokenId, 5, abi.encode(recipient)); uint256[] memory burnBatchIds = new uint256[](1); uint256[] memory burnBatchValues = new uint256[](1); @@ -1159,9 +1233,14 @@ contract ZoraCreator1155Test is Test { vm.prank(admin); target.addPermission(tokenId, address(simpleMinter), minterRole); - vm.deal(admin, 1 ether); + uint256 totalReward = target.computeTotalReward(1000); + uint256 totalSale = 1 ether; + uint256 totalValue = totalReward + totalSale; + + vm.deal(admin, totalValue); + vm.prank(admin); - target.mint{value: 1 ether}(simpleMinter, tokenId, 1000, abi.encode(recipient)); + target.mint{value: totalValue}(simpleMinter, tokenId, 1000, abi.encode(recipient)); vm.prank(admin); target.withdraw(); @@ -1169,8 +1248,7 @@ contract ZoraCreator1155Test is Test { assertEq(admin.balance, 1 ether); } - function test_withdrawAll_revertETHWithdrawFailed(uint256 purchaseAmount, uint256 withdrawAmount) external { - vm.assume(withdrawAmount <= purchaseAmount); + function test_withdrawAll_revertETHWithdrawFailed() external { init(); vm.prank(admin); @@ -1187,11 +1265,15 @@ contract ZoraCreator1155Test is Test { vm.prank(admin); target.addPermission(0, address(simpleMinter), fundsManagerRole); - vm.deal(admin, 1 ether); + uint256 totalReward = target.computeTotalReward(1000); + uint256 totalSale = 1 ether; + uint256 totalValue = totalReward + totalSale; + + vm.deal(admin, totalValue); vm.prank(admin); - target.mint{value: 1 ether}(simpleMinter, tokenId, 1000, abi.encode(recipient)); + target.mint{value: totalValue}(simpleMinter, tokenId, 1000, abi.encode(recipient)); - vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155.ETHWithdrawFailed.selector, simpleMinter, 1 ether)); + vm.expectRevert(abi.encodeWithSelector(IZoraCreator1155Errors.ETHWithdrawFailed.selector, simpleMinter, 1 ether)); vm.prank(address(simpleMinter)); target.withdraw(); } @@ -1262,7 +1344,10 @@ contract ZoraCreator1155Test is Test { ICreatorRoyaltiesControl.RoyaltyConfiguration({royaltyMintSchedule: royaltyMintSchedule, royaltyBPS: 0, royaltyRecipient: admin}) ); - target.mint(minter, tokenId, mintQuantity, abi.encode(recipient)); + uint256 totalReward = target.computeTotalReward(mintQuantity); + vm.deal(admin, totalReward); + + target.mint{value: totalReward}(minter, tokenId, mintQuantity, abi.encode(recipient)); uint256 totalRoyaltyMintsForPurchase = mintQuantity / (royaltyMintSchedule - 1); totalRoyaltyMintsForPurchase = MathUpgradeable.min(totalRoyaltyMintsForPurchase, editionSize - mintQuantity); @@ -1283,12 +1368,15 @@ contract ZoraCreator1155Test is Test { vm.prank(admin); target.addPermission(tokenId, address(minter), adminRole); + uint256 totalReward = target.computeTotalReward(80); + vm.deal(admin, totalReward); + vm.startPrank(admin); target.updateRoyaltiesForToken( tokenId, ICreatorRoyaltiesControl.RoyaltyConfiguration({royaltyMintSchedule: 5, royaltyBPS: 0, royaltyRecipient: admin}) ); - target.mint(minter, tokenId, 80, abi.encode(recipient)); + target.mint{value: totalReward}(minter, tokenId, 80, abi.encode(recipient)); assertEq(target.balanceOf(recipient, tokenId), 80); assertEq(target.balanceOf(admin, tokenId), 20); @@ -1306,13 +1394,16 @@ contract ZoraCreator1155Test is Test { vm.prank(admin); target.addPermission(tokenId, address(minter), adminRole); + uint256 totalReward = target.computeTotalReward(92); + vm.deal(admin, totalReward); + vm.startPrank(admin); target.updateRoyaltiesForToken( tokenId, ICreatorRoyaltiesControl.RoyaltyConfiguration({royaltyMintSchedule: 3, royaltyBPS: 0, royaltyRecipient: admin}) ); - target.mint(minter, tokenId, 92, abi.encode(recipient)); + target.mint{value: totalReward}(minter, tokenId, 92, abi.encode(recipient)); assertEq(target.balanceOf(recipient, tokenId), 92); assertEq(target.balanceOf(admin, tokenId), 45); @@ -1330,18 +1421,23 @@ contract ZoraCreator1155Test is Test { vm.prank(admin); target.addPermission(tokenId, address(minter), adminRole); + uint256 totalReward = target.computeTotalReward(92); + vm.deal(admin, totalReward); + vm.startPrank(admin); target.updateRoyaltiesForToken( tokenId, ICreatorRoyaltiesControl.RoyaltyConfiguration({royaltyMintSchedule: 3, royaltyBPS: 0, royaltyRecipient: admin}) ); - target.mint(minter, tokenId, 92, abi.encode(recipient)); + target.mint{value: totalReward}(minter, tokenId, 92, abi.encode(recipient)); assertEq(target.balanceOf(recipient, tokenId), 92); assertEq(target.balanceOf(admin, tokenId), 46); - target.mint(minter, tokenId, 1, abi.encode(recipient)); + vm.deal(admin, 0.000777 ether); + + target.mint{value: 0.000777 ether}(minter, tokenId, 1, abi.encode(recipient)); assertEq(target.balanceOf(recipient, tokenId), 93); assertEq(target.balanceOf(admin, tokenId), 46); diff --git a/test/nft/ZoraCreator1155AccessControlGeneralTest.sol b/test/nft/ZoraCreator1155AccessControlGeneralTest.sol index 070cc2da8..72f70c1e4 100644 --- a/test/nft/ZoraCreator1155AccessControlGeneralTest.sol +++ b/test/nft/ZoraCreator1155AccessControlGeneralTest.sol @@ -5,7 +5,7 @@ import "forge-std/Test.sol"; import {ProtocolRewards} from "@zoralabs/protocol-rewards/src/ProtocolRewards.sol"; import {ZoraCreator1155Impl} from "../../src/nft/ZoraCreator1155Impl.sol"; import {Zora1155} from "../../src/proxies/Zora1155.sol"; -import {IZoraCreator1155} from "../../src/interfaces/IZoraCreator1155.sol"; +import {IZoraCreator1155Errors} from "../../src/interfaces/IZoraCreator1155Errors.sol"; import {IRenderer1155} from "../../src/interfaces/IRenderer1155.sol"; import {IMinter1155} from "../../src/interfaces/IMinter1155.sol"; import {IZoraCreator1155TypesV1} from "../../src/nft/IZoraCreator1155TypesV1.sol"; diff --git a/test/premint/Zora1155PremintExecutorProxy.t.sol b/test/premint/Zora1155PremintExecutorProxy.t.sol new file mode 100644 index 000000000..7873e48aa --- /dev/null +++ b/test/premint/Zora1155PremintExecutorProxy.t.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import "forge-std/Test.sol"; +import {Zora1155FactoryFixtures} from "../fixtures/Zora1155FactoryFixtures.sol"; +import {Zora1155PremintFixtures} from "../fixtures/Zora1155PremintFixtures.sol"; +import {ZoraCreator1155FactoryImpl} from "../../src/factory/ZoraCreator1155FactoryImpl.sol"; +import {Zora1155PremintExecutorProxy} from "../../src/proxies/Zora1155PremintExecutorProxy.sol"; +import {ZoraCreator1155Impl} from "../../src/nft/ZoraCreator1155Impl.sol"; +import {ZoraCreator1155PremintExecutor} from "../../src/premint/ZoraCreator1155PremintExecutor.sol"; +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/premint/ZoraCreator1155Attribution.sol"; +import {IOwnable2StepUpgradeable} from "../../src/utils/ownable/IOwnable2StepUpgradeable.sol"; +import {IHasContractName} from "../../src/interfaces/IContractMetadata.sol"; + +contract Zora1155PremintExecutorProxyTest is Test, IHasContractName { + address internal owner; + uint256 internal creatorPrivateKey; + address internal creator; + address internal collector; + address internal zora; + Zora1155Factory internal factoryProxy; + ZoraCreator1155FactoryImpl factoryAtProxy; + uint256 internal mintFeeAmount = 0.000777 ether; + ZoraCreator1155PremintExecutor preminterAtProxy; + + function setUp() external { + zora = makeAddr("zora"); + owner = makeAddr("owner"); + collector = makeAddr("collector"); + (creator, creatorPrivateKey) = makeAddrAndKey("creator"); + + vm.startPrank(zora); + (, , factoryProxy) = Zora1155FactoryFixtures.setup1155AndFactoryProxy(mintFeeAmount, zora, zora); + factoryAtProxy = ZoraCreator1155FactoryImpl(address(factoryProxy)); + vm.stopPrank(); + + // create preminter implementation + ZoraCreator1155PremintExecutor preminterImplementation = new ZoraCreator1155PremintExecutor(ZoraCreator1155FactoryImpl(address(factoryProxy))); + + // build the proxy + Zora1155PremintExecutorProxy proxy = new Zora1155PremintExecutorProxy(address(preminterImplementation), ""); + + // access the executor implementation via the proxy, and initialize the admin + preminterAtProxy = ZoraCreator1155PremintExecutor(address(proxy)); + preminterAtProxy.initialize(owner); + } + + function test_canInvokeImplementationMethods() external { + // create premint config + IMinter1155 fixedPriceMinter = ZoraCreator1155FactoryImpl(address(factoryProxy)).fixedPriceMinter(); + + PremintConfig memory premintConfig = PremintConfig({ + tokenConfig: Zora1155PremintFixtures.makeDefaultTokenCreationConfig(fixedPriceMinter, creator), + uid: 100, + version: 0, + deleted: false + }); + + // now interface with proxy preminter - sign and execute the premint + ContractCreationConfig memory contractConfig = Zora1155PremintFixtures.makeDefaultContractCreationConfig(creator); + address deterministicAddress = preminterAtProxy.getContractAddress(contractConfig); + + // sign the premint + bytes32 digest = ZoraCreator1155Attribution.premintHashedTypeDataV4(premintConfig, deterministicAddress, block.chainid); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(creatorPrivateKey, digest); + + uint256 quantityToMint = 1; + + bytes memory signature = abi.encodePacked(r, s, v); + + // execute the premint + vm.deal(collector, mintFeeAmount); + vm.prank(collector); + uint256 tokenId = preminterAtProxy.premint{value: mintFeeAmount}(contractConfig, premintConfig, signature, quantityToMint, ""); + + assertEq(ZoraCreator1155Impl(deterministicAddress).balanceOf(collector, tokenId), 1); + } + + function test_onlyOwnerCanUpgrade() external { + // try to upgrade as non-owner + ZoraCreator1155PremintExecutor newImplementation = new ZoraCreator1155PremintExecutor(factoryAtProxy); + + vm.expectRevert(IOwnable2StepUpgradeable.ONLY_OWNER.selector); + vm.prank(creator); + preminterAtProxy.upgradeTo(address(newImplementation)); + } + + /// giving this a contract name so that it can be used to fail upgrading preminter contract + function contractName() public pure returns (string memory) { + return "Test Contract"; + } + + function test_canOnlyBeUpgradedToContractWithSameName() external { + // upgrade to bad contract with has wrong name (this contract has mismatched name) + vm.expectRevert( + abi.encodeWithSelector(ZoraCreator1155PremintExecutor.UpgradeToMismatchedContractName.selector, preminterAtProxy.contractName(), contractName()) + ); + vm.prank(owner); + preminterAtProxy.upgradeTo(address(this)); + + // upgrade to good contract which has correct name - it shouldn't revert + ZoraCreator1155PremintExecutor newImplementation = new ZoraCreator1155PremintExecutor(ZoraCreator1155FactoryImpl(address(factoryProxy))); + + vm.prank(owner); + preminterAtProxy.upgradeTo(address(newImplementation)); + } +} diff --git a/test/premint/ZoraCreator1155PremintExecutor.t.sol b/test/premint/ZoraCreator1155PremintExecutor.t.sol new file mode 100644 index 000000000..55907c7ed --- /dev/null +++ b/test/premint/ZoraCreator1155PremintExecutor.t.sol @@ -0,0 +1,693 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import "forge-std/Test.sol"; +import {Zora1155FactoryFixtures} from "../fixtures/Zora1155FactoryFixtures.sol"; +import {ProtocolRewards} from "@zoralabs/protocol-rewards/src/ProtocolRewards.sol"; + +import {ZoraCreator1155Impl} from "../../src/nft/ZoraCreator1155Impl.sol"; +import {Zora1155} from "../../src/proxies/Zora1155.sol"; +import {IZoraCreator1155Errors} from "../../src/interfaces/IZoraCreator1155Errors.sol"; +import {IZoraCreator1155} from "../../src/interfaces/IZoraCreator1155.sol"; +import {IMinter1155} from "../../src/interfaces/IMinter1155.sol"; +import {ICreatorRoyaltiesControl} from "../../src/interfaces/ICreatorRoyaltiesControl.sol"; +import {ZoraCreatorFixedPriceSaleStrategy} from "../../src/minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.sol"; +import {Zora1155Factory} from "../../src/proxies/Zora1155Factory.sol"; +import {ZoraCreator1155FactoryImpl} from "../../src/factory/ZoraCreator1155FactoryImpl.sol"; +import {ZoraCreator1155PremintExecutor} from "../../src/premint/ZoraCreator1155PremintExecutor.sol"; +import {ZoraCreator1155Attribution, ContractCreationConfig, TokenCreationConfig, PremintConfig} from "../../src/premint/ZoraCreator1155Attribution.sol"; +import {ForkDeploymentConfig} from "../../src/deployment/DeploymentConfig.sol"; +import {ProxyShim} from "../../src/utils/ProxyShim.sol"; + +contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test { + uint256 internal constant CONTRACT_BASE_ID = 0; + uint256 internal constant PERMISSION_BIT_MINTER = 2 ** 2; + + ZoraCreator1155PremintExecutor internal preminter; + Zora1155Factory factoryProxy; + ZoraCreator1155FactoryImpl factoryImpl; + + ICreatorRoyaltiesControl.RoyaltyConfiguration internal defaultRoyaltyConfig; + uint256 internal mintFeeAmount = 0.000777 ether; + + // setup contract config + uint256 internal creatorPrivateKey; + address internal creator; + address internal zora; + address internal premintExecutor; + address internal collector; + + event Preminted( + address indexed contractAddress, + uint256 indexed tokenId, + bool indexed createdNewContract, + uint32 uid, + ContractCreationConfig contractConfig, + TokenCreationConfig tokenConfig, + address minter, + uint256 quantityMinted + ); + + function setUp() external { + (creator, creatorPrivateKey) = makeAddrAndKey("creator"); + zora = makeAddr("zora"); + premintExecutor = makeAddr("premintExecutor"); + collector = makeAddr("collector"); + + vm.startPrank(zora); + (, , factoryProxy) = Zora1155FactoryFixtures.setup1155AndFactoryProxy(mintFeeAmount, zora, zora); + vm.stopPrank(); + + factoryImpl = ZoraCreator1155FactoryImpl(address(factoryProxy)); + + preminter = new ZoraCreator1155PremintExecutor(factoryImpl); + } + + function makeDefaultContractCreationConfig() internal view returns (ContractCreationConfig memory) { + return ContractCreationConfig({contractAdmin: creator, contractName: "blah", contractURI: "blah.contract"}); + } + + function makeDefaultTokenCreationConfig() internal view returns (TokenCreationConfig memory) { + IMinter1155 fixedPriceMinter = factoryImpl.defaultMinters()[0]; + return + TokenCreationConfig({ + 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) + }); + } + + function makeDefaultPremintConfig() internal view returns (PremintConfig memory) { + return PremintConfig({tokenConfig: makeDefaultTokenCreationConfig(), uid: 100, version: 0, deleted: false}); + } + + function test_successfullyMintsTokens() external { + // 1. Make contract creation params + + // configuration of contract to create + ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); + PremintConfig 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); + + // 3. Sign the digest + // create a signature with the digest for the params + bytes memory signature = _sign(creatorPrivateKey, digest); + + 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); + + // 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); + + // get the contract address from the preminter based on the contract hash id. + IZoraCreator1155 created1155Contract = IZoraCreator1155(contractAddress); + + // get the created contract, and make sure that tokens have been minted to the address + assertEq(created1155Contract.balanceOf(premintExecutor, tokenId), quantityToMint); + + // alter the token creation config, create a new signature with the existing + // contract config and new token config + premintConfig.tokenConfig.tokenURI = "blah2.token"; + premintConfig.uid++; + + digest = ZoraCreator1155Attribution.premintHashedTypeDataV4(premintConfig, contractAddress, 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); + + // 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 + assertEq(created1155Contract.balanceOf(premintExecutor, tokenId), quantityToMint); + } + + event CreatorAttribution(bytes32 structHash, string domainName, string version, address creator, bytes signature); + + function test_premint_emitsCreatorAttribution_fromErc1155Contract() external { + // build a premint + ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); + PremintConfig 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); + + 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); + + vm.prank(collector); + + // verify CreatorAttribution was emitted from the erc1155 contract + vm.expectEmit(true, false, false, false, deterministicAddress); + emit CreatorAttribution(structHash, ZoraCreator1155Attribution.NAME, ZoraCreator1155Attribution.VERSION, creator, signature); + + // create contract and token via premint + preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, comment); + } + + /// @notice gets the chains to do fork tests on, by reading environment var FORK_TEST_CHAINS. + /// Chains are by name, and must match whats under `rpc_endpoints` in the foundry.toml + function getForkTestChains() private view returns (string[] memory result) { + try vm.envString("FORK_TEST_CHAINS", ",") returns (string[] memory forkTestChains) { + result = forkTestChains; + } catch { + console.log("could not get fork test chains - make sure the environment variable FORK_TEST_CHAINS is set"); + result = new string[](0); + } + } + + function testTheForkPremint(string memory chainName) private { + console.log("testing on fork: ", chainName); + + // create and select the fork, which will be used for all subsequent calls + // it will also affect the current block chain id based on the rpc url returned + vm.createSelectFork(vm.rpcUrl(chainName)); + + // get contract hash, which is unique per contract creation config, and can be used + // retreive the address created for a contract + address preminterAddress = getDeployment().preminter; + + if (preminterAddress == address(0)) { + console.log("preminter not configured for chain...skipping"); + return; + } + + preminter = ZoraCreator1155PremintExecutor(preminterAddress); + + factoryImpl = ZoraCreator1155FactoryImpl(getDeployment().factoryImpl); + + console.log("building defaults"); + + // configuration of contract to create + ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); + PremintConfig 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); + + // 3. Sign the digest + // create a signature with the digest for the params + bytes memory signature = _sign(creatorPrivateKey, digest); + + // this account will be used to execute the premint, and should result in a contract being created + premintExecutor = vm.addr(701); + uint256 mintCost = quantityToMint * 0.000777 ether; + // 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); + + // get the contract address from the preminter based on the contract hash id. + IZoraCreator1155 created1155Contract = IZoraCreator1155(contractAddress); + + // get the created contract, and make sure that tokens have been minted to the address + assertEq(created1155Contract.balanceOf(premintExecutor, tokenId), quantityToMint); + } + + function test_fork_successfullyMintsTokens() external { + string[] memory forkTestChains = getForkTestChains(); + for (uint256 i = 0; i < forkTestChains.length; i++) { + testTheForkPremint(forkTestChains[i]); + } + } + + function test_signatureForSameContractandUid_shouldMintExistingToken() external { + // 1. Make contract creation params + + // configuration of contract to create + ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); + PremintConfig memory premintConfig = makeDefaultPremintConfig(); + + // how many tokens are minted to the executor + uint256 quantityToMint = 2; + uint256 chainId = block.chainid; + string memory comment = "I love it"; + + address contractAddress = preminter.getContractAddress(contractConfig); + IZoraCreator1155 created1155Contract = IZoraCreator1155(contractAddress); + + uint256 firstTokenId = _signAndExecutePremint(contractConfig, premintConfig, creatorPrivateKey, chainId, premintExecutor, quantityToMint, comment); + + // create a sig for another token with same uid, it should mint tokens for the uid's original token + premintConfig.tokenConfig.tokenURI = "blah2.token"; + bytes memory signature = _signPremint(contractAddress, premintConfig, creatorPrivateKey, chainId); + + uint256 mintCost = mintFeeAmount * quantityToMint; + vm.deal(collector, mintCost); + + uint256 nextTokenId; + + 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); + + assertEq(nextTokenId, firstTokenId); + assertEq(created1155Contract.balanceOf(collector, firstTokenId), quantityToMint); + + // change the version, it should still point to the first token + premintConfig.version++; + signature = _signPremint(contractAddress, premintConfig, creatorPrivateKey, chainId); + + 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); + vm.stopPrank(); + + assertEq(nextTokenId, firstTokenId); + assertEq(created1155Contract.balanceOf(collector, firstTokenId), quantityToMint * 2); + } + + function testCreateTokenPerUid() public { + ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); + PremintConfig memory premintConfig = makeDefaultPremintConfig(); + + uint256 quantityToMint = 2; + uint256 chainId = block.chainid; + string memory comment = "I love it"; + + address contractAddress = preminter.getContractAddress(contractConfig); + + uint256 firstTokenId = _signAndExecutePremint(contractConfig, premintConfig, creatorPrivateKey, chainId, premintExecutor, quantityToMint, comment); + + // creator signs a new uid, it should create a new token + premintConfig.uid++; + bytes memory signature = _signPremint(contractAddress, premintConfig, creatorPrivateKey, chainId); + + uint256 mintCost = mintFeeAmount * quantityToMint; + vm.deal(collector, mintCost); + + 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); + + assertEq(firstTokenId, 1); + assertEq(nextTokenId, 2); + } + + function test_deleted_preventsTokenFromBeingMinted() external { + ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); + PremintConfig memory premintConfig = makeDefaultPremintConfig(); + + premintConfig.deleted = true; + uint chainId = block.chainid; + uint256 quantityToMint = 2; + string memory comment = "I love it"; + + address contractAddress = preminter.getContractAddress(contractConfig); + + // 2. Call smart contract to get digest to sign for creation params. + bytes memory signature = _signPremint(contractAddress, premintConfig, creatorPrivateKey, chainId); + + // now call the premint function, using the same config that was used to generate the digest, and the signature + vm.expectRevert(ZoraCreator1155Attribution.PremintDeleted.selector); + vm.prank(premintExecutor); + uint256 newTokenId = preminter.premint(contractConfig, premintConfig, signature, quantityToMint, comment); + + assertEq(newTokenId, 0, "tokenId"); + + // make sure no contract was created + assertEq(contractAddress.code.length, 0, "contract has been deployed"); + } + + function test_emitsPremint_whenNewContract() external { + ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); + PremintConfig memory premintConfig = makeDefaultPremintConfig(); + address contractAddress = preminter.getContractAddress(contractConfig); + + // how many tokens are minted to the executor + uint256 quantityToMint = 4; + uint256 chainId = block.chainid; + + // Sign the premint + bytes memory signature = _signPremint(contractAddress, premintConfig, creatorPrivateKey, chainId); + + 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); + + 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); + } + + function test_onlyOwner_hasAdminRights_onCreatedToken() public { + ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); + PremintConfig memory premintConfig = makeDefaultPremintConfig(); + + // how many tokens are minted to the executor + uint256 quantityToMint = 4; + uint256 chainId = block.chainid; + + string memory comment = "I love it"; + + address createdContractAddress = preminter.getContractAddress(contractConfig); + + uint256 newTokenId = _signAndExecutePremint(contractConfig, premintConfig, creatorPrivateKey, chainId, premintExecutor, quantityToMint, comment); + + // get the contract address from the preminter based on the contract hash id. + IZoraCreator1155 created1155Contract = IZoraCreator1155(createdContractAddress); + + ZoraCreatorFixedPriceSaleStrategy.SalesConfig memory newSalesConfig = ZoraCreatorFixedPriceSaleStrategy.SalesConfig({ + pricePerToken: 5 ether, + saleStart: 0, + saleEnd: 0, + maxTokensPerAddress: 5, + fundsRecipient: creator + }); + + IMinter1155 fixedPrice = factoryImpl.fixedPriceMinter(); + + // have the premint contract try to set the sales config - it should revert with + // the expected UserMissingRole error + vm.expectRevert( + abi.encodeWithSelector( + IZoraCreator1155Errors.UserMissingRoleForToken.selector, + address(preminter), + newTokenId, + ZoraCreator1155Impl(address(created1155Contract)).PERMISSION_BIT_SALES() + ) + ); + vm.prank(address(preminter)); + created1155Contract.callSale( + newTokenId, + fixedPrice, + abi.encodeWithSelector(ZoraCreatorFixedPriceSaleStrategy.setSale.selector, newTokenId, newSalesConfig) + ); + + // have admin/creator try to set the sales config - it should succeed + vm.prank(creator); + created1155Contract.callSale( + newTokenId, + fixedPrice, + abi.encodeWithSelector(ZoraCreatorFixedPriceSaleStrategy.setSale.selector, newTokenId, newSalesConfig) + ); + + // have the premint contract try to set royalties config - it should revert + vm.expectRevert( + abi.encodeWithSelector( + IZoraCreator1155Errors.UserMissingRoleForToken.selector, + address(preminter), + newTokenId, + ZoraCreator1155Impl(address(created1155Contract)).PERMISSION_BIT_FUNDS_MANAGER() + ) + ); + vm.prank(address(preminter)); + created1155Contract.updateRoyaltiesForToken(newTokenId, defaultRoyaltyConfig); + + // have admin/creator try to set royalties config - it should succeed + vm.prank(creator); + created1155Contract.updateRoyaltiesForToken(newTokenId, defaultRoyaltyConfig); + } + + function test_premintStatus_getsStatus() external { + PremintConfig memory premintConfig = makeDefaultPremintConfig(); + + // how many tokens are minted to the executor + uint256 quantityToMint = 4; + uint256 chainId = block.chainid; + string memory comment = "I love it"; + + uint32 firstUid = premintConfig.uid; + uint32 secondUid = firstUid + 1; + + ContractCreationConfig memory firstContractConfig = makeDefaultContractCreationConfig(); + ContractCreationConfig memory secondContractConfig = ContractCreationConfig( + firstContractConfig.contractAdmin, + firstContractConfig.contractURI, + string.concat(firstContractConfig.contractName, "4") + ); + + address firstContractAddress = preminter.getContractAddress(firstContractConfig); + uint256 tokenId = _signAndExecutePremint(firstContractConfig, premintConfig, creatorPrivateKey, chainId, premintExecutor, quantityToMint, comment); + + assertEq(IZoraCreator1155(firstContractAddress).balanceOf(premintExecutor, tokenId), quantityToMint); + + premintConfig.uid = secondUid; + tokenId = _signAndExecutePremint(firstContractConfig, premintConfig, creatorPrivateKey, chainId, premintExecutor, quantityToMint, comment); + + assertEq(IZoraCreator1155(firstContractAddress).balanceOf(premintExecutor, tokenId), quantityToMint); + + address secondContractAddress = preminter.getContractAddress(secondContractConfig); + tokenId = _signAndExecutePremint(secondContractConfig, premintConfig, creatorPrivateKey, chainId, premintExecutor, quantityToMint, comment); + + assertEq(IZoraCreator1155(secondContractAddress).balanceOf(premintExecutor, tokenId), quantityToMint); + } + + function test_premintCanOnlyBeExecutedAfterStartDate(uint8 startDate, uint8 currentTime) external { + bool shouldRevert; + if (startDate == 0) { + shouldRevert = false; + } else { + // should revert if before the start date + shouldRevert = currentTime < startDate; + } + vm.warp(currentTime); + + ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); + PremintConfig 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); + + if (shouldRevert) { + vm.expectRevert(ZoraCreator1155Attribution.MintNotYetStarted.selector); + } + + uint256 mintCost = mintFeeAmount * quantityToMint; + vm.deal(premintExecutor, mintCost); + + vm.prank(premintExecutor); + preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, comment); + } + + function test_premintCanOnlyBeExecutedUpToDurationFromFirstMint(uint8 startDate, uint8 duration, uint8 timeOfFirstMint, uint8 timeOfSecondMint) external { + vm.assume(timeOfFirstMint >= startDate); + vm.assume(timeOfSecondMint >= timeOfFirstMint); + + bool shouldRevert; + if (duration == 0) { + shouldRevert = false; + } else { + // should revert if after the duration + shouldRevert = uint16(timeOfSecondMint) > uint16(timeOfFirstMint) + duration; + } + + // build a premint with a token that has the given start date and duration + ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); + PremintConfig memory premintConfig = makeDefaultPremintConfig(); + address contractAddress = preminter.getContractAddress(contractConfig); + + premintConfig.tokenConfig.mintStart = startDate; + premintConfig.tokenConfig.mintDuration = duration; + + uint256 chainId = block.chainid; + + // get signature for the premint: + bytes memory signature = _signPremint(contractAddress, premintConfig, creatorPrivateKey, chainId); + + uint256 quantityToMint = 2; + string memory comment = "I love it"; + + uint256 mintCost = mintFeeAmount * quantityToMint; + vm.deal(premintExecutor, mintCost); + + vm.startPrank(premintExecutor); + + vm.warp(timeOfFirstMint); + uint256 tokenId = preminter.premint{value: mintCost}(contractConfig, premintConfig, signature, quantityToMint, comment); + + vm.warp(timeOfSecondMint); + + // execute mint directly on the contract - and check make sure it reverts if minted after sale start + IMinter1155 fixedPriceMinter = factoryImpl.defaultMinters()[0]; + if (shouldRevert) { + vm.expectRevert(ZoraCreatorFixedPriceSaleStrategy.SaleEnded.selector); + } + + vm.deal(premintExecutor, mintCost); + IZoraCreator1155(contractAddress).mint{value: mintCost}(fixedPriceMinter, tokenId, quantityToMint, abi.encode(premintExecutor, comment)); + + vm.stopPrank(); + } + + function test_premintStatus_getsIfContractHasBeenCreatedAndTokenIdForPremint() external { + // build a premint + ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); + PremintConfig memory premintConfig = makeDefaultPremintConfig(); + + // get premint status + (bool contractCreated, uint256 tokenId) = preminter.premintStatus(preminter.getContractAddress(contractConfig), premintConfig.uid); + // contract should not be created and token id should be 0 + assertEq(contractCreated, false); + assertEq(tokenId, 0); + + // sign and execute premint + uint256 newTokenId = _signAndExecutePremint(contractConfig, premintConfig, creatorPrivateKey, block.chainid, vm.addr(701), 1, "hi"); + + // get status + (contractCreated, tokenId) = preminter.premintStatus(preminter.getContractAddress(contractConfig), premintConfig.uid); + // contract should be created and token id should be same as one that was created + assertEq(contractCreated, true); + assertEq(tokenId, newTokenId); + + // get status for another uid + (contractCreated, tokenId) = preminter.premintStatus(preminter.getContractAddress(contractConfig), premintConfig.uid + 1); + // contract should be created and token id should be 0 + assertEq(contractCreated, true); + assertEq(tokenId, 0); + } + + function test_premint_whenContractCreated_premintCanOnlyBeExecutedByPermissionBitMinter() external { + // build a premint + ContractCreationConfig memory contractConfig = makeDefaultContractCreationConfig(); + PremintConfig memory premintConfig = makeDefaultPremintConfig(); + + // sign and execute premint + bytes memory signature = _signPremint(preminter.getContractAddress(contractConfig), premintConfig, creatorPrivateKey, block.chainid); + + (bool isValidSignature, address contractAddress, ) = preminter.isValidSignature(contractConfig, premintConfig, signature); + + assertTrue(isValidSignature); + + _signAndExecutePremint(contractConfig, premintConfig, creatorPrivateKey, block.chainid, premintExecutor, 1, "hi"); + + // contract has been created + + // have another creator sign a premint + uint256 newCreatorPrivateKey = 0xA11CF; + address newCreator = vm.addr(newCreatorPrivateKey); + PremintConfig 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); + + assertFalse(isValidSignature); + + uint256 quantityToMint = 1; + uint256 mintCost = mintFeeAmount * quantityToMint; + vm.deal(premintExecutor, mintCost); + + // 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"); + + // 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); + + vm.deal(premintExecutor, mintCost); + + // try to mint again, should not revert + vm.prank(premintExecutor); + preminter.premint{value: mintCost}(contractConfig, premintConfig2, newCreatorSignature, quantityToMint, "yo"); + } + + function _signAndExecutePremint( + ContractCreationConfig memory contractConfig, + PremintConfig memory premintConfig, + uint256 privateKey, + uint256 chainId, + address executor, + uint256 quantityToMint, + string memory comment + ) private returns (uint256 newTokenId) { + bytes memory signature = _signPremint(preminter.getContractAddress(contractConfig), premintConfig, privateKey, chainId); + + 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); + } + + function _signPremint( + address contractAddress, + PremintConfig memory premintConfig, + uint256 privateKey, + uint256 chainId + ) private pure returns (bytes memory) { + bytes32 digest = ZoraCreator1155Attribution.premintHashedTypeDataV4(premintConfig, contractAddress, chainId); + + // 3. Sign the digest + // create a signature with the digest for the params + return _sign(privateKey, digest); + } + + function _sign(uint256 privateKey, bytes32 digest) private pure returns (bytes memory) { + // sign the message + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); + + // combine into a single bytes array + return abi.encodePacked(r, s, v); + } +} diff --git a/test/royalties/CreatorRoyaltiesControl.t.sol b/test/royalties/CreatorRoyaltiesControl.t.sol index 6d0fc7b34..0adeaf1d1 100644 --- a/test/royalties/CreatorRoyaltiesControl.t.sol +++ b/test/royalties/CreatorRoyaltiesControl.t.sol @@ -5,7 +5,7 @@ import "forge-std/Test.sol"; import {ProtocolRewards} from "@zoralabs/protocol-rewards/src/ProtocolRewards.sol"; import {ZoraCreator1155Impl} from "../../src/nft/ZoraCreator1155Impl.sol"; import {Zora1155} from "../../src/proxies/Zora1155.sol"; -import {IZoraCreator1155} from "../../src/interfaces/IZoraCreator1155.sol"; +import {IZoraCreator1155Errors} from "../../src/interfaces/IZoraCreator1155Errors.sol"; import {IZoraCreator1155TypesV1} from "../../src/nft/IZoraCreator1155TypesV1.sol"; import {ICreatorRoyaltiesControl} from "../../src/interfaces/ICreatorRoyaltiesControl.sol"; import {IZoraCreator1155Factory} from "../../src/interfaces/IZoraCreator1155Factory.sol"; diff --git a/test/utils/Ownable2StepUpgradable.t.sol b/test/utils/Ownable2StepUpgradable.t.sol index 51ae29f3c..f2c807a3d 100644 --- a/test/utils/Ownable2StepUpgradable.t.sol +++ b/test/utils/Ownable2StepUpgradable.t.sol @@ -6,6 +6,7 @@ import {Ownable2StepUpgradeable} from "../../src/utils/ownable/Ownable2StepUpgra import {IOwnable2StepUpgradeable} from "../../src/utils/ownable/IOwnable2StepUpgradeable.sol"; import {ZoraCreator1155FactoryImpl} from "../../src/factory/ZoraCreator1155FactoryImpl.sol"; import {Zora1155Factory} from "../../src/proxies/Zora1155Factory.sol"; +import {IZoraCreator1155Errors} from "../../src/interfaces/IZoraCreator1155Errors.sol"; import {IZoraCreator1155} from "../../src/interfaces/IZoraCreator1155.sol"; import {IMinter1155} from "../../src/interfaces/IMinter1155.sol"; import {IZoraCreator1155Factory} from "../../src/interfaces/IZoraCreator1155Factory.sol"; diff --git a/uml/gasslessCreate-collecting-activity.puml b/uml/gasslessCreate-collecting-activity.puml new file mode 100644 index 000000000..c0d41f3b5 --- /dev/null +++ b/uml/gasslessCreate-collecting-activity.puml @@ -0,0 +1,20 @@ +@startuml + +title Collecting with a premint signature + +start + +:Load signature by\ncontract hash & uid; +if (sig with contract hash + uid\nalready executed) then (yes) + :Redirect to\nstandard mint page; + :Mint on erc1155 contract; + stop +else (no) + if (contract already created) then (yes) + :Show contract address; + endif + :Mint on premint contract. Submit:\ncontract & token params, uid, signature\nquantity, comment; +endif +stop + +@enduml diff --git a/uml/gasslessCreate-collecting-sequence.puml b/uml/gasslessCreate-collecting-sequence.puml new file mode 100644 index 000000000..2eccc4c56 --- /dev/null +++ b/uml/gasslessCreate-collecting-sequence.puml @@ -0,0 +1,47 @@ +@startuml +actor Collector +entity PremintCollectPage +boundary SignatureAPI +entity SignatureDB +entity PreminterContract +entity 1155FactoryContract +entity 1155Contract + +Collector -> PremintCollectPage: Open, param is \ndeterministic collection address\n+ token uid +Activate PremintCollectPage +PremintCollectPage -> SignatureAPI: Fetch by collection address\n+ token uid +SignatureAPI -> SignatureDB: Fetch most recent signature\nby contract hash token uid +SignatureDB --> SignatureAPI: contract + token creation params\n+ signature +SignatureAPI --> PremintCollectPage: contract + token creation params\n+ signature +PremintCollectPage -> PreminterContract: Check if signature has been used (by contract hash + token uid) +PreminterContract --> PremintCollectPage: Signature has been used or not + +Group signature has been used + + PremintCollectPage -> Collector: Redirect to \nstandard collect page + +end + +Collector -> PremintCollectPage: mint +PremintCollectPage -> Collector: Submit transaction +deactivate PremintCollectPage +Collector -> PreminterContract: Submit premint transaction containing \nsignature, contract creation & token creation params +activate PreminterContract +PreminterContract -> PreminterContract: record signature used;\nrevert if already used + +Group contract doesnt exist + + PreminterContract -> 1155FactoryContract: create contract + 1155FactoryContract -> 1155Contract: create + activate 1155Contract + +end + +PreminterContract -> 1155Contract: create new token\nwith signature +PreminterContract -> 1155Contract: mint tokens to collector + +deactivate PreminterContract +1155Contract --> Collector: Minted tokens +deactivate 1155Contract + +@enduml \ No newline at end of file diff --git a/uml/gasslessCreate-creation-activity.puml b/uml/gasslessCreate-creation-activity.puml new file mode 100644 index 000000000..363927823 --- /dev/null +++ b/uml/gasslessCreate-creation-activity.puml @@ -0,0 +1,27 @@ +@startuml + +title Creating a token signature + +start + +if (new token) then (yes) + if (new contract) then (yes) + :Ask creator for new\ncontract creation params; + if (contract exists\nwith same params) then (yes) + :switch ui to create token\non existing contract; + else (no) + endif + else (no) + :load existing\ncontract creation parameters\nby collection address; + endif + :Get new uid\nfrom backend server; +else (no) + :load existing\ncontract + token creation parameters\nby collection address + uid; +endif +:Ask creator for new\ntoken creation params; +:Request signature with:\ncollection address + token params + uid; +:Submit to backend server:\ncollection + token params + uid + signature\n; + +stop + +@enduml diff --git a/uml/gasslessCreate-creation-sequence.puml b/uml/gasslessCreate-creation-sequence.puml new file mode 100644 index 000000000..1d7213f43 --- /dev/null +++ b/uml/gasslessCreate-creation-sequence.puml @@ -0,0 +1,51 @@ +@startuml + +title Creating a signature for a new erc1155 contract + token + +actor Creator +entity CreatePage +boundary SignatureAPI +boundary PremintContract +entity SignatureDB + + +Group Signature not created for contract yet + + activate CreatePage + Creator -> CreatePage: setup NEW contract name + image + CreatePage -> SignatureAPI: validate that contract \nwith same params for\ncreator doesnt exist + SignatureAPI -> SignatureDB: check if signature with hash \nfor contract is already stored + SignatureAPI --> CreatePage: validation results + +end + +Group Signature has been created for contract + + Creator -> CreatePage: load page by determinstic collection address + CreatePage -> SignatureAPI: load collection creation params + SignatureAPI -> SignatureDB: fetch collection creation params\nby hash + SignatureAPI --> CreatePage: contract creation params + +end + +Creator -> CreatePage: setup new token +CreatePage -> PremintContract: get determnistic collection address +PremintContract --> CreatePage: determinstic collection address +CreatePage -> SignatureAPI: get new uid for collection address +SignatureAPI -> SignatureDB: get next token uid\nscoped to collection address +SignatureDB --> SignatureAPI: next token uid +SignatureAPI --> CreatePage: next token uid +Creator -> CreatePage: Submit new token creation params +CreatePage -> Creator: request signature of\n contract + token creation params + token uid +deactivate CreatePage +Creator -> SignatureAPI: Submit signature + contract + token params + token uid +SignatureAPI -> PremintContract: validate signature +PremintContract --> SignatureAPI: validation results (true/false & recovered signer) + +Group Signature is valid + + SignatureAPI -> SignatureDB: store signature + \ncontract creation + \ntoken creation params + \ncollection address + \ntoken uid + +end + +@enduml \ No newline at end of file diff --git a/uml/generated/gasslessCreate-collecting-activity.svg b/uml/generated/gasslessCreate-collecting-activity.svg new file mode 100644 index 000000000..141d18db0 --- /dev/null +++ b/uml/generated/gasslessCreate-collecting-activity.svg @@ -0,0 +1 @@ +Collecting with a premint signatureLoad signature bycontract hash & uidsig with contract hash + uidalready executedyesnoRedirect tostandard mint pageMint on erc1155 contractShow contract addressyescontract already createdMint on premint contract. Submit:contract & token params, uid, signaturequantity, comment \ No newline at end of file diff --git a/uml/generated/gasslessCreate-collecting-sequence.svg b/uml/generated/gasslessCreate-collecting-sequence.svg new file mode 100644 index 000000000..0115a8fed --- /dev/null +++ b/uml/generated/gasslessCreate-collecting-sequence.svg @@ -0,0 +1 @@ +CollectorCollectorPremintCollectPagePremintCollectPageSignatureAPISignatureAPISignatureDBSignatureDBPreminterContractPreminterContract1155FactoryContract1155FactoryContract1155Contract1155ContractOpen, param isdeterministic collection address+ token uidFetch by collection address+ token uidFetch most recent signatureby contract hash token uidcontract + token creation params+ signaturecontract + token creation params+ signatureCheck if signature has been used (by contract hash + token uid)Signature has been used or notsignature has been usedRedirect tostandard collect pagemintSubmit transactionSubmit premint transaction containingsignature, contract creation & token creation paramsrecord signature used;revert if already usedcontract doesnt existcreate contractcreatecreate new tokenwith signaturemint tokens to collectorMinted tokens \ No newline at end of file diff --git a/uml/generated/gasslessCreate-creation-activity.svg b/uml/generated/gasslessCreate-creation-activity.svg new file mode 100644 index 000000000..b0e6d67cc --- /dev/null +++ b/uml/generated/gasslessCreate-creation-activity.svg @@ -0,0 +1 @@ +Creating a token signaturenew tokenyesnonew contractyesnoAsk creator for newcontract creation paramsswitch ui to create tokenon existing contractyescontract existswith same paramsnoload existingcontract creation parametersby collection addressGet new uidfrom backend serverload existingcontract + token creation parametersby collection address + uidAsk creator for newtoken creation paramsRequest signature with:collection address + token params + uidSubmit to backend server:collection + token params + uid + signature  \ No newline at end of file diff --git a/uml/generated/gasslessCreate-creation-sequence.svg b/uml/generated/gasslessCreate-creation-sequence.svg new file mode 100644 index 000000000..4d2fbdad9 --- /dev/null +++ b/uml/generated/gasslessCreate-creation-sequence.svg @@ -0,0 +1 @@ +Creating a signature for a new erc1155 contract + tokenCreatorCreatorCreatePageCreatePageSignatureAPISignatureAPIPremintContractPremintContractSignatureDBSignatureDBSignature not created for contract yetsetup NEW contract name + imagevalidate that contractwith same params forcreator doesnt existcheck if signature with hashfor contract is already storedvalidation resultsSignature has been created for contractload page by determinstic collection addressload collection creation paramsfetch collection creation paramsby hashcontract creation paramssetup new tokenget determnistic collection addressdeterminstic collection addressget new uid for collection addressget next token uidscoped to collection addressnext token uidnext token uidSubmit new token creation paramsrequest signature ofcontract + token creation params + token uidSubmit signature + contract + token params + token uidvalidate signaturevalidation results (true/false & recovered signer)Signature is validstore signature +contract creation +token creation params +collection address +token uid \ No newline at end of file diff --git a/wagmi.config.ts b/wagmi.config.ts index 14a93ba76..8c2d131b1 100644 --- a/wagmi.config.ts +++ b/wagmi.config.ts @@ -8,7 +8,8 @@ type ContractNames = | "ZoraCreatorFixedPriceSaleStrategy" | "ZoraCreatorMerkleMinterStrategy" | "ZoraCreatorRedeemMinterFactory" - | "ZoraCreatorRedeemMinterStrategy"; + | "ZoraCreatorRedeemMinterStrategy" + | "ZoraCreator1155PremintExecutor"; type Address = `0x${string}`; @@ -19,6 +20,7 @@ const contractFilesToInclude: ContractNames[] = [ "ZoraCreatorMerkleMinterStrategy", "ZoraCreatorRedeemMinterFactory", "ZoraCreatorRedeemMinterStrategy", + "ZoraCreator1155PremintExecutor", ]; type Addresses = { @@ -35,8 +37,9 @@ const getAddresses = () => { const addAddress = ( contractName: ContractNames, chainId: number, - address: Address + address?: Address ) => { + if (!address) return; if (!addresses[contractName]) { addresses[contractName] = {}; } @@ -54,6 +57,7 @@ const getAddresses = () => { "1155_IMPL": Address; FACTORY_IMPL: Address; FACTORY_PROXY: Address; + PREMINTER?: Address; }; const chainId = parseInt(addressesFile.split(".")[0]); @@ -78,6 +82,11 @@ const getAddresses = () => { chainId, jsonAddress.REDEEM_MINTER_FACTORY ); + addAddress( + "ZoraCreator1155PremintExecutor", + chainId, + jsonAddress.PREMINTER + ); } return addresses; diff --git a/yarn.lock b/yarn.lock index d96f0959b..814d325d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8,32 +8,33 @@ integrity sha512-iowxq3U30sghZotgl4s/oJRci6WPBfNO5YYgk2cIOMCHr3LeGPcsZjCEr+33Q4N+oV3OABDAtA+pyvWjbvBifQ== "@babel/code-frame@^7.0.0": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658" - integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ== + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.10.tgz#1c20e612b768fefa75f6e90d6ecb86329247f0a3" + integrity sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA== dependencies: - "@babel/highlight" "^7.22.5" + "@babel/highlight" "^7.22.10" + chalk "^2.4.2" "@babel/helper-validator-identifier@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== -"@babel/highlight@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031" - integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw== +"@babel/highlight@^7.22.10": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.10.tgz#02a3f6d8c1cb4521b2fd0ab0da8f4739936137d7" + integrity sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ== dependencies: "@babel/helper-validator-identifier" "^7.22.5" - chalk "^2.0.0" + chalk "^2.4.2" js-tokens "^4.0.0" "@babel/runtime@^7.20.1", "@babel/runtime@^7.5.5": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" - integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ== + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.10.tgz#ae3e9631fd947cb7e3610d3e9d8fef5f76696682" + integrity sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ== dependencies: - regenerator-runtime "^0.13.11" + regenerator-runtime "^0.14.0" "@changesets/apply-release-plan@^6.1.4": version "6.1.4" @@ -486,33 +487,28 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/resolve-uri@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== "@jridgewell/set-array@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== -"@jridgewell/sourcemap-codec@1.4.14": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.15": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": version "1.4.15" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== "@jridgewell/trace-mapping@^0.3.9": - version "0.3.18" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" - integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== + version "0.3.19" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" + integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" "@manypkg/find-root@^1.1.0": version "1.1.0" @@ -536,13 +532,6 @@ globby "^11.0.0" read-yaml-file "^1.1.0" -"@noble/curves@1.0.0", "@noble/curves@~1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.0.0.tgz#e40be8c7daf088aaf291887cbc73f43464a92932" - integrity sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw== - dependencies: - "@noble/hashes" "1.3.0" - "@noble/curves@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" @@ -550,6 +539,13 @@ dependencies: "@noble/hashes" "1.3.1" +"@noble/curves@~1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.0.0.tgz#e40be8c7daf088aaf291887cbc73f43464a92932" + integrity sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw== + dependencies: + "@noble/hashes" "1.3.0" + "@noble/hashes@1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1" @@ -644,21 +640,16 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== -"@types/node@*": - version "20.4.10" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.10.tgz#73c9480791e3ddeb4887a660fc93a7f59353ad45" - integrity sha512-vwzFiiy8Rn6E0MtA13/Cxxgpan/N6UeNYR9oUu6kuJWxu6zCk98trcDp8CBhbtaeuq9SykCmXkFr2lWLoPcvLg== +"@types/node@*", "@types/node@^20.1.2": + version "20.5.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.0.tgz#7fc8636d5f1aaa3b21e6245e97d56b7f56702313" + integrity sha512-Mgq7eCtoTjT89FqNoTzzXg2XvCi5VMhRV6+I2aYanc6kQCBImeNaAYRs/DyoVqk1YEUJK5gN9VO7HRIdz4Wo3Q== "@types/node@^12.7.1": version "12.20.55" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== -"@types/node@^20.1.2": - version "20.4.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.5.tgz#9dc0a5cb1ccce4f7a731660935ab70b9c00a5d69" - integrity sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg== - "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -764,10 +755,10 @@ resolved "https://registry.yarnpkg.com/@zoralabs/openzeppelin-contracts-upgradeable/-/openzeppelin-contracts-upgradeable-4.8.4.tgz#130b69cd5ff70b1f67da11fe53fe8b2323464b84" integrity sha512-5vhL88tz00Gv2+NUhLdYBRqb9RRekfyQAodXTQxJU2LYxxy6jr1mPycTZempQ1kmw5wIwFbSIoYzpaxOx6UK6Q== -"@zoralabs/protocol-rewards@1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@zoralabs/protocol-rewards/-/protocol-rewards-1.1.1.tgz#e482c93598e92ce172f4e92584534e0521ffa8b2" - integrity sha512-lD5Jf/qWDQMG6rhnq1y2BHVEYIewDSUeAJwQD/1YIyXw1ANksLTmZZbrq8jXfFK2GbJeCzDqXKbFXSWidtyodw== +"@zoralabs/protocol-rewards@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@zoralabs/protocol-rewards/-/protocol-rewards-1.1.2.tgz#bc151601b44a74c508f5bf376a7eac355a3a468c" + integrity sha512-VLNaGth3AWNi8ehJM2eoIdWzzJIKSYqxQmlFpsjtOgBi2YnKJTJQPxrCk4Hlh/T3BBEGa1auSkGk1ynOCW0dUg== abitype@0.8.7: version "0.8.7" @@ -1066,7 +1057,7 @@ chai@^4.3.7: pathval "^1.1.1" type-detect "^4.0.5" -chalk@^2.0.0, chalk@^2.1.0: +chalk@^2.1.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1400,11 +1391,12 @@ emoji-regex@^9.2.2: integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== enquirer@^2.3.0: - version "2.3.6" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" - integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + version "2.4.1" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56" + integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== dependencies: ansi-colors "^4.1.1" + strip-ansi "^6.0.1" error-ex@^1.3.1: version "1.3.2" @@ -1831,9 +1823,9 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" -"forge-std@https://github.com/foundry-rs/forge-std#cd7d533f9a0ee0ec02ad81e0a8f262bc4203c653": - version "1.1.1" - resolved "https://github.com/foundry-rs/forge-std#cd7d533f9a0ee0ec02ad81e0a8f262bc4203c653" +"forge-std@https://github.com/foundry-rs/forge-std#705263c95892a906d7af65f0f73ce8a4a0c80b80": + version "1.6.0" + resolved "https://github.com/foundry-rs/forge-std#705263c95892a906d7af65f0f73ce8a4a0c80b80" formdata-polyfill@^4.0.10: version "4.0.10" @@ -2180,10 +2172,10 @@ is-ci@^3.0.1: dependencies: ci-info "^3.2.0" -is-core-module@^2.11.0: - version "2.12.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd" - integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg== +is-core-module@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== dependencies: has "^1.0.3" @@ -2329,9 +2321,9 @@ isomorphic-ws@5.0.0: integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== jackspeak@^2.0.3: - version "2.2.2" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.2.2.tgz#707c62733924b8dc2a0a629dc6248577788b5385" - integrity sha512-mgNtVv4vUuaKA97yxUHoA3+FkuhtxkjdXEWOyB/N76fjy0FjezEt34oy3epBtvCvS+7DyKwqCFWx/oJLV5+kCg== + version "2.3.0" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.0.tgz#aa228a94de830f31d4e4f0184427ce91c4ff1493" + integrity sha512-uKmsITSsF4rUWQHzqaRUuyAir3fZfW3f202Ee34lz/gZCi970CPZwyQXLGNgWJvvZbvFyzeyGq0+4fcG/mBKZg== dependencies: "@isaacs/cliui" "^8.0.2" optionalDependencies: @@ -2407,9 +2399,9 @@ lines-and-columns@^1.1.6: integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== listr2@^6.4.2: - version "6.6.0" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-6.6.0.tgz#cb8a0f45fb93ae50c43fb34f934759f8de9395ce" - integrity sha512-qkLg7IeYcZGkxo5sZzl676xHwQzNZ8qAQLQSDMA88sLM1SDcabwyXD1mXHi/PGQHyt/mu81adJdkqsCSUSuQzQ== + version "6.6.1" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-6.6.1.tgz#08b2329e7e8ba6298481464937099f4a2cd7f95d" + integrity sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg== dependencies: cli-truncate "^3.1.0" colorette "^2.0.20" @@ -2523,9 +2515,9 @@ lru-cache@^6.0.0: yallist "^4.0.0" "lru-cache@^9.1.1 || ^10.0.0": - version "10.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.0.tgz#b9e2a6a72a129d81ab317202d93c7691df727e61" - integrity sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw== + version "10.0.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a" + integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g== magic-string@^0.30.0: version "0.30.2" @@ -2625,9 +2617,9 @@ minimist-options@^4.0.2: kind-of "^6.0.3" "minipass@^5.0.0 || ^6.0.2 || ^7.0.0": - version "7.0.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.2.tgz#58a82b7d81c7010da5bd4b2c0c85ac4b4ec5131e" - integrity sha512-eL79dXrE1q9dBbDCLg7xfn/vl7MS4F1gvJAgjJrQli/jbQWdUttuVawphqpffoIYfRdq78LHx6GP4bU/EQ2ATA== + version "7.0.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.3.tgz#05ea638da44e475037ed94d1c7efcc76a25e1974" + integrity sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg== mixme@^0.5.1: version "0.5.9" @@ -2976,9 +2968,9 @@ postcss-load-config@^3.0.1: yaml "^1.10.2" postcss@^8.4.27: - version "8.4.27" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.27.tgz#234d7e4b72e34ba5a92c29636734349e0d9c3057" - integrity sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ== + version "8.4.28" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.28.tgz#c6cc681ed00109072816e1557f889ef51cf950a5" + integrity sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw== dependencies: nanoid "^3.3.6" picocolors "^1.0.0" @@ -3095,10 +3087,10 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -regenerator-runtime@^0.13.11: - version "0.13.11" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== regexp.prototype.flags@^1.5.0: version "1.5.0" @@ -3125,11 +3117,11 @@ resolve-from@^5.0.0: integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve@^1.10.0: - version "1.22.2" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" - integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== + version "1.22.4" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" + integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg== dependencies: - is-core-module "^2.11.0" + is-core-module "^2.13.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -3151,14 +3143,7 @@ rfdc@^1.3.0: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== -rollup@^3.2.5: - version "3.26.3" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.26.3.tgz#bbc8818cadd0aebca348dbb3d68d296d220967b8" - integrity sha512-7Tin0C8l86TkpcMtXvQu6saWH93nhG3dGQ1/+l5V2TDMceTxO7kDiK6GzbfLWNNxqJXm591PcEZUozZm51ogwQ== - optionalDependencies: - fsevents "~2.3.2" - -rollup@^3.27.1: +rollup@^3.2.5, rollup@^3.27.1: version "3.28.0" resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.28.0.tgz#a3c70004b01934760c0cb8df717c7a1d932389a2" integrity sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw== @@ -3271,9 +3256,9 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== signal-exit@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.2.tgz#ff55bb1d9ff2114c13b400688fa544ac63c36967" - integrity sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q== + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== slash@^3.0.0: version "3.0.0" @@ -3318,6 +3303,11 @@ solidity-comments-extractor@^0.0.7: resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19" integrity sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw== +solmate@^6.1.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/solmate/-/solmate-6.2.0.tgz#edd29b5f3d6faafafdcf65fe4d1d959b4841cfa8" + integrity sha512-AM38ioQ2P8zRsA42zenb9or6OybRjOLXIu3lhIT8rhddUuduCt76pUEuLxOIg9GByGojGz+EbpFdCB6B+QZVVA== + source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" @@ -3759,22 +3749,7 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -viem@^1.0.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/viem/-/viem-1.4.1.tgz#490f39b3f371bf58910b1b338c237e19066805cf" - integrity sha512-MtaoBHDSJDqa+QyXKG5d+S6EQSebRO0tzw6anSP4zC7AbC614vMeg9Y8LbkmEkWCw8swFYkort+H9l7GkWB0uA== - dependencies: - "@adraffy/ens-normalize" "1.9.0" - "@noble/curves" "1.0.0" - "@noble/hashes" "1.3.0" - "@scure/bip32" "1.3.0" - "@scure/bip39" "1.2.0" - "@wagmi/chains" "1.6.0" - abitype "0.9.3" - isomorphic-ws "5.0.0" - ws "8.12.0" - -viem@^1.6.0: +viem@^1.0.0, viem@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/viem/-/viem-1.6.0.tgz#8befa678c3ac79b9558dfd1708130b2ecb1994f4" integrity sha512-ae9Twkd0q2Qlj4yYpWjb4DzYAhKY0ibEpRH8FJaTywZXNpTjFidSdBaT0CVn1BaH7O7cnX4/O47zvDUMGJD1AA== @@ -4049,6 +4024,6 @@ yocto-queue@^1.0.0: integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== zod@^3.21.4: - version "3.21.4" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db" - integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw== + version "3.22.1" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.1.tgz#815f850baf933fef96c1061322dbe579b1a80c27" + integrity sha512-+qUhAMl414+Elh+fRNtpU+byrwjDFOS1N7NioLY+tSlcADTx4TkCUua/hxJvxwDXcV4397/nZ420jy4n4+3WUg==