Skip to content

Commit 6efded6

Browse files
committed
feat: adds prepare functions for zksync deploys
1 parent 2591c75 commit 6efded6

File tree

4 files changed

+186
-63
lines changed

4 files changed

+186
-63
lines changed

packages/thirdweb/src/contract/deployment/zksync/zkDeployContract.ts

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import { normalizeFunctionParams } from "../../../utils/abi/normalizeFunctionPar
88
import { CONTRACT_DEPLOYER_ADDRESS } from "../../../utils/any-evm/zksync/constants.js";
99
import type { Hex } from "../../../utils/encoding/hex.js";
1010
import type { ClientAndChainAndAccount } from "../../../utils/types.js";
11-
import { zkDeployContractDeterministic } from "./zkDeployDeterministic.js";
11+
import { prepareZkDeployContractDeterministicTransaction } from "./zkDeployDeterministic.js";
1212

1313
/**
1414
* @internal
1515
*/
16-
export async function zkDeployContract(
16+
export async function prepareZkDeployContractTransaction(
1717
options: ClientAndChainAndAccount & {
1818
abi: Abi;
1919
bytecode: Hex;
@@ -22,11 +22,6 @@ export async function zkDeployContract(
2222
deploymentType?: "create" | "create2";
2323
},
2424
) {
25-
if (options.salt !== undefined) {
26-
// if a salt is provided, use the deterministic deployer
27-
return zkDeployContractDeterministic(options);
28-
}
29-
3025
const data = encodeDeployData({
3126
abi: options.abi,
3227
bytecode: options.bytecode,
@@ -37,18 +32,39 @@ export async function zkDeployContract(
3732
),
3833
});
3934

35+
return prepareTransaction({
36+
chain: options.chain,
37+
client: options.client,
38+
to: CONTRACT_DEPLOYER_ADDRESS,
39+
data,
40+
eip712: {
41+
factoryDeps: [options.bytecode],
42+
// TODO (zksync): allow passing in a paymaster
43+
},
44+
});
45+
}
46+
47+
/**
48+
* @internal
49+
*/
50+
export async function zkDeployContract(
51+
options: ClientAndChainAndAccount & {
52+
abi: Abi;
53+
bytecode: Hex;
54+
params?: Record<string, unknown>;
55+
salt?: string;
56+
deploymentType?: "create" | "create2";
57+
},
58+
) {
59+
// if a salt is provided, use the deterministic deployer
60+
const transaction =
61+
options.salt !== undefined
62+
? await prepareZkDeployContractDeterministicTransaction(options)
63+
: await prepareZkDeployContractTransaction(options);
64+
4065
const receipt = await sendAndConfirmTransaction({
4166
account: options.account,
42-
transaction: prepareTransaction({
43-
chain: options.chain,
44-
client: options.client,
45-
to: CONTRACT_DEPLOYER_ADDRESS,
46-
data,
47-
eip712: {
48-
factoryDeps: [options.bytecode],
49-
// TODO (zksync): allow passing in a paymaster
50-
},
51-
}),
67+
transaction,
5268
});
5369

5470
const events = parseEventLogs({

packages/thirdweb/src/contract/deployment/zksync/zkDeployCreate2Factory.ts

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,14 @@ import type { ClientAndChainAndAccount } from "../../../utils/types.js";
1212
import { toWei } from "../../../utils/units.js";
1313
import { privateKeyToAccount } from "../../../wallets/private-key.js";
1414
import { getWalletBalance } from "../../../wallets/utils/getWalletBalance.js";
15-
import { zkDeployContract } from "./zkDeployContract.js";
15+
import { prepareZkDeployContractTransaction } from "./zkDeployContract.js";
1616

1717
/**
1818
* @internal
1919
*/
20-
export async function zkDeployCreate2Factory(
20+
export async function prepareZkDeployCreate2FactoryTransaction(
2121
options: ClientAndChainAndAccount,
2222
) {
23-
const isDeployed = await isContractDeployed({
24-
address: ZKSYNC_SINGLETON_FACTORY,
25-
chain: options.chain,
26-
client: options.client,
27-
});
28-
29-
if (isDeployed) {
30-
return ZKSYNC_SINGLETON_FACTORY;
31-
}
32-
33-
if (!PUBLISHED_PRIVATE_KEY) {
34-
throw new Error(
35-
`Unable to deploy create2 factory on chain ${options.chain.id} - please contact us via https://thirdweb.com/support to enable this chain`,
36-
);
37-
}
38-
3923
const create2Signer = privateKeyToAccount({
4024
client: options.client,
4125
privateKey: PUBLISHED_PRIVATE_KEY,
@@ -49,25 +33,52 @@ export async function zkDeployCreate2Factory(
4933
});
5034

5135
if (balance.value < valueToSend) {
52-
await sendAndConfirmTransaction({
53-
account: options.account,
54-
transaction: prepareTransaction({
55-
chain: options.chain,
56-
client: options.client,
57-
to: create2Signer.address,
58-
value: valueToSend,
59-
}),
36+
return prepareTransaction({
37+
chain: options.chain,
38+
client: options.client,
39+
to: create2Signer.address,
40+
value: valueToSend,
6041
});
6142
}
6243

63-
await zkDeployContract({
44+
return prepareZkDeployContractTransaction({
6445
client: options.client,
6546
chain: options.chain,
6647
account: create2Signer,
6748
abi: parseAbi(singletonFactoryAbi),
6849
bytecode: singletonFactoryBytecode,
6950
deploymentType: "create2",
7051
});
52+
}
53+
54+
/**
55+
* @internal
56+
*/
57+
export async function zkDeployCreate2Factory(
58+
options: ClientAndChainAndAccount,
59+
) {
60+
const isDeployed = await isContractDeployed({
61+
address: ZKSYNC_SINGLETON_FACTORY,
62+
chain: options.chain,
63+
client: options.client,
64+
});
65+
66+
if (isDeployed) {
67+
return ZKSYNC_SINGLETON_FACTORY;
68+
}
69+
70+
if (!PUBLISHED_PRIVATE_KEY) {
71+
throw new Error(
72+
`Unable to deploy create2 factory on chain ${options.chain.id} - please contact us via https://thirdweb.com/support to enable this chain`,
73+
);
74+
}
75+
76+
const transaction = await prepareZkDeployCreate2FactoryTransaction(options);
77+
78+
await sendAndConfirmTransaction({
79+
account: options.account,
80+
transaction,
81+
});
7182

7283
return ZKSYNC_SINGLETON_FACTORY;
7384
}

packages/thirdweb/src/contract/deployment/zksync/zkDeployDeterministic.ts

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,72 @@ import { getContract } from "../../contract.js";
1919
import { zkDeployContract } from "./zkDeployContract.js";
2020
import { zkDeployCreate2Factory } from "./zkDeployCreate2Factory.js";
2121

22+
/**
23+
* @internal
24+
*/
25+
export async function prepareZkDeployContractDeterministicTransaction(
26+
options: ClientAndChainAndAccount & {
27+
abi: Abi;
28+
bytecode: Hex;
29+
params?: Record<string, unknown>;
30+
salt?: string;
31+
},
32+
) {
33+
const constructorAbi = options.abi.find(
34+
(x) => "type" in x && x.type === "constructor",
35+
) || { inputs: [] };
36+
const encodedArgs = encodeAbiParameters(
37+
constructorAbi.inputs,
38+
normalizeFunctionParams(constructorAbi as AbiConstructor, options.params),
39+
);
40+
const create2FactoryAddress = await zkDeployCreate2Factory({
41+
client: options.client,
42+
chain: options.chain,
43+
account: options.account,
44+
});
45+
const bytecode = ensureBytecodePrefix(options.bytecode);
46+
const bytecodeHash = uint8ArrayToHex(hashBytecode(bytecode));
47+
48+
// check if bytecodehash is known
49+
const knownCodesStorageContract = getContract({
50+
address: KNOWN_CODES_STORAGE,
51+
chain: options.chain,
52+
client: options.client,
53+
});
54+
const marker = await readContract({
55+
contract: knownCodesStorageContract,
56+
method: "function getMarker(bytes32 _hash) view returns (uint256 marker)",
57+
params: [bytecodeHash],
58+
});
59+
// if not known, publish the bytecodehash
60+
if (marker !== 1n) {
61+
await zkDeployContract({
62+
client: options.client,
63+
chain: options.chain,
64+
account: options.account,
65+
abi: options.abi,
66+
bytecode,
67+
params: options.params,
68+
});
69+
}
70+
71+
// deploy with create2 factory
72+
const factory = getContract({
73+
address: create2FactoryAddress,
74+
chain: options.chain,
75+
client: options.client,
76+
abi: parseAbi(singletonFactoryAbi),
77+
});
78+
79+
const salt = options?.salt ? keccakId(options.salt) : keccakId("thirdweb");
80+
81+
return prepareContractCall({
82+
contract: factory,
83+
method: "deploy",
84+
params: [salt, bytecodeHash, encodedArgs],
85+
});
86+
}
87+
2288
/**
2389
* @internal
2490
*/
@@ -30,6 +96,7 @@ export async function zkDeployContractDeterministic(
3096
salt?: string;
3197
},
3298
) {
99+
// We have to keep the full address prediction logic in this function to preserve the behavior of just returning the address if the contract is already deployed.
33100
const constructorAbi = options.abi.find(
34101
(x) => "type" in x && x.type === "constructor",
35102
) || { inputs: [] };
@@ -79,27 +146,12 @@ export async function zkDeployContractDeterministic(
79146
});
80147
}
81148

82-
console.log(
83-
`deploying contract via create2 factory at: ${predictedAddress}`,
84-
);
85-
86-
// deploy with create2 factory
87-
const factory = getContract({
88-
address: create2FactoryAddress,
89-
chain: options.chain,
90-
client: options.client,
91-
abi: parseAbi(singletonFactoryAbi),
92-
});
93-
94-
const salt = options?.salt ? keccakId(options.salt) : keccakId("thirdweb");
149+
const transaction =
150+
await prepareZkDeployContractDeterministicTransaction(options);
95151

96152
await sendAndConfirmTransaction({
97153
account: options.account,
98-
transaction: prepareContractCall({
99-
contract: factory,
100-
method: "deploy",
101-
params: [salt, bytecodeHash, encodedArgs],
102-
}),
154+
transaction,
103155
});
104156
}
105157

packages/thirdweb/src/contract/deployment/zksync/zkDeployProxy.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,51 @@ import { resolvePromisedValue } from "../../../utils/promise/resolve-promised-va
99
import { randomBytesHex } from "../../../utils/random.js";
1010
import type { ClientAndChainAndAccount } from "../../../utils/types.js";
1111
import type { ThirdwebContract } from "../../contract.js";
12-
import { zkDeployContractDeterministic } from "./zkDeployDeterministic.js";
12+
import {
13+
prepareZkDeployContractDeterministicTransaction,
14+
zkDeployContractDeterministic,
15+
} from "./zkDeployDeterministic.js";
16+
17+
/**
18+
* @internal
19+
*/
20+
export async function prepareZkDeployProxyTransaction(
21+
options: ClientAndChainAndAccount & {
22+
cloneFactoryContract: ThirdwebContract;
23+
initializeTransaction: PreparedTransaction;
24+
salt?: string;
25+
},
26+
) {
27+
const implementationAddress = await resolvePromisedValue(
28+
options.initializeTransaction.to,
29+
);
30+
if (!implementationAddress) {
31+
throw new Error("initializeTransaction must have a 'to' field set");
32+
}
33+
const deployed = await isContractDeployed({
34+
address: implementationAddress,
35+
chain: options.chain,
36+
client: options.client,
37+
});
38+
if (!deployed) {
39+
throw new Error(
40+
`Implementation contract at ${implementationAddress} is not deployed`,
41+
);
42+
}
43+
// deploy tw proxy of the implementation
44+
return prepareZkDeployContractDeterministicTransaction({
45+
client: options.client,
46+
chain: options.chain,
47+
account: options.account,
48+
abi: twProxyAbi,
49+
bytecode: twProxyBytecode,
50+
params: {
51+
_logic: implementationAddress,
52+
_data: await encode(options.initializeTransaction),
53+
},
54+
salt: options.salt || randomBytesHex(32),
55+
});
56+
}
1357

1458
/**
1559
* @internal

0 commit comments

Comments
 (0)