UNPKG

ox

Version:

Ethereum Standard Library

426 lines (401 loc) 10.6 kB
import { describe, expect, test } from 'vp/test' import * as z_RpcSchema from '../RpcSchema.js' import * as z from 'zod/mini' const address = '0x0000000000000000000000000000000000000000' const hash = `0x${'11'.repeat(32)}` as const const topic = `0x${'33'.repeat(32)}` as const describe('decodeParams', () => { test('decodes params (block number / tag coercion)', () => { expect( z_RpcSchema.decodeParams(z_RpcSchema.Eth, 'eth_getBlockByNumber', [ '0x1b4', true, ]), ).toMatchInlineSnapshot(` [ 436n, true, ] `) expect( z_RpcSchema.decodeParams(z_RpcSchema.Eth, 'eth_getBlockByNumber', [ 'latest', false, ]), ).toMatchInlineSnapshot(` [ "latest", false, ] `) }) test('decodes wallet params', () => { expect( z_RpcSchema.decodeParams( z_RpcSchema.Wallet, 'wallet_switchEthereumChain', [{ chainId: '0x1' }], ), ).toMatchInlineSnapshot(` [ { "chainId": "0x1", }, ] `) }) }) describe('decodeReturns', () => { test('decodes scalar return types', () => { expect( z_RpcSchema.decodeReturns(z_RpcSchema.Eth, 'eth_blockNumber', '0x1b4'), ).toMatchInlineSnapshot(`436n`) expect( z_RpcSchema.decodeReturns(z_RpcSchema.Eth, 'eth_chainId', '0x1'), ).toMatchInlineSnapshot(`1`) expect( z_RpcSchema.decodeReturns( z_RpcSchema.Eth, 'eth_getTransactionCount', '0x2', ), ).toMatchInlineSnapshot(`2`) expect( z_RpcSchema.decodeReturns(z_RpcSchema.Eth, 'eth_call', '0x1234'), ).toMatchInlineSnapshot(`"0x1234"`) expect( z_RpcSchema.decodeReturns(z_RpcSchema.Eth, 'eth_accounts', [address]), ).toMatchInlineSnapshot(` [ "0x0000000000000000000000000000000000000000", ] `) }) test('decodes fee history return type', () => { expect( z_RpcSchema.decodeReturns(z_RpcSchema.Eth, 'eth_feeHistory', { baseFeePerGas: ['0x1', '0x2'], gasUsedRatio: [0.5], oldestBlock: '0x10', reward: [['0x3']], }), ).toMatchInlineSnapshot(` { "baseFeePerGas": [ 1n, 2n, ], "gasUsedRatio": [ 0.5, ], "oldestBlock": 16n, "reward": [ [ 3n, ], ], } `) }) test('decodes log return types', () => { expect( z_RpcSchema.decodeReturns(z_RpcSchema.Eth, 'eth_getLogs', [ { address, blockHash: hash, blockNumber: '0x1', data: '0x', logIndex: '0x0', topics: [topic], transactionHash: hash, transactionIndex: '0x0', removed: false, }, ]), ).toMatchInlineSnapshot(` [ { "address": "0x0000000000000000000000000000000000000000", "blockHash": "0x1111111111111111111111111111111111111111111111111111111111111111", "blockNumber": 1n, "data": "0x", "logIndex": 0, "removed": false, "topics": [ "0x3333333333333333333333333333333333333333333333333333333333333333", ], "transactionHash": "0x1111111111111111111111111111111111111111111111111111111111111111", "transactionIndex": 0, }, ] `) }) test('decodes nullable return types', () => { expect( z_RpcSchema.decodeReturns( z_RpcSchema.Eth, 'eth_getTransactionReceipt', null, ), ).toMatchInlineSnapshot(`null`) }) }) describe('encodeParams', () => { test('encodes params (native → wire)', () => { expect( z_RpcSchema.encodeParams(z_RpcSchema.Eth, 'eth_getBlockByNumber', [ 436n, true, ]), ).toMatchInlineSnapshot(` [ "0x1b4", true, ] `) }) test('encodes eth_call request params with numberish quantities', () => { expect( z_RpcSchema.encodeParams(z_RpcSchema.Eth, 'eth_call', [ { gas: '0x2dc6c0', maxFeePerGas: 100_000_000_000n, maxPriorityFeePerGas: 1, nonce: 0, to: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', value: '0x0', }, 'latest', ]), ).toMatchInlineSnapshot(` [ { "gas": "0x2dc6c0", "maxFeePerGas": "0x174876e800", "maxPriorityFeePerGas": "0x1", "nonce": "0x0", "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", "value": "0x0", }, "latest", ] `) }) test('encodes eth_call request params with state + block overrides', () => { expect( z_RpcSchema.encodeParams(z_RpcSchema.Eth, 'eth_call', [ { to: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', value: 1n }, 'latest', {}, { number: 420n }, ]), ).toMatchInlineSnapshot(` [ { "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", "value": "0x1", }, "latest", {}, { "number": "0x1a4", }, ] `) }) }) describe('encodeReturns', () => { test('encodes scalar return types (native → wire)', () => { expect( z_RpcSchema.encodeReturns(z_RpcSchema.Eth, 'eth_blockNumber', 436n), ).toMatchInlineSnapshot(`"0x1b4"`) expect( z_RpcSchema.encodeReturns(z_RpcSchema.Eth, 'eth_chainId', 1), ).toMatchInlineSnapshot(`"0x1"`) expect( z_RpcSchema.encodeReturns(z_RpcSchema.Eth, 'eth_call', '0x1234'), ).toMatchInlineSnapshot(`"0x1234"`) }) }) describe('encodeRequest', () => { test('encodes a request (native → wire)', () => { expect( z_RpcSchema.encodeRequest(z_RpcSchema.Eth, { method: 'eth_getBlockByNumber', params: [436n, true], }), ).toMatchInlineSnapshot(` { "method": "eth_getBlockByNumber", "params": [ "0x1b4", true, ], } `) }) }) describe('parseItem', () => { test('looks up a method schema', () => { expect( z_RpcSchema.parseItem(z_RpcSchema.Eth, 'eth_blockNumber').method, ).toMatchInlineSnapshot(`"eth_blockNumber"`) }) test('throws for unknown methods', () => { expect(() => z_RpcSchema.parseItem(z_RpcSchema.Eth, 'eth_unknownMethod' as never), ).toThrowErrorMatchingInlineSnapshot( `[RpcSchema.MethodNotFoundError: Method \`eth_unknownMethod\` does not exist on the schema.]`, ) }) test('codecs accept a resolved item', () => { const item = z_RpcSchema.parseItem( z_RpcSchema.Eth, 'eth_getBlockTransactionCountByNumber', ) expect(z_RpcSchema.encodeParams(item, [1n])).toMatchInlineSnapshot(` [ "0x1", ] `) expect(z_RpcSchema.decodeParams(item, ['0x1'])).toMatchInlineSnapshot(` [ 1n, ] `) expect(z_RpcSchema.decodeReturns(item, '0x1')).toMatchInlineSnapshot(`1`) expect(z_RpcSchema.encodeReturns(item, 1)).toMatchInlineSnapshot(`"0x1"`) }) }) describe('decodeRequest', () => { test('dispatches by method', () => { expect( z_RpcSchema.decodeRequest(z_RpcSchema.Eth, { method: 'eth_getBlockByNumber', params: ['0x1', true], }), ).toMatchInlineSnapshot(` { "method": "eth_getBlockByNumber", "params": [ 1n, true, ], } `) }) test('parse is an alias of decodeRequest', () => { expect( z_RpcSchema.parse(z_RpcSchema.Wallet, { method: 'wallet_switchEthereumChain', params: [{ chainId: '0x1' }], }), ).toMatchInlineSnapshot(` { "method": "wallet_switchEthereumChain", "params": [ { "chainId": "0x1", }, ], } `) }) test('Default dispatches across eth_ and wallet_ methods', () => { expect( z_RpcSchema.decodeRequest(z_RpcSchema.Default, { method: 'eth_getBlockByNumber', params: ['0x1', true], }), ).toMatchInlineSnapshot(` { "method": "eth_getBlockByNumber", "params": [ 1n, true, ], } `) expect( z_RpcSchema.decodeRequest(z_RpcSchema.Default, { method: 'wallet_switchEthereumChain', params: [{ chainId: '0x1' }], }), ).toMatchInlineSnapshot(` { "method": "wallet_switchEthereumChain", "params": [ { "chainId": "0x1", }, ], } `) }) test('rejects unknown methods', () => { expect( z.safeDecode( z_RpcSchema.parseItem(z_RpcSchema.Eth, 'eth_getBalance').params, ['not-an-address', 'latest'] as never, ).success, ).toMatchInlineSnapshot(`false`) expect(() => z_RpcSchema.decodeRequest(z_RpcSchema.Eth, { method: 'eth_unknownMethod', params: [], } as never), ).toThrow() }) }) describe('from', () => { test('single method returns an Item', () => { const item = z_RpcSchema.from({ method: 'eth_blockNumber', params: z.optional(z.tuple([])), returns: z.string(), }) expect(item.method).toMatchInlineSnapshot(`"eth_blockNumber"`) expect(item.request).toBeDefined() }) test('namespace normalizes keyed params/returns into Items', () => { const schema = z_RpcSchema.from({ abe_foo: { params: z.tuple([z.number()]), returns: z.string(), }, }) expect(schema.abe_foo.method).toMatchInlineSnapshot(`"abe_foo"`) expect(z_RpcSchema.decodeParams(schema, 'abe_foo', [1])) .toMatchInlineSnapshot(` [ 1, ] `) expect( z_RpcSchema.decodeReturns(schema, 'abe_foo', 'hello'), ).toMatchInlineSnapshot(`"hello"`) expect( z_RpcSchema.decodeRequest(schema, { method: 'abe_foo', params: [1] }), ).toMatchInlineSnapshot(` { "method": "abe_foo", "params": [ 1, ], } `) }) test('namespace spreads existing ox/zod namespaces', () => { const schema = z_RpcSchema.from({ ...z_RpcSchema.Eth, abe_foo: { params: z.tuple([z.number()]), returns: z.string(), }, }) expect(schema.eth_blockNumber.method).toMatchInlineSnapshot( `"eth_blockNumber"`, ) expect(schema.abe_foo.method).toMatchInlineSnapshot(`"abe_foo"`) }) })