feat: zksync deploys (#4346)

## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR increases the size limit for `thirdweb (cjs)` to 105 kB, adds an event `ContractDeployed`, and improves error handling and EIP712 transaction support.

### Detailed summary
- Increased size limit for `thirdweb (cjs)` to 105 kB
- Added `ContractDeployed` event
- Improved error messages with detailed information
- Added EIP712 transaction support for zksync
- Refactored deployment address computation and metadata handling

> The following files were skipped due to too many changes: `packages/thirdweb/src/contract/deployment/zksync/zkDeployContract.ts`, `packages/thirdweb/src/transaction/actions/zksync/send-eip712-transaction.test.ts`, `packages/thirdweb/src/contract/deployment/zksync/zkDeployProxy.ts`, `packages/thirdweb/package.json`, `packages/thirdweb/src/contract/deployment/zksync/zkDeployCreate2Factory.ts`, `packages/thirdweb/src/contract/deployment/utils/bootstrap.ts`, `packages/thirdweb/src/contract/deployment/zksync/zkDeployDeterministic.ts`, `packages/thirdweb/src/extensions/prebuilts/deploy-published.ts`, `packages/thirdweb/src/utils/any-evm/zksync/constants.ts`, `pnpm-lock.yaml`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
This commit is contained in:
joaquim-verges
2024-09-04 07:14:25 +00:00
parent 4ad5f7fd50
commit 2ce8840d80
27 changed files with 920 additions and 358 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
{
"name": "thirdweb (cjs)",
"path": "./dist/cjs/exports/thirdweb.js",
"limit": "100 kB"
"limit": "105 kB"
},
{
"name": "thirdweb (minimal + tree-shaking)",
+16 -48
View File
@@ -117,54 +117,22 @@
},
"typesVersions": {
"*": {
"adapters/*": [
"./dist/types/exports/adapters/*.d.ts"
],
"auth": [
"./dist/types/exports/auth.d.ts"
],
"chains": [
"./dist/types/exports/chains.d.ts"
],
"contract": [
"./dist/types/exports/contract.d.ts"
],
"deploys": [
"./dist/types/exports/deploys.d.ts"
],
"event": [
"./dist/types/exports/event.d.ts"
],
"extensions/*": [
"./dist/types/exports/extensions/*.d.ts"
],
"pay": [
"./dist/types/exports/pay.d.ts"
],
"react": [
"./dist/types/exports/react.d.ts"
],
"react-native": [
"./dist/types/exports/react-native.d.ts"
],
"rpc": [
"./dist/types/exports/rpc.d.ts"
],
"storage": [
"./dist/types/exports/storage.d.ts"
],
"transaction": [
"./dist/types/exports/transaction.d.ts"
],
"utils": [
"./dist/types/exports/utils.d.ts"
],
"wallets": [
"./dist/types/exports/wallets.d.ts"
],
"wallets/*": [
"./dist/types/exports/wallets/*.d.ts"
]
"adapters/*": ["./dist/types/exports/adapters/*.d.ts"],
"auth": ["./dist/types/exports/auth.d.ts"],
"chains": ["./dist/types/exports/chains.d.ts"],
"contract": ["./dist/types/exports/contract.d.ts"],
"deploys": ["./dist/types/exports/deploys.d.ts"],
"event": ["./dist/types/exports/event.d.ts"],
"extensions/*": ["./dist/types/exports/extensions/*.d.ts"],
"pay": ["./dist/types/exports/pay.d.ts"],
"react": ["./dist/types/exports/react.d.ts"],
"react-native": ["./dist/types/exports/react-native.d.ts"],
"rpc": ["./dist/types/exports/rpc.d.ts"],
"storage": ["./dist/types/exports/storage.d.ts"],
"transaction": ["./dist/types/exports/transaction.d.ts"],
"utils": ["./dist/types/exports/utils.d.ts"],
"wallets": ["./dist/types/exports/wallets.d.ts"],
"wallets/*": ["./dist/types/exports/wallets/*.d.ts"]
}
},
"browser": {
@@ -0,0 +1,3 @@
[
"event ContractDeployed(address indexed deployerAddress, bytes32 indexed bytecodeHash, address indexed contractAddress)"
]
@@ -7,6 +7,7 @@ import { encode } from "../../transaction/actions/encode.js";
import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js";
import type { PreparedTransaction } from "../../transaction/prepare-transaction.js";
import { keccakId } from "../../utils/any-evm/keccak-id.js";
import { isZkSyncChain } from "../../utils/any-evm/zksync/isZkSyncChain.js";
import { toHex } from "../../utils/encoding/hex.js";
import { resolvePromisedValue } from "../../utils/promise/resolve-promised-value.js";
import type {
@@ -14,6 +15,7 @@ import type {
ClientAndChainAndAccount,
} from "../../utils/types.js";
import type { ThirdwebContract } from "../contract.js";
import { zkDeployProxy } from "./zksync/zkDeployProxy.js";
/**
* @internal
@@ -69,6 +71,17 @@ export async function deployViaAutoFactory(
cloneFactoryContract,
initializeTransaction,
} = options;
if (isZkSyncChain(chain)) {
return zkDeployProxy({
chain,
client,
account,
cloneFactoryContract,
initializeTransaction,
});
}
const tx = prepareAutoFactoryDeployTransaction({
chain,
client,
@@ -30,6 +30,11 @@ export async function fetchPublishedContractMetadata(options: {
contractId: options.contractId,
version: options.version,
});
if (!publishedContract.publishMetadataUri) {
throw new Error(
`No published metadata URI found for ${options.contractId}`,
);
}
return fetchDeployMetadata({
client: options.client,
uri: publishedContract.publishMetadataUri,
@@ -1,5 +1,10 @@
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
import { isZkSyncChain } from "../../../utils/any-evm/zksync/isZkSyncChain.js";
import type { ClientAndChainAndAccount } from "../../../utils/types.js";
import { type ThirdwebContract, getContract } from "../../contract.js";
import { fetchPublishedContractMetadata } from "../publisher.js";
import { zkDeployCreate2Factory } from "../zksync/zkDeployCreate2Factory.js";
import { zkDeployContractDeterministic } from "../zksync/zkDeployDeterministic.js";
import { getDeployedCloneFactoryContract } from "./clone-factory.js";
import {
deployCreate2Factory,
@@ -19,10 +24,56 @@ export async function getOrDeployInfraForPublishedContract(
contractId: string;
constructorParams: unknown[];
publisher?: string;
version?: string;
},
) {
const { chain, client, account, contractId, constructorParams, publisher } =
args;
): Promise<{
cloneFactoryContract: ThirdwebContract;
implementationContract: ThirdwebContract;
}> {
const {
chain,
client,
account,
contractId,
constructorParams,
publisher,
version,
} = args;
if (isZkSyncChain(chain)) {
const cloneFactoryContract = await zkDeployCreate2Factory({
chain,
client,
account,
});
const { compilerMetadata } = await fetchPublishedContractMetadata({
client,
contractId: `${contractId}_ZkSync`, // different contract id for zkSync
publisher,
version,
});
const implementationContract = await zkDeployContractDeterministic({
chain,
client,
account,
abi: compilerMetadata.abi,
bytecode: compilerMetadata.bytecode,
params: constructorParams,
});
return {
cloneFactoryContract: getContract({
address: cloneFactoryContract,
chain,
client,
}),
implementationContract: getContract({
address: implementationContract,
chain,
client,
}),
};
}
let [cloneFactoryContract, implementationContract] = await Promise.all([
getDeployedCloneFactoryContract({
chain,
@@ -34,6 +85,7 @@ export async function getOrDeployInfraForPublishedContract(
contractId,
constructorParams,
publisher,
version,
}),
]);
@@ -51,6 +103,7 @@ export async function getOrDeployInfraForPublishedContract(
contractId,
constructorParams,
publisher,
version,
});
}
return { cloneFactoryContract, implementationContract };
@@ -0,0 +1,52 @@
import type { Abi } from "abitype";
import { encodeDeployData } from "viem/zksync";
import { parseEventLogs } from "../../../event/actions/parse-logs.js";
import { contractDeployedEvent } from "../../../extensions/zksync/__generated__/ContractDeployer/events/ContractDeployed.js";
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
import { prepareTransaction } from "../../../transaction/prepare-transaction.js";
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";
/**
* @internal
*/
export async function zkDeployContract(
options: ClientAndChainAndAccount & {
abi: Abi;
bytecode: Hex;
params?: unknown[];
},
) {
const data = encodeDeployData({
abi: options.abi,
bytecode: options.bytecode,
deploymentType: "create",
args: options.params,
});
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
},
}),
});
const events = parseEventLogs({
logs: receipt.logs,
events: [contractDeployedEvent()],
});
const contractAddress = events[0]?.args.contractAddress;
if (!contractAddress) {
throw new Error("Contract creation failed");
}
return contractAddress;
}
@@ -0,0 +1,65 @@
import { parseAbi } from "abitype";
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
import { prepareTransaction } from "../../../transaction/prepare-transaction.js";
import {
PUBLISHED_PRIVATE_KEY,
ZKSYNC_SINGLETON_FACTORY,
singletonFactoryAbi,
singletonFactoryBytecode,
} from "../../../utils/any-evm/zksync/constants.js";
import { isContractDeployed } from "../../../utils/bytecode/is-contract-deployed.js";
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";
/**
* @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;
}
const create2Signer = privateKeyToAccount({
client: options.client,
privateKey: PUBLISHED_PRIVATE_KEY,
});
const valueToSend = toWei("0.01");
const balance = await getWalletBalance({
address: create2Signer.address,
chain: options.chain,
client: options.client,
});
if (balance.value < valueToSend) {
await sendAndConfirmTransaction({
account: options.account,
transaction: prepareTransaction({
chain: options.chain,
client: options.client,
to: create2Signer.address,
value: valueToSend,
}),
});
}
await zkDeployContract({
client: options.client,
chain: options.chain,
account: options.account,
abi: parseAbi(singletonFactoryAbi),
bytecode: singletonFactoryBytecode,
});
return ZKSYNC_SINGLETON_FACTORY;
}
@@ -0,0 +1,106 @@
import { type Abi, parseAbi } from "abitype";
import { hashBytecode } from "viem/zksync";
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
import { prepareContractCall } from "../../../transaction/prepare-contract-call.js";
import { readContract } from "../../../transaction/read-contract.js";
import { encodeAbiParameters } from "../../../utils/abi/encodeAbiParameters.js";
import { keccakId } from "../../../utils/any-evm/keccak-id.js";
import { computeDeploymentAddress } from "../../../utils/any-evm/zksync/computeDeploymentAddress.js";
import {
KNOWN_CODES_STORAGE,
singletonFactoryAbi,
} from "../../../utils/any-evm/zksync/constants.js";
import { isContractDeployed } from "../../../utils/bytecode/is-contract-deployed.js";
import { ensureBytecodePrefix } from "../../../utils/bytecode/prefix.js";
import { type Hex, uint8ArrayToHex } from "../../../utils/encoding/hex.js";
import type { ClientAndChainAndAccount } from "../../../utils/types.js";
import { getContract } from "../../contract.js";
import { zkDeployContract } from "./zkDeployContract.js";
import { zkDeployCreate2Factory } from "./zkDeployCreate2Factory.js";
/**
* @internal
*/
export async function zkDeployContractDeterministic(
options: ClientAndChainAndAccount & {
abi: Abi;
bytecode: Hex;
params: unknown[];
salt?: string;
},
) {
const constructorAbi = options.abi.find(
(x) => "type" in x && x.type === "constructor",
) || { inputs: [] };
const encodedArgs = encodeAbiParameters(
constructorAbi.inputs,
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));
const predictedAddress = computeDeploymentAddress({
bytecodeHash,
encodedArgs,
create2FactoryAddress,
salt: options.salt,
});
const deployed = await isContractDeployed({
address: predictedAddress,
chain: options.chain,
client: options.client,
});
if (!deployed) {
// 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,
});
}
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");
await sendAndConfirmTransaction({
account: options.account,
transaction: prepareContractCall({
contract: factory,
method: "deploy",
params: [salt, bytecodeHash, encodedArgs],
}),
});
}
return predictedAddress;
}
@@ -0,0 +1,55 @@
import { encode } from "../../../transaction/actions/encode.js";
import type { PreparedTransaction } from "../../../transaction/prepare-transaction.js";
import {
twProxyAbi,
twProxyBytecode,
} from "../../../utils/any-evm/zksync/constants.js";
import { isContractDeployed } from "../../../utils/bytecode/is-contract-deployed.js";
import { resolvePromisedValue } from "../../../utils/promise/resolve-promised-value.js";
import type { ClientAndChainAndAccount } from "../../../utils/types.js";
import type { ThirdwebContract } from "../../contract.js";
import { zkDeployContract } from "./zkDeployContract.js";
/**
* @internal
*/
export async function zkDeployProxy(
options: ClientAndChainAndAccount & {
cloneFactoryContract: ThirdwebContract;
initializeTransaction: PreparedTransaction;
salt?: string;
},
) {
// TODO ensure implementation is deployed
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
const proxyAddress = await zkDeployContract({
client: options.client,
chain: options.chain,
account: options.account,
abi: twProxyAbi,
bytecode: twProxyBytecode,
params: [
implementationAddress,
await encode(options.initializeTransaction),
],
});
// return address of proxy
return proxyAddress;
}
@@ -1,12 +1,15 @@
import type { AbiConstructor } from "abitype";
import type { AbiConstructor, AbiFunction } from "abitype";
import type { Chain } from "../../chains/types.js";
import type { ThirdwebClient } from "../../client/client.js";
import { getContract } from "../../contract/contract.js";
import { fetchPublishedContractMetadata } from "../../contract/deployment/publisher.js";
import { zkDeployContract } from "../../contract/deployment/zksync/zkDeployContract.js";
import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js";
import { simulateTransaction } from "../../transaction/actions/simulate.js";
import { prepareContractCall } from "../../transaction/prepare-contract-call.js";
import { resolveMethod } from "../../transaction/resolve-method.js";
import type { CompilerMetadata } from "../../utils/any-evm/deploy-metadata.js";
import { isZkSyncChain } from "../../utils/any-evm/zksync/isZkSyncChain.js";
import type { Account } from "../../wallets/interfaces/wallet.js";
/**
@@ -58,26 +61,19 @@ export async function deployPublishedContract(
const { compilerMetadata, extendedMetadata } =
await fetchPublishedContractMetadata({
client,
contractId,
contractId: isZkSyncChain(chain) ? `${contractId}_ZkSync` : contractId,
publisher,
version,
});
switch (extendedMetadata?.deployType) {
case "standard": {
const { deployContract } = await import(
"../../contract/deployment/deploy-with-abi.js"
);
return deployContract({
return directDeploy({
account,
client,
chain,
bytecode: compilerMetadata.bytecode,
constructorAbi:
(compilerMetadata.abi.find(
(i) => i.type === "constructor",
) as AbiConstructor) || [],
constructorParams: contractParams,
compilerMetadata,
contractParams,
});
}
case "autoFactory": {
@@ -97,16 +93,23 @@ export async function deployPublishedContract(
constructorParams: implementationConstructorParams || [],
publisher,
});
const initializeFunction = compilerMetadata.abi.find(
(i) =>
i.type === "function" &&
i.name ===
(extendedMetadata.factoryDeploymentData
?.implementationInitializerFunction || "initialize"),
) as AbiFunction;
if (!initializeFunction) {
throw new Error(`Could not find initialize function for ${contractId}`);
}
const initializeTransaction = prepareContractCall({
contract: getContract({
client,
chain,
address: implementationContract.address,
}),
method: resolveMethod(
extendedMetadata.factoryDeploymentData
?.implementationInitializerFunction || "initialize",
),
method: initializeFunction,
params: contractParams,
});
@@ -154,19 +157,12 @@ export async function deployPublishedContract(
}
case undefined: {
// Default to standard deployment if none was specified
const { deployContract } = await import(
"../../contract/deployment/deploy-with-abi.js"
);
return deployContract({
return directDeploy({
account,
client,
chain,
bytecode: compilerMetadata.bytecode,
constructorAbi:
(compilerMetadata.abi.find(
(i) => i.type === "constructor",
) as AbiConstructor) || [],
constructorParams: contractParams,
compilerMetadata,
contractParams,
});
}
default:
@@ -176,3 +172,39 @@ export async function deployPublishedContract(
);
}
}
async function directDeploy(options: {
account: Account;
client: ThirdwebClient;
chain: Chain;
compilerMetadata: CompilerMetadata;
contractParams: unknown[];
}) {
const { account, client, chain, compilerMetadata, contractParams } = options;
if (isZkSyncChain(chain)) {
return zkDeployContract({
account,
client,
chain,
bytecode: compilerMetadata.bytecode,
abi: compilerMetadata.abi,
params: contractParams,
});
}
const { deployContract } = await import(
"../../contract/deployment/deploy-with-abi.js"
);
return deployContract({
account,
client,
chain,
bytecode: compilerMetadata.bytecode,
constructorAbi:
(compilerMetadata.abi.find(
(i) => i.type === "constructor",
) as AbiConstructor) || [],
constructorParams: contractParams,
});
}
@@ -0,0 +1,55 @@
import { prepareEvent } from "../../../../../event/prepare-event.js";
import type { AbiParameterToPrimitiveType } from "abitype";
/**
* Represents the filters for the "ContractDeployed" event.
*/
export type ContractDeployedEventFilters = Partial<{
deployerAddress: AbiParameterToPrimitiveType<{
type: "address";
name: "deployerAddress";
indexed: true;
}>;
bytecodeHash: AbiParameterToPrimitiveType<{
type: "bytes32";
name: "bytecodeHash";
indexed: true;
}>;
contractAddress: AbiParameterToPrimitiveType<{
type: "address";
name: "contractAddress";
indexed: true;
}>;
}>;
/**
* Creates an event object for the ContractDeployed event.
* @param filters - Optional filters to apply to the event.
* @returns The prepared event object.
* @extension ZKSYNC
* @example
* ```ts
* import { getContractEvents } from "thirdweb";
* import { contractDeployedEvent } from "thirdweb/extensions/zksync";
*
* const events = await getContractEvents({
* contract,
* events: [
* contractDeployedEvent({
* deployerAddress: ...,
* bytecodeHash: ...,
* contractAddress: ...,
* })
* ],
* });
* ```
*/
export function contractDeployedEvent(
filters: ContractDeployedEventFilters = {},
) {
return prepareEvent({
signature:
"event ContractDeployed(address indexed deployerAddress, bytes32 indexed bytecodeHash, address indexed contractAddress)",
filters,
});
}
@@ -144,6 +144,17 @@ export async function sendTransaction(
await account.onTransactionRequested(transaction);
}
// if zksync transaction params are set, send with eip712
if (options.transaction.eip712) {
const { sendEip712Transaction } = await import(
"./zksync/send-eip712-transaction.js"
);
return sendEip712Transaction({
account,
transaction,
});
}
const serializableTransaction = await toSerializableTransaction({
transaction: transaction,
from: account.address,
@@ -3,9 +3,7 @@ import { beforeAll, describe, expect, test } from "vitest";
import { TEST_WALLET_B } from "../../../test/src/addresses.js";
import { FORKED_ETHEREUM_CHAIN } from "../../../test/src/chains.js";
import { TEST_CLIENT } from "../../../test/src/test-clients.js";
import { arbitrumSepolia } from "../../chains/chain-definitions/arbitrum-sepolia.js";
import { ZERO_ADDRESS } from "../../constants/addresses.js";
import { toWei } from "../../utils/units.js";
import {
type PreparedTransaction,
@@ -403,15 +401,16 @@ describe.runIf(process.env.TW_SECRET_KEY)("toSerializableTransaction", () => {
});
});
// skipping this test for now, it fails when batching eth_estimateGas with eth_maxFeePerGas together on arbitrumSepolia
// works if you run it individually, but when run in parallel the batching kicks in and it fails
test("should respect 0 maxPriorityFeePerGas chains", async () => {
const serializableTransaction = await toSerializableTransaction({
transaction: prepareTransaction({
to: TEST_WALLET_B,
chain: arbitrumSepolia,
value: toWei("0.000001"),
value: 0n,
client: TEST_CLIENT,
}),
from: ZERO_ADDRESS,
});
// gasPrice should be undefined for arbSepolia which has 0 maxPriorityFeePerGas
@@ -1,5 +1,7 @@
import type { TransactionSerializable } from "viem";
import { hashBytecode } from "viem/zksync";
import type { Address } from "../../../utils/address.js";
import { toHex } from "../../../utils/encoding/hex.js";
import type {
EIP712SerializedTransaction,
EIP712TransactionOptions,
@@ -56,6 +58,7 @@ function transactionToMessage(
paymasterInput,
gasPerPubdata,
data,
factoryDeps,
} = transaction;
return {
@@ -70,8 +73,7 @@ function transactionToMessage(
nonce: nonce ? BigInt(nonce) : 0n,
value: value ?? 0n,
data: data ? data : "0x0",
// TODO suport factoryDeps
factoryDeps: [],
factoryDeps: factoryDeps?.map((dep) => toHex(hashBytecode(dep))) ?? [],
paymasterInput: paymasterInput ? paymasterInput : "0x",
};
}
@@ -4,7 +4,8 @@ import {
TEST_ACCOUNT_A,
TEST_ACCOUNT_B,
} from "../../../../test/src/test-wallets.js";
import { defineChain } from "../../../chains/utils.js";
import { zkSyncSepolia } from "../../../chains/chain-definitions/zksync-sepolia.js";
import { deployPublishedContract } from "../../../extensions/prebuilts/deploy-published.js";
import { prepareTransaction } from "../../prepare-transaction.js";
import { sendEip712Transaction } from "./send-eip712-transaction.js";
@@ -12,7 +13,7 @@ describe("sendEip712Transaction", () => {
// re-enable for testing, but disable for CI since it requires testnet funds
it.skip("should send eip712 transaction", async () => {
const transaction = prepareTransaction({
chain: defineChain(300),
chain: zkSyncSepolia, // TODO make zksync fork chain work
client: TEST_CLIENT,
value: 0n,
to: TEST_ACCOUNT_B.address,
@@ -28,4 +29,28 @@ describe("sendEip712Transaction", () => {
});
expect(res.transactionHash.length).toBe(66);
});
it.skip("should deploy a published autofactory contract on zksync", async () => {
const address = await deployPublishedContract({
client: TEST_CLIENT,
chain: zkSyncSepolia, // TODO make zksync fork chain work
account: TEST_ACCOUNT_A,
contractId: "DropERC1155",
contractParams: [
TEST_ACCOUNT_A.address, // defaultAdmin
"test", // name
"", // symbol
"", // contractURI
[], // trustedForwarders
TEST_ACCOUNT_A.address, // saleRecipient
TEST_ACCOUNT_A.address, // royaltyRecipient
0n, // royaltyBps
0n, // platformFeeBps
TEST_ACCOUNT_A.address, // platformFeeRecipient
],
});
console.log("deployed address", address);
expect(address).toBeDefined();
expect(address.length).toBe(42);
});
});
@@ -2,6 +2,8 @@ import type { Abi } from "abitype";
import { type Hex, decodeErrorResult } from "viem";
import { resolveContractAbi } from "../contract/actions/resolve-abi.js";
import type { ThirdwebContract } from "../contract/contract.js";
import { isHex } from "../utils/encoding/hex.js";
import { stringify } from "../utils/json.js";
/**
* @internal
@@ -19,7 +21,7 @@ export async function extractError<abi extends Abi>(args: {
data?: Hex;
};
if (errorObj.data) {
if (errorObj.data !== "0x") {
if (errorObj.data !== "0x" && isHex(errorObj.data)) {
let abi = contract?.abi;
if (contract && !abi) {
abi = await resolveContractAbi(contract).catch(() => undefined);
@@ -35,7 +37,10 @@ export async function extractError<abi extends Abi>(args: {
contract,
);
}
return new TransactionError("Execution Reverted", contract);
return new TransactionError(
`Execution Reverted: ${stringify(errorObj)}`,
contract,
);
}
}
return error;
@@ -30,7 +30,7 @@ describe("encodeAbiParameters", () => {
it("should throw error when encoding invalid address", () => {
const invalidAddress = "0x_hello_world";
expect(() => encodeAddress(invalidAddress)).toThrowError(
"Invalid address.",
`Invalid address: ${invalidAddress}`,
);
});
@@ -160,7 +160,7 @@ function encodeParams(preparedParams: PreparedParam[]): Hex {
export function encodeAddress(value: Hex): PreparedParam {
// We allow empty strings for deployment transactions where there is no to address
if ((value as string) !== "" && value !== undefined && !isAddress(value)) {
throw new Error("Invalid address.");
throw new Error(`Invalid address: ${value}`);
}
return { dynamic: false, encoded: padHex(value.toLowerCase() as Hex) };
}
@@ -53,13 +53,22 @@ async function fetchCompilerMetadata(
uri: options.uri,
client: options.client,
}).then((r) => r.json());
// TODO: proper handling of different compiler metadata types
const metadataUri =
rawMeta.compilers?.zksolc?.length > 0 && rawMeta.name.endsWith("_ZkSync")
? rawMeta.compilers.zksolc[0].metadataUri
: rawMeta.metadataUri;
const bytecodeUri =
rawMeta.compilers?.zksolc?.length > 0 && rawMeta.name.endsWith("_ZkSync")
? rawMeta.compilers.zksolc[0].bytecodeUri
: rawMeta.bytecodeUri;
const [deployBytecode, parsedMeta] = await Promise.all([
download({ uri: rawMeta.bytecodeUri, client: options.client }).then(
download({ uri: bytecodeUri, client: options.client }).then(
(res) => res.text() as Promise<Hex>,
),
fetchAndParseCompilerMetadata({
client: options.client,
uri: rawMeta.metadataUri,
uri: metadataUri,
}),
]);
@@ -0,0 +1,24 @@
import type { Address } from "../../address.js";
import type { Hex } from "../../encoding/hex.js";
import { keccakId } from "../keccak-id.js";
import { create2Address } from "./create2Address.js";
type ComputeDeploymentAddressOptions = {
bytecodeHash: Hex;
encodedArgs: Hex;
create2FactoryAddress: Address;
salt?: string;
};
export function computeDeploymentAddress(
options: ComputeDeploymentAddressOptions,
) {
const saltHash = options.salt ? keccakId(options.salt) : keccakId("thirdweb");
return create2Address({
sender: options.create2FactoryAddress,
bytecodeHash: options.bytecodeHash,
salt: saltHash,
input: options.encodedArgs,
});
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,26 @@
import { concat, padHex } from "viem";
import { type Address, getAddress } from "../../address.js";
import type { Hex } from "../../encoding/hex.js";
import { stringToBytes } from "../../encoding/to-bytes.js";
import { keccak256 } from "../../hashing/keccak256.js";
export function create2Address(options: {
sender: Address;
bytecodeHash: Hex;
salt: Hex;
input?: Hex;
}): string {
const { sender, bytecodeHash, salt, input } = options;
const prefix = keccak256(stringToBytes("zksyncCreate2"));
const inputHash = input ? keccak256(input) : "0x";
const addressBytes = keccak256(
concat([
prefix,
padHex(sender, { size: 32 }),
salt,
bytecodeHash,
inputHash,
]),
).slice(26);
return getAddress(`0x${addressBytes}`);
}
@@ -0,0 +1,10 @@
import type { Chain } from "../../../chains/types.js";
export function isZkSyncChain(chain: Chain) {
return (
chain.id === 324 ||
chain.id === 300 ||
chain.id === 302 ||
chain.id === 11124
);
}
+2 -3
View File
@@ -19,6 +19,7 @@ import {
} from "../../transaction/actions/zksync/send-eip712-transaction.js";
import type { PreparedTransaction } from "../../transaction/prepare-transaction.js";
import { getAddress } from "../../utils/address.js";
import { isZkSyncChain } from "../../utils/any-evm/zksync/isZkSyncChain.js";
import { concatHex } from "../../utils/encoding/helpers/concat-hex.js";
import type { Hex } from "../../utils/encoding/hex.js";
import { parseTypedData } from "../../utils/signatures/helpers/parseTypedData.js";
@@ -48,7 +49,6 @@ import {
signUserOp,
waitForUserOpReceipt,
} from "./lib/userop.js";
import { isNativeAAChain } from "./lib/utils.js";
import type {
BundlerOptions,
PaymasterResult,
@@ -58,7 +58,6 @@ import type {
UserOperationV06,
UserOperationV07,
} from "./types.js";
/**
* Checks if the provided wallet is a smart wallet.
*
@@ -104,7 +103,7 @@ export async function connectSmartWallet(
const sponsorGas =
"gasless" in options ? options.gasless : options.sponsorGas;
if (isNativeAAChain(chain)) {
if (isZkSyncChain(chain)) {
return [
createZkSyncAccount({
creationOptions,
@@ -1,5 +1,4 @@
import { concat } from "viem";
import type { Chain } from "../../../chains/types.js";
import { isHex, numberToHex, toHex } from "../../../utils/encoding/hex.js";
import type {
UserOperationV06,
@@ -48,12 +47,3 @@ export function hexlifyUserOp(
]),
) as UserOperationV06Hexed | UserOperationV07Hexed;
}
export function isNativeAAChain(chain: Chain) {
return (
chain.id === 324 ||
chain.id === 300 ||
chain.id === 302 ||
chain.id === 11124
);
}
+259 -253
View File
File diff suppressed because it is too large Load Diff