Skip to content

Compute addresses with ref contracts #5699

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fresh-gorillas-sip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"thirdweb": patch
---

Compute addresses with ref contracts
Original file line number Diff line number Diff line change
@@ -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<string | string[]> {
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");
}

Check warning on line 39 in packages/thirdweb/src/extensions/prebuilts/compute-ref-deployments.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/extensions/prebuilts/compute-ref-deployments.ts#L38-L39

Added lines #L38 - L39 were not covered by tests
const salt =
contracts[0]?.salt && contracts[0]?.salt.length > 0
? contracts[0]?.salt

Check warning on line 42 in packages/thirdweb/src/extensions/prebuilts/compute-ref-deployments.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/extensions/prebuilts/compute-ref-deployments.ts#L42

Added line #L42 was not covered by tests
: "";

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");
}

Check warning on line 60 in packages/thirdweb/src/extensions/prebuilts/compute-ref-deployments.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/extensions/prebuilts/compute-ref-deployments.ts#L59-L60

Added lines #L59 - L60 were not covered by tests
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");
}

Check warning on line 84 in packages/thirdweb/src/extensions/prebuilts/compute-ref-deployments.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/extensions/prebuilts/compute-ref-deployments.ts#L83-L84

Added lines #L83 - L84 were not covered by tests
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);

Check warning on line 94 in packages/thirdweb/src/extensions/prebuilts/compute-ref-deployments.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/extensions/prebuilts/compute-ref-deployments.ts#L94

Added line #L94 was not covered by tests
} 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");
}

Check warning on line 118 in packages/thirdweb/src/extensions/prebuilts/compute-ref-deployments.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/extensions/prebuilts/compute-ref-deployments.ts#L117-L118

Added lines #L117 - L118 were not covered by tests
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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -29,16 +30,16 @@ 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({
client: TEST_CLIENT,
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({
Expand All @@ -47,35 +48,34 @@ 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({
client: TEST_CLIENT,
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({
client: TEST_CLIENT,
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({
Expand All @@ -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({
Expand Down Expand Up @@ -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,
);
});
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -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<string | string[]> {
Expand All @@ -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,
Expand All @@ -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({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -51,6 +53,23 @@ export async function computeDeploymentInfoFromMetadata(args: {
constructorParams?: Record<string, unknown>;
salt?: string;
}) {
const { client, chain, constructorParams, contractMetadata } = args;
const definedConstructorParams =
constructorParams || contractMetadata.constructorParams;
let processedConstructorParams: Record<string, string | string[]> | 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,
Expand All @@ -60,7 +79,7 @@ export async function computeDeploymentInfoFromMetadata(args: {
client: args.client,
chain: args.chain,
}),
constructorParams: args.constructorParams,
constructorParams: processedConstructorParams,
salt: args.salt,
});
}
Expand Down
Loading