From 5607e5c1896294352e585fc43d275b017d67368d Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Thu, 11 Aug 2022 13:30:57 -0700 Subject: [PATCH] Allow deploying any released contracts from SDK --- ...contractdeployer.deployreleasedcontract.md | 26 ++++ docs/sdk.contractdeployer.md | 1 + etc/sdk.api.md | 7 + src/core/classes/contract-deployer.ts | 118 ++++++++++++++++- src/core/classes/contract-publisher.ts | 123 +----------------- test/custom.test.ts | 3 +- test/publisher.test.ts | 34 ++--- 7 files changed, 166 insertions(+), 146 deletions(-) create mode 100644 docs/sdk.contractdeployer.deployreleasedcontract.md diff --git a/docs/sdk.contractdeployer.deployreleasedcontract.md b/docs/sdk.contractdeployer.deployreleasedcontract.md new file mode 100644 index 000000000..8c24a52c2 --- /dev/null +++ b/docs/sdk.contractdeployer.deployreleasedcontract.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [@thirdweb-dev/sdk](./sdk.md) > [ContractDeployer](./sdk.contractdeployer.md) > [deployReleasedContract](./sdk.contractdeployer.deployreleasedcontract.md) + +## ContractDeployer.deployReleasedContract() method + +Deploy any released contract by its name + +Signature: + +```typescript +deployReleasedContract(releaserAddress: string, contractName: string, constructorParams: any[]): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| releaserAddress | string | the address of the releaser | +| contractName | string | the name of the contract to deploy | +| constructorParams | any\[\] | the constructor params to pass to the contract | + +Returns: + +Promise<string> + diff --git a/docs/sdk.contractdeployer.md b/docs/sdk.contractdeployer.md index 4a802bd56..528c62088 100644 --- a/docs/sdk.contractdeployer.md +++ b/docs/sdk.contractdeployer.md @@ -30,6 +30,7 @@ export declare class ContractDeployer extends RPCConnectionHandler | [deployNFTCollection(metadata)](./sdk.contractdeployer.deploynftcollection.md) | | Deploys an NFT Collection contract | | [deployNFTDrop(metadata)](./sdk.contractdeployer.deploynftdrop.md) | | Deploys a new NFTDrop contract | | [deployPack(metadata)](./sdk.contractdeployer.deploypack.md) | | Deploys a new Pack contract | +| [deployReleasedContract(releaserAddress, contractName, constructorParams)](./sdk.contractdeployer.deployreleasedcontract.md) | | Deploy any released contract by its name | | [deploySignatureDrop(metadata)](./sdk.contractdeployer.deploysignaturedrop.md) | | Deploys a new SignatureDrop contract | | [deploySplit(metadata)](./sdk.contractdeployer.deploysplit.md) | | Deploys a new Split contract | | [deployToken(metadata)](./sdk.contractdeployer.deploytoken.md) | | Deploys a new Token contract | diff --git a/etc/sdk.api.md b/etc/sdk.api.md index 285b5441b..ee9f0bbed 100644 --- a/etc/sdk.api.md +++ b/etc/sdk.api.md @@ -1151,6 +1151,12 @@ export class ContractDeployer extends RPCConnectionHandler { constructor(network: NetworkOrSignerOrProvider, options: SDKOptions, storage: IStorage); // @internal deployBuiltInContract(contractType: TContract["contractType"], contractMetadata: z.input): Promise; + // @internal (undocumented) + deployContractFromUri(publishMetadataUri: string, constructorParamValues: any[]): Promise; + // @internal (undocumented) + deployContractWithAbi(abi: ContractInterface, bytecode: BytesLike | { + object: string; + }, constructorParams: Array): Promise; deployEdition(metadata: NFTContractDeployMetadata): Promise; deployEditionDrop(metadata: NFTContractDeployMetadata): Promise; deployMarketplace(metadata: MarketplaceContractDeployMetadata): Promise; @@ -1159,6 +1165,7 @@ export class ContractDeployer extends RPCConnectionHandler { deployNFTCollection(metadata: NFTContractDeployMetadata): Promise; deployNFTDrop(metadata: NFTContractDeployMetadata): Promise; deployPack(metadata: NFTContractDeployMetadata): Promise; + deployReleasedContract(releaserAddress: string, contractName: string, constructorParams: any[]): Promise; deploySignatureDrop(metadata: NFTContractDeployMetadata): Promise; deploySplit(metadata: SplitContractDeployMetadata): Promise; deployToken(metadata: TokenContractDeployMetadata): Promise; diff --git a/src/core/classes/contract-deployer.ts b/src/core/classes/contract-deployer.ts index 97a3bdb6b..31194bb47 100644 --- a/src/core/classes/contract-deployer.ts +++ b/src/core/classes/contract-deployer.ts @@ -12,8 +12,8 @@ import { Marketplace, NFTCollection, NFTDrop, - SignatureDrop, Pack, + SignatureDrop, Split, Token, Vote, @@ -28,6 +28,13 @@ import { } from "../../types/deploy/deploy-metadata"; import { TokenDrop } from "../../contracts/token-drop"; import { Multiwrap } from "../../contracts/multiwrap"; +import { ThirdwebSDK } from "../sdk"; +import invariant from "tiny-invariant"; +import { + extractConstructorParamsFromAbi, + fetchPreDeployMetadata, +} from "../../common/index"; +import { BigNumber, BytesLike, ContractInterface, ethers } from "ethers"; /** * Handles deploying new contracts @@ -341,6 +348,26 @@ export class ContractDeployer extends RPCConnectionHandler { return await factory.deploy(contractType, contractMetadata); } + /** + * Deploy any released contract by its name + * @param releaserAddress the address of the releaser + * @param contractName the name of the contract to deploy + * @param constructorParams the constructor params to pass to the contract + */ + public async deployReleasedContract( + releaserAddress: string, + contractName: string, + constructorParams: any[], + ): Promise { + const release = await new ThirdwebSDK("polygon") + .getPublisher() + .getLatest(releaserAddress, contractName); + return await this.deployContractFromUri( + release.metadataUri, + constructorParams, + ); + } + /** * @internal */ @@ -410,4 +437,93 @@ export class ContractDeployer extends RPCConnectionHandler { registry.updateSignerOrProvider(this.getSignerOrProvider()); }); } + + /** + * @internal + * @param publishMetadataUri + * @param constructorParamValues + */ + public async deployContractFromUri( + publishMetadataUri: string, + constructorParamValues: any[], + ) { + const signer = this.getSigner(); + invariant(signer, "A signer is required"); + const metadata = await fetchPreDeployMetadata( + publishMetadataUri, + this.storage, + ); + const bytecode = metadata.bytecode.startsWith("0x") + ? metadata.bytecode + : `0x${metadata.bytecode}`; + if (!ethers.utils.isHexString(bytecode)) { + throw new Error(`Contract bytecode is invalid.\n\n${bytecode}`); + } + const constructorParamTypes = extractConstructorParamsFromAbi( + metadata.abi, + ).map((p) => p.type); + const paramValues = this.convertParamValues( + constructorParamTypes, + constructorParamValues, + ); + return this.deployContractWithAbi(metadata.abi, bytecode, paramValues); + } + + private convertParamValues( + constructorParamTypes: string[], + constructorParamValues: any[], + ) { + // check that both arrays are same length + if (constructorParamTypes.length !== constructorParamValues.length) { + throw Error("Passed the wrong number of constructor arguments"); + } + return constructorParamTypes.map((p, index) => { + if (p === "tuple" || p.endsWith("[]")) { + if (typeof constructorParamValues[index] === "string") { + return JSON.parse(constructorParamValues[index]); + } else { + return constructorParamValues[index]; + } + } + if (p === "bytes32") { + invariant( + ethers.utils.isHexString(constructorParamValues[index]), + `Could not parse bytes32 value. Expected valid hex string but got "${constructorParamValues[index]}".`, + ); + return ethers.utils.hexZeroPad(constructorParamValues[index], 32); + } + if (p.startsWith("bytes")) { + invariant( + ethers.utils.isHexString(constructorParamValues[index]), + `Could not parse bytes value. Expected valid hex string but got "${constructorParamValues[index]}".`, + ); + return ethers.utils.toUtf8Bytes(constructorParamValues[index]); + } + if (p.startsWith("uint") || p.startsWith("int")) { + return BigNumber.from(constructorParamValues[index].toString()); + } + return constructorParamValues[index]; + }); + } + + /** + * @internal + * @param abi + * @param bytecode + * @param constructorParams + */ + public async deployContractWithAbi( + abi: ContractInterface, + bytecode: BytesLike | { object: string }, + constructorParams: Array, + ): Promise { + const signer = this.getSigner(); + invariant(signer, "Signer is required to deploy contracts"); + const deployer = await new ethers.ContractFactory(abi, bytecode) + .connect(signer) + .deploy(...constructorParams); + const deployedContract = await deployer.deployed(); + // TODO parse transaction receipt + return deployedContract.address; + } } diff --git a/src/core/classes/contract-publisher.ts b/src/core/classes/contract-publisher.ts index f70bd2f2b..a70f1851e 100644 --- a/src/core/classes/contract-publisher.ts +++ b/src/core/classes/contract-publisher.ts @@ -2,18 +2,10 @@ import { NetworkOrSignerOrProvider, TransactionResult } from "../types"; import { SDKOptions } from "../../schema/sdk-options"; import { IStorage } from "../interfaces"; import { RPCConnectionHandler } from "./rpc-connection-handler"; -import { - BigNumber, - BytesLike, - constants, - ContractInterface, - ethers, - utils, -} from "ethers"; +import { constants, utils } from "ethers"; import invariant from "tiny-invariant"; import { extractConstructorParams, - extractConstructorParamsFromAbi, extractFunctions, fetchContractMetadataFromAddress, fetchPreDeployMetadata, @@ -376,119 +368,6 @@ export class ContractPublisher extends RPCConnectionHandler { }; } - /** - * @internal - * @param publisherAddress - * @param contractId - * @param constructorParamValues - * @param contractMetadata - */ - public async deployPublishedContract( - publisherAddress: string, - contractId: string, - constructorParamValues: any[], - ): Promise { - // TODO this gets the latest version, should we allow deploying a certain version? - const contract = await this.publisher.readContract.getPublishedContract( - publisherAddress, - contractId, - ); - return this.deployContract( - contract.publishMetadataUri, - constructorParamValues, - ); - } - - /** - * @internal - * @param publishMetadataUri - * @param constructorParamValues - */ - public async deployContract( - publishMetadataUri: string, - constructorParamValues: any[], - ) { - const signer = this.getSigner(); - invariant(signer, "A signer is required"); - const metadata = await fetchPreDeployMetadata( - publishMetadataUri, - this.storage, - ); - const bytecode = metadata.bytecode.startsWith("0x") - ? metadata.bytecode - : `0x${metadata.bytecode}`; - if (!ethers.utils.isHexString(bytecode)) { - throw new Error(`Contract bytecode is invalid.\n\n${bytecode}`); - } - const constructorParamTypes = extractConstructorParamsFromAbi( - metadata.abi, - ).map((p) => p.type); - const paramValues = this.convertParamValues( - constructorParamTypes, - constructorParamValues, - ); - - return this.deployContractWithAbi(metadata.abi, bytecode, paramValues); - } - - private convertParamValues( - constructorParamTypes: string[], - constructorParamValues: any[], - ) { - // check that both arrays are same length - if (constructorParamTypes.length !== constructorParamValues.length) { - throw Error("Passed the wrong number of constructor arguments"); - } - return constructorParamTypes.map((p, index) => { - if (p === "tuple" || p.endsWith("[]")) { - if (typeof constructorParamValues[index] === "string") { - return JSON.parse(constructorParamValues[index]); - } else { - return constructorParamValues[index]; - } - } - if (p === "bytes32") { - invariant( - ethers.utils.isHexString(constructorParamValues[index]), - `Could not parse bytes32 value. Expected valid hex string but got "${constructorParamValues[index]}".`, - ); - return ethers.utils.hexZeroPad(constructorParamValues[index], 32); - } - if (p.startsWith("bytes")) { - invariant( - ethers.utils.isHexString(constructorParamValues[index]), - `Could not parse bytes value. Expected valid hex string but got "${constructorParamValues[index]}".`, - ); - return ethers.utils.toUtf8Bytes(constructorParamValues[index]); - } - if (p.startsWith("uint") || p.startsWith("int")) { - return BigNumber.from(constructorParamValues[index].toString()); - } - return constructorParamValues[index]; - }); - } - - /** - * @internal - * @param abi - * @param bytecode - * @param constructorParams - */ - public async deployContractWithAbi( - abi: ContractInterface, - bytecode: BytesLike | { object: string }, - constructorParams: Array, - ): Promise { - const signer = this.getSigner(); - invariant(signer, "Signer is required to deploy contracts"); - const deployer = await new ethers.ContractFactory(abi, bytecode) - .connect(signer) - .deploy(...constructorParams); - const deployedContract = await deployer.deployed(); - // TODO parse transaction receipt - return deployedContract.address; - } - private toPublishedContract( contractModel: IContractPublisher.CustomContractInstanceStruct, ): PublishedContract { diff --git a/test/custom.test.ts b/test/custom.test.ts index 1675c2056..6d41255d0 100644 --- a/test/custom.test.ts +++ b/test/custom.test.ts @@ -42,8 +42,7 @@ describe("Custom Contracts", async () => { beforeEach(async () => { sdk.updateSignerOrProvider(adminWallet); - const publisher = sdk.getPublisher(); - customContractAddress = await publisher.deployContract( + customContractAddress = await sdk.deployer.deployContractFromUri( simpleContractUri, [], ); diff --git a/test/publisher.test.ts b/test/publisher.test.ts index 1000a4804..1b475b0be 100644 --- a/test/publisher.test.ts +++ b/test/publisher.test.ts @@ -106,9 +106,8 @@ describe("Publishing", async () => { version: "0.0.1", }); const contract = await tx.data(); - const deployedAddr = await publisher.deployPublishedContract( - adminWallet.address, - contract.id, + const deployedAddr = await sdk.deployer.deployContractFromUri( + contract.metadataUri, [], ); expect(deployedAddr.length).to.be.gt(0); @@ -132,9 +131,8 @@ describe("Publishing", async () => { version: "0.0.2", }); const contract = await tx.data(); - const deployedAddr = await publisher.deployPublishedContract( - adminWallet.address, - contract.id, + const deployedAddr = await sdk.deployer.deployContractFromUri( + contract.metadataUri, [], ); expect(deployedAddr.length).to.be.gt(0); @@ -210,9 +208,8 @@ describe("Publishing", async () => { version: "0.0.1", }); const contract = await tx.data(); - const deployedAddr = await publisher.deployPublishedContract( - bobWallet.address, - contract.id, + const deployedAddr = await sdk.deployer.deployContractFromUri( + contract.metadataUri, [ adminWallet.address, "0x1234", @@ -234,9 +231,8 @@ describe("Publishing", async () => { version: "0.0.1", }); const contract = await tx.data(); - const deployedAddr = await pub.deployPublishedContract( - adminWallet.address, - contract.id, + const deployedAddr = await sdk.deployer.deployContractFromUri( + contract.metadataUri, [], ); const c = await realSDK.getContract(deployedAddr); @@ -253,9 +249,8 @@ describe("Publishing", async () => { version: "0.0.1", }); const contract = await tx.data(); - const deployedAddr = await pub.deployPublishedContract( - adminWallet.address, - contract.id, + const deployedAddr = await sdk.deployer.deployContractFromUri( + contract.metadataUri, [10, "bar"], ); const c = await sdk.getContract(deployedAddr); @@ -284,9 +279,8 @@ describe("Publishing", async () => { }); it("ERC721Dropable multiphase feature detection", async () => { - const pub = sdk.getPublisher(); const ipfsUri = "ipfs://QmWaidQMSYHPzYYZCxMc2nSk2vrD28mS43Xc9k7QFyAGja/0"; - const addr = await pub.deployContract(ipfsUri, []); + const addr = await sdk.deployer.deployContractFromUri(ipfsUri, []); const c = await sdk.getContract(addr); invariant(c.nft, "nft must be defined"); @@ -312,9 +306,8 @@ describe("Publishing", async () => { }); it("ERC721Drop base feature detection", async () => { - const pub = sdk.getPublisher(); const ipfsUri = "ipfs://QmfQwWiMbKaSmng5GN1P5bgCfdEy4Uyg7BznwbaP1bvj7f/0"; - const addr = await pub.deployContract(ipfsUri, []); + const addr = await sdk.deployer.deployContractFromUri(ipfsUri, []); const c = await sdk.getContract(addr); invariant(c.nft, "nft must be defined"); @@ -353,9 +346,8 @@ describe("Publishing", async () => { }); it("Constructor params with tuples", async () => { - const pub = await sdk.getPublisher(); const ipfsUri = "ipfs://QmZQa56Cj1gFnZgKSkvGE5uzhaQrQV3nU6upDWDusCaCwY/0"; - const addr = await pub.deployContract(ipfsUri, [ + const addr = await sdk.deployer.deployContractFromUri(ipfsUri, [ "0x1234", "123", JSON.stringify(["0x1234", "0x4567"]),