diff --git a/.changeset/fresh-gorillas-sip.md b/.changeset/fresh-gorillas-sip.md new file mode 100644 index 00000000000..b61dcc77a7e --- /dev/null +++ b/.changeset/fresh-gorillas-sip.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +Compute addresses with ref contracts diff --git a/packages/thirdweb/src/extensions/prebuilts/compute-ref-deployments.ts b/packages/thirdweb/src/extensions/prebuilts/compute-ref-deployments.ts new file mode 100644 index 00000000000..5d33071c4bc --- /dev/null +++ b/packages/thirdweb/src/extensions/prebuilts/compute-ref-deployments.ts @@ -0,0 +1,161 @@ +import type { Chain } from "../../chains/types.js"; +import type { ThirdwebClient } from "../../client/client.js"; +import { encodeAbiParameters } from "../../utils/abi/encodeAbiParameters.js"; +import { computePublishedContractAddress } from "../../utils/any-evm/compute-published-contract-address.js"; +import type { ImplementationConstructorParam } from "./process-ref-deployments.js"; + +type ComputeRefDeploymentsOptions = { + client: ThirdwebClient; + chain: Chain; + paramValue: string | ImplementationConstructorParam; +}; + +/** + * Computes addresses for published contract references in constructor params. + * @returns Param value after processing references. + * @internal + */ +export async function computeRefDeployments( + options: ComputeRefDeploymentsOptions, +): Promise { + const { client, chain, paramValue } = options; + + if (typeof paramValue === "object") { + if ( + "defaultValue" in paramValue && + paramValue.defaultValue && + paramValue.defaultValue.length > 0 + ) { + return paramValue.defaultValue; + } + + if ("dynamicValue" in paramValue && paramValue.dynamicValue) { + const dynamicValue = paramValue.dynamicValue; + const contracts = dynamicValue.refContracts; + + if (dynamicValue.type === "address") { + if (!contracts || contracts.length === 0 || !contracts[0]?.contractId) { + throw new Error("Invalid or empty param value"); + } + const salt = + contracts[0]?.salt && contracts[0]?.salt.length > 0 + ? contracts[0]?.salt + : ""; + + const addr = await computePublishedContractAddress({ + client, + chain, + contractId: contracts[0]?.contractId, + publisher: contracts[0]?.publisherAddress, + version: contracts[0]?.version, + salt, + }); + + return addr; + } + + if (dynamicValue.type === "address[]") { + if (!contracts || contracts.length === 0) { + throw new Error("Invalid or empty param value"); + } + const addressArray: string[] = []; + + for (const c of contracts) { + const salt = c?.salt && c?.salt.length > 0 ? c?.salt : ""; + + addressArray.push( + await computePublishedContractAddress({ + client, + chain, + contractId: c.contractId, + publisher: c.publisherAddress, + version: c.version, + salt, + }), + ); + } + + return addressArray; + } + + if (dynamicValue.type === "bytes") { + if (!dynamicValue.paramsToEncode) { + throw new Error("Invalid or empty param value"); + } + const paramsToEncode = dynamicValue.paramsToEncode[0]; + + if (paramsToEncode) { + const types: string[] = []; + const values: (string | string[])[] = []; + for (const v of paramsToEncode) { + types.push(v.type); + + if (v.defaultValue) { + values.push(v.defaultValue); + } else if (v.dynamicValue) { + values.push( + await computeRefDeployments({ + client, + chain, + paramValue: v, + }), + ); + } + } + + return encodeAbiParameters( + types.map((t) => { + return { type: t }; + }), + values, + ); + } + } + + if (dynamicValue.type === "bytes[]") { + if (!dynamicValue.paramsToEncode) { + throw new Error("Invalid or empty param value"); + } + const bytesArray: string[] = []; + const paramArray = dynamicValue.paramsToEncode; + + for (const a of paramArray) { + const paramsToEncode = a; + + if (paramsToEncode) { + const types: string[] = []; + const values: (string | string[])[] = []; + for (const v of paramsToEncode) { + types.push(v.type); + + if (v.defaultValue) { + values.push(v.defaultValue); + } else if (v.dynamicValue) { + values.push( + await computeRefDeployments({ + client, + chain, + paramValue: v, + }), + ); + } + } + + bytesArray.push( + encodeAbiParameters( + types.map((t) => { + return { type: t }; + }), + values, + ), + ); + } + } + + return bytesArray; + } + } + } + + return paramValue as string; +} diff --git a/packages/thirdweb/src/extensions/prebuilts/process-ref-deployments.test.ts b/packages/thirdweb/src/extensions/prebuilts/process-ref-deployments.test.ts index 9c377cd698d..11ab7c72c34 100644 --- a/packages/thirdweb/src/extensions/prebuilts/process-ref-deployments.test.ts +++ b/packages/thirdweb/src/extensions/prebuilts/process-ref-deployments.test.ts @@ -3,6 +3,7 @@ import { ANVIL_CHAIN } from "../../../test/src/chains.js"; import { TEST_CLIENT } from "../../../test/src/test-clients.js"; import { TEST_ACCOUNT_A } from "../../../test/src/test-wallets.js"; import { getContract } from "../../contract/contract.js"; +import { getDeployedInfraContract } from "../../contract/deployment/utils/infra.js"; import { readContract } from "../../transaction/read-contract.js"; import { getInstalledModules } from "../modules/__generated__/IModularCore/read/getInstalledModules.js"; import { deployPublishedContract } from "./deploy-published.js"; @@ -29,7 +30,7 @@ describe.runIf(process.env.TW_SECRET_KEY)( account: TEST_ACCOUNT_A, contractId: "MultiSig", version: "0.0.4", - salt: "tw", + salt: "", publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936", }); dummyContractAddress = await deployPublishedContract({ @@ -37,8 +38,8 @@ describe.runIf(process.env.TW_SECRET_KEY)( chain: ANVIL_CHAIN, account: TEST_ACCOUNT_A, contractId: "ContractWithBytes", - version: "0.0.1", - salt: "tw", + version: "0.0.2", + salt: "", publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936", }); mintfeeManagerModuleAddress = await deployPublishedContract({ @@ -47,7 +48,7 @@ describe.runIf(process.env.TW_SECRET_KEY)( account: TEST_ACCOUNT_A, contractId: "MintFeeManagerModule", version: "0.0.1", - salt: "tw", + salt: "", publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936", }); mintfeeManagerCoreAddress = await deployPublishedContract({ @@ -55,8 +56,8 @@ describe.runIf(process.env.TW_SECRET_KEY)( chain: ANVIL_CHAIN, account: TEST_ACCOUNT_A, contractId: "MintFeeManagerCore", - version: "0.0.25", - salt: "tw", + version: "0.0.26", + salt: "", publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936", }); claimableModuleAddress = await deployPublishedContract({ @@ -64,18 +65,17 @@ describe.runIf(process.env.TW_SECRET_KEY)( chain: ANVIL_CHAIN, account: TEST_ACCOUNT_A, contractId: "ClaimableERC721", - version: "0.0.13", - salt: "tw", + version: "0.0.14", + salt: "", publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936", }); - wethAddress = await deployPublishedContract({ client: TEST_CLIENT, chain: ANVIL_CHAIN, account: TEST_ACCOUNT_A, contractId: "WETH9", version: "0.0.1", - salt: "thirdweb", + salt: "", publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936", }); forwarderAddress = await deployPublishedContract({ @@ -84,7 +84,7 @@ describe.runIf(process.env.TW_SECRET_KEY)( account: TEST_ACCOUNT_A, contractId: "Forwarder", version: "0.0.1", - salt: "thirdweb", + salt: "", publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936", }); multiwrapAddress = await deployPublishedContract({ @@ -192,5 +192,67 @@ describe.runIf(process.env.TW_SECRET_KEY)( expect(fetchedWethAddress.toLowerCase()).to.eq(wethAddress); expect(isTrustedForwarder).to.be.true; }); + + it("should correctly compute addresses for deployed contracts with refs", async () => { + const [ + multisigAddressComputed, + dummyContractAddressComputed, + mintfeeManagerModuleAddressComputed, + mintfeeManagerCoreAddressComputed, + claimableModuleAddressComputed, + ] = await Promise.all([ + getDeployedInfraContract({ + client: TEST_CLIENT, + chain: ANVIL_CHAIN, + contractId: "MultiSig", + version: "0.0.4", + publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936", + }), + getDeployedInfraContract({ + client: TEST_CLIENT, + chain: ANVIL_CHAIN, + contractId: "ContractWithBytes", + version: "0.0.2", + publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936", + }), + getDeployedInfraContract({ + client: TEST_CLIENT, + chain: ANVIL_CHAIN, + contractId: "MintFeeManagerModule", + version: "0.0.1", + publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936", + }), + getDeployedInfraContract({ + client: TEST_CLIENT, + chain: ANVIL_CHAIN, + contractId: "MintFeeManagerCore", + version: "0.0.26", + publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936", + }), + getDeployedInfraContract({ + client: TEST_CLIENT, + chain: ANVIL_CHAIN, + contractId: "ClaimableERC721", + version: "0.0.14", + publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936", + }), + ]); + + expect(multisigAddressComputed?.address.toLowerCase()).to.eq( + multisigAddress, + ); + expect(dummyContractAddressComputed?.address.toLowerCase()).to.eq( + dummyContractAddress, + ); + expect(mintfeeManagerModuleAddressComputed?.address.toLowerCase()).to.eq( + mintfeeManagerModuleAddress, + ); + expect(mintfeeManagerCoreAddressComputed?.address.toLowerCase()).to.eq( + mintfeeManagerCoreAddress, + ); + expect(claimableModuleAddressComputed?.address.toLowerCase()).to.eq( + claimableModuleAddress, + ); + }); }, ); diff --git a/packages/thirdweb/src/extensions/prebuilts/process-ref-deployments.ts b/packages/thirdweb/src/extensions/prebuilts/process-ref-deployments.ts index 673ba418bb3..f3b6e0cb110 100644 --- a/packages/thirdweb/src/extensions/prebuilts/process-ref-deployments.ts +++ b/packages/thirdweb/src/extensions/prebuilts/process-ref-deployments.ts @@ -17,6 +17,11 @@ type ProcessRefDeploymentsOptions = { paramValue: string | ImplementationConstructorParam; }; +/** + * Processes published contract references in constructor params. Deploys recursively if needed. + * @returns Param value after processing references. + * @internal + */ export async function processRefDeployments( options: ProcessRefDeploymentsOptions, ): Promise { @@ -42,7 +47,7 @@ export async function processRefDeployments( const salt = contracts[0]?.salt && contracts[0]?.salt.length > 0 ? contracts[0]?.salt - : "thirdweb"; + : ""; const addr = await deployPublishedContract({ client, @@ -64,7 +69,7 @@ export async function processRefDeployments( const addressArray = []; for (const c of contracts) { - const salt = c?.salt && c?.salt.length > 0 ? c?.salt : "thirdweb"; + const salt = c?.salt && c?.salt.length > 0 ? c?.salt : ""; addressArray.push( await deployPublishedContract({ diff --git a/packages/thirdweb/src/utils/any-evm/compute-published-contract-deploy-info.ts b/packages/thirdweb/src/utils/any-evm/compute-published-contract-deploy-info.ts index 2c3c212547d..e95d6fe66ac 100644 --- a/packages/thirdweb/src/utils/any-evm/compute-published-contract-deploy-info.ts +++ b/packages/thirdweb/src/utils/any-evm/compute-published-contract-deploy-info.ts @@ -3,6 +3,8 @@ import type { Chain } from "../../chains/types.js"; import type { ThirdwebClient } from "../../client/client.js"; import { fetchPublishedContractMetadata } from "../../contract/deployment/publisher.js"; import { computeCreate2FactoryAddress } from "../../contract/deployment/utils/create-2-factory.js"; +import { computeRefDeployments } from "../../extensions/prebuilts/compute-ref-deployments.js"; +import type { ImplementationConstructorParam } from "../../extensions/prebuilts/process-ref-deployments.js"; import { encodeAbiParameters } from "../abi/encodeAbiParameters.js"; import { normalizeFunctionParams } from "../abi/normalizeFunctionParams.js"; import { ensureBytecodePrefix } from "../bytecode/prefix.js"; @@ -51,6 +53,23 @@ export async function computeDeploymentInfoFromMetadata(args: { constructorParams?: Record; salt?: string; }) { + const { client, chain, constructorParams, contractMetadata } = args; + const definedConstructorParams = + constructorParams || contractMetadata.constructorParams; + let processedConstructorParams: Record | undefined; + if (definedConstructorParams) { + processedConstructorParams = {}; + for (const key in definedConstructorParams) { + processedConstructorParams[key] = await computeRefDeployments({ + client, + chain, + paramValue: definedConstructorParams[key] as + | string + | ImplementationConstructorParam, + }); + } + } + return computeDeploymentInfoFromBytecode({ client: args.client, chain: args.chain, @@ -60,7 +79,7 @@ export async function computeDeploymentInfoFromMetadata(args: { client: args.client, chain: args.chain, }), - constructorParams: args.constructorParams, + constructorParams: processedConstructorParams, salt: args.salt, }); }