From 6efded6f4d757fcd47415add75035c84aff90047 Mon Sep 17 00:00:00 2001 From: gregfromstl Date: Tue, 19 Nov 2024 12:52:42 -0800 Subject: [PATCH 1/2] feat: adds prepare functions for zksync deploys --- .../deployment/zksync/zkDeployContract.ts | 50 +++++++---- .../zksync/zkDeployCreate2Factory.ts | 65 ++++++++------ .../zksync/zkDeployDeterministic.ts | 88 +++++++++++++++---- .../deployment/zksync/zkDeployProxy.ts | 46 +++++++++- 4 files changed, 186 insertions(+), 63 deletions(-) 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..06ff16246ba 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( +export 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, }); } diff --git a/packages/thirdweb/src/contract/deployment/zksync/zkDeployProxy.ts b/packages/thirdweb/src/contract/deployment/zksync/zkDeployProxy.ts index c0876c2230b..b82253ce423 100644 --- a/packages/thirdweb/src/contract/deployment/zksync/zkDeployProxy.ts +++ b/packages/thirdweb/src/contract/deployment/zksync/zkDeployProxy.ts @@ -9,7 +9,51 @@ import { resolvePromisedValue } from "../../../utils/promise/resolve-promised-va import { randomBytesHex } from "../../../utils/random.js"; import type { ClientAndChainAndAccount } from "../../../utils/types.js"; import type { ThirdwebContract } from "../../contract.js"; -import { zkDeployContractDeterministic } from "./zkDeployDeterministic.js"; +import { + prepareZkDeployContractDeterministicTransaction, + zkDeployContractDeterministic, +} from "./zkDeployDeterministic.js"; + +/** + * @internal + */ +export async function prepareZkDeployProxyTransaction( + options: ClientAndChainAndAccount & { + cloneFactoryContract: ThirdwebContract; + initializeTransaction: PreparedTransaction; + salt?: string; + }, +) { + const implementationAddress = await resolvePromisedValue( + options.initializeTransaction.to, + ); + if (!implementationAddress) { + throw new Error("initializeTransaction must have a 'to' field set"); + } + const deployed = await isContractDeployed({ + address: implementationAddress, + chain: options.chain, + client: options.client, + }); + if (!deployed) { + throw new Error( + `Implementation contract at ${implementationAddress} is not deployed`, + ); + } + // deploy tw proxy of the implementation + return prepareZkDeployContractDeterministicTransaction({ + client: options.client, + chain: options.chain, + account: options.account, + abi: twProxyAbi, + bytecode: twProxyBytecode, + params: { + _logic: implementationAddress, + _data: await encode(options.initializeTransaction), + }, + salt: options.salt || randomBytesHex(32), + }); +} /** * @internal From e2ca996de23356f002f694359f315f90066b1eb1 Mon Sep 17 00:00:00 2001 From: gregfromstl Date: Tue, 19 Nov 2024 12:55:38 -0800 Subject: [PATCH 2/2] lint --- .../zksync/zkDeployCreate2Factory.ts | 2 +- .../deployment/zksync/zkDeployProxy.ts | 46 +------------------ 2 files changed, 2 insertions(+), 46 deletions(-) diff --git a/packages/thirdweb/src/contract/deployment/zksync/zkDeployCreate2Factory.ts b/packages/thirdweb/src/contract/deployment/zksync/zkDeployCreate2Factory.ts index 06ff16246ba..93604a1fddd 100644 --- a/packages/thirdweb/src/contract/deployment/zksync/zkDeployCreate2Factory.ts +++ b/packages/thirdweb/src/contract/deployment/zksync/zkDeployCreate2Factory.ts @@ -17,7 +17,7 @@ import { prepareZkDeployContractTransaction } from "./zkDeployContract.js"; /** * @internal */ -export async function prepareZkDeployCreate2FactoryTransaction( +async function prepareZkDeployCreate2FactoryTransaction( options: ClientAndChainAndAccount, ) { const create2Signer = privateKeyToAccount({ diff --git a/packages/thirdweb/src/contract/deployment/zksync/zkDeployProxy.ts b/packages/thirdweb/src/contract/deployment/zksync/zkDeployProxy.ts index b82253ce423..c0876c2230b 100644 --- a/packages/thirdweb/src/contract/deployment/zksync/zkDeployProxy.ts +++ b/packages/thirdweb/src/contract/deployment/zksync/zkDeployProxy.ts @@ -9,51 +9,7 @@ import { resolvePromisedValue } from "../../../utils/promise/resolve-promised-va import { randomBytesHex } from "../../../utils/random.js"; import type { ClientAndChainAndAccount } from "../../../utils/types.js"; import type { ThirdwebContract } from "../../contract.js"; -import { - prepareZkDeployContractDeterministicTransaction, - zkDeployContractDeterministic, -} from "./zkDeployDeterministic.js"; - -/** - * @internal - */ -export async function prepareZkDeployProxyTransaction( - options: ClientAndChainAndAccount & { - cloneFactoryContract: ThirdwebContract; - initializeTransaction: PreparedTransaction; - salt?: string; - }, -) { - const implementationAddress = await resolvePromisedValue( - options.initializeTransaction.to, - ); - if (!implementationAddress) { - throw new Error("initializeTransaction must have a 'to' field set"); - } - const deployed = await isContractDeployed({ - address: implementationAddress, - chain: options.chain, - client: options.client, - }); - if (!deployed) { - throw new Error( - `Implementation contract at ${implementationAddress} is not deployed`, - ); - } - // deploy tw proxy of the implementation - return prepareZkDeployContractDeterministicTransaction({ - client: options.client, - chain: options.chain, - account: options.account, - abi: twProxyAbi, - bytecode: twProxyBytecode, - params: { - _logic: implementationAddress, - _data: await encode(options.initializeTransaction), - }, - salt: options.salt || randomBytesHex(32), - }); -} +import { zkDeployContractDeterministic } from "./zkDeployDeterministic.js"; /** * @internal