UNPKG

ox

Version:

Ethereum Standard Library

385 lines (351 loc) 10.4 kB
import { TransactionEnvelope, TxEnvelopeEip1559, TxEnvelopeEip2930, TxEnvelopeEip4844, TxEnvelopeEip7702, TxEnvelopeLegacy, } from 'ox' import { describe, expect, test } from 'vp/test' const legacy = TxEnvelopeLegacy.from({ chainId: 1, gas: 21000n, gasPrice: 10n, nonce: 0n, to: '0x0000000000000000000000000000000000000000', value: 1n, }) const eip2930 = TxEnvelopeEip2930.from({ accessList: [], chainId: 1, gas: 21000n, gasPrice: 10n, nonce: 0n, to: '0x0000000000000000000000000000000000000000', value: 1n, }) const eip1559 = TxEnvelopeEip1559.from({ chainId: 1, gas: 21000n, maxFeePerGas: 10n, maxPriorityFeePerGas: 1n, nonce: 0n, to: '0x0000000000000000000000000000000000000000', value: 1n, }) const eip4844 = TxEnvelopeEip4844.from({ blobVersionedHashes: [ '0x0100000000000000000000000000000000000000000000000000000000000000', ], chainId: 1, gas: 21000n, maxFeePerBlobGas: 10n, maxFeePerGas: 10n, maxPriorityFeePerGas: 1n, nonce: 0n, to: '0x0000000000000000000000000000000000000000', value: 1n, }) const eip7702 = TxEnvelopeEip7702.from({ authorizationList: [], chainId: 1, gas: 21000n, maxFeePerGas: 10n, maxPriorityFeePerGas: 1n, nonce: 0n, to: '0x0000000000000000000000000000000000000000', value: 1n, }) test('exports', () => { expect(Object.keys(TransactionEnvelope)).toMatchInlineSnapshot(` [ "assert", "deserialize", "from", "getSignPayload", "getType", "getSerializedType", "hash", "serialize", "toRpc", "toTransactionRequest", "validate", "FeeCapTooHighError", "GasPriceTooHighError", "InvalidChainIdError", "InvalidSerializedError", "InvalidSerializedTypeError", "InvalidTypeError", "TipAboveFeeCapError", ] `) }) describe('getType', () => { test('behavior: routes explicit transaction types', () => { expect(TransactionEnvelope.getType(legacy)).toBe('legacy') expect(TransactionEnvelope.getType(eip2930)).toBe('eip2930') expect(TransactionEnvelope.getType(eip1559)).toBe('eip1559') expect(TransactionEnvelope.getType(eip4844)).toBe('eip4844') expect(TransactionEnvelope.getType(eip7702)).toBe('eip7702') }) test('behavior: routes implicit transaction types', () => { expect(TransactionEnvelope.getType({ gasPrice: 1n })).toBe('legacy') expect(TransactionEnvelope.getType({ accessList: [], gasPrice: 1n })).toBe( 'eip2930', ) expect(TransactionEnvelope.getType({ maxFeePerGas: 1n })).toBe('eip1559') expect(TransactionEnvelope.getType({ blobs: [] })).toBe('eip4844') expect(TransactionEnvelope.getType({ maxFeePerBlobGas: 1n })).toBe( 'eip4844', ) expect(TransactionEnvelope.getType({ authorizationList: [] })).toBe( 'eip7702', ) }) test('behavior: ignores undefined discriminator fields', () => { expect( TransactionEnvelope.getType({ chainId: 1, gasPrice: undefined, maxFeePerGas: 1n, }), ).toBe('eip1559') expect( TransactionEnvelope.getType({ accessList: [], chainId: 1, gasPrice: 1n, maxFeePerGas: undefined, maxPriorityFeePerGas: undefined, }), ).toBe('eip2930') expect( TransactionEnvelope.getType({ gasPrice: 1n, maxFeePerGas: undefined, maxPriorityFeePerGas: undefined, }), ).toBe('legacy') }) test('behavior: returns explicit transaction types', () => { expect(TransactionEnvelope.getType({ type: '0x7e' })).toMatchInlineSnapshot( `"0x7e"`, ) }) test('behavior: defaults ambiguous transactions to EIP-1559', () => { expect(TransactionEnvelope.getType({})).toBe('eip1559') expect( TransactionEnvelope.getType({ chainId: 1, data: '0x1234', nonce: 69n, }), ).toBe('eip1559') }) }) describe('getSerializedType', () => { test('behavior: routes serialized transaction types', () => { expect( TransactionEnvelope.getSerializedType(TxEnvelopeLegacy.serialize(legacy)), ).toBe('legacy') expect( TransactionEnvelope.getSerializedType( TxEnvelopeEip2930.serialize(eip2930), ), ).toBe('eip2930') expect( TransactionEnvelope.getSerializedType( TxEnvelopeEip1559.serialize(eip1559), ), ).toBe('eip1559') expect( TransactionEnvelope.getSerializedType( TxEnvelopeEip4844.serialize(eip4844), ), ).toBe('eip4844') expect( TransactionEnvelope.getSerializedType( TxEnvelopeEip7702.serialize(eip7702), ), ).toBe('eip7702') }) test('behavior: rejects invalid serialized transaction types', () => { expect(() => TransactionEnvelope.getSerializedType('0x00')) .toThrowErrorMatchingInlineSnapshot(` [TransactionEnvelope.InvalidSerializedTypeError: Serialized transaction type is invalid. Serialized Transaction: "0x00"] `) }) test('behavior: rejects unknown serialized transaction types', () => { expect(() => TransactionEnvelope.getSerializedType('0x7ec0')) .toThrowErrorMatchingInlineSnapshot(` [TransactionEnvelope.InvalidSerializedTypeError: Serialized transaction type is invalid. Serialized Transaction: "0x7ec0"] `) }) }) describe('deserialize', () => { test('behavior: routes to the matching concrete deserializer', () => { const serialized = TxEnvelopeEip1559.serialize(eip1559) expect(TransactionEnvelope.deserialize(serialized)).toEqual( TxEnvelopeEip1559.deserialize(serialized), ) }) }) describe('from', () => { test('behavior: routes objects to the matching concrete converter', () => { expect(TransactionEnvelope.from({ maxFeePerGas: 10n, chainId: 1 })).toEqual( TxEnvelopeEip1559.from({ maxFeePerGas: 10n, chainId: 1 }), ) }) test('behavior: falls back unknown explicit types to EIP-1559', () => { expect( TransactionEnvelope.from({ chainId: 1, maxFeePerGas: 10n, type: '0x7e', }), ).toMatchInlineSnapshot(` { "chainId": 1, "maxFeePerGas": 10n, "type": "eip1559", } `) }) test('behavior: routes serialized envelopes to the matching concrete converter', () => { const serialized = TxEnvelopeEip2930.serialize(eip2930) expect(TransactionEnvelope.from(serialized)).toEqual( TxEnvelopeEip2930.from(serialized), ) }) }) describe('getSignPayload', () => { test('behavior: routes to the matching concrete payload helper', () => { expect(TransactionEnvelope.getSignPayload(eip1559)).toBe( TxEnvelopeEip1559.getSignPayload(eip1559), ) }) }) describe('hash', () => { test('behavior: routes to the matching concrete hash helper', () => { expect(TransactionEnvelope.hash(eip1559, { presign: true })).toBe( TxEnvelopeEip1559.hash(eip1559, { presign: true }), ) }) }) describe('serialize', () => { test('behavior: routes to the matching concrete serializer', () => { expect(TransactionEnvelope.serialize(eip1559)).toBe( TxEnvelopeEip1559.serialize(eip1559), ) }) test('behavior: defaults ambiguous transactions to EIP-1559', () => { expect( TransactionEnvelope.serialize({ chainId: 1, data: '0x1234', nonce: 69n, }), ).toBe( TxEnvelopeEip1559.serialize({ chainId: 1, data: '0x1234', nonce: 69n, }), ) }) }) describe('toRpc', () => { test('behavior: routes to the matching concrete RPC converter', () => { expect(TransactionEnvelope.toRpc(eip1559)).toEqual( TxEnvelopeEip1559.toRpc(eip1559), ) }) }) describe('assert', () => { test('behavior: routes to the matching concrete assertion', () => { expect(() => TransactionEnvelope.assert({ chainId: 1, maxFeePerGas: 1n, maxPriorityFeePerGas: 2n, }), ).toThrowErrorMatchingInlineSnapshot( `[TransactionEnvelope.TipAboveFeeCapError: The provided tip (\`maxPriorityFeePerGas\` = 0.000000002 gwei) cannot be higher than the fee cap (\`maxFeePerGas\` = 0.000000001 gwei).]`, ) }) }) describe('validate', () => { test('behavior: returns validation result for routed envelope', () => { expect(TransactionEnvelope.validate(eip1559)).toBe(true) expect( TransactionEnvelope.validate({ chainId: 1, maxFeePerGas: 1n, maxPriorityFeePerGas: 2n, }), ).toBe(false) }) }) describe('getType', () => { test('error: rejects RPC-style type strings', () => { expect(() => TransactionEnvelope.getType({ chainId: 1, maxFeePerGas: 1n, type: '0x2', }), ).toThrowErrorMatchingInlineSnapshot( `[TransactionEnvelope.InvalidTypeError: Transaction type "0x2" is invalid.]`, ) }) }) describe('toTransactionRequest', () => { test('behavior: eip1559', () => { const request = TransactionEnvelope.toTransactionRequest(eip1559) expect(request).toMatchInlineSnapshot(` { "chainId": 1, "gas": 21000n, "maxFeePerGas": 10n, "maxPriorityFeePerGas": 1n, "nonce": 0n, "to": "0x0000000000000000000000000000000000000000", "type": "eip1559", "value": 1n, } `) }) test('behavior: preserves signature fields', () => { const request = TransactionEnvelope.toTransactionRequest({ ...eip1559, r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', v: 27, yParity: 0, }) expect(request.r).toBe( '0x0000000000000000000000000000000000000000000000000000000000000001', ) expect(request.s).toBe( '0x0000000000000000000000000000000000000000000000000000000000000002', ) expect(request.yParity).toBe(0) expect(request.v).toBe(27) }) test('behavior: legacy round-trips', () => { const request = TransactionEnvelope.toTransactionRequest(legacy) expect(request).toMatchInlineSnapshot(` { "chainId": 1, "gas": 21000n, "gasPrice": 10n, "nonce": 0n, "to": "0x0000000000000000000000000000000000000000", "type": "legacy", "value": 1n, } `) }) })