Skip to content

Commit

Permalink
Premint - show how to decode creator attribution event (#260)
Browse files Browse the repository at this point in the history
* fix preminter tests to use the fork only (since deployment is tricky with libs

* Added example in viem for how to decode the emitted CreatorAttribution event

* better comments

* fix preminter tests

* format
  • Loading branch information
oveddan authored Oct 16, 2023
1 parent 64f028a commit 3b164f3
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 119 deletions.
4 changes: 2 additions & 2 deletions packages/1155-contracts/.env.anvil
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
FORK_RPC_URL="https://testnet.rpc.zora.co/"
FORK_BLOCK_NUMBER=916572
FORK_RPC_URL="https://rpc.zora.co/"
FORK_BLOCK_NUMBER=5141442
2 changes: 1 addition & 1 deletion packages/1155-contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"tsup": "^7.2.0",
"tsx": "^3.13.0",
"typescript": "^5.0.4",
"viem": "^1.6.0",
"viem": "^1.16.2",
"vite": "^4.1.4",
"vitest": "~0.30.1"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/1155-contracts/package/batchPublish.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ function parseCreate1155Receipt(receipt: TransactionReceipt): {
return { tokenId, contractAddress };
}

describe("ZoraCreator1155Preminter", () => {
describe("Zora1155", () => {
it(
"can batch publish tokens",
async () => {
Expand Down
257 changes: 143 additions & 114 deletions packages/1155-contracts/package/preminter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ import {
http,
createWalletClient,
createPublicClient,
keccak256,
Hex,
concat,
recoverAddress,
hashDomain,
} from "viem";
import { foundry, zoraTestnet } from "viem/chains";
import { foundry, zora } from "viem/chains";
import { describe, it, beforeEach, expect } from "vitest";
import { parseEther } from "viem";
import {
Expand All @@ -14,12 +19,7 @@ import {
zoraCreator1155FactoryImplAddress,
zoraCreator1155FactoryImplConfig,
} from "./wagmiGenerated";
import ZoraCreator1155Attribution from "../out/ZoraCreator1155Attribution.sol/ZoraCreator1155Attribution.json";
import zoraCreator1155PremintExecutor from "../out/ZoraCreator1155PremintExecutorImpl.sol/ZoraCreator1155PremintExecutorImpl.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,
Expand Down Expand Up @@ -50,15 +50,9 @@ const publicClient = createPublicClient({

type Address = `0x${string}`;

const zeroAddress: Address = "0x0000000000000000000000000000000000000000";

// JSON-RPC Account
const [
deployerAccount,
creatorAccount,
collectorAccount,
mintFeeRecipientAccount,
] = (await walletClient.getAddresses()) as [Address, Address, Address, Address];
const [deployerAccount, creatorAccount, collectorAccount] =
(await walletClient.getAddresses()) as [Address, Address, Address, Address];

type TestContext = {
preminterAddress: `0x${string}`;
Expand All @@ -68,81 +62,6 @@ type TestContext = {
fixedPriceMinterAddress: Address;
};

const deployContractAndGetAddress = async (
args: Parameters<typeof walletClient.deployContract>[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,
Expand Down Expand Up @@ -176,8 +95,6 @@ const defaultPremintConfig = (fixedPriceMinter: Address): PremintConfig => ({
version: 0,
});

const useForkContract = true;

describe("ZoraCreator1155Preminter", () => {
beforeEach<TestContext>(async (ctx) => {
// deploy signature minter contract
Expand All @@ -186,35 +103,22 @@ describe("ZoraCreator1155Preminter", () => {
value: parseEther("10"),
});

ctx.forkedChainId = zoraTestnet.id;
ctx.forkedChainId = zora.id;
ctx.anvilChainId = foundry.id;

let preminterAddress: Address;

if (useForkContract) {
const factoryProxyAddress =
zoraCreator1155FactoryImplAddress[ctx.forkedChainId];
ctx.fixedPriceMinterAddress = await publicClient.readContract({
abi: zoraCreator1155FactoryImplConfig.abi,
address: zoraCreator1155FactoryImplAddress[ctx.forkedChainId],
functionName: "fixedPriceMinter",
});
const deployed = await deployPreminterContract(factoryProxyAddress);
preminterAddress = deployed.preminterAddress;
} else {
const factoryProxyAddress = (await deployFactoryProxy())
.factoryProxyAddress;
const deployed = await deployPreminterContract(factoryProxyAddress);
preminterAddress = deployed.preminterAddress;
}

ctx.fixedPriceMinterAddress = await publicClient.readContract({
abi: zoraCreator1155FactoryImplConfig.abi,
address: zoraCreator1155FactoryImplAddress[ctx.forkedChainId],
functionName: "fixedPriceMinter",
});
ctx.zoraMintFee = parseEther("0.000777");

ctx.preminterAddress = preminterAddress;
ctx.preminterAddress =
zoraCreator1155PremintExecutorAddress[ctx.forkedChainId];
}, 20 * 1000);

// skip for now - we need to make this work on zora testnet chain too
it.skip<TestContext>(
it<TestContext>(
"can sign on the forked premint contract",
async ({ fixedPriceMinterAddress, forkedChainId }) => {
const premintConfig = defaultPremintConfig(fixedPriceMinterAddress);
Expand Down Expand Up @@ -474,4 +378,129 @@ describe("ZoraCreator1155Preminter", () => {
// 10 second timeout
40 * 1000
);

it<TestContext>("can decode the CreatorAttribution event", async ({
zoraMintFee,
anvilChainId,
preminterAddress: preminterAddress,
fixedPriceMinterAddress,
}) => {
const premintConfig = defaultPremintConfig(fixedPriceMinterAddress);
const contractConfig = defaultContractConfig({
contractAdmin: creatorAccount,
});

// lets make it a random number to not break the existing tests that expect fresh data
premintConfig.uid = Math.round(Math.random() * 1000000);

let contractAddress = await publicClient.readContract({
abi: preminterAbi,
address: preminterAddress,
functionName: "getContractAddress",
args: [contractConfig],
});

// have creator sign the message to create the contract
// and the token
const signedMessage = await walletClient.signTypedData({
...preminterTypedDataDefinition({
verifyingContract: contractAddress,
// we need to sign here for the anvil chain, cause thats where it is run on
chainId: anvilChainId,
premintConfig,
}),
account: creatorAccount,
});

const quantityToMint = 2n;

const valueToSend =
(zoraMintFee + premintConfig.tokenConfig.pricePerToken) * quantityToMint;

const comment = "I love this!";

await testClient.setBalance({
address: collectorAccount,
value: parseEther("10"),
});

// now have the collector execute the first signed message;
// it should create the contract, the token,
// and min the quantity to mint tokens to the collector
// the signature along with contract + token creation
// parameters are required to call this function
const mintHash = await walletClient.writeContract({
abi: preminterAbi,
functionName: "premint",
account: collectorAccount,
address: preminterAddress,
args: [
contractConfig,
premintConfig,
signedMessage,
quantityToMint,
comment,
],
value: valueToSend,
});

// ensure it succeeded
const receipt = await publicClient.waitForTransactionReceipt({
hash: mintHash,
});

expect(receipt.status).toBe("success");

// get the CreatorAttribution event from the erc1155 contract:
const topics = await publicClient.getContractEvents({
abi: zoraCreator1155ImplABI,
address: contractAddress,
eventName: "CreatorAttribution",
});

expect(topics.length).toBe(1);

const creatorAttributionEvent = topics[0]!;

const { creator, domainName, signature, structHash, version } =
creatorAttributionEvent.args;

const chainId = anvilChainId;

// hash the eip712 domain based on the parameters emitted from the event:
const hashedDomain = hashDomain({
domain: {
chainId,
name: domainName,
verifyingContract: contractAddress,
version,
},
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{
name: "chainId",
type: "uint256",
},
{
name: "verifyingContract",
type: "address",
},
],
},
});

// re-build the eip-712 typed data hash, consisting of the hashed domain and the structHash emitted from the event:
const parts: Hex[] = ["0x1901", hashedDomain, structHash!];

const hashedTypedData = keccak256(concat(parts));

const recoveredSigner = await recoverAddress({
hash: hashedTypedData,
signature: signature!,
});

expect(recoveredSigner).toBe(creator);
});
});
21 changes: 20 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2468,6 +2468,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf"
integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==

[email protected]:
version "1.0.3"
resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.3.tgz#93c1cf0575daf56e7120bab5c8c448b0809d0d74"
integrity sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg==

jackspeak@^2.3.5:
version "2.3.6"
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8"
Expand Down Expand Up @@ -4090,7 +4095,7 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"

viem@^1.0.0, viem@^1.6.0:
viem@^1.0.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/viem/-/viem-1.14.0.tgz#e4b305c4cce500e04a66b951c01856d7b04ab403"
integrity sha512-4d+4/H3lnbkSAbrpQ15i1nBA7hne06joLFy3L3m0ZpMc+g+Zr3D4nuSTyeiqbHAYs9m2P9Kjap0HlyGkehasgg==
Expand All @@ -4105,6 +4110,20 @@ viem@^1.0.0, viem@^1.6.0:
isomorphic-ws "5.0.0"
ws "8.13.0"

viem@^1.16.2:
version "1.16.2"
resolved "https://registry.yarnpkg.com/viem/-/viem-1.16.2.tgz#7e9719dd19e7464284b94d9c00f94f86f5858ccd"
integrity sha512-ZQ8kemNvRVwucwcsj4/SjKohK+wZv9Vxx/gXAlwqGMCW7r+niOeECtFku/1L7UPTmPgdmq4kic9R71t6XQDmGw==
dependencies:
"@adraffy/ens-normalize" "1.9.4"
"@noble/curves" "1.2.0"
"@noble/hashes" "1.3.2"
"@scure/bip32" "1.3.2"
"@scure/bip39" "1.2.1"
abitype "0.9.8"
isows "1.0.3"
ws "8.13.0"

[email protected]:
version "0.30.1"
resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.30.1.tgz#ab0ed1553019c7d81ac95529c57ab8ac9e82347d"
Expand Down

0 comments on commit 3b164f3

Please sign in to comment.