diff --git a/packages/thirdweb/src/contract/deployment/zksync/zkDeployContract.ts b/packages/thirdweb/src/contract/deployment/zksync/zkDeployContract.ts index d01fa4ad974..838414acef5 100644 --- a/packages/thirdweb/src/contract/deployment/zksync/zkDeployContract.ts +++ b/packages/thirdweb/src/contract/deployment/zksync/zkDeployContract.ts @@ -8,12 +8,12 @@ import { normalizeFunctionParams } from "../../../utils/abi/normalizeFunctionPar import { CONTRACT_DEPLOYER_ADDRESS } from "../../../utils/any-evm/zksync/constants.js"; import type { Hex } from "../../../utils/encoding/hex.js"; import type { ClientAndChainAndAccount } from "../../../utils/types.js"; -import { zkDeployContractDeterministic } from "./zkDeployDeterministic.js"; +import { prepareZkDeployContractDeterministicTransaction } from "./zkDeployDeterministic.js"; /** * @internal */ -export async function zkDeployContract( +export async function prepareZkDeployContractTransaction( options: ClientAndChainAndAccount & { abi: Abi; bytecode: Hex; @@ -22,11 +22,6 @@ export async function zkDeployContract( deploymentType?: "create" | "create2"; }, ) { - if (options.salt !== undefined) { - // if a salt is provided, use the deterministic deployer - return zkDeployContractDeterministic(options); - } - const data = encodeDeployData({ abi: options.abi, bytecode: options.bytecode, @@ -37,18 +32,39 @@ export async function zkDeployContract( ), }); + return prepareTransaction({ + chain: options.chain, + client: options.client, + to: CONTRACT_DEPLOYER_ADDRESS, + data, + eip712: { + factoryDeps: [options.bytecode], + // TODO (zksync): allow passing in a paymaster + }, + }); +} + +/** + * @internal + */ +export async function zkDeployContract( + options: ClientAndChainAndAccount & { + abi: Abi; + bytecode: Hex; + params?: Record; + salt?: string; + deploymentType?: "create" | "create2"; + }, +) { + // if a salt is provided, use the deterministic deployer + const transaction = + options.salt !== undefined + ? await prepareZkDeployContractDeterministicTransaction(options) + : await prepareZkDeployContractTransaction(options); + const receipt = await sendAndConfirmTransaction({ account: options.account, - transaction: prepareTransaction({ - chain: options.chain, - client: options.client, - to: CONTRACT_DEPLOYER_ADDRESS, - data, - eip712: { - factoryDeps: [options.bytecode], - // TODO (zksync): allow passing in a paymaster - }, - }), + transaction, }); const events = parseEventLogs({ diff --git a/packages/thirdweb/src/contract/deployment/zksync/zkDeployCreate2Factory.ts b/packages/thirdweb/src/contract/deployment/zksync/zkDeployCreate2Factory.ts index 0aebba6ff7b..93604a1fddd 100644 --- a/packages/thirdweb/src/contract/deployment/zksync/zkDeployCreate2Factory.ts +++ b/packages/thirdweb/src/contract/deployment/zksync/zkDeployCreate2Factory.ts @@ -12,30 +12,14 @@ import type { ClientAndChainAndAccount } from "../../../utils/types.js"; import { toWei } from "../../../utils/units.js"; import { privateKeyToAccount } from "../../../wallets/private-key.js"; import { getWalletBalance } from "../../../wallets/utils/getWalletBalance.js"; -import { zkDeployContract } from "./zkDeployContract.js"; +import { prepareZkDeployContractTransaction } from "./zkDeployContract.js"; /** * @internal */ -export async function zkDeployCreate2Factory( +async function prepareZkDeployCreate2FactoryTransaction( options: ClientAndChainAndAccount, ) { - const isDeployed = await isContractDeployed({ - address: ZKSYNC_SINGLETON_FACTORY, - chain: options.chain, - client: options.client, - }); - - if (isDeployed) { - return ZKSYNC_SINGLETON_FACTORY; - } - - if (!PUBLISHED_PRIVATE_KEY) { - throw new Error( - `Unable to deploy create2 factory on chain ${options.chain.id} - please contact us via https://thirdweb.com/support to enable this chain`, - ); - } - const create2Signer = privateKeyToAccount({ client: options.client, privateKey: PUBLISHED_PRIVATE_KEY, @@ -49,18 +33,15 @@ export async function zkDeployCreate2Factory( }); if (balance.value < valueToSend) { - await sendAndConfirmTransaction({ - account: options.account, - transaction: prepareTransaction({ - chain: options.chain, - client: options.client, - to: create2Signer.address, - value: valueToSend, - }), + return prepareTransaction({ + chain: options.chain, + client: options.client, + to: create2Signer.address, + value: valueToSend, }); } - await zkDeployContract({ + return prepareZkDeployContractTransaction({ client: options.client, chain: options.chain, account: create2Signer, @@ -68,6 +49,36 @@ export async function zkDeployCreate2Factory( bytecode: singletonFactoryBytecode, deploymentType: "create2", }); +} + +/** + * @internal + */ +export async function zkDeployCreate2Factory( + options: ClientAndChainAndAccount, +) { + const isDeployed = await isContractDeployed({ + address: ZKSYNC_SINGLETON_FACTORY, + chain: options.chain, + client: options.client, + }); + + if (isDeployed) { + return ZKSYNC_SINGLETON_FACTORY; + } + + if (!PUBLISHED_PRIVATE_KEY) { + throw new Error( + `Unable to deploy create2 factory on chain ${options.chain.id} - please contact us via https://thirdweb.com/support to enable this chain`, + ); + } + + const transaction = await prepareZkDeployCreate2FactoryTransaction(options); + + await sendAndConfirmTransaction({ + account: options.account, + transaction, + }); return ZKSYNC_SINGLETON_FACTORY; } diff --git a/packages/thirdweb/src/contract/deployment/zksync/zkDeployDeterministic.ts b/packages/thirdweb/src/contract/deployment/zksync/zkDeployDeterministic.ts index ac2c5c565b7..53286bfbe46 100644 --- a/packages/thirdweb/src/contract/deployment/zksync/zkDeployDeterministic.ts +++ b/packages/thirdweb/src/contract/deployment/zksync/zkDeployDeterministic.ts @@ -19,6 +19,72 @@ import { getContract } from "../../contract.js"; import { zkDeployContract } from "./zkDeployContract.js"; import { zkDeployCreate2Factory } from "./zkDeployCreate2Factory.js"; +/** + * @internal + */ +export async function prepareZkDeployContractDeterministicTransaction( + options: ClientAndChainAndAccount & { + abi: Abi; + bytecode: Hex; + params?: Record; + salt?: string; + }, +) { + const constructorAbi = options.abi.find( + (x) => "type" in x && x.type === "constructor", + ) || { inputs: [] }; + const encodedArgs = encodeAbiParameters( + constructorAbi.inputs, + normalizeFunctionParams(constructorAbi as AbiConstructor, options.params), + ); + const create2FactoryAddress = await zkDeployCreate2Factory({ + client: options.client, + chain: options.chain, + account: options.account, + }); + const bytecode = ensureBytecodePrefix(options.bytecode); + const bytecodeHash = uint8ArrayToHex(hashBytecode(bytecode)); + + // check if bytecodehash is known + const knownCodesStorageContract = getContract({ + address: KNOWN_CODES_STORAGE, + chain: options.chain, + client: options.client, + }); + const marker = await readContract({ + contract: knownCodesStorageContract, + method: "function getMarker(bytes32 _hash) view returns (uint256 marker)", + params: [bytecodeHash], + }); + // if not known, publish the bytecodehash + if (marker !== 1n) { + await zkDeployContract({ + client: options.client, + chain: options.chain, + account: options.account, + abi: options.abi, + bytecode, + params: options.params, + }); + } + + // deploy with create2 factory + const factory = getContract({ + address: create2FactoryAddress, + chain: options.chain, + client: options.client, + abi: parseAbi(singletonFactoryAbi), + }); + + const salt = options?.salt ? keccakId(options.salt) : keccakId("thirdweb"); + + return prepareContractCall({ + contract: factory, + method: "deploy", + params: [salt, bytecodeHash, encodedArgs], + }); +} + /** * @internal */ @@ -30,6 +96,7 @@ export async function zkDeployContractDeterministic( salt?: string; }, ) { + // 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. const constructorAbi = options.abi.find( (x) => "type" in x && x.type === "constructor", ) || { inputs: [] }; @@ -79,27 +146,12 @@ export async function zkDeployContractDeterministic( }); } - console.log( - `deploying contract via create2 factory at: ${predictedAddress}`, - ); - - // deploy with create2 factory - const factory = getContract({ - address: create2FactoryAddress, - chain: options.chain, - client: options.client, - abi: parseAbi(singletonFactoryAbi), - }); - - const salt = options?.salt ? keccakId(options.salt) : keccakId("thirdweb"); + const transaction = + await prepareZkDeployContractDeterministicTransaction(options); await sendAndConfirmTransaction({ account: options.account, - transaction: prepareContractCall({ - contract: factory, - method: "deploy", - params: [salt, bytecodeHash, encodedArgs], - }), + transaction, }); }