ox
Version:
Ethereum Standard Library
1,056 lines (977 loc) • 36.9 kB
text/typescript
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",
]
`)
})