From 3b85b4b3bd38553db35b1ec5e3c31c2ad787f612 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Tue, 17 Sep 2024 09:39:07 -0400 Subject: [PATCH] Implement new rpc methods (#64) * add methods, schemas and types * rpc * add tests * add msg proof test * fix tests --- .github/workflows/build.yml | 2 +- src/rpc.methods.ts | 123 +++++++++++++++++++++++++++++- src/schemas.ts | 114 +++++++++++++++++++++++++++ src/types.ts | 55 +++++++++++++ test/integration/rpc.paid.test.ts | 32 ++++++++ test/integration/rpc.test.ts | 52 +++++++++++++ 6 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 test/integration/rpc.paid.test.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1494ed8..a56a2c6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,4 +20,4 @@ jobs: - run: yarn build - run: export PRIVATE_KEY=${{secrets.PRIVATE_KEY}} - run: yarn test:unit - - run: yarn test test/integration/rpc test/integration/mainnet test/integration/zksync test/integration/utils + - run: yarn test test/integration/rpc.test test/integration/mainnet test/integration/zksync test/integration/utils diff --git a/src/rpc.methods.ts b/src/rpc.methods.ts index 5699cc2..05fead2 100644 --- a/src/rpc.methods.ts +++ b/src/rpc.methods.ts @@ -3,7 +3,7 @@ import * as web3Utils from 'web3-utils'; import type * as web3Types from 'web3-types'; import { DEFAULT_RETURN_FORMAT } from 'web3'; import type { DataFormat } from 'web3-types/src/data_format_types'; -import type { +import { BatchDetails, BlockDetails, BridgeAddresses, @@ -15,6 +15,11 @@ import type { WalletBalances, TransactionRequest, Address, + ProtocolVersion, + FeeParams, + Token, + RawTransactionWithDetailedOutput, + MessageProof, } from './types'; import { AddressSchema, @@ -23,11 +28,15 @@ import { BridgeAddressesSchema, BytesArraySchema, BytesSchema, + ConfirmedTokensSchema, EstimateFeeSchema, + FeeParamsSchema, IntSchema, L2ToL1ProofSchema, ProofSchema, + ProtocolVersionSchema, RawBlockTransactionSchema, + RawTransactionWithDetailedOutputSchema, TransactionDetailsSchema, UintSchema, } from './schemas'; @@ -392,4 +401,116 @@ export class RpcMethods { returnFormat, ) as Address; } + + /** + * Lists confirmed tokens. Confirmed in the method name means any token bridged to ZKsync Era via the official bridge. + * The tokens are returned in alphabetical order by their symbol. This means the token id is its position in an alphabetically sorted array of tokens. + * + * @param fromTokenId - The token id to start from. + * @param limit - The number of tokens to return. + * @param returnFormat - The format of the return value. + */ + public async getConfirmedTokens( + fromTokenId: number, + limit: number, + returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return web3Utils.format( + ConfirmedTokensSchema, + await this._send('zks_getConfirmedTokens', [fromTokenId, limit]), + returnFormat, + ) as Token[]; + } + + /** + * Retrieves the proof for an L2 to L1 message. + * + * @param l2BlockNumber - The L2 block number. + * @param senderAddress - The sender address. + * @param messageHash - The message hash. + * @param l2LogPosition - The log position in L2. + * @param returnFormat - The format of the return value. + */ + public async getL2ToL1MsgProof( + l2BlockNumber: number, + senderAddress: web3Types.Address, + messageHash: web3Types.Bytes, + l2LogPosition?: number, + returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return web3Utils.format( + L2ToL1ProofSchema, + await this._send('zks_getL2ToL1MsgProof', [ + l2BlockNumber, + senderAddress, + messageHash, + l2LogPosition || 0, + ]), + returnFormat, + ) as MessageProof; + } + + /** + * Retrieves the current L1 gas price. + * + * @param returnFormat - The format of the return value. + */ + public async getL1GasPrice(returnFormat: DataFormat = DEFAULT_RETURN_FORMAT): Promise { + return web3Utils.format( + { format: 'uint' }, + await this._send('zks_getL1GasPrice', []), + returnFormat, + ) as BigInt; + } + + /** + * Retrieves the current fee parameters. + * + * @param returnFormat - The format of the return value. + */ + public async getFeeParams( + returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return web3Utils.format( + FeeParamsSchema, + await this._send('zks_getFeeParams', []), + returnFormat, + ) as FeeParams; + } + + /** + * Gets the protocol version. + * + * @param versionId - The version ID. + * @param returnFormat - The format of the return value. + */ + public async getProtocolVersion( + versionId?: number, + returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return web3Utils.format( + ProtocolVersionSchema, + await this._send('zks_getProtocolVersion', [versionId]), + returnFormat, + ) as ProtocolVersion; + } + + /** + * Executes a transaction and returns its hash, storage logs, and events that would have been generated if the transaction had already been included in the block. The API has a similar behaviour to eth_sendRawTransaction but with some extra data returned from it. + * With this API Consumer apps can apply "optimistic" events in their applications instantly without having to wait for ZKsync block confirmation time. + * It’s expected that the optimistic logs of two uncommitted transactions that modify the same state will not have causal relationships between each other. + * + * @param data - The transaction data. + * @param returnFormat - The format of the return value. + */ + public async sendRawTransactionWithDetailedOutput( + data: web3Types.Bytes, + returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return web3Utils.format( + RawTransactionWithDetailedOutputSchema, + await this._send('zks_sendRawTransactionWithDetailedOutput', [data]), + returnFormat, + ) as RawTransactionWithDetailedOutput; + } } diff --git a/src/schemas.ts b/src/schemas.ts index f7cfa47..22ad635 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -293,3 +293,117 @@ export const ZKTransactionReceiptSchema = { }, }, }; + +export const RawTransactionWithDetailedOutputSchema = { + type: 'object', + properties: { + transactionHash: { format: 'bytes32' }, + storageLogs: { + type: 'array', + items: { + type: 'object', + properties: { + address: { format: 'address' }, + key: { format: 'bytes32' }, + writtenValue: { format: 'bytes32' }, + }, + }, + }, + events: { + type: 'array', + items: { + type: 'object', + properties: { + address: { format: 'address' }, + topics: { + type: 'array', + items: { + type: 'object', + properties: { + topic: { format: 'bytes32' }, + }, + }, + }, + data: { format: 'bytes' }, + blockHash: { format: 'bytes32' }, + blockNumber: { format: 'uint' }, + l1BatchNumber: { format: 'uint' }, + transactionHash: { format: 'bytes32' }, + transactionIndex: { format: 'uint' }, + logIndex: { format: 'uint' }, + transactionLogIndex: { format: 'uint' }, + logType: { format: 'string' }, + removed: { format: 'bool' }, + }, + }, + }, + }, +}; + +export const ProtocolVersionSchema = { + type: 'object', + properties: { + version_id: { format: 'uint' }, + timestamp: { format: 'uint' }, + verification_keys_hashes: { + type: 'object', + properties: { + params: { + type: 'object', + properties: { + recursion_node_level_vk_hash: { format: 'bytes32' }, + recursion_leaf_level_vk_hash: { format: 'bytes32' }, + recursion_circuits_set_vks_hash: { format: 'bytes32' }, + }, + }, + recursion_scheduler_level_vk_hash: { format: 'bytes32' }, + }, + }, + base_system_contracts: { + type: 'object', + properties: { + bootloader: { format: 'bytes32' }, + default_aa: { format: 'bytes32' }, + }, + }, + l2_system_upgrade_tx_hash: { format: 'bytes32' }, + }, +}; + +export const FeeParamsSchema = { + type: 'object', + properties: { + V2: { + type: 'object', + properties: { + config: { + type: 'object', + properties: { + minimal_l2_gas_price: { format: 'uint' }, + compute_overhead_part: { format: 'uint' }, + pubdata_overhead_part: { format: 'uint' }, + batch_overhead_l1_gas: { format: 'uint' }, + max_gas_per_batch: { format: 'uint' }, + max_pubdata_per_batch: { format: 'uint' }, + }, + }, + l1_gas_price: { format: 'uint' }, + l1_pubdata_price: { format: 'uint' }, + }, + }, + }, +}; + +export const ConfirmedTokensSchema = { + type: 'array', + items: { + type: 'object', + properties: { + l1Address: { format: 'address' }, + l2Address: { format: 'address' }, + name: { format: 'string' }, + symbol: { format: 'string' }, + decimals: { format: 'uint' }, + }, + }, +}; diff --git a/src/types.ts b/src/types.ts index 08c6edb..e5ba147 100644 --- a/src/types.ts +++ b/src/types.ts @@ -625,6 +625,61 @@ export type ZKSyncContractsCollection = { }; }; +export type RawTransactionWithDetailedOutput = { + transactionHash: string; + storageLogs: { + address: string; + key: string; + writtenValue: string; + }[]; + events: { + address: string; + topics: string[]; + data: string; + blockHash: string; + blockNumber: Numbers; + l1BatchNumber: Numbers; + transactionHash: string; + transactionIndex: Numbers; + logIndex: Numbers; + transactionLogIndex: Numbers; + logType: string; + removed: boolean; + }[]; +}; + +export type ProtocolVersion = { + version_id: number; + timestamp: number; + verification_keys_hashes: { + params: { + recursion_node_level_vk_hash: string; + recursion_leaf_level_vk_hash: string; + recursion_circuits_set_vks_hash: string; + }; + recursion_scheduler_level_vk_hash: string; + }; + base_system_contracts: { + bootloader: string; + default_aa: string; + }; + l2_system_upgrade_tx_hash: string; +}; + +export type FeeParams = { + V2: { + config: { + minimal_l2_gas_price: Numbers; + compute_overhead_part: Numbers; + pubdata_overhead_part: Numbers; + batch_overhead_l1_gas: Numbers; + max_gas_per_batch: Numbers; + max_pubdata_per_batch: Numbers; + }; + l1_gas_price: Numbers; + l1_pubdata_price: Numbers; + }; +}; export type DepositTransactionDetails = { /** * The address of the token to deposit. diff --git a/test/integration/rpc.paid.test.ts b/test/integration/rpc.paid.test.ts new file mode 100644 index 0000000..a8d1afd --- /dev/null +++ b/test/integration/rpc.paid.test.ts @@ -0,0 +1,32 @@ +import { Web3 } from 'web3'; +import { ZKsyncPlugin } from '../../src'; +import { PRIVATE_KEY1 } from '../utils'; +import { privateKeyToAccount } from 'web3-eth-accounts'; +const PRIVATE_KEY = (process.env.PRIVATE_KEY as string) || PRIVATE_KEY1; +const mainAccount = privateKeyToAccount(PRIVATE_KEY); +describe('ZkSyncPlugin rpc paid tests', () => { + let web3: Web3; + + beforeAll(() => { + web3 = new Web3(); + web3.registerPlugin(new ZKsyncPlugin('https://sepolia.era.zksync.dev')); + web3.ZKsync.L2.eth.accounts.wallet.add(mainAccount); + }); + + it('sendRawTransactionWithDetailedOutput', async () => { + const populated = await web3.ZKsync.L2.populateTransaction({ + from: mainAccount.address, + to: '0x9a6de0f62aa270a8bcb1e2610078650d539b1ef9', + value: 1n, + }); + const signed = await web3.ZKsync.L2.signTransaction(populated); + const res = await web3.ZKsync.rpc.sendRawTransactionWithDetailedOutput(signed); + expect(res.transactionHash).toBeDefined(); + expect(res.storageLogs).toBeDefined(); + expect(Array.isArray(res.storageLogs)).toBeTruthy(); + expect(res.storageLogs.length).toBeGreaterThan(0); + expect(res.events).toBeDefined(); + expect(Array.isArray(res.events)).toBeTruthy(); + expect(res.events.length).toBeGreaterThan(0); + }); +}); diff --git a/test/integration/rpc.test.ts b/test/integration/rpc.test.ts index 4ec50e2..6a1daf2 100644 --- a/test/integration/rpc.test.ts +++ b/test/integration/rpc.test.ts @@ -72,4 +72,56 @@ describe('ZkSyncPlugin rpc tests', () => { const res = await web3.ZKsync.rpc.getBridgehubContractAddress(); expect(res).toEqual('0x35a54c8c757806eb6820629bc82d90e056394c92'); // @todo: set bridge hub contract address }); + + it('getProtocolVersion', async () => { + const res = await web3.ZKsync.rpc.getProtocolVersion(); + expect(res.version_id).toBe(24n); + expect(res.timestamp).toBeDefined(); + expect(res.base_system_contracts).toBeDefined(); + expect(res.l2_system_upgrade_tx_hash).toBeDefined(); + expect(res.verification_keys_hashes).toBeDefined(); + }); + it('getFeeParams', async () => { + const res = await web3.ZKsync.rpc.getFeeParams(); + expect(res.V2).toBeDefined(); + expect(res.V2.config).toBeDefined(); + expect(res.V2.config.compute_overhead_part).toBeDefined(); + expect(res.V2.config.batch_overhead_l1_gas).toBeGreaterThan(0n); + expect(res.V2.config.max_gas_per_batch).toBeGreaterThan(0n); + expect(res.V2.config.max_pubdata_per_batch).toBeGreaterThan(0n); + expect(res.V2.config.minimal_l2_gas_price).toBeGreaterThan(0n); + expect(res.V2.config.pubdata_overhead_part).toBeGreaterThan(0n); + expect(res.V2.l1_gas_price).toBeGreaterThan(0n); + expect(res.V2.l1_pubdata_price).toBeGreaterThan(0n); + }); + it('getL1GasPrice', async () => { + const res = await web3.ZKsync.rpc.getL1GasPrice(); + expect(res).toBeDefined(); + expect(res).toBeGreaterThan(0n); + }); + it('getL2ToL1MsgProof', async () => { + const res = await web3.ZKsync.rpc.getL2ToL1MsgProof( + 2610857, + '0x466ff3c5C76445823b49dF047d72663B8eAe9272', + '0x4ba6379f4d5c7f9eae393022467be6d05f2426b51efeb0011705d9bb5c3ce263', + 3, + ); + expect(res).toBeDefined(); + expect(res.id).toBeDefined(); + expect(res.proof).toBeDefined(); + expect(res.root).toBeDefined(); + expect(Array.isArray(res.proof)).toBeTruthy(); + expect(res.proof.length > 0).toBeTruthy(); + }); + it('getConfirmedTokens', async () => { + const res = await web3.ZKsync.rpc.getConfirmedTokens(0, 10); + expect(res).toBeDefined(); + expect(Array.isArray(res)).toBeTruthy(); + expect(res.length).toBeGreaterThan(0); + expect(res[0].decimals).toBeGreaterThan(0n); + expect(res[0].l1Address).toBeDefined(); + expect(res[0].l2Address).toBeDefined(); + expect(res[0].name).toBeDefined(); + expect(res[0].symbol).toBeDefined(); + }); });