From c42e94dca8f0276aa36eeaf7ade8004db8f567e2 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Mon, 15 Aug 2022 14:57:22 -0700 Subject: [PATCH 1/7] WIP release with factory deploy --- src/core/classes/contract-deployer.ts | 39 ++++++++++++++ src/schema/contracts/custom.ts | 77 +++++++++++++++------------ 2 files changed, 81 insertions(+), 35 deletions(-) diff --git a/src/core/classes/contract-deployer.ts b/src/core/classes/contract-deployer.ts index 31194bb47..a20e410d6 100644 --- a/src/core/classes/contract-deployer.ts +++ b/src/core/classes/contract-deployer.ts @@ -35,6 +35,7 @@ import { fetchPreDeployMetadata, } from "../../common/index"; import { BigNumber, BytesLike, ContractInterface, ethers } from "ethers"; +import { ExtraPublishMetadataSchema } from "../../schema/contracts/custom"; /** * Handles deploying new contracts @@ -368,6 +369,23 @@ export class ContractDeployer extends RPCConnectionHandler { ); } + // TODO IFactory interface + wrapper that makes sense + public async deployViaFactory( + factoryAddress: string, + factoryAbi: ContractInterface, + deployFunctionNAme: string, + args: any[], + ) { + const signer = this.getSigner(); + invariant(signer, "signer is required"); + const factory = new ethers.Contract(factoryAddress, factoryAbi, signer); + const tx = await factory.functions[deployFunctionNAme](...args); + const receipt = await tx.wait(); + return { + receipt, + }; + } + /** * @internal */ @@ -453,6 +471,27 @@ export class ContractDeployer extends RPCConnectionHandler { publishMetadataUri, this.storage, ); + + try { + const extra = ExtraPublishMetadataSchema.parse(metadata); + const factoryAbi = extra.factoryAbi; + const factoryAddresses = extra.factoryAddresses; + const deployFunctionName = extra.deployFunction; + const deployArgs = extra.deployArgs || []; + if (factoryAbi && factoryAddresses && deployFunctionName) { + const chainId = (await this.getProvider().getNetwork()).chainId; + const factoryAddress = factoryAddresses[chainId]; + return await this.deployViaFactory( + factoryAddress, + factoryAbi, + deployFunctionName, + deployArgs, + ); + } + } catch (e) { + // no extra metadata, proceed with normal deploy + } + const bytecode = metadata.bytecode.startsWith("0x") ? metadata.bytecode : `0x${metadata.bytecode}`; diff --git a/src/schema/contracts/custom.ts b/src/schema/contracts/custom.ts index 41f85b9c9..568a1ef07 100644 --- a/src/schema/contracts/custom.ts +++ b/src/schema/contracts/custom.ts @@ -62,6 +62,41 @@ export const CustomContractSchema = { input: CustomContractInput, }; +/** + * @internal + */ +const AbiTypeBaseSchema = z + .object({ + type: z.string(), + name: z.string(), + }) + .catchall(z.any()); + +/** + * @internal + */ +export const AbiTypeSchema = AbiTypeBaseSchema.extend({ + stateMutability: z.string().optional(), + components: z.array(AbiTypeBaseSchema).optional(), +}).catchall(z.any()); + +/** + * @internal + */ +export const AbiObjectSchema = z + .object({ + type: z.string(), + name: z.string().default(""), + inputs: z.array(AbiTypeSchema).default([]), + outputs: z.array(AbiTypeSchema).default([]), + }) + .catchall(z.any()); + +/** + * @internal + */ +export const AbiSchema = z.array(AbiObjectSchema); + /** * @internal */ @@ -74,6 +109,8 @@ export const PreDeployMetadata = z }) .catchall(z.any()); +export const ChainIdToAddressSchema = z.record(z.number(), z.string()); + /** * @internal */ @@ -100,6 +137,11 @@ export const ExtraPublishMetadataSchema = z license: z.string().optional(), changelog: z.string().optional(), tags: z.array(z.string()).optional(), + logo: FileBufferOrStringSchema.nullable().optional(), + factoryAddresses: ChainIdToAddressSchema.optional(), + factoryAbi: AbiSchema.optional(), + deployFunction: z.string().optional(), + deployArgs: z.array(z.any()).optional(), }) .catchall(z.any()); export type ExtraPublishMetadata = z.infer; @@ -137,41 +179,6 @@ export const ProfileSchemaOutput = ProfileSchemaInput.extend({ export type ProfileMetadataInput = z.infer; export type ProfileMetadata = z.infer; -/** - * @internal - */ -const AbiTypeBaseSchema = z - .object({ - type: z.string(), - name: z.string(), - }) - .catchall(z.any()); - -/** - * @internal - */ -export const AbiTypeSchema = AbiTypeBaseSchema.extend({ - stateMutability: z.string().optional(), - components: z.array(AbiTypeBaseSchema).optional(), -}).catchall(z.any()); - -/** - * @internal - */ -export const AbiObjectSchema = z - .object({ - type: z.string(), - name: z.string().default(""), - inputs: z.array(AbiTypeSchema).default([]), - outputs: z.array(AbiTypeSchema).default([]), - }) - .catchall(z.any()); - -/** - * @internal - */ -export const AbiSchema = z.array(AbiObjectSchema); - /** * @internal */ From 0063155a9c4be0b11af9c90c116e613cf22fc9a1 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 16 Aug 2022 09:36:39 -0700 Subject: [PATCH 2/7] implement API to release/deploy proxy contracts --- docs/sdk.chainidtoaddressschema.md | 11 ++ docs/sdk.contractdeployer.deployviafactory.md | 28 ++++ docs/sdk.contractdeployer.md | 1 + docs/sdk.factorydeploymentschema.md | 23 ++++ docs/sdk.md | 2 + etc/sdk.api.md | 102 ++++++++++++++- src/common/feature-detection.ts | 28 ++++ src/core/classes/contract-deployer.ts | 120 +++++++++++++----- src/core/classes/contract-metadata.ts | 2 +- src/core/classes/factory.ts | 37 +++++- src/schema/contracts/custom.ts | 29 ++++- test/before-setup.ts | 5 +- test/publisher.test.ts | 49 ++++++- 13 files changed, 386 insertions(+), 51 deletions(-) create mode 100644 docs/sdk.chainidtoaddressschema.md create mode 100644 docs/sdk.contractdeployer.deployviafactory.md create mode 100644 docs/sdk.factorydeploymentschema.md diff --git a/docs/sdk.chainidtoaddressschema.md b/docs/sdk.chainidtoaddressschema.md new file mode 100644 index 000000000..faec7fe2d --- /dev/null +++ b/docs/sdk.chainidtoaddressschema.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@thirdweb-dev/sdk](./sdk.md) > [ChainIdToAddressSchema](./sdk.chainidtoaddressschema.md) + +## ChainIdToAddressSchema variable + +Signature: + +```typescript +ChainIdToAddressSchema: z.ZodRecord +``` diff --git a/docs/sdk.contractdeployer.deployviafactory.md b/docs/sdk.contractdeployer.deployviafactory.md new file mode 100644 index 000000000..c140ff33e --- /dev/null +++ b/docs/sdk.contractdeployer.deployviafactory.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [@thirdweb-dev/sdk](./sdk.md) > [ContractDeployer](./sdk.contractdeployer.md) > [deployViaFactory](./sdk.contractdeployer.deployviafactory.md) + +## ContractDeployer.deployViaFactory() method + +Deploy a proxy contract of a given implementation via the given factory + +Signature: + +```typescript +deployViaFactory(factoryAddress: string, implementationAddress: string, implementationAbi: ContractInterface, initializerFunction: string, initializerArgs: any[]): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| factoryAddress | string | | +| implementationAddress | string | | +| implementationAbi | ContractInterface | | +| initializerFunction | string | | +| initializerArgs | any\[\] | | + +Returns: + +Promise<string> + diff --git a/docs/sdk.contractdeployer.md b/docs/sdk.contractdeployer.md index 528c62088..ee9b06dd3 100644 --- a/docs/sdk.contractdeployer.md +++ b/docs/sdk.contractdeployer.md @@ -35,6 +35,7 @@ export declare class ContractDeployer extends RPCConnectionHandler | [deploySplit(metadata)](./sdk.contractdeployer.deploysplit.md) | | Deploys a new Split contract | | [deployToken(metadata)](./sdk.contractdeployer.deploytoken.md) | | Deploys a new Token contract | | [deployTokenDrop(metadata)](./sdk.contractdeployer.deploytokendrop.md) | | Deploys a new Token Drop contract | +| [deployViaFactory(factoryAddress, implementationAddress, implementationAbi, initializerFunction, initializerArgs)](./sdk.contractdeployer.deployviafactory.md) | | Deploy a proxy contract of a given implementation via the given factory | | [deployVote(metadata)](./sdk.contractdeployer.deployvote.md) | | Deploys a new Vote contract | | [updateSignerOrProvider(network)](./sdk.contractdeployer.updatesignerorprovider.md) | | | diff --git a/docs/sdk.factorydeploymentschema.md b/docs/sdk.factorydeploymentschema.md new file mode 100644 index 000000000..838e23aa9 --- /dev/null +++ b/docs/sdk.factorydeploymentschema.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [@thirdweb-dev/sdk](./sdk.md) > [FactoryDeploymentSchema](./sdk.factorydeploymentschema.md) + +## FactoryDeploymentSchema variable + +Signature: + +```typescript +FactoryDeploymentSchema: z.ZodObject<{ + implementationAddresses: z.ZodRecord; + implementationInitializerFunction: z.ZodDefault; + factoryAddresses: z.ZodDefault>; +}, "strip", z.ZodTypeAny, { + implementationAddresses: Record; + implementationInitializerFunction: string; + factoryAddresses: Record; +}, { + implementationInitializerFunction?: string | undefined; + factoryAddresses?: Record | undefined; + implementationAddresses: Record; +}> +``` diff --git a/docs/sdk.md b/docs/sdk.md index 019ceebe7..ab502ed2b 100644 --- a/docs/sdk.md +++ b/docs/sdk.md @@ -119,6 +119,8 @@ | Variable | Description | | --- | --- | | [ALL\_ROLES](./sdk.all_roles.md) | | +| [ChainIdToAddressSchema](./sdk.chainidtoaddressschema.md) | | +| [FactoryDeploymentSchema](./sdk.factorydeploymentschema.md) | | | [MintRequest1155](./sdk.mintrequest1155.md) | | | [MintRequest20](./sdk.mintrequest20.md) | | | [MintRequest721](./sdk.mintrequest721.md) | | diff --git a/etc/sdk.api.md b/etc/sdk.api.md index ce38151ba..f03e083c7 100644 --- a/etc/sdk.api.md +++ b/etc/sdk.api.md @@ -542,6 +542,9 @@ export enum ChainId { Rinkeby = 4 } +// @public (undocumented) +export const ChainIdToAddressSchema: z.ZodRecord; + // Warning: (ae-internal-missing-underscore) The name "ChainOrRpc" should be prefixed with an underscore because the declaration is marked as @internal // // @internal (undocumented) @@ -1170,6 +1173,7 @@ export class ContractDeployer extends RPCConnectionHandler { deploySplit(metadata: SplitContractDeployMetadata): Promise; deployToken(metadata: TokenContractDeployMetadata): Promise; deployTokenDrop(metadata: TokenContractDeployMetadata): Promise; + deployViaFactory(factoryAddress: string, implementationAddress: string, implementationAbi: ContractInterface, initializerFunction: string, initializerArgs: any[]): Promise; deployVote(metadata: VoteContractDeployMetadata): Promise; // Warning: (ae-forgotten-export) The symbol "ContractRegistry" needs to be exported by the entry point index.d.ts // @@ -1560,7 +1564,9 @@ export const CustomContractOutput: z.ZodObject>; external_link: z.ZodOptional; }, { - image: z.ZodOptional; + image: z.ZodOptional; /** + * @internal + */ }>, { merkle: z.ZodOptional>>; seller_fee_basis_points: z.ZodOptional>; @@ -1642,7 +1648,9 @@ export const CustomContractSchema: { image: z.ZodOptional>; external_link: z.ZodOptional; }, { - image: z.ZodOptional; + image: z.ZodOptional; /** + * @internal + */ }>, { merkle: z.ZodOptional>>; seller_fee_basis_points: z.ZodOptional>; @@ -2794,6 +2802,21 @@ export function extractConstructorParamsFromAbi(abi: z.input): // @internal (undocumented) export function extractEventsFromAbi(abi: z.input, metadata?: Record): AbiEvent[]; +// Warning: (ae-internal-missing-underscore) The name "extractFunctionParamsFromAbi" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal (undocumented) +export function extractFunctionParamsFromAbi(abi: z.input, functionName: string): { + [x: string]: any; + components?: { + [x: string]: any; + type: string; + name: string; + }[] | undefined; + stateMutability?: string | undefined; + type: string; + name: string; +}[]; + // Warning: (ae-internal-missing-underscore) The name "extractFunctions" should be prefixed with an underscore because the declaration is marked as @internal // // @internal (undocumented) @@ -2820,6 +2843,20 @@ export const ExtraPublishMetadataSchema: z.ZodObject<{ license: z.ZodOptional; changelog: z.ZodOptional; tags: z.ZodOptional>; + logo: z.ZodOptional>>; + factoryDeployment: z.ZodOptional>; + implementationInitializerFunction: z.ZodOptional>; + factoryAddresses: z.ZodOptional>>; + }, "strip", z.ZodTypeAny, { + implementationAddresses?: Record | undefined; + implementationInitializerFunction?: string | undefined; + factoryAddresses?: Record | undefined; + }, { + implementationAddresses?: Record | undefined; + implementationInitializerFunction?: string | undefined; + factoryAddresses?: Record | undefined; + }>>; }, "strip", z.ZodAny, { [x: string]: any; description?: string | undefined; @@ -2828,6 +2865,12 @@ export const ExtraPublishMetadataSchema: z.ZodObject<{ license?: string | undefined; changelog?: string | undefined; tags?: string[] | undefined; + logo?: any; + factoryDeployment?: { + implementationAddresses?: Record | undefined; + implementationInitializerFunction?: string | undefined; + factoryAddresses?: Record | undefined; + } | undefined; version: string; }, { [x: string]: any; @@ -2837,9 +2880,30 @@ export const ExtraPublishMetadataSchema: z.ZodObject<{ license?: string | undefined; changelog?: string | undefined; tags?: string[] | undefined; + logo?: any; + factoryDeployment?: { + implementationAddresses?: Record | undefined; + implementationInitializerFunction?: string | undefined; + factoryAddresses?: Record | undefined; + } | undefined; version: string; }>; +// @public (undocumented) +export const FactoryDeploymentSchema: z.ZodObject<{ + implementationAddresses: z.ZodRecord; + implementationInitializerFunction: z.ZodDefault; + factoryAddresses: z.ZodDefault>; +}, "strip", z.ZodTypeAny, { + implementationAddresses: Record; + implementationInitializerFunction: string; + factoryAddresses: Record; +}, { + implementationInitializerFunction?: string | undefined; + factoryAddresses?: Record | undefined; + implementationAddresses: Record; +}>; + // Warning: (ae-internal-missing-underscore) The name "fetchContractMetadata" should be prefixed with an underscore because the declaration is marked as @internal // // @internal (undocumented) @@ -2988,6 +3052,20 @@ export const FullPublishMetadataSchema: z.ZodObject; changelog: z.ZodOptional; tags: z.ZodOptional>; + logo: z.ZodOptional>>; + factoryDeployment: z.ZodOptional>; + implementationInitializerFunction: z.ZodOptional>; + factoryAddresses: z.ZodOptional>>; + }, "strip", z.ZodTypeAny, { + implementationAddresses?: Record | undefined; + implementationInitializerFunction?: string | undefined; + factoryAddresses?: Record | undefined; + }, { + implementationAddresses?: Record | undefined; + implementationInitializerFunction?: string | undefined; + factoryAddresses?: Record | undefined; + }>>; }>, { publisher: z.ZodOptional>; }>, "strip", z.ZodAny, { @@ -3000,6 +3078,12 @@ export const FullPublishMetadataSchema: z.ZodObject | undefined; + implementationInitializerFunction?: string | undefined; + factoryAddresses?: Record | undefined; + } | undefined; name: string; version: string; metadataUri: string; @@ -3014,6 +3098,12 @@ export const FullPublishMetadataSchema: z.ZodObject | undefined; + implementationInitializerFunction?: string | undefined; + factoryAddresses?: Record | undefined; + } | undefined; name: string; version: string; metadataUri: string; @@ -7152,10 +7242,10 @@ export class WrongListingTypeError extends Error { // Warnings were encountered during analysis: // -// dist/src/schema/contracts/custom.d.ts:1279:5 - (ae-incompatible-release-tags) The symbol "inputs" is marked as @public, but its signature references "AbiTypeSchema" which is marked as @internal -// dist/src/schema/contracts/custom.d.ts:1280:5 - (ae-incompatible-release-tags) The symbol "outputs" is marked as @public, but its signature references "AbiTypeSchema" which is marked as @internal -// dist/src/schema/contracts/custom.d.ts:1287:5 - (ae-incompatible-release-tags) The symbol "inputs" is marked as @public, but its signature references "AbiTypeSchema" which is marked as @internal -// dist/src/schema/contracts/custom.d.ts:1288:5 - (ae-incompatible-release-tags) The symbol "outputs" is marked as @public, but its signature references "AbiTypeSchema" which is marked as @internal +// dist/src/schema/contracts/custom.d.ts:1349:5 - (ae-incompatible-release-tags) The symbol "inputs" is marked as @public, but its signature references "AbiTypeSchema" which is marked as @internal +// dist/src/schema/contracts/custom.d.ts:1350:5 - (ae-incompatible-release-tags) The symbol "outputs" is marked as @public, but its signature references "AbiTypeSchema" which is marked as @internal +// dist/src/schema/contracts/custom.d.ts:1357:5 - (ae-incompatible-release-tags) The symbol "inputs" is marked as @public, but its signature references "AbiTypeSchema" which is marked as @internal +// dist/src/schema/contracts/custom.d.ts:1358:5 - (ae-incompatible-release-tags) The symbol "outputs" is marked as @public, but its signature references "AbiTypeSchema" which is marked as @internal // (No @packageDocumentation comment for this package) diff --git a/src/common/feature-detection.ts b/src/common/feature-detection.ts index e3bab51fd..61460a153 100644 --- a/src/common/feature-detection.ts +++ b/src/common/feature-detection.ts @@ -117,6 +117,25 @@ export function extractConstructorParamsFromAbi( return []; } +/** + * + * @param abi + * @param functionName + * @returns + * @internal + */ +export function extractFunctionParamsFromAbi( + abi: z.input, + functionName: string, +) { + for (const input of abi) { + if (input.type === "function" && input.name === functionName) { + return input.inputs ?? []; + } + } + return []; +} + /** * @internal * @param abi @@ -228,6 +247,15 @@ export async function resolveContractUriFromAddress( `Contract at ${address} does not exist on chain '${chain.name}' (chainId: ${chain.chainId})`, ); } + // EIP-1167 clone proxy - https://eips.ethereum.org/EIPS/eip-1167 + if (bytecode.startsWith("0x363d3d373d3d3d363d")) { + const implementationAddress = bytecode.slice(22, 62); + return resolveContractUriFromAddress( + `0x${implementationAddress}`, + provider, + ); + } + // TODO support other types of proxies like erc1967 return extractIPFSHashFromBytecode(bytecode); } diff --git a/src/core/classes/contract-deployer.ts b/src/core/classes/contract-deployer.ts index a20e410d6..22121b9ec 100644 --- a/src/core/classes/contract-deployer.ts +++ b/src/core/classes/contract-deployer.ts @@ -32,10 +32,14 @@ import { ThirdwebSDK } from "../sdk"; import invariant from "tiny-invariant"; import { extractConstructorParamsFromAbi, + extractFunctionParamsFromAbi, fetchPreDeployMetadata, } from "../../common/index"; import { BigNumber, BytesLike, ContractInterface, ethers } from "ethers"; -import { ExtraPublishMetadataSchema } from "../../schema/contracts/custom"; +import { + FactoryDeploymentSchema, + FullPublishMetadataSchema, +} from "../../schema/contracts/custom"; /** * Handles deploying new contracts @@ -369,21 +373,36 @@ export class ContractDeployer extends RPCConnectionHandler { ); } - // TODO IFactory interface + wrapper that makes sense + /** + * Deploy a proxy contract of a given implementation via the given factory + * @param factoryAddress + * @param implementationAddress + * @param implementationAbi + * @param initializerFunction + * @param initializerArgs + */ public async deployViaFactory( factoryAddress: string, - factoryAbi: ContractInterface, - deployFunctionNAme: string, - args: any[], - ) { + implementationAddress: string, + implementationAbi: ContractInterface, + initializerFunction: string, + initializerArgs: any[], + ): Promise { const signer = this.getSigner(); invariant(signer, "signer is required"); - const factory = new ethers.Contract(factoryAddress, factoryAbi, signer); - const tx = await factory.functions[deployFunctionNAme](...args); - const receipt = await tx.wait(); - return { - receipt, - }; + // TODO only require factory interface here - IProxyFactory + const proxyFactory = new ContractFactory( + factoryAddress, + this.getSignerOrProvider(), + this.storage, + {}, + ); + return await proxyFactory.deployProxyByImplementation( + implementationAddress, + implementationAbi, + initializerFunction, + initializerArgs, + ); } /** @@ -467,45 +486,78 @@ export class ContractDeployer extends RPCConnectionHandler { ) { const signer = this.getSigner(); invariant(signer, "A signer is required"); - const metadata = await fetchPreDeployMetadata( + const compilerMetadata = await fetchPreDeployMetadata( publishMetadataUri, this.storage, ); + let factoryDeployment; try { - const extra = ExtraPublishMetadataSchema.parse(metadata); - const factoryAbi = extra.factoryAbi; - const factoryAddresses = extra.factoryAddresses; - const deployFunctionName = extra.deployFunction; - const deployArgs = extra.deployArgs || []; - if (factoryAbi && factoryAddresses && deployFunctionName) { - const chainId = (await this.getProvider().getNetwork()).chainId; - const factoryAddress = factoryAddresses[chainId]; - return await this.deployViaFactory( - factoryAddress, - factoryAbi, - deployFunctionName, - deployArgs, - ); - } + const meta = await this.storage.getRaw(publishMetadataUri); + const publishMetadata = FullPublishMetadataSchema.parse(JSON.parse(meta)); + factoryDeployment = FactoryDeploymentSchema.parse( + publishMetadata.factoryDeployment, + ); } catch (e) { - // no extra metadata, proceed with normal deploy + // not a factory deployment, ignore } - const bytecode = metadata.bytecode.startsWith("0x") - ? metadata.bytecode - : `0x${metadata.bytecode}`; + if (factoryDeployment) { + const chainId = (await this.getProvider().getNetwork()).chainId; + invariant( + factoryDeployment.factoryAddresses, + "factoryAddresses is required", + ); + invariant( + factoryDeployment.implementationAddresses, + "implementationAddresses is required", + ); + const factoryAddress = factoryDeployment.factoryAddresses[chainId]; + const implementationAddress = + factoryDeployment.implementationAddresses[chainId]; + invariant( + factoryAddress, + `factoryAddress not found for chainId '${chainId}'`, + ); + invariant( + implementationAddress, + `implementationAddress not found for chainId '${chainId}'`, + ); + const initializerParamTypes = extractFunctionParamsFromAbi( + compilerMetadata.abi, + factoryDeployment.implementationInitializerFunction, + ).map((p) => p.type); + const paramValues = this.convertParamValues( + initializerParamTypes, + constructorParamValues, + ); + return await this.deployViaFactory( + factoryAddress, + implementationAddress, + compilerMetadata.abi, + factoryDeployment.implementationInitializerFunction, + paramValues, + ); + } + + const bytecode = compilerMetadata.bytecode.startsWith("0x") + ? compilerMetadata.bytecode + : `0x${compilerMetadata.bytecode}`; if (!ethers.utils.isHexString(bytecode)) { throw new Error(`Contract bytecode is invalid.\n\n${bytecode}`); } const constructorParamTypes = extractConstructorParamsFromAbi( - metadata.abi, + compilerMetadata.abi, ).map((p) => p.type); const paramValues = this.convertParamValues( constructorParamTypes, constructorParamValues, ); - return this.deployContractWithAbi(metadata.abi, bytecode, paramValues); + return this.deployContractWithAbi( + compilerMetadata.abi, + bytecode, + paramValues, + ); } private convertParamValues( diff --git a/src/core/classes/contract-metadata.ts b/src/core/classes/contract-metadata.ts index 95dedd057..9f279c435 100644 --- a/src/core/classes/contract-metadata.ts +++ b/src/core/classes/contract-metadata.ts @@ -81,7 +81,7 @@ export class ContractMetadata< let data; if (this.supportsContractMetadata(this.contractWrapper)) { const uri = await this.contractWrapper.readContract.contractURI(); - if (uri) { + if (uri && uri.length > 0) { data = await this.storage.get(uri); } } diff --git a/src/core/classes/factory.ts b/src/core/classes/factory.ts index d4c10bf26..96358a068 100644 --- a/src/core/classes/factory.ts +++ b/src/core/classes/factory.ts @@ -1,5 +1,11 @@ import { TWFactory, TWFactory__factory } from "contracts"; -import { BigNumber, constants, Contract, ethers } from "ethers"; +import { + BigNumber, + constants, + Contract, + ContractInterface, + ethers, +} from "ethers"; import { z } from "zod"; import { CONTRACTS_MAP, REMOTE_CONTRACT_NAME } from "../../contracts/maps"; import { Edition } from "../../contracts/edition"; @@ -92,6 +98,35 @@ export class ContractFactory extends ContractWrapper { return events[0].args.proxy; } + // TODO once IContractFactory is implemented, this can be probably be moved to its own class + public async deployProxyByImplementation( + implementationAddress: string, + implementationAbi: ContractInterface, + initializerFunction: string, + initializerArgs: any[], + ): Promise { + const encodedFunc = Contract.getInterface( + implementationAbi, + ).encodeFunctionData(initializerFunction, initializerArgs); + + const blockNumber = await this.getProvider().getBlockNumber(); + const receipt = await this.sendTransaction("deployProxyByImplementation", [ + implementationAddress, + encodedFunc, + ethers.utils.formatBytes32String(blockNumber.toString()), + ]); + + const events = this.parseLogs( + "ProxyDeployed", + receipt.logs, + ); + if (events.length < 1) { + throw new Error("No ProxyDeployed event found"); + } + + return events[0].args.proxy; + } + private async getDeployArguments( contractType: TContract["contractType"], metadata: z.input, diff --git a/src/schema/contracts/custom.ts b/src/schema/contracts/custom.ts index 568a1ef07..e3a655f62 100644 --- a/src/schema/contracts/custom.ts +++ b/src/schema/contracts/custom.ts @@ -17,6 +17,7 @@ import { } from "../shared"; import { BigNumberish } from "ethers"; import { toSemver } from "../../common/index"; +import { ChainId, CONTRACT_ADDRESSES } from "../../constants/index"; /** * @internal @@ -109,7 +110,28 @@ export const PreDeployMetadata = z }) .catchall(z.any()); -export const ChainIdToAddressSchema = z.record(z.number(), z.string()); +export const ChainIdToAddressSchema = z.record(z.string(), z.string()); + +export const FactoryDeploymentSchema = z.object({ + implementationAddresses: ChainIdToAddressSchema, + implementationInitializerFunction: z.string().default("initialize"), + factoryAddresses: ChainIdToAddressSchema.default({ + [ChainId.Mainnet]: CONTRACT_ADDRESSES[ChainId.Mainnet].twFactory, + [ChainId.Goerli]: CONTRACT_ADDRESSES[ChainId.Goerli].twFactory, + [ChainId.Rinkeby]: CONTRACT_ADDRESSES[ChainId.Rinkeby].twFactory, + [ChainId.Polygon]: CONTRACT_ADDRESSES[ChainId.Polygon].twFactory, + [ChainId.Mumbai]: CONTRACT_ADDRESSES[ChainId.Mumbai].twFactory, + [ChainId.Fantom]: CONTRACT_ADDRESSES[ChainId.Fantom].twFactory, + [ChainId.FantomTestnet]: + CONTRACT_ADDRESSES[ChainId.FantomTestnet].twFactory, + [ChainId.Optimism]: CONTRACT_ADDRESSES[ChainId.Optimism].twFactory, + [ChainId.OptimismTestnet]: + CONTRACT_ADDRESSES[ChainId.OptimismTestnet].twFactory, + [ChainId.Arbitrum]: CONTRACT_ADDRESSES[ChainId.Arbitrum].twFactory, + [ChainId.ArbitrumTestnet]: + CONTRACT_ADDRESSES[ChainId.ArbitrumTestnet].twFactory, + }), +}); /** * @internal @@ -138,10 +160,7 @@ export const ExtraPublishMetadataSchema = z changelog: z.string().optional(), tags: z.array(z.string()).optional(), logo: FileBufferOrStringSchema.nullable().optional(), - factoryAddresses: ChainIdToAddressSchema.optional(), - factoryAbi: AbiSchema.optional(), - deployFunction: z.string().optional(), - deployArgs: z.array(z.any()).optional(), + factoryDeployment: FactoryDeploymentSchema.partial().optional(), }) .catchall(z.any()); export type ExtraPublishMetadata = z.infer; diff --git a/test/before-setup.ts b/test/before-setup.ts index b342abc93..af786d5d1 100644 --- a/test/before-setup.ts +++ b/test/before-setup.ts @@ -46,7 +46,6 @@ import { } from "../src"; import { MockStorage } from "./mock/MockStorage"; import { ChainId } from "../src/constants/chains"; -import { AddressZero } from "@ethersproject/constants"; const RPC_URL = "http://localhost:8545"; @@ -59,6 +58,7 @@ const ipfsGatewayUrl = DEFAULT_IPFS_GATEWAY; let signer: SignerWithAddress; let signers: SignerWithAddress[]; let storage: IStorage; +let implementations: { [key in ContractType]?: string }; const fastForwardTime = async (timeInSeconds: number): Promise => { const now = Math.floor(Date.now() / 1000); @@ -77,6 +77,7 @@ export const expectError = (e: unknown, message: string) => { before(async () => { signers = await hardhatEthers.getSigners(); + implementations = {}; [signer] = signers; @@ -219,6 +220,7 @@ before(async () => { deployedContract.address, ); await tx.wait(); + implementations[contractType as ContractType] = deployedContract.address; } process.env.registryAddress = thirdwebRegistryAddress; @@ -245,4 +247,5 @@ export { registryAddress, fastForwardTime, storage, + implementations, }; diff --git a/test/publisher.test.ts b/test/publisher.test.ts index dd04c8392..dbea1599c 100644 --- a/test/publisher.test.ts +++ b/test/publisher.test.ts @@ -1,7 +1,8 @@ -import { signers } from "./before-setup"; +import { implementations, signers } from "./before-setup"; import { readFileSync } from "fs"; import { expect } from "chai"; import { + ChainId, IpfsStorage, isFeatureEnabled, resolveContractUriFromAddress, @@ -9,8 +10,13 @@ import { } from "../src"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import invariant from "tiny-invariant"; -import { DropERC721__factory, TokenERC721__factory } from "../typechain"; -import { ethers } from "ethers"; +import { + DropERC721__factory, + IContractFactory__factory, + TokenERC721__factory, + TWFactory__factory, +} from "../typechain"; +import { Contract, ethers } from "ethers"; import { AddressZero } from "@ethersproject/constants"; global.fetch = require("cross-fetch"); @@ -223,6 +229,43 @@ describe("Publishing", async () => { expect(all.length).to.be.eq(2); // mock publisher always returns a mock contract }); + it("test factory deploy", async () => { + const realSDK = new ThirdwebSDK(adminWallet); + const pub = await realSDK.getPublisher(); + const tx = await pub.publish( + "ipfs://QmfGqbJKvrVDhw747YPXKf26GiuXXo4GkwUg3FcjgYzx8r", + { + version: "0.0.1", + factoryDeployment: { + implementationAddresses: { + [ChainId.Hardhat]: implementations["nft-collection"] || "", + }, + factoryAddresses: { + [ChainId.Hardhat]: (process.env.factoryAddress as string) || "", + }, + }, + }, + ); + const contract = await tx.data(); + expect(contract.id).to.eq("TokenERC721"); + const deployedAddr = await realSDK.deployer.deployContractFromUri( + contract.metadataUri, + [ + adminWallet.address, + "test factory", + "ffs", + "", + [], + adminWallet.address, + adminWallet.address, + 0, + 0, + adminWallet.address, + ], + ); + expect(deployedAddr.length).to.be.gt(0); + }); + it("SimpleAzuki enumerable", async () => { const realSDK = new ThirdwebSDK(adminWallet); const pub = await realSDK.getPublisher(); From 4a54bfecd701444605c53ccb975be1d431ada814 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 16 Aug 2022 11:12:32 -0700 Subject: [PATCH 3/7] try to detect our prebuilt first --- src/common/feature-detection.ts | 1 + src/core/sdk.ts | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/common/feature-detection.ts b/src/common/feature-detection.ts index 61460a153..67bb9f96b 100644 --- a/src/common/feature-detection.ts +++ b/src/common/feature-detection.ts @@ -325,6 +325,7 @@ export async function fetchContractMetadataFromAddress( address, provider, ); + console.log(`Fetching metadata from ${compilerMetadataUri}`); if (!compilerMetadataUri) { throw new Error(`Could not resolve metadata for contract at ${address}`); } diff --git a/src/core/sdk.ts b/src/core/sdk.ts index e53bb6137..5d39eefe6 100644 --- a/src/core/sdk.ts +++ b/src/core/sdk.ts @@ -437,18 +437,18 @@ export class ThirdwebSDK extends RPCConnectionHandler { return this.contractCache.get(address) as SmartContract; } try { - const publisher = this.getPublisher(); - const metadata = await publisher.fetchCompilerMetadataFromAddress( - address, - ); - return this.getContractFromAbi(address, metadata.abi); - } catch (e) { + // try built in contract first, eventually all our contracts will have bytecode metadata + const contractType = await this.resolveContractType(address); + const abi = KNOWN_CONTRACTS_MAP[contractType].contractAbi; + return this.getContractFromAbi(address, abi); + } catch (err) { try { - // try built in contract instead, eventually all our contracts will have bytecode metadata - const contractType = await this.resolveContractType(address); - const abi = KNOWN_CONTRACTS_MAP[contractType].contractAbi; - return this.getContractFromAbi(address, abi); - } catch (err) { + const publisher = this.getPublisher(); + const metadata = await publisher.fetchCompilerMetadataFromAddress( + address, + ); + return this.getContractFromAbi(address, metadata.abi); + } catch (e) { throw new Error(`Error fetching ABI for this contract\n\n${err}`); } } From 5bb49f794ee0b0d35fc5b44d73872422d519d8b9 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 16 Aug 2022 16:47:17 -0700 Subject: [PATCH 4/7] iterations, make it all work --- docs/sdk.chainidtoaddressschema.md | 11 ------- docs/sdk.factorydeploymentschema.md | 23 --------------- docs/sdk.md | 2 -- etc/sdk.api.md | 41 ++++++++++++++++++-------- src/common/feature-detection.ts | 17 +++++++++++ src/core/classes/contract-deployer.ts | 39 +++++++++++++----------- src/core/classes/contract-metadata.ts | 2 +- src/core/classes/contract-publisher.ts | 10 +++---- src/schema/contracts/custom.ts | 10 ++++++- test/publisher.test.ts | 12 +++----- 10 files changed, 86 insertions(+), 81 deletions(-) delete mode 100644 docs/sdk.chainidtoaddressschema.md delete mode 100644 docs/sdk.factorydeploymentschema.md diff --git a/docs/sdk.chainidtoaddressschema.md b/docs/sdk.chainidtoaddressschema.md deleted file mode 100644 index faec7fe2d..000000000 --- a/docs/sdk.chainidtoaddressschema.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@thirdweb-dev/sdk](./sdk.md) > [ChainIdToAddressSchema](./sdk.chainidtoaddressschema.md) - -## ChainIdToAddressSchema variable - -Signature: - -```typescript -ChainIdToAddressSchema: z.ZodRecord -``` diff --git a/docs/sdk.factorydeploymentschema.md b/docs/sdk.factorydeploymentschema.md deleted file mode 100644 index 838e23aa9..000000000 --- a/docs/sdk.factorydeploymentschema.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@thirdweb-dev/sdk](./sdk.md) > [FactoryDeploymentSchema](./sdk.factorydeploymentschema.md) - -## FactoryDeploymentSchema variable - -Signature: - -```typescript -FactoryDeploymentSchema: z.ZodObject<{ - implementationAddresses: z.ZodRecord; - implementationInitializerFunction: z.ZodDefault; - factoryAddresses: z.ZodDefault>; -}, "strip", z.ZodTypeAny, { - implementationAddresses: Record; - implementationInitializerFunction: string; - factoryAddresses: Record; -}, { - implementationInitializerFunction?: string | undefined; - factoryAddresses?: Record | undefined; - implementationAddresses: Record; -}> -``` diff --git a/docs/sdk.md b/docs/sdk.md index ab502ed2b..019ceebe7 100644 --- a/docs/sdk.md +++ b/docs/sdk.md @@ -119,8 +119,6 @@ | Variable | Description | | --- | --- | | [ALL\_ROLES](./sdk.all_roles.md) | | -| [ChainIdToAddressSchema](./sdk.chainidtoaddressschema.md) | | -| [FactoryDeploymentSchema](./sdk.factorydeploymentschema.md) | | | [MintRequest1155](./sdk.mintrequest1155.md) | | | [MintRequest20](./sdk.mintrequest20.md) | | | [MintRequest721](./sdk.mintrequest721.md) | | diff --git a/etc/sdk.api.md b/etc/sdk.api.md index a0ad5d322..60f50ef14 100644 --- a/etc/sdk.api.md +++ b/etc/sdk.api.md @@ -542,7 +542,9 @@ export enum ChainId { Rinkeby = 4 } -// @public (undocumented) +// Warning: (ae-internal-missing-underscore) The name "ChainIdToAddressSchema" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal (undocumented) export const ChainIdToAddressSchema: z.ZodRecord; // Warning: (ae-internal-missing-underscore) The name "ChainOrRpc" should be prefixed with an underscore because the declaration is marked as @internal @@ -2844,7 +2846,8 @@ export const ExtraPublishMetadataSchema: z.ZodObject<{ changelog: z.ZodOptional; tags: z.ZodOptional>; logo: z.ZodOptional>>; - factoryDeployment: z.ZodOptional; + factoryDeploymentData: z.ZodOptional>; implementationInitializerFunction: z.ZodOptional>; factoryAddresses: z.ZodOptional>>; @@ -2866,7 +2869,8 @@ export const ExtraPublishMetadataSchema: z.ZodObject<{ changelog?: string | undefined; tags?: string[] | undefined; logo?: any; - factoryDeployment?: { + isDeployableViaFactory?: boolean | undefined; + factoryDeploymentData?: { implementationAddresses?: Record | undefined; implementationInitializerFunction?: string | undefined; factoryAddresses?: Record | undefined; @@ -2881,7 +2885,8 @@ export const ExtraPublishMetadataSchema: z.ZodObject<{ changelog?: string | undefined; tags?: string[] | undefined; logo?: any; - factoryDeployment?: { + isDeployableViaFactory?: boolean | undefined; + factoryDeploymentData?: { implementationAddresses?: Record | undefined; implementationInitializerFunction?: string | undefined; factoryAddresses?: Record | undefined; @@ -2889,7 +2894,9 @@ export const ExtraPublishMetadataSchema: z.ZodObject<{ version: string; }>; -// @public (undocumented) +// Warning: (ae-internal-missing-underscore) The name "FactoryDeploymentSchema" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal (undocumented) export const FactoryDeploymentSchema: z.ZodObject<{ implementationAddresses: z.ZodRecord; implementationInitializerFunction: z.ZodDefault; @@ -2960,9 +2967,14 @@ export class FetchError extends Error { innerError?: Error; } +// Warning: (ae-internal-missing-underscore) The name "fetchExtendedReleaseMetadata" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal +export function fetchExtendedReleaseMetadata(publishMetadataUri: string, storage: IStorage): Promise; + // Warning: (ae-internal-missing-underscore) The name "fetchPreDeployMetadata" should be prefixed with an underscore because the declaration is marked as @internal // -// @internal (undocumented) +// @internal export function fetchPreDeployMetadata(publishMetadataUri: string, storage: IStorage): Promise; // Warning: (ae-internal-missing-underscore) The name "fetchRawPredeployMetadata" should be prefixed with an underscore because the declaration is marked as @internal @@ -3053,7 +3065,8 @@ export const FullPublishMetadataSchema: z.ZodObject; tags: z.ZodOptional>; logo: z.ZodOptional>>; - factoryDeployment: z.ZodOptional; + factoryDeploymentData: z.ZodOptional>; implementationInitializerFunction: z.ZodOptional>; factoryAddresses: z.ZodOptional>>; @@ -3079,7 +3092,8 @@ export const FullPublishMetadataSchema: z.ZodObject | undefined; implementationInitializerFunction?: string | undefined; factoryAddresses?: Record | undefined; @@ -3099,7 +3113,8 @@ export const FullPublishMetadataSchema: z.ZodObject | undefined; implementationInitializerFunction?: string | undefined; factoryAddresses?: Record | undefined; @@ -7242,10 +7257,10 @@ export class WrongListingTypeError extends Error { // Warnings were encountered during analysis: // -// dist/src/schema/contracts/custom.d.ts:1349:5 - (ae-incompatible-release-tags) The symbol "inputs" is marked as @public, but its signature references "AbiTypeSchema" which is marked as @internal -// dist/src/schema/contracts/custom.d.ts:1350:5 - (ae-incompatible-release-tags) The symbol "outputs" is marked as @public, but its signature references "AbiTypeSchema" which is marked as @internal -// dist/src/schema/contracts/custom.d.ts:1357:5 - (ae-incompatible-release-tags) The symbol "inputs" is marked as @public, but its signature references "AbiTypeSchema" which is marked as @internal -// dist/src/schema/contracts/custom.d.ts:1358:5 - (ae-incompatible-release-tags) The symbol "outputs" is marked as @public, but its signature references "AbiTypeSchema" which is marked as @internal +// dist/src/schema/contracts/custom.d.ts:1361:5 - (ae-incompatible-release-tags) The symbol "inputs" is marked as @public, but its signature references "AbiTypeSchema" which is marked as @internal +// dist/src/schema/contracts/custom.d.ts:1362:5 - (ae-incompatible-release-tags) The symbol "outputs" is marked as @public, but its signature references "AbiTypeSchema" which is marked as @internal +// dist/src/schema/contracts/custom.d.ts:1369:5 - (ae-incompatible-release-tags) The symbol "inputs" is marked as @public, but its signature references "AbiTypeSchema" which is marked as @internal +// dist/src/schema/contracts/custom.d.ts:1370:5 - (ae-incompatible-release-tags) The symbol "outputs" is marked as @public, but its signature references "AbiTypeSchema" which is marked as @internal // (No @packageDocumentation comment for this package) diff --git a/src/common/feature-detection.ts b/src/common/feature-detection.ts index 67bb9f96b..a005280e9 100644 --- a/src/common/feature-detection.ts +++ b/src/common/feature-detection.ts @@ -8,6 +8,8 @@ import { AbiTypeSchema, ContractInfoSchema, ContractSource, + FullPublishMetadata, + FullPublishMetadataSchema, PreDeployMetadata, PreDeployMetadataFetched, PreDeployMetadataFetchedSchema, @@ -420,6 +422,7 @@ export async function fetchRawPredeployMetadata( } /** + * Fetch the metadata coming from CLI, this is before deploying or releasing the contract. * @internal * @param publishMetadataUri * @param storage @@ -438,6 +441,20 @@ export async function fetchPreDeployMetadata( }); } +/** + * Fetch and parse the full metadata AFTER creating a release, with all the extra information (version, readme, etc) + * @internal + * @param publishMetadataUri + * @param storage + */ +export async function fetchExtendedReleaseMetadata( + publishMetadataUri: string, + storage: IStorage, +): Promise { + const meta = await storage.getRaw(publishMetadataUri); + return FullPublishMetadataSchema.parse(JSON.parse(meta)); +} + /** * Processes ALL supported features and sets whether the passed in abi supports each individual feature * @internal diff --git a/src/core/classes/contract-deployer.ts b/src/core/classes/contract-deployer.ts index 22121b9ec..350426727 100644 --- a/src/core/classes/contract-deployer.ts +++ b/src/core/classes/contract-deployer.ts @@ -33,13 +33,11 @@ import invariant from "tiny-invariant"; import { extractConstructorParamsFromAbi, extractFunctionParamsFromAbi, + fetchExtendedReleaseMetadata, fetchPreDeployMetadata, } from "../../common/index"; import { BigNumber, BytesLike, ContractInterface, ethers } from "ethers"; -import { - FactoryDeploymentSchema, - FullPublishMetadataSchema, -} from "../../schema/contracts/custom"; +import { FactoryDeploymentSchema } from "../../schema/contracts/custom"; /** * Handles deploying new contracts @@ -490,31 +488,34 @@ export class ContractDeployer extends RPCConnectionHandler { publishMetadataUri, this.storage, ); - - let factoryDeployment; + let isDeployableViaFactory; + let factoryDeploymentData; try { - const meta = await this.storage.getRaw(publishMetadataUri); - const publishMetadata = FullPublishMetadataSchema.parse(JSON.parse(meta)); - factoryDeployment = FactoryDeploymentSchema.parse( - publishMetadata.factoryDeployment, + const extendedMetadata = await fetchExtendedReleaseMetadata( + publishMetadataUri, + this.storage, + ); + isDeployableViaFactory = extendedMetadata.isDeployableViaFactory; + factoryDeploymentData = FactoryDeploymentSchema.parse( + extendedMetadata.factoryDeploymentData, ); } catch (e) { // not a factory deployment, ignore } - if (factoryDeployment) { + if (isDeployableViaFactory && factoryDeploymentData) { const chainId = (await this.getProvider().getNetwork()).chainId; invariant( - factoryDeployment.factoryAddresses, + factoryDeploymentData.factoryAddresses, "factoryAddresses is required", ); invariant( - factoryDeployment.implementationAddresses, + factoryDeploymentData.implementationAddresses, "implementationAddresses is required", ); - const factoryAddress = factoryDeployment.factoryAddresses[chainId]; + const factoryAddress = factoryDeploymentData.factoryAddresses[chainId]; const implementationAddress = - factoryDeployment.implementationAddresses[chainId]; + factoryDeploymentData.implementationAddresses[chainId]; invariant( factoryAddress, `factoryAddress not found for chainId '${chainId}'`, @@ -523,9 +524,13 @@ export class ContractDeployer extends RPCConnectionHandler { implementationAddress, `implementationAddress not found for chainId '${chainId}'`, ); + invariant( + factoryDeploymentData.implementationInitializerFunction, + `implementationInitializerFunction not set'`, + ); const initializerParamTypes = extractFunctionParamsFromAbi( compilerMetadata.abi, - factoryDeployment.implementationInitializerFunction, + factoryDeploymentData.implementationInitializerFunction, ).map((p) => p.type); const paramValues = this.convertParamValues( initializerParamTypes, @@ -535,7 +540,7 @@ export class ContractDeployer extends RPCConnectionHandler { factoryAddress, implementationAddress, compilerMetadata.abi, - factoryDeployment.implementationInitializerFunction, + factoryDeploymentData.implementationInitializerFunction, paramValues, ); } diff --git a/src/core/classes/contract-metadata.ts b/src/core/classes/contract-metadata.ts index 9f279c435..e9d30552a 100644 --- a/src/core/classes/contract-metadata.ts +++ b/src/core/classes/contract-metadata.ts @@ -81,7 +81,7 @@ export class ContractMetadata< let data; if (this.supportsContractMetadata(this.contractWrapper)) { const uri = await this.contractWrapper.readContract.contractURI(); - if (uri && uri.length > 0) { + if (uri && uri.trim().length > 0) { data = await this.storage.get(uri); } } diff --git a/src/core/classes/contract-publisher.ts b/src/core/classes/contract-publisher.ts index a70f1851e..7512c7df3 100644 --- a/src/core/classes/contract-publisher.ts +++ b/src/core/classes/contract-publisher.ts @@ -8,6 +8,7 @@ import { extractConstructorParams, extractFunctions, fetchContractMetadataFromAddress, + fetchExtendedReleaseMetadata, fetchPreDeployMetadata, fetchRawPredeployMetadata, fetchSourceFilesFromMetadata, @@ -148,7 +149,7 @@ export class ContractPublisher extends RPCConnectionHandler { return { name: contract.id, publishedTimestamp: contract.timestamp, - publishedMetadata: await this.fetchPublishedMetadata( + publishedMetadata: await this.fetchFullPublishMetadata( contract.metadataUri, ), }; @@ -158,11 +159,10 @@ export class ContractPublisher extends RPCConnectionHandler { * @internal * @param publishedMetadataUri */ - public async fetchPublishedMetadata( + public async fetchFullPublishMetadata( publishedMetadataUri: string, ): Promise { - const meta = await this.storage.getRaw(publishedMetadataUri); - return FullPublishMetadataSchema.parse(JSON.parse(meta)); + return fetchExtendedReleaseMetadata(publishedMetadataUri, this.storage); } /** @@ -186,7 +186,7 @@ export class ContractPublisher extends RPCConnectionHandler { return await Promise.all( publishedMetadataUri .filter((uri) => uri.length > 0) - .map((uri) => this.fetchPublishedMetadata(uri)), + .map((uri) => this.fetchFullPublishMetadata(uri)), ); } diff --git a/src/schema/contracts/custom.ts b/src/schema/contracts/custom.ts index a13d8bc40..7143af4d6 100644 --- a/src/schema/contracts/custom.ts +++ b/src/schema/contracts/custom.ts @@ -110,8 +110,15 @@ export const PreDeployMetadata = z }) .catchall(z.any()); +/** + * @internal + */ export const ChainIdToAddressSchema = z.record(z.string(), z.string()); +/** + * @internal + */ +// TODO should have an input and ouput version of this schema export const FactoryDeploymentSchema = z.object({ implementationAddresses: ChainIdToAddressSchema, implementationInitializerFunction: z.string().default("initialize"), @@ -160,7 +167,8 @@ export const ExtraPublishMetadataSchema = z changelog: z.string().optional(), tags: z.array(z.string()).optional(), logo: FileBufferOrStringSchema.nullable().optional(), - factoryDeployment: FactoryDeploymentSchema.partial().optional(), + isDeployableViaFactory: z.boolean().optional(), + factoryDeploymentData: FactoryDeploymentSchema.partial().optional(), }) .catchall(z.any()); export type ExtraPublishMetadata = z.infer; diff --git a/test/publisher.test.ts b/test/publisher.test.ts index dbea1599c..a79481f80 100644 --- a/test/publisher.test.ts +++ b/test/publisher.test.ts @@ -10,13 +10,8 @@ import { } from "../src"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import invariant from "tiny-invariant"; -import { - DropERC721__factory, - IContractFactory__factory, - TokenERC721__factory, - TWFactory__factory, -} from "../typechain"; -import { Contract, ethers } from "ethers"; +import { DropERC721__factory, TokenERC721__factory } from "../typechain"; +import { ethers } from "ethers"; import { AddressZero } from "@ethersproject/constants"; global.fetch = require("cross-fetch"); @@ -236,7 +231,8 @@ describe("Publishing", async () => { "ipfs://QmfGqbJKvrVDhw747YPXKf26GiuXXo4GkwUg3FcjgYzx8r", { version: "0.0.1", - factoryDeployment: { + isDeployableViaFactory: true, + factoryDeploymentData: { implementationAddresses: { [ChainId.Hardhat]: implementations["nft-collection"] || "", }, From bc7309781f33c193fb341e28656a534c88129abd Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 16 Aug 2022 16:50:51 -0700 Subject: [PATCH 5/7] v2.3.37-0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c215a976b..cc3e90778 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@thirdweb-dev/sdk", - "version": "2.3.36", + "version": "2.3.37-0", "description": "The main thirdweb SDK.", "repository": { "type": "git", From 4db3572fb3eca1b9e829568927e135d5cafe0623 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 17 Aug 2022 14:20:19 -0700 Subject: [PATCH 6/7] remove log --- src/common/feature-detection.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/common/feature-detection.ts b/src/common/feature-detection.ts index a005280e9..2e723da2e 100644 --- a/src/common/feature-detection.ts +++ b/src/common/feature-detection.ts @@ -327,7 +327,6 @@ export async function fetchContractMetadataFromAddress( address, provider, ); - console.log(`Fetching metadata from ${compilerMetadataUri}`); if (!compilerMetadataUri) { throw new Error(`Could not resolve metadata for contract at ${address}`); } From c8517287f29a8aff8468dd66fc3791eb5aa666c7 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 17 Aug 2022 20:25:01 -0700 Subject: [PATCH 7/7] run build and more specific uri check for metadata --- src/core/classes/contract-metadata.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/classes/contract-metadata.ts b/src/core/classes/contract-metadata.ts index e9d30552a..0d01e5d13 100644 --- a/src/core/classes/contract-metadata.ts +++ b/src/core/classes/contract-metadata.ts @@ -81,7 +81,7 @@ export class ContractMetadata< let data; if (this.supportsContractMetadata(this.contractWrapper)) { const uri = await this.contractWrapper.readContract.contractURI(); - if (uri && uri.trim().length > 0) { + if (uri && uri.includes("://")) { data = await this.storage.get(uri); } }