ox
Version:
Ethereum Standard Library
426 lines (401 loc) • 10.6 kB
text/typescript
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"`)
})
})