Skip to content

Commit 1273cc7

Browse files
committed
Compute addresses with ref contracts (#5699)
TOOL-2796 (XPROT-934) ## Problem solved This was an issue when installing modules published with ref contracts. Module installation goes through infra deployment functions which use computed addresses. SDK should process ref contracts when computing published contract addresses. <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing the handling of published contract references in the `thirdweb` library, particularly in the deployment process. It introduces a new function to compute addresses for these references and updates existing functions and tests accordingly. ### Detailed summary - Added `processRefDeployments` function to process contract references in constructor params. - Introduced `computeRefDeployments` function for computing addresses of published contract references. - Updated the handling of `salt` values to default to an empty string instead of `"thirdweb"`. - Modified `computeDeploymentInfoFromMetadata` to use processed constructor parameters. - Adjusted tests to reflect changes in contract deployment parameters and added new tests for address computation of deployed contracts with references. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 6911102 commit 1273cc7

File tree

5 files changed

+266
-14
lines changed

5 files changed

+266
-14
lines changed

.changeset/fresh-gorillas-sip.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Compute addresses with ref contracts
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import type { Chain } from "../../chains/types.js";
2+
import type { ThirdwebClient } from "../../client/client.js";
3+
import { encodeAbiParameters } from "../../utils/abi/encodeAbiParameters.js";
4+
import { computePublishedContractAddress } from "../../utils/any-evm/compute-published-contract-address.js";
5+
import type { ImplementationConstructorParam } from "./process-ref-deployments.js";
6+
7+
type ComputeRefDeploymentsOptions = {
8+
client: ThirdwebClient;
9+
chain: Chain;
10+
paramValue: string | ImplementationConstructorParam;
11+
};
12+
13+
/**
14+
* Computes addresses for published contract references in constructor params.
15+
* @returns Param value after processing references.
16+
* @internal
17+
*/
18+
export async function computeRefDeployments(
19+
options: ComputeRefDeploymentsOptions,
20+
): Promise<string | string[]> {
21+
const { client, chain, paramValue } = options;
22+
23+
if (typeof paramValue === "object") {
24+
if (
25+
"defaultValue" in paramValue &&
26+
paramValue.defaultValue &&
27+
paramValue.defaultValue.length > 0
28+
) {
29+
return paramValue.defaultValue;
30+
}
31+
32+
if ("dynamicValue" in paramValue && paramValue.dynamicValue) {
33+
const dynamicValue = paramValue.dynamicValue;
34+
const contracts = dynamicValue.refContracts;
35+
36+
if (dynamicValue.type === "address") {
37+
if (!contracts || contracts.length === 0 || !contracts[0]?.contractId) {
38+
throw new Error("Invalid or empty param value");
39+
}
40+
const salt =
41+
contracts[0]?.salt && contracts[0]?.salt.length > 0
42+
? contracts[0]?.salt
43+
: "";
44+
45+
const addr = await computePublishedContractAddress({
46+
client,
47+
chain,
48+
contractId: contracts[0]?.contractId,
49+
publisher: contracts[0]?.publisherAddress,
50+
version: contracts[0]?.version,
51+
salt,
52+
});
53+
54+
return addr;
55+
}
56+
57+
if (dynamicValue.type === "address[]") {
58+
if (!contracts || contracts.length === 0) {
59+
throw new Error("Invalid or empty param value");
60+
}
61+
const addressArray: string[] = [];
62+
63+
for (const c of contracts) {
64+
const salt = c?.salt && c?.salt.length > 0 ? c?.salt : "";
65+
66+
addressArray.push(
67+
await computePublishedContractAddress({
68+
client,
69+
chain,
70+
contractId: c.contractId,
71+
publisher: c.publisherAddress,
72+
version: c.version,
73+
salt,
74+
}),
75+
);
76+
}
77+
78+
return addressArray;
79+
}
80+
81+
if (dynamicValue.type === "bytes") {
82+
if (!dynamicValue.paramsToEncode) {
83+
throw new Error("Invalid or empty param value");
84+
}
85+
const paramsToEncode = dynamicValue.paramsToEncode[0];
86+
87+
if (paramsToEncode) {
88+
const types: string[] = [];
89+
const values: (string | string[])[] = [];
90+
for (const v of paramsToEncode) {
91+
types.push(v.type);
92+
93+
if (v.defaultValue) {
94+
values.push(v.defaultValue);
95+
} else if (v.dynamicValue) {
96+
values.push(
97+
await computeRefDeployments({
98+
client,
99+
chain,
100+
paramValue: v,
101+
}),
102+
);
103+
}
104+
}
105+
106+
return encodeAbiParameters(
107+
types.map((t) => {
108+
return { type: t };
109+
}),
110+
values,
111+
);
112+
}
113+
}
114+
115+
if (dynamicValue.type === "bytes[]") {
116+
if (!dynamicValue.paramsToEncode) {
117+
throw new Error("Invalid or empty param value");
118+
}
119+
const bytesArray: string[] = [];
120+
const paramArray = dynamicValue.paramsToEncode;
121+
122+
for (const a of paramArray) {
123+
const paramsToEncode = a;
124+
125+
if (paramsToEncode) {
126+
const types: string[] = [];
127+
const values: (string | string[])[] = [];
128+
for (const v of paramsToEncode) {
129+
types.push(v.type);
130+
131+
if (v.defaultValue) {
132+
values.push(v.defaultValue);
133+
} else if (v.dynamicValue) {
134+
values.push(
135+
await computeRefDeployments({
136+
client,
137+
chain,
138+
paramValue: v,
139+
}),
140+
);
141+
}
142+
}
143+
144+
bytesArray.push(
145+
encodeAbiParameters(
146+
types.map((t) => {
147+
return { type: t };
148+
}),
149+
values,
150+
),
151+
);
152+
}
153+
}
154+
155+
return bytesArray;
156+
}
157+
}
158+
}
159+
160+
return paramValue as string;
161+
}

packages/thirdweb/src/extensions/prebuilts/process-ref-deployments.test.ts

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ANVIL_CHAIN } from "../../../test/src/chains.js";
33
import { TEST_CLIENT } from "../../../test/src/test-clients.js";
44
import { TEST_ACCOUNT_A } from "../../../test/src/test-wallets.js";
55
import { getContract } from "../../contract/contract.js";
6+
import { getDeployedInfraContract } from "../../contract/deployment/utils/infra.js";
67
import { readContract } from "../../transaction/read-contract.js";
78
import { getInstalledModules } from "../modules/__generated__/IModularCore/read/getInstalledModules.js";
89
import { deployPublishedContract } from "./deploy-published.js";
@@ -29,16 +30,16 @@ describe.runIf(process.env.TW_SECRET_KEY)(
2930
account: TEST_ACCOUNT_A,
3031
contractId: "MultiSig",
3132
version: "0.0.4",
32-
salt: "tw",
33+
salt: "",
3334
publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936",
3435
});
3536
dummyContractAddress = await deployPublishedContract({
3637
client: TEST_CLIENT,
3738
chain: ANVIL_CHAIN,
3839
account: TEST_ACCOUNT_A,
3940
contractId: "ContractWithBytes",
40-
version: "0.0.1",
41-
salt: "tw",
41+
version: "0.0.2",
42+
salt: "",
4243
publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936",
4344
});
4445
mintfeeManagerModuleAddress = await deployPublishedContract({
@@ -47,35 +48,34 @@ describe.runIf(process.env.TW_SECRET_KEY)(
4748
account: TEST_ACCOUNT_A,
4849
contractId: "MintFeeManagerModule",
4950
version: "0.0.1",
50-
salt: "tw",
51+
salt: "",
5152
publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936",
5253
});
5354
mintfeeManagerCoreAddress = await deployPublishedContract({
5455
client: TEST_CLIENT,
5556
chain: ANVIL_CHAIN,
5657
account: TEST_ACCOUNT_A,
5758
contractId: "MintFeeManagerCore",
58-
version: "0.0.25",
59-
salt: "tw",
59+
version: "0.0.26",
60+
salt: "",
6061
publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936",
6162
});
6263
claimableModuleAddress = await deployPublishedContract({
6364
client: TEST_CLIENT,
6465
chain: ANVIL_CHAIN,
6566
account: TEST_ACCOUNT_A,
6667
contractId: "ClaimableERC721",
67-
version: "0.0.13",
68-
salt: "tw",
68+
version: "0.0.14",
69+
salt: "",
6970
publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936",
7071
});
71-
7272
wethAddress = await deployPublishedContract({
7373
client: TEST_CLIENT,
7474
chain: ANVIL_CHAIN,
7575
account: TEST_ACCOUNT_A,
7676
contractId: "WETH9",
7777
version: "0.0.1",
78-
salt: "thirdweb",
78+
salt: "",
7979
publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936",
8080
});
8181
forwarderAddress = await deployPublishedContract({
@@ -84,7 +84,7 @@ describe.runIf(process.env.TW_SECRET_KEY)(
8484
account: TEST_ACCOUNT_A,
8585
contractId: "Forwarder",
8686
version: "0.0.1",
87-
salt: "thirdweb",
87+
salt: "",
8888
publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936",
8989
});
9090
multiwrapAddress = await deployPublishedContract({
@@ -192,5 +192,67 @@ describe.runIf(process.env.TW_SECRET_KEY)(
192192
expect(fetchedWethAddress.toLowerCase()).to.eq(wethAddress);
193193
expect(isTrustedForwarder).to.be.true;
194194
});
195+
196+
it("should correctly compute addresses for deployed contracts with refs", async () => {
197+
const [
198+
multisigAddressComputed,
199+
dummyContractAddressComputed,
200+
mintfeeManagerModuleAddressComputed,
201+
mintfeeManagerCoreAddressComputed,
202+
claimableModuleAddressComputed,
203+
] = await Promise.all([
204+
getDeployedInfraContract({
205+
client: TEST_CLIENT,
206+
chain: ANVIL_CHAIN,
207+
contractId: "MultiSig",
208+
version: "0.0.4",
209+
publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936",
210+
}),
211+
getDeployedInfraContract({
212+
client: TEST_CLIENT,
213+
chain: ANVIL_CHAIN,
214+
contractId: "ContractWithBytes",
215+
version: "0.0.2",
216+
publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936",
217+
}),
218+
getDeployedInfraContract({
219+
client: TEST_CLIENT,
220+
chain: ANVIL_CHAIN,
221+
contractId: "MintFeeManagerModule",
222+
version: "0.0.1",
223+
publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936",
224+
}),
225+
getDeployedInfraContract({
226+
client: TEST_CLIENT,
227+
chain: ANVIL_CHAIN,
228+
contractId: "MintFeeManagerCore",
229+
version: "0.0.26",
230+
publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936",
231+
}),
232+
getDeployedInfraContract({
233+
client: TEST_CLIENT,
234+
chain: ANVIL_CHAIN,
235+
contractId: "ClaimableERC721",
236+
version: "0.0.14",
237+
publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936",
238+
}),
239+
]);
240+
241+
expect(multisigAddressComputed?.address.toLowerCase()).to.eq(
242+
multisigAddress,
243+
);
244+
expect(dummyContractAddressComputed?.address.toLowerCase()).to.eq(
245+
dummyContractAddress,
246+
);
247+
expect(mintfeeManagerModuleAddressComputed?.address.toLowerCase()).to.eq(
248+
mintfeeManagerModuleAddress,
249+
);
250+
expect(mintfeeManagerCoreAddressComputed?.address.toLowerCase()).to.eq(
251+
mintfeeManagerCoreAddress,
252+
);
253+
expect(claimableModuleAddressComputed?.address.toLowerCase()).to.eq(
254+
claimableModuleAddress,
255+
);
256+
});
195257
},
196258
);

packages/thirdweb/src/extensions/prebuilts/process-ref-deployments.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ type ProcessRefDeploymentsOptions = {
1717
paramValue: string | ImplementationConstructorParam;
1818
};
1919

20+
/**
21+
* Processes published contract references in constructor params. Deploys recursively if needed.
22+
* @returns Param value after processing references.
23+
* @internal
24+
*/
2025
export async function processRefDeployments(
2126
options: ProcessRefDeploymentsOptions,
2227
): Promise<string | string[]> {
@@ -42,7 +47,7 @@ export async function processRefDeployments(
4247
const salt =
4348
contracts[0]?.salt && contracts[0]?.salt.length > 0
4449
? contracts[0]?.salt
45-
: "thirdweb";
50+
: "";
4651

4752
const addr = await deployPublishedContract({
4853
client,
@@ -64,7 +69,7 @@ export async function processRefDeployments(
6469
const addressArray = [];
6570

6671
for (const c of contracts) {
67-
const salt = c?.salt && c?.salt.length > 0 ? c?.salt : "thirdweb";
72+
const salt = c?.salt && c?.salt.length > 0 ? c?.salt : "";
6873

6974
addressArray.push(
7075
await deployPublishedContract({

packages/thirdweb/src/utils/any-evm/compute-published-contract-deploy-info.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import type { Chain } from "../../chains/types.js";
33
import type { ThirdwebClient } from "../../client/client.js";
44
import { fetchPublishedContractMetadata } from "../../contract/deployment/publisher.js";
55
import { computeCreate2FactoryAddress } from "../../contract/deployment/utils/create-2-factory.js";
6+
import { computeRefDeployments } from "../../extensions/prebuilts/compute-ref-deployments.js";
7+
import type { ImplementationConstructorParam } from "../../extensions/prebuilts/process-ref-deployments.js";
68
import { encodeAbiParameters } from "../abi/encodeAbiParameters.js";
79
import { normalizeFunctionParams } from "../abi/normalizeFunctionParams.js";
810
import { ensureBytecodePrefix } from "../bytecode/prefix.js";
@@ -51,6 +53,23 @@ export async function computeDeploymentInfoFromMetadata(args: {
5153
constructorParams?: Record<string, unknown>;
5254
salt?: string;
5355
}) {
56+
const { client, chain, constructorParams, contractMetadata } = args;
57+
const definedConstructorParams =
58+
constructorParams || contractMetadata.constructorParams;
59+
let processedConstructorParams: Record<string, string | string[]> | undefined;
60+
if (definedConstructorParams) {
61+
processedConstructorParams = {};
62+
for (const key in definedConstructorParams) {
63+
processedConstructorParams[key] = await computeRefDeployments({
64+
client,
65+
chain,
66+
paramValue: definedConstructorParams[key] as
67+
| string
68+
| ImplementationConstructorParam,
69+
});
70+
}
71+
}
72+
5473
return computeDeploymentInfoFromBytecode({
5574
client: args.client,
5675
chain: args.chain,
@@ -60,7 +79,7 @@ export async function computeDeploymentInfoFromMetadata(args: {
6079
client: args.client,
6180
chain: args.chain,
6281
}),
63-
constructorParams: args.constructorParams,
82+
constructorParams: processedConstructorParams,
6483
salt: args.salt,
6584
});
6685
}

0 commit comments

Comments
 (0)