UNPKG

ox

Version:

Ethereum Standard Library

1,056 lines (977 loc) 36.9 kB
import { Authorization, Hex, Rlp, Secp256k1, TxEnvelopeEip1559, TxEnvelopeEip7702, Value, } from 'ox' import { assertType, describe, expect, test } from 'vp/test' import { wagmiContractConfig } from '../../../test/constants/abis.js' import { accounts } from '../../../test/constants/accounts.js' import { anvilMainnet } from '../../../test/prool.js' describe('assert', () => { test('invalid chainId', () => { expect(() => TxEnvelopeEip7702.assert({ authorizationList: [ { address: '0x0000000000000000000000000000000000000000', chainId: -1, nonce: 0n, r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000000', yParity: 0, }, ], chainId: 1, }), ).toThrowErrorMatchingInlineSnapshot( `[TransactionEnvelope.InvalidChainIdError: Chain ID "-1" is invalid.]`, ) }) test('invalid address', () => { expect(() => TxEnvelopeEip7702.assert({ authorizationList: [ { address: '0x000000000000000000000000000000000000000z', chainId: 0, nonce: 0n, r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000000', yParity: 0, }, ], chainId: 1, }), ).toThrowErrorMatchingInlineSnapshot( ` [Address.InvalidAddressError: Address "0x000000000000000000000000000000000000000z" is invalid. Details: Address is not a 20 byte (40 hexadecimal character) value.] `, ) }) test('fee cap too high', () => { expect(() => TxEnvelopeEip7702.assert({ authorizationList: [ { address: '0x0000000000000000000000000000000000000000', chainId: 1, nonce: 0n, r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000000', yParity: 0, }, ], maxFeePerGas: 2n ** 256n - 1n + 1n, chainId: 1, }), ).toThrowErrorMatchingInlineSnapshot( '[TransactionEnvelope.FeeCapTooHighError: The fee cap (`maxFeePerGas`/`maxPriorityFeePerGas` = 115792089237316195423570985008687907853269984665640564039457584007913.129639936 gwei) cannot be higher than the maximum allowed value (2^256-1).]', ) }) }) describe('deserialize', () => { const authorization_1 = Authorization.from({ address: wagmiContractConfig.address, chainId: 1, nonce: 785n, }) const signature_1 = Secp256k1.sign({ payload: Authorization.getSignPayload(authorization_1), privateKey: accounts[0].privateKey, }) const authorization_2 = Authorization.from({ address: wagmiContractConfig.address, chainId: 10, nonce: 786n, }) const signature_2 = Secp256k1.sign({ payload: Authorization.getSignPayload(authorization_2), privateKey: accounts[0].privateKey, }) const transaction = TxEnvelopeEip7702.from({ authorizationList: [ Authorization.from(authorization_1, { signature: signature_1 }), Authorization.from(authorization_2, { signature: signature_2 }), ], chainId: 1, nonce: 785n, to: accounts[1].address, value: Value.fromEther('1'), maxFeePerGas: Value.fromGwei('2'), maxPriorityFeePerGas: Value.fromGwei('2'), }) test('default', () => { const serialized = TxEnvelopeEip7702.serialize(transaction) const deserialized = TxEnvelopeEip7702.deserialize(serialized) assertType<TxEnvelopeEip7702.TxEnvelopeEip7702>(deserialized) expect(deserialized).toEqual(transaction) }) test('minimal', () => { const transaction = TxEnvelopeEip7702.from({ authorizationList: [], chainId: 1, nonce: 0n, }) const serialized = TxEnvelopeEip7702.serialize(transaction) expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual(transaction) }) test('gas', () => { const transaction_gas = TxEnvelopeEip7702.from({ ...transaction, gas: 21001n, }) const serialized = TxEnvelopeEip7702.serialize(transaction_gas) expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual(transaction_gas) }) test('accessList', () => { const transaction_accessList = TxEnvelopeEip7702.from({ ...transaction, accessList: [ { address: '0x0000000000000000000000000000000000000000', storageKeys: [ '0x0000000000000000000000000000000000000000000000000000000000000001', '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', ], }, ], }) const serialized = TxEnvelopeEip7702.serialize(transaction_accessList) expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual( transaction_accessList, ) }) test('data', () => { const transaction_data = TxEnvelopeEip7702.from({ ...transaction, data: '0x1234', }) const serialized = TxEnvelopeEip7702.serialize(transaction_data) expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual(transaction_data) }) test('signature', () => { const signature = Secp256k1.sign({ payload: TxEnvelopeEip7702.getSignPayload(transaction), privateKey: accounts[0].privateKey, }) const serialized = TxEnvelopeEip7702.serialize(transaction, { signature, }) expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual({ ...transaction, ...signature, }) }) describe('raw', () => { test('default', () => { const serialized = `0x04${Rlp.fromHex([ Hex.fromNumber(1), // chainId Hex.fromNumber(0), // nonce Hex.fromNumber(1), // maxPriorityFeePerGas Hex.fromNumber(1), // maxFeePerGas Hex.fromNumber(1), // gas '0x0000000000000000000000000000000000000000', // to Hex.fromNumber(0), // value '0x', // data '0x', // accessList '0x', // authorizationList ]).slice(2)}` as const expect(TxEnvelopeEip7702.deserialize(serialized)).toMatchInlineSnapshot(` { "chainId": 1, "gas": 1n, "maxFeePerGas": 1n, "maxPriorityFeePerGas": 1n, "nonce": 0n, "to": "0x0000000000000000000000000000000000000000", "type": "eip7702", "value": 0n, } `) }) test('empty sig', () => { const serialized = `0x04${Rlp.fromHex([ Hex.fromNumber(1), // chainId Hex.fromNumber(0), // nonce Hex.fromNumber(1), // maxPriorityFeePerGas Hex.fromNumber(1), // maxFeePerGas Hex.fromNumber(1), // gas '0x0000000000000000000000000000000000000000', // to Hex.fromNumber(0), // value '0x', // data '0x', // accessList '0x', // authorizationList '0x', // r '0x', // v '0x', // s ]).slice(2)}` as const expect(TxEnvelopeEip7702.deserialize(serialized)).toMatchInlineSnapshot(` { "chainId": 1, "gas": 1n, "maxFeePerGas": 1n, "maxPriorityFeePerGas": 1n, "nonce": 0n, "r": "0x0000000000000000000000000000000000000000000000000000000000000000", "s": "0x0000000000000000000000000000000000000000000000000000000000000000", "to": "0x0000000000000000000000000000000000000000", "type": "eip7702", "value": 0n, "yParity": 0, } `) }) test('low sig coords', () => { const serialized = `0x04${Rlp.fromHex([ Hex.fromNumber(1), // chainId Hex.fromNumber(0), // nonce Hex.fromNumber(1), // maxPriorityFeePerGas Hex.fromNumber(1), // maxFeePerGas Hex.fromNumber(1), // gas '0x0000000000000000000000000000000000000000', // to Hex.fromNumber(0), // value '0x', // data '0x', // accessList '0x', // authorizationList '0x', // r Hex.fromNumber(69), // v Hex.fromNumber(420), // s ]).slice(2)}` as const expect(TxEnvelopeEip7702.deserialize(serialized)).toMatchInlineSnapshot(` { "chainId": 1, "gas": 1n, "maxFeePerGas": 1n, "maxPriorityFeePerGas": 1n, "nonce": 0n, "r": "0x0000000000000000000000000000000000000000000000000000000000000045", "s": "0x00000000000000000000000000000000000000000000000000000000000001a4", "to": "0x0000000000000000000000000000000000000000", "type": "eip7702", "value": 0n, "yParity": 0, } `) }) }) describe('errors', () => { test('invalid access list (invalid address)', () => { expect(() => TxEnvelopeEip7702.deserialize( `0x04${Rlp.fromHex([ Hex.fromNumber(1), // chainId Hex.fromNumber(0), // nonce Hex.fromNumber(1), // maxPriorityFeePerGas Hex.fromNumber(1), // maxFeePerGas Hex.fromNumber(1), // gas '0x0000000000000000000000000000000000000000', // to Hex.fromNumber(0), // value '0x', // data [ [ '0x', [ '0x0000000000000000000000000000000000000000000000000000000000000001', ], ], ], // accessList '0x', // authorizationList ]).slice(2)}`, ), ).toThrowErrorMatchingInlineSnapshot(` [Address.InvalidAddressError: Address "0x" is invalid. Details: Address is not a 20 byte (40 hexadecimal character) value.] `) expect(() => TxEnvelopeEip7702.deserialize( `0x04${Rlp.fromHex([ Hex.fromNumber(1), // chainId Hex.fromNumber(0), // nonce Hex.fromNumber(1), // maxPriorityFeePerGas Hex.fromNumber(1), // maxFeePerGas Hex.fromNumber(1), // gas '0x0000000000000000000000000000000000000000', // to Hex.fromNumber(0), // value '0x', // data [['0x123456', ['0x0']]], // accessList '0x', // authorizationList ]).slice(2)}`, ), ).toThrowErrorMatchingInlineSnapshot(` [Address.InvalidAddressError: Address "0x123456" is invalid. Details: Address is not a 20 byte (40 hexadecimal character) value.] `) }) test('invalid transaction (all missing)', () => { expect(() => TxEnvelopeEip7702.deserialize(`0x04${Rlp.fromHex([]).slice(2)}`), ).toThrowErrorMatchingInlineSnapshot(` [TransactionEnvelope.InvalidSerializedError: Invalid serialized transaction of type "eip7702" was provided. Serialized Transaction: "0x04c0" Missing Attributes: chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, authorizationList] `) }) test('invalid transaction (some missing)', () => { expect(() => TxEnvelopeEip7702.deserialize( `0x04${Rlp.fromHex(['0x00', '0x01']).slice(2)}`, ), ).toThrowErrorMatchingInlineSnapshot(` [TransactionEnvelope.InvalidSerializedError: Invalid serialized transaction of type "eip7702" was provided. Serialized Transaction: "0x04c20001" Missing Attributes: maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, authorizationList] `) }) test('invalid transaction (missing signature)', () => { expect(() => TxEnvelopeEip7702.deserialize( `0x04${Rlp.fromHex([ '0x', '0x', '0x', '0x', '0x', '0x', '0x', '0x', '0x', '0x', '0x', ]).slice(2)}`, ), ).toThrowErrorMatchingInlineSnapshot(` [TransactionEnvelope.InvalidSerializedError: Invalid serialized transaction of type "eip7702" was provided. Serialized Transaction: "0x04cb8080808080808080808080" Missing Attributes: r, s] `) }) }) }) describe('from', () => { test('default', () => { { const envelope = TxEnvelopeEip7702.from({ authorizationList: [], chainId: 1, nonce: 0n, }) expect(envelope).toMatchInlineSnapshot(` { "authorizationList": [], "chainId": 1, "nonce": 0n, "type": "eip7702", } `) const serialized = TxEnvelopeEip7702.serialize(envelope) const envelope2 = TxEnvelopeEip7702.from(serialized) expect(envelope2).toEqual(envelope) } { const envelope = TxEnvelopeEip7702.from({ authorizationList: [], chainId: 1, nonce: 0n, r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000001', yParity: 0, }) expect(envelope).toMatchInlineSnapshot(` { "authorizationList": [], "chainId": 1, "nonce": 0n, "r": "0x0000000000000000000000000000000000000000000000000000000000000000", "s": "0x0000000000000000000000000000000000000000000000000000000000000001", "type": "eip7702", "yParity": 0, } `) const serialized = TxEnvelopeEip7702.serialize(envelope) const envelope2 = TxEnvelopeEip7702.from(serialized) expect(envelope2).toEqual(envelope) } }) test('options: signature', () => { const envelope = TxEnvelopeEip7702.from( { authorizationList: [], chainId: 1, nonce: 0n, }, { signature: { r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000001', yParity: 0, }, }, ) expect(envelope).toMatchInlineSnapshot(` { "authorizationList": [], "chainId": 1, "nonce": 0n, "r": "0x0000000000000000000000000000000000000000000000000000000000000000", "s": "0x0000000000000000000000000000000000000000000000000000000000000001", "type": "eip7702", "yParity": 0, } `) const serialized = TxEnvelopeEip7702.serialize(envelope) const envelope2 = TxEnvelopeEip7702.from(serialized) expect(envelope2).toEqual(envelope) }) }) describe('getSignPayload', () => { test('default', () => { const authorization = Authorization.from({ address: wagmiContractConfig.address, chainId: 1, nonce: 785n, }) const signature = Secp256k1.sign({ payload: Authorization.getSignPayload(authorization), privateKey: accounts[0].privateKey, }) const envelope = TxEnvelopeEip7702.from({ authorizationList: [Authorization.from(authorization, { signature })], chainId: 1, gas: 21000n, maxFeePerGas: 13000000000n, maxPriorityFeePerGas: 1000000000n, nonce: 665n, value: 1000000000000000000n, to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', }) const hash = TxEnvelopeEip7702.hash(envelope, { presign: true }) expect(hash).toMatchInlineSnapshot( `"0x6458c62f981287bcbf1c85861e69de9c7344793116dc9388d90274ab153da8d8"`, ) const signature_tx = Secp256k1.sign({ payload: TxEnvelopeEip7702.getSignPayload(envelope), privateKey: accounts[0].privateKey, }) const envelope_signed = TxEnvelopeEip7702.from(envelope, { signature: signature_tx, }) { const hash = TxEnvelopeEip7702.hash(envelope_signed) expect(hash).toMatchInlineSnapshot( `"0x9714058a95376ce5963b33200dce2fb3afc6ebbefd47a65d9d137c0f66a5e7bf"`, ) } { const hash_presign = TxEnvelopeEip7702.getSignPayload(envelope_signed) expect(hash_presign).toEqual(hash) } }) }) describe('hash', () => { test('default', () => { const envelope = TxEnvelopeEip7702.from({ authorizationList: [], chainId: 1, gas: 21000n, maxFeePerGas: 13000000000n, maxPriorityFeePerGas: 1000000000n, nonce: 665n, value: 1000000000000000000n, to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', r: '0xacf664dcd984d082b68c434feb66ac684711babdeefe6f101bf8df88fc367a37', s: '0x5e0800058a9b5c2250bed60ee969a45b7445e562a8298c2d222d114e6dfbfcb9', yParity: 0, }) const hash = TxEnvelopeEip7702.hash(envelope) expect(hash).toMatchInlineSnapshot( `"0xa5ccd813e459b9154917af19270091cf3312bd759af08e0e18cd2560ef586cf4"`, ) }) }) describe('serialize', () => { const authorization_1 = Authorization.from({ address: wagmiContractConfig.address, chainId: 1, nonce: 785n, }) const signature_1 = Secp256k1.sign({ payload: Authorization.getSignPayload(authorization_1), privateKey: accounts[0].privateKey, }) const authorization_2 = Authorization.from({ address: wagmiContractConfig.address, chainId: 10, nonce: 786n, }) const signature_2 = Secp256k1.sign({ payload: Authorization.getSignPayload(authorization_2), privateKey: accounts[0].privateKey, }) const transaction = TxEnvelopeEip7702.from({ authorizationList: [ Authorization.from(authorization_1, { signature: signature_1 }), Authorization.from(authorization_2, { signature: signature_2 }), ], chainId: 1, nonce: 785n, to: accounts[1].address, value: Value.fromEther('1'), maxFeePerGas: Value.fromGwei('2'), maxPriorityFeePerGas: Value.fromGwei('2'), }) test('default', () => { const serialized = TxEnvelopeEip7702.serialize(transaction) assertType<TxEnvelopeEip7702.Serialized>(serialized) expect(serialized).toEqual( '0x04f8ed0182031184773594008477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8bcf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c282031101a05f9eab9d5d717098430d03780924a379e350e5cdd38fb7ffe707393d587e4e35a07e28e4c4eadc81dafea5d73c8e5327034306d4ad2dc4dbd94ef829bec57f04dbf85c0a94fba3912ca04dd458c843e2ee08967fc04f3579c282031280a0f4181dd27f6b801404cd3bc24207aa1b07b1ff8991ea847b0af1a6c3e4ba9e99a00fda7db5a05f0c5298339ffa7ec9be2d2a483f5c0af7fe267237b01290bc9582', ) expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual(transaction) }) test('default (all zeros)', () => { const transaction = TxEnvelopeEip7702.from({ authorizationList: [], to: undefined, nonce: 0n, chainId: 1, maxFeePerGas: 0n, maxPriorityFeePerGas: 0n, value: 0n, }) const serialized = TxEnvelopeEip7702.serialize(transaction) expect(serialized).toEqual('0x04ca0180808080808080c0c0') expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual({ authorizationList: [], chainId: 1, nonce: 0n, type: 'eip7702', }) }) test('minimal (w/ type)', () => { const transaction = TxEnvelopeEip7702.from({ authorizationList: [], chainId: 1, nonce: 0n, }) const serialized = TxEnvelopeEip7702.serialize(transaction) expect(serialized).toEqual('0x04ca0180808080808080c0c0') expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual(transaction) }) test('minimal (w/ maxFeePerGas)', () => { const transaction = TxEnvelopeEip7702.from({ authorizationList: [], chainId: 1, maxFeePerGas: 1n, nonce: 0n, }) const serialized = TxEnvelopeEip7702.serialize(transaction) expect(serialized).toEqual('0x04ca0180800180808080c0c0') expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual(transaction) }) test('gas', () => { const transaction_gas = TxEnvelopeEip7702.from({ ...transaction, gas: 21001n, }) const serialized = TxEnvelopeEip7702.serialize(transaction_gas) expect(serialized).toEqual( '0x04f8ef01820311847735940084773594008252099470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8bcf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c282031101a05f9eab9d5d717098430d03780924a379e350e5cdd38fb7ffe707393d587e4e35a07e28e4c4eadc81dafea5d73c8e5327034306d4ad2dc4dbd94ef829bec57f04dbf85c0a94fba3912ca04dd458c843e2ee08967fc04f3579c282031280a0f4181dd27f6b801404cd3bc24207aa1b07b1ff8991ea847b0af1a6c3e4ba9e99a00fda7db5a05f0c5298339ffa7ec9be2d2a483f5c0af7fe267237b01290bc9582', ) expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual(transaction_gas) }) test('accessList', () => { const transaction_accessList = TxEnvelopeEip7702.from({ ...transaction, accessList: [ { address: '0x0000000000000000000000000000000000000000', storageKeys: [ '0x0000000000000000000000000000000000000000000000000000000000000001', '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', ], }, ], }) const serialized = TxEnvelopeEip7702.serialize(transaction_accessList) expect(serialized).toEqual( '0x04f901490182031184773594008477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080f85bf859940000000000000000000000000000000000000000f842a00000000000000000000000000000000000000000000000000000000000000001a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fef8bcf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c282031101a05f9eab9d5d717098430d03780924a379e350e5cdd38fb7ffe707393d587e4e35a07e28e4c4eadc81dafea5d73c8e5327034306d4ad2dc4dbd94ef829bec57f04dbf85c0a94fba3912ca04dd458c843e2ee08967fc04f3579c282031280a0f4181dd27f6b801404cd3bc24207aa1b07b1ff8991ea847b0af1a6c3e4ba9e99a00fda7db5a05f0c5298339ffa7ec9be2d2a483f5c0af7fe267237b01290bc9582', ) expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual( transaction_accessList, ) }) test('data', () => { const transaction_data = TxEnvelopeEip7702.from({ ...transaction, data: '0x1234', }) const serialized = TxEnvelopeEip7702.serialize(transaction_data) expect(serialized).toEqual( '0x04f8ef0182031184773594008477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a7640000821234c0f8bcf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c282031101a05f9eab9d5d717098430d03780924a379e350e5cdd38fb7ffe707393d587e4e35a07e28e4c4eadc81dafea5d73c8e5327034306d4ad2dc4dbd94ef829bec57f04dbf85c0a94fba3912ca04dd458c843e2ee08967fc04f3579c282031280a0f4181dd27f6b801404cd3bc24207aa1b07b1ff8991ea847b0af1a6c3e4ba9e99a00fda7db5a05f0c5298339ffa7ec9be2d2a483f5c0af7fe267237b01290bc9582', ) expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual(transaction_data) }) test('options: signature', async () => { const signature = Secp256k1.sign({ payload: TxEnvelopeEip7702.getSignPayload(transaction), privateKey: accounts[0].privateKey, }) const serialized = TxEnvelopeEip7702.serialize(transaction, { signature, }) expect(serialized).toEqual( '0x04f901300182031184773594008477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8bcf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c282031101a05f9eab9d5d717098430d03780924a379e350e5cdd38fb7ffe707393d587e4e35a07e28e4c4eadc81dafea5d73c8e5327034306d4ad2dc4dbd94ef829bec57f04dbf85c0a94fba3912ca04dd458c843e2ee08967fc04f3579c282031280a0f4181dd27f6b801404cd3bc24207aa1b07b1ff8991ea847b0af1a6c3e4ba9e99a00fda7db5a05f0c5298339ffa7ec9be2d2a483f5c0af7fe267237b01290bc958280a0c6e17df66749225c040251e51ab7c732b7c92d7844ed731c80ee5ff3b15baf42a03b4fb995198301b08f630d1aecfa4a567a722e184eda6e3789481091a0cd1dd5', ) expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual({ ...transaction, ...signature, }) }) test('options: signature', () => { expect( TxEnvelopeEip7702.serialize(transaction, { signature: { r: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', s: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', yParity: 1, }, }), ).toEqual( '0x04f901300182031184773594008477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8bcf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c282031101a05f9eab9d5d717098430d03780924a379e350e5cdd38fb7ffe707393d587e4e35a07e28e4c4eadc81dafea5d73c8e5327034306d4ad2dc4dbd94ef829bec57f04dbf85c0a94fba3912ca04dd458c843e2ee08967fc04f3579c282031280a0f4181dd27f6b801404cd3bc24207aa1b07b1ff8991ea847b0af1a6c3e4ba9e99a00fda7db5a05f0c5298339ffa7ec9be2d2a483f5c0af7fe267237b01290bc958201a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', ) expect( TxEnvelopeEip7702.serialize(transaction, { signature: { r: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', s: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', yParity: 0, }, }), ).toEqual( '0x04f901300182031184773594008477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8bcf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c282031101a05f9eab9d5d717098430d03780924a379e350e5cdd38fb7ffe707393d587e4e35a07e28e4c4eadc81dafea5d73c8e5327034306d4ad2dc4dbd94ef829bec57f04dbf85c0a94fba3912ca04dd458c843e2ee08967fc04f3579c282031280a0f4181dd27f6b801404cd3bc24207aa1b07b1ff8991ea847b0af1a6c3e4ba9e99a00fda7db5a05f0c5298339ffa7ec9be2d2a483f5c0af7fe267237b01290bc958280a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', ) expect( TxEnvelopeEip7702.serialize(transaction, { signature: { r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000000', yParity: 0, }, }), ).toEqual( '0x04f8f00182031184773594008477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8bcf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c282031101a05f9eab9d5d717098430d03780924a379e350e5cdd38fb7ffe707393d587e4e35a07e28e4c4eadc81dafea5d73c8e5327034306d4ad2dc4dbd94ef829bec57f04dbf85c0a94fba3912ca04dd458c843e2ee08967fc04f3579c282031280a0f4181dd27f6b801404cd3bc24207aa1b07b1ff8991ea847b0af1a6c3e4ba9e99a00fda7db5a05f0c5298339ffa7ec9be2d2a483f5c0af7fe267237b01290bc9582808080', ) }) test('behavior: network', async () => { const authority = accounts[0] const delegate = accounts[1] // Deploy delegate contract const contractAddress = await (async () => { const nonce = await anvilMainnet.request({ method: 'eth_getTransactionCount', params: [delegate.address, 'pending'], }) const envelope = TxEnvelopeEip1559.from({ chainId: 1, nonce: BigInt(nonce), gas: 3_000_000n, data: '0x66365f5f37365fa05f5260076019f3', maxFeePerGas: Value.fromGwei('20'), maxPriorityFeePerGas: Value.fromGwei('10'), }) const signature = Secp256k1.sign({ payload: TxEnvelopeEip1559.getSignPayload(envelope), privateKey: delegate.privateKey, }) const serialized = TxEnvelopeEip1559.serialize(envelope, { signature, }) const hash = await anvilMainnet.request({ method: 'eth_sendRawTransaction', params: [serialized], }) await anvilMainnet.request({ method: 'anvil_mine', params: ['0x1', '0x0'], }) const receipt = await anvilMainnet.request({ method: 'eth_getTransactionReceipt', params: [hash], }) return receipt!.contractAddress })() // Authorize injection of delegation contract onto authority const authorization = await (async () => { const nonce = await anvilMainnet.request({ method: 'eth_getTransactionCount', params: [authority.address, 'pending'], }) const authorization = Authorization.from({ address: contractAddress!, chainId: 1, nonce: BigInt(nonce), }) const signature = Secp256k1.sign({ payload: Authorization.getSignPayload(authorization), privateKey: authority.privateKey, }) return Authorization.from(authorization, { signature }) })() const nonce = await anvilMainnet.request({ method: 'eth_getTransactionCount', params: [delegate.address, 'pending'], }) const envelope = TxEnvelopeEip7702.from({ chainId: 1, authorizationList: [authorization], data: '0xdeadbeef', nonce: BigInt(nonce), gas: 1_000_000n, to: authority.address, maxFeePerGas: Value.fromGwei('20'), maxPriorityFeePerGas: Value.fromGwei('10'), }) const signature = Secp256k1.sign({ payload: TxEnvelopeEip7702.getSignPayload(envelope), privateKey: delegate.privateKey, }) const serialized = TxEnvelopeEip7702.serialize(envelope, { signature, }) const hash = await anvilMainnet.request({ method: 'eth_sendRawTransaction', params: [serialized], }) const tx = await anvilMainnet.request({ method: 'eth_getTransactionByHash', params: [hash], }) expect(tx).toMatchObject({ accessList: [], chainId: '0x1', from: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', gas: '0xf4240', input: '0xdeadbeef', maxFeePerGas: '0x4a817c800', maxPriorityFeePerGas: '0x2540be400', to: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', transactionIndex: '0x0', type: '0x4', value: '0x0', }) expect(tx?.authorizationList).toHaveLength(1) expect(tx?.authorizationList?.[0]).toMatchObject({ chainId: '0x1', }) const receipt = await anvilMainnet.request({ method: 'eth_getTransactionReceipt', params: [hash], }) expect({ ...receipt, blockHash: null, blockNumber: null, blockTimestamp: null, cumulativeGasUsed: null, effectiveGasPrice: null, gasUsed: null, transactionHash: null, logs: receipt!.logs.map((log) => ({ ...log, blockHash: null, blockNumber: null, blockTimestamp: null, transactionHash: null, })), }).toMatchInlineSnapshot(` { "blobGasPrice": "0x1", "blockHash": null, "blockNumber": null, "blockTimestamp": null, "contractAddress": null, "cumulativeGasUsed": null, "effectiveGasPrice": null, "from": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "gasUsed": null, "logs": [ { "address": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "blockHash": null, "blockNumber": null, "blockTimestamp": null, "data": "0xdeadbeef", "logIndex": "0x0", "removed": false, "topics": [], "transactionHash": null, "transactionIndex": "0x0", }, ], "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "status": "0x1", "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "transactionHash": null, "transactionIndex": "0x0", "type": "0x4", } `) }) }) describe('toRpc', () => { test('default', () => { const transaction = TxEnvelopeEip7702.toRpc({ authorizationList: [ { address: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', chainId: 1, nonce: 0n, r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', yParity: 0, }, ], chainId: 1, nonce: 0n, gas: 21000n, maxFeePerGas: 1000000000n, to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', value: 1000000000000000000n, r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', yParity: 0, }) expect(transaction).toMatchInlineSnapshot(` { "authorizationList": [ { "address": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "chainId": "0x1", "nonce": "0x0", "r": "0x0000000000000000000000000000000000000000000000000000000000000001", "s": "0x0000000000000000000000000000000000000000000000000000000000000002", "yParity": "0x0", }, ], "chainId": "0x1", "data": undefined, "gas": "0x5208", "maxFeePerGas": "0x3b9aca00", "nonce": "0x0", "r": "0x0000000000000000000000000000000000000000000000000000000000000001", "s": "0x0000000000000000000000000000000000000000000000000000000000000002", "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "type": "0x4", "value": "0xde0b6b3a7640000", "yParity": "0x0", } `) }) test('numberish inputs', () => { const fromBigint = TxEnvelopeEip7702.toRpc({ authorizationList: [ { address: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', chainId: 1, nonce: 0n, r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', yParity: 0, }, ], chainId: 1, nonce: 0n, gas: 21000n, maxFeePerGas: 1000n, maxPriorityFeePerGas: 100n, to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', value: 1000n, r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', yParity: 0, }) const fromNumber = TxEnvelopeEip7702.toRpc({ authorizationList: [ { address: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', chainId: 1, nonce: 0n, r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', yParity: 0, }, ], chainId: 1, nonce: 0, gas: 21000, maxFeePerGas: 1000, maxPriorityFeePerGas: 100, to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', value: 1000, r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', yParity: 0, }) const fromHex = TxEnvelopeEip7702.toRpc({ authorizationList: [ { address: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', chainId: 1, nonce: 0n, r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', yParity: 0, }, ], chainId: '0x1', nonce: '0x0', gas: '0x5208', maxFeePerGas: '0x3e8', maxPriorityFeePerGas: '0x64', to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', value: '0x3e8', r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', yParity: '0x0', }) expect(fromBigint).toEqual(fromNumber) expect(fromBigint).toEqual(fromHex) }) }) describe('validate', () => { test('default', () => { expect( TxEnvelopeEip7702.validate({ authorizationList: [], chainId: 1, }), ).toBe(true) expect( TxEnvelopeEip7702.validate({ authorizationList: [], maxFeePerGas: 2n ** 257n, chainId: 1, }), ).toBe(false) }) }) test('exports', () => { expect(Object.keys(TxEnvelopeEip7702)).toMatchInlineSnapshot(` [ "serializedType", "type", "assert", "deserialize", "from", "getSignPayload", "hash", "serialize", "toRpc", "validate", ] `) })