From ccfa13be605093096da7ab9c8c46b424da9fa500 Mon Sep 17 00:00:00 2001 From: Audrey Penven Date: Fri, 29 Sep 2023 18:21:35 +0200 Subject: [PATCH 01/15] serializing cip-64 and tests for the serializer --- src/chains/celo/formatters.ts | 1 + src/chains/celo/parsers.ts | 83 +++++- src/chains/celo/serializers.test.ts | 375 ++++++++++++++++++++++------ src/chains/celo/serializers.ts | 129 ++++++++-- src/chains/celo/types.ts | 22 ++ 5 files changed, 507 insertions(+), 103 deletions(-) diff --git a/src/chains/celo/formatters.ts b/src/chains/celo/formatters.ts index 04e2c7467a..6f40c763ce 100644 --- a/src/chains/celo/formatters.ts +++ b/src/chains/celo/formatters.ts @@ -78,6 +78,7 @@ export const formattersCelo = { gatewayFeeRecipient: args.gatewayFeeRecipient, } as CeloRpcTransactionRequest if (args.type === 'cip42') request.type = '0x7c' + if (args.type === 'cip64') request.type = '0x7b' return request }, }), diff --git a/src/chains/celo/parsers.ts b/src/chains/celo/parsers.ts index e7cff880cb..be26e02188 100644 --- a/src/chains/celo/parsers.ts +++ b/src/chains/celo/parsers.ts @@ -11,12 +11,17 @@ import { parseTransaction, toTransactionArray, } from '../../utils/transaction/parseTransaction.js' -import { assertTransactionCIP42 } from './serializers.js' +import { + assertTransactionCIP42, + assertTransactionCIP64, +} from './serializers.js' import type { CeloTransactionSerialized, CeloTransactionType, TransactionSerializableCIP42, + TransactionSerializableCIP64, TransactionSerializedCIP42, + TransactionSerializedCIP64, } from './types.js' export type ParseTransactionCeloReturnType< @@ -38,6 +43,11 @@ export function parseTransactionCelo< serializedTransaction as TransactionSerializedCIP42, ) as ParseTransactionCeloReturnType + if (serializedType === '0x7b') + return parseTransactionCIP64( + serializedTransaction as TransactionSerializedCIP64, + ) as ParseTransactionCeloReturnType + return parseTransaction( serializedTransaction, ) as ParseTransactionCeloReturnType @@ -121,3 +131,74 @@ function parseTransactionCIP42( return transaction as TransactionSerializableCIP42 } + +function parseTransactionCIP64( + serializedTransaction: TransactionSerializedCIP64, +): TransactionSerializableCIP64 { + const transactionArray = toTransactionArray(serializedTransaction) + + const [ + chainId, + nonce, + maxPriorityFeePerGas, + maxFeePerGas, + gas, + feeCurrency, + to, + value, + data, + accessList, + v, + r, + s, + ] = transactionArray + + if (transactionArray.length !== 13 && transactionArray.length !== 11) { + throw new InvalidSerializedTransactionError({ + attributes: { + chainId, + nonce, + maxPriorityFeePerGas, + maxFeePerGas, + gas, + feeCurrency, + to, + value, + data, + accessList, + ...(transactionArray.length > 11 + ? { + v, + r, + s, + } + : {}), + }, + serializedTransaction, + type: 'cip64', + }) + } + + const transaction: Partial = { + chainId: hexToNumber(chainId as Hex), + type: 'cip64', + } + + if (isHex(to) && to !== '0x') transaction.to = to + if (isHex(gas) && gas !== '0x') transaction.gas = hexToBigInt(gas) + if (isHex(data) && data !== '0x') transaction.data = data + if (isHex(nonce) && nonce !== '0x') transaction.nonce = hexToNumber(nonce) + if (isHex(value) && value !== '0x') transaction.value = hexToBigInt(value) + if (isHex(feeCurrency) && feeCurrency !== '0x') + transaction.feeCurrency = feeCurrency + if (isHex(maxFeePerGas) && maxFeePerGas !== '0x') + transaction.maxFeePerGas = hexToBigInt(maxFeePerGas) + if (isHex(maxPriorityFeePerGas) && maxPriorityFeePerGas !== '0x') + transaction.maxPriorityFeePerGas = hexToBigInt(maxPriorityFeePerGas) + if (accessList.length !== 0 && accessList !== '0x') + transaction.accessList = parseAccessList(accessList as RecursiveArray) + + assertTransactionCIP64(transaction as TransactionSerializableCIP64) + + return transaction as TransactionSerializableCIP64 +} diff --git a/src/chains/celo/serializers.test.ts b/src/chains/celo/serializers.test.ts index 61bd030784..118f061d77 100644 --- a/src/chains/celo/serializers.test.ts +++ b/src/chains/celo/serializers.test.ts @@ -12,18 +12,29 @@ import { parseTransaction, } from '../../index.js' import { serializeTransactionCelo } from './serializers.js' -import type { TransactionSerializableCIP42 } from './types.js' +import type { + TransactionSerializableCIP42, + TransactionSerializableCIP64, +} from './types.js' -const baseCip42: TransactionSerializableCIP42 = { +const commonBaseTx = { to: accounts[0].address, chainId: 42220, nonce: 0, maxFeePerGas: parseGwei('2'), maxPriorityFeePerGas: parseGwei('2'), - value: parseEther('1'), feeCurrency: '0x765de816845861e75a25fca122bb6898b8b1282a', - type: 'cip42', + value: parseEther('1'), } +const baseCip42 = { + ...commonBaseTx, + type: 'cip42', +} as TransactionSerializableCIP42 + +const baseCip64 = { + ...commonBaseTx, + type: 'cip64', +} as TransactionSerializableCIP64 describe('cip42', () => { test('should be able to serialize a cip42 transaction', () => { @@ -167,116 +178,232 @@ describe('cip42', () => { test('type is undefined but has cip42 fields', () => { const transaction: TransactionSerializableCIP42 = { ...baseCip42, - feeCurrency: '0xd8763cba276a3738e6de85b4b3bf5fded6d6ca73', + gatewayFeeRecipient: accounts[7].address, + gatewayFee: 1000023434343n, type: undefined, } expect(serializeTransactionCelo(transaction)).toEqual( - '0x7cf84682a4ec80847735940084773594008094d8763cba276a3738e6de85b4b3bf5fded6d6ca73808094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c0', + '0x7cf85f82a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a9414dc79964da2c08b23698b3d3cc7ca32193d995585e8d60aa46794f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c0', ) }) -}) -test('signed', async () => { - const signed = await signTransaction({ - privateKey: accounts[0].privateKey, - transaction: baseCip42, - serializer: serializeTransactionCelo, + test('signed', async () => { + const signed = await signTransaction({ + privateKey: accounts[0].privateKey, + transaction: baseCip42, + serializer: serializeTransactionCelo, + }) + + expect(signed).toEqual( + '0x7cf88982a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a808094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c080a01ae1d60446ad5fdd620e1982050dc315ff9a0f61b32bcc2a3cdadd0571a76df7a073aba459b3aef6796d5f2a9979551c29f66586821b5613d5080d00782b07c280', + ) }) - expect(signed).toEqual( - '0x7cf88982a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a808094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c080a01ae1d60446ad5fdd620e1982050dc315ff9a0f61b32bcc2a3cdadd0571a76df7a073aba459b3aef6796d5f2a9979551c29f66586821b5613d5080d00782b07c280', - ) -}) + test('signature', () => { + expect( + serializeTransactionCelo( + baseCip42, -test('signature', () => { - expect( - serializeTransactionCelo( - baseCip42, - - { - r: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', - s: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', - v: 28n, - }, - ), - ).toEqual( - '0x7cf88982a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a808094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c001a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', - ) - expect( - serializeTransactionCelo( - baseCip42, - - { - r: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', - s: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', - v: 27n, - }, - ), - ).toEqual( - '0x7cf88982a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a808094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c080a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', - ) + { + r: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', + s: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', + v: 28n, + }, + ), + ).toEqual( + '0x7cf88982a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a808094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c001a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', + ) + expect( + serializeTransactionCelo( + baseCip42, + + { + r: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', + s: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', + v: 27n, + }, + ), + ).toEqual( + '0x7cf88982a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a808094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c080a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', + ) + }) }) -describe('invalid params', () => { - test('invalid to', () => { - const transaction: TransactionSerializableCIP42 = { - ...baseCip42, - to: '0xdeadbeef', +describe('cip64', () => { + test('should be able to serialize a cip64 transaction', () => { + const transaction: TransactionSerializableCIP64 = { + ...baseCip64, } - expect(() => serializeTransactionCelo(transaction)).toThrowError( - InvalidAddressError, + + expect(serializeTransactionCelo(transaction)).toEqual( + '0x7bf84482a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c0', ) }) - test('maxPriorityFeePerGas is higher than maxPriorityFee', () => { - const transaction: TransactionSerializableCIP42 = { - ...baseCip42, - maxPriorityFeePerGas: parseGwei('5000000000'), - maxFeePerGas: parseGwei('1'), + test('args: accessList', () => { + const transaction: TransactionSerializableCIP64 = { + ...baseCip64, + accessList: [ + { + address: '0x0000000000000000000000000000000000000000', + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000001', + '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', + ], + }, + ], } - expect(() => serializeTransactionCelo(transaction)).toThrowError( - TipAboveFeeCapError, + + expect(serializeTransactionCelo(transaction)).toEqual( + '0x7bf8a082a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080f85bf859940000000000000000000000000000000000000000f842a00000000000000000000000000000000000000000000000000000000000000001a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', ) }) - test('maxFeePerGas is too high', () => { - const transaction: TransactionSerializableCIP42 = { - ...baseCip42, - maxPriorityFeePerGas: parseGwei('5000000000'), - maxFeePerGas: - 115792089237316195423570985008687907853269984665640564039457584007913129639938n, + test('args: data', () => { + const args: TransactionSerializableCIP64 = { + ...baseCip64, + data: '0x1234', } - expect(() => serializeTransactionCelo(transaction)).toThrowError( - FeeCapTooHighError, + const serialized = serializeTransactionCelo(args) + expect(serialized).toEqual( + '0x7bf84682a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a7640000821234c0', ) }) - test('feeCurrency is not an address', () => { - const transaction: TransactionSerializableCIP42 = { - ...baseCip42, + test('args: gas', () => { + const transaction: TransactionSerializableCIP64 = { + ...baseCip64, + gas: 69420n, + } + expect(serializeTransactionCelo(transaction)).toEqual( + '0x7bf84782a4ec808477359400847735940083010f2c94765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c0', + ) + }) + + test('args: gas (absent)', () => { + const transaction: TransactionSerializableCIP64 = { + ...baseCip64, + gas: undefined, + } + expect(serializeTransactionCelo(transaction)).toEqual( + '0x7bf84482a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c0', + ) + }) + + test('args: maxFeePerGas (absent)', () => { + const transaction: TransactionSerializableCIP64 = { + ...baseCip64, // @ts-expect-error - feeCurrency: 'CUSD', + maxFeePerGas: undefined, } - expect(() => serializeTransactionCelo(transaction)).toThrowError( - '`feeCurrency` MUST be a token address for CIP-42 transactions.', + expect(serializeTransactionCelo(transaction)).toEqual( + '0x7bf84082a4ec808477359400808094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c0', ) }) - test('gasPrice is defined', () => { - const transaction: TransactionSerializableCIP42 = { - ...baseCip42, + test('args: maxPriorityFeePerGas (absent)', () => { + const transaction: TransactionSerializableCIP64 = { + ...baseCip64, // @ts-expect-error - gasPrice: 1, + maxPriorityFeePerGas: undefined, } - expect(() => serializeTransactionCelo(transaction)).toThrowError( - '`gasPrice` is not a valid CIP-42 Transaction attribute.', + expect(serializeTransactionCelo(transaction)).toEqual( + '0x7bf84082a4ec808084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c0', + ) + }) + + test('args: nonce', () => { + const transaction: TransactionSerializableCIP64 = { + ...baseCip64, + nonce: 20, + } + expect(serializeTransactionCelo(transaction)).toEqual( + '0x7bf84482a4ec14847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c0', + ) + }) + + test('args: to (absent)', () => { + const args: TransactionSerializableCIP64 = { + ...baseCip64, + to: undefined, + } + const serialized = serializeTransactionCelo(args) + expect(serialized).toEqual( + '0x7bf082a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a80880de0b6b3a764000080c0', ) }) + test('args: value (absent)', () => { + const args: TransactionSerializableCIP64 = { + ...baseCip64, + value: undefined, + } + const serialized = serializeTransactionCelo(args) + expect(serialized).toEqual( + '0x7bf83c82a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb922668080c0', + ) + }) + + test('type is undefined but has cip64 fields (feeCurrency, but not gateway*)', () => { + const transaction: TransactionSerializableCIP64 = { + ...baseCip64, + feeCurrency: '0xd8763cba276a3738e6de85b4b3bf5fded6d6ca73', + type: undefined, + } + + expect(serializeTransactionCelo(transaction)).toEqual( + '0x7bf84482a4ec80847735940084773594008094d8763cba276a3738e6de85b4b3bf5fded6d6ca7394f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c0', + ) + }) + + test('signed', async () => { + const signed = await signTransaction({ + privateKey: accounts[0].privateKey, + transaction: baseCip64, + serializer: serializeTransactionCelo, + }) + + expect(signed).toEqual( + '0x7bf88782a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c001a0f147642519264184aa71cb5d0d3439b828fc933034e99161c5846882d08b3be2a067a99b5678a8ead52e3c0793378d1995c02354f2ed4440047cce900fe85f2c83', + ) + }) + + test('signature', () => { + expect( + serializeTransactionCelo( + baseCip64, + + { + r: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', + s: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', + v: 28n, + }, + ), + ).toEqual( + '0x7bf88782a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c001a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', + ) + expect( + serializeTransactionCelo( + baseCip64, + + { + r: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', + s: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', + v: 27n, + }, + ), + ).toEqual( + '0x7bf88782a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c080a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', + ) + }) +}) + +describe('invalid params specific to CIP-42', () => { test('only one of the gateWayFee fields is defined', () => { const transactionA: TransactionSerializableCIP42 = { ...baseCip42, + gatewayFee: undefined, gatewayFeeRecipient: accounts[7].address, } expect(() => serializeTransactionCelo(transactionA)).toThrowError( @@ -285,14 +412,14 @@ describe('invalid params', () => { const transactionB: TransactionSerializableCIP42 = { ...baseCip42, gatewayFee: 1000023434343n, + gatewayFeeRecipient: undefined, } expect(() => serializeTransactionCelo(transactionB)).toThrowError( '`gatewayFee` and `gatewayFeeRecipient` must be provided together.', ) }) - test('transaction looks like cip42 but does not have values for either feeCurrency or gatewayFee', () => { - const transaction = { + const transaction: TransactionSerializableCIP42 = { ...baseCip42, feeCurrency: undefined, gatewayFee: undefined, @@ -302,10 +429,96 @@ describe('invalid params', () => { 'Either `feeCurrency` or `gatewayFeeRecipient` must be provided for CIP-42 transactions.', ) }) +}) + +describe('invalid params specific to CIP-64', () => { + test('transaction looks like cip64 but does not have a value for feeCurrency', () => { + const transaction: TransactionSerializableCIP64 = { + ...baseCip64, + feeCurrency: undefined, + } + expect(() => serializeTransactionCelo(transaction)).toThrowError( + '`feeCurrency` must be provided for CIP-64 transactions.', + ) + }) +}) + +describe.each([ + { typeName: 'CIP-42', baseTransaction: baseCip42 }, + { typeName: 'CIP-64', baseTransaction: baseCip64 }, +])('Common invalid params (for $typeName)', ({ typeName, baseTransaction }) => { + test('invalid to', () => { + const transaction: + | TransactionSerializableCIP42 + | TransactionSerializableCIP64 = { + ...baseTransaction, + to: '0xdeadbeef', + } + expect(() => serializeTransactionCelo(transaction)).toThrowError( + InvalidAddressError, + ) + }) + + test('maxPriorityFeePerGas is higher than maxPriorityFee', () => { + const transaction: + | TransactionSerializableCIP42 + | TransactionSerializableCIP64 = { + ...baseTransaction, + maxPriorityFeePerGas: parseGwei('5000000000'), + maxFeePerGas: parseGwei('1'), + } + expect(() => serializeTransactionCelo(transaction)).toThrowError( + TipAboveFeeCapError, + ) + }) + + test('maxFeePerGas is too high', () => { + const transaction: + | TransactionSerializableCIP42 + | TransactionSerializableCIP64 = { + ...baseTransaction, + maxPriorityFeePerGas: parseGwei('5000000000'), + maxFeePerGas: + 115792089237316195423570985008687907853269984665640564039457584007913129639938n, + } + expect(() => serializeTransactionCelo(transaction)).toThrowError( + FeeCapTooHighError, + ) + }) + + test('feeCurrency is not an address', () => { + const transaction: + | TransactionSerializableCIP42 + | TransactionSerializableCIP64 = { + ...baseTransaction, + // @ts-expect-error + feeCurrency: 'CUSD', + } + + expect(() => serializeTransactionCelo(transaction)).toThrowError( + `\`feeCurrency\` MUST be a token address for ${typeName} transactions.`, + ) + }) + + test('gasPrice is defined', () => { + const transaction: + | TransactionSerializableCIP42 + | TransactionSerializableCIP64 = { + ...baseTransaction, + // @ts-expect-error + gasPrice: BigInt(1), + } + + expect(() => serializeTransactionCelo(transaction)).toThrowError( + `\`gasPrice\` is not a valid ${typeName} Transaction attribute.`, + ) + }) test('chainID is invalid', () => { - const transaction: TransactionSerializableCIP42 = { - ...baseCip42, + const transaction: + | TransactionSerializableCIP42 + | TransactionSerializableCIP64 = { + ...baseTransaction, chainId: -1, } diff --git a/src/chains/celo/serializers.ts b/src/chains/celo/serializers.ts index 375a109a84..473b2a69e0 100644 --- a/src/chains/celo/serializers.ts +++ b/src/chains/celo/serializers.ts @@ -18,20 +18,32 @@ import { import type { CeloTransactionSerializable, TransactionSerializableCIP42, + TransactionSerializableCIP64, TransactionSerializedCIP42, + TransactionSerializedCIP64, } from './types.js' export const serializeTransactionCelo: SerializeTransactionFn< CeloTransactionSerializable > = (tx, signature) => { - // Handle CIP-42 transactions - if (isCIP42(tx)) - return serializeTransactionCIP42( - tx as TransactionSerializableCIP42, - signature, - ) - - // Handle other transaction types + if ('maxFeePerGas' in tx && 'maxPriorityFeePerGas' in tx) { + // process as CIP42 if any of these fields are present. realistically gatewayfee is not used but is part of spec + if ( + 'gatewayFee' in tx || + 'gatewayFeeRecipient' in tx || + ('feeCurrency' in tx && tx.type && tx.type === 'cip42') + ) { + return serializeTransactionCIP42( + tx as TransactionSerializableCIP42, + signature, + ) + } else if ('feeCurrency' in tx) { + return serializeTransactionCIP64( + tx as TransactionSerializableCIP64, + signature, + ) + } + } return serializeTransaction(tx as TransactionSerializable, signature) } @@ -43,6 +55,7 @@ export const serializersCelo = { // Serializers export type SerializeTransactionCIP42ReturnType = TransactionSerializedCIP42 +export type SerializeTransactionCIP64ReturnType = TransactionSerializedCIP64 // There shall be a typed transaction with the code 0x7c that has the following format: // 0x7c || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, feecurrency, gatewayFeeRecipient, gatewayfee, destination, amount, data, access_list, signature_y_parity, signature_r, signature_s]). @@ -96,22 +109,54 @@ function serializeTransactionCIP42( ]) as SerializeTransactionCIP42ReturnType } -////////////////////////////////////////////////////////////////////////////// -// Utilities +function serializeTransactionCIP64( + transaction: TransactionSerializableCIP64, + signature?: Signature, +): SerializeTransactionCIP64ReturnType { + assertTransactionCIP64(transaction) + const { + chainId, + gas, + nonce, + to, + value, + maxFeePerGas, + maxPriorityFeePerGas, + accessList, + feeCurrency, + data, + } = transaction -// process as CIP42 if any of these fields are present. realistically gatewayfee is not used but is part of spec -function isCIP42(transaction: CeloTransactionSerializable) { - if ( - 'maxFeePerGas' in transaction && - 'maxPriorityFeePerGas' in transaction && - ('feeCurrency' in transaction || - 'gatewayFee' in transaction || - 'gatewayFeeRecipient' in transaction) - ) - return true - return false + const serializedTransaction = [ + toHex(chainId), + nonce ? toHex(nonce) : '0x', + maxPriorityFeePerGas ? toHex(maxPriorityFeePerGas) : '0x', + maxFeePerGas ? toHex(maxFeePerGas) : '0x', + gas ? toHex(gas) : '0x', + feeCurrency ?? '0x', + to ?? '0x', + value ? toHex(value) : '0x', + data ?? '0x', + serializeAccessList(accessList), + ] + + if (signature) { + serializedTransaction.push( + signature.v === 27n ? '0x' : toHex(1), // yParity + trim(signature.r), + trim(signature.s), + ) + } + + return concatHex([ + '0x7b', + toRlp(serializedTransaction), + ]) as SerializeTransactionCIP64ReturnType } +////////////////////////////////////////////////////////////////////////////// +// Utilities + // maxFeePerGas must be less than 2^256 - 1: however writing like that caused exceptions to be raised const MAX_MAX_FEE_PER_GAS = 115792089237316195423570985008687907853269984665640564039457584007913129639935n @@ -167,3 +212,45 @@ export function assertTransactionCIP42( ) } } + +export function assertTransactionCIP64( + transaction: TransactionSerializableCIP64, +) { + const { + chainId, + maxPriorityFeePerGas, + gasPrice, + maxFeePerGas, + to, + feeCurrency, + } = transaction + + if (chainId <= 0) throw new InvalidChainIdError({ chainId }) + if (to && !isAddress(to)) throw new InvalidAddressError({ address: to }) + + if (gasPrice) + throw new BaseError( + '`gasPrice` is not a valid CIP-64 Transaction attribute.', + ) + + if (maxFeePerGas && maxFeePerGas > MAX_MAX_FEE_PER_GAS) + throw new FeeCapTooHighError({ maxFeePerGas }) + if ( + maxPriorityFeePerGas && + maxFeePerGas && + maxPriorityFeePerGas > maxFeePerGas + ) + throw new TipAboveFeeCapError({ maxFeePerGas, maxPriorityFeePerGas }) + + if (feeCurrency && !feeCurrency?.startsWith('0x')) { + throw new BaseError( + '`feeCurrency` MUST be a token address for CIP-64 transactions.', + ) + } + + if (!feeCurrency) { + throw new BaseError( + '`feeCurrency` must be provided for CIP-64 transactions.', + ) + } +} diff --git a/src/chains/celo/types.ts b/src/chains/celo/types.ts index 81854a36c0..ea3b99f86f 100644 --- a/src/chains/celo/types.ts +++ b/src/chains/celo/types.ts @@ -103,9 +103,11 @@ export type CeloTransactionReceipt = TransactionReceipt & export type CeloTransactionRequest = | TransactionRequest | TransactionRequestCIP42 + | TransactionRequestCIP64 export type CeloTransactionSerializable = | TransactionSerializableCIP42 + | TransactionSerializableCIP64 | TransactionSerializable export type CeloTransactionSerialized< @@ -182,6 +184,13 @@ export type TransactionRequestCIP42 = TransactionRequestBase & type?: 'cip42' } +export type TransactionRequestCIP64 = TransactionRequestBase & + Partial & { + accessList?: AccessList + feeCurrency?: Address + type?: 'cip64' + } + export type TransactionSerializableCIP42< TQuantity = bigint, TIndex = number, @@ -196,4 +205,17 @@ export type TransactionSerializableCIP42< type?: 'cip42' } +export type TransactionSerializableCIP64< + TQuantity = bigint, + TIndex = number, +> = TransactionSerializableBase & + FeeValuesEIP1559 & { + accessList?: AccessList + gasPrice?: never + feeCurrency?: Address + chainId: number + type?: 'cip64' + } + export type TransactionSerializedCIP42 = `0x7c${string}` +export type TransactionSerializedCIP64 = `0x7b${string}` From f7128866fbe5b597ea59c820f716cc09cb67dfe9 Mon Sep 17 00:00:00 2001 From: Audrey Penven Date: Mon, 2 Oct 2023 15:58:37 +0200 Subject: [PATCH 02/15] finish adding CIP-64 changes to types --- src/chains/celo/formatters.test-d.ts | 2 +- src/chains/celo/types.ts | 34 ++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/chains/celo/formatters.test-d.ts b/src/chains/celo/formatters.test-d.ts index a101ce3700..164a3f8c81 100644 --- a/src/chains/celo/formatters.test-d.ts +++ b/src/chains/celo/formatters.test-d.ts @@ -175,7 +175,7 @@ describe('smoke', () => { `0x${string}` | null >() expectTypeOf(transaction.type).toEqualTypeOf< - 'legacy' | 'eip2930' | 'eip1559' | 'cip42' + 'legacy' | 'eip2930' | 'eip1559' | 'cip42' | 'cip64' >() }) diff --git a/src/chains/celo/types.ts b/src/chains/celo/types.ts index ea3b99f86f..ae2feab2e8 100644 --- a/src/chains/celo/types.ts +++ b/src/chains/celo/types.ts @@ -75,6 +75,7 @@ export type CeloRpcBlock< export type CeloRpcTransaction = | RpcTransaction | RpcTransactionCIP42 + | RpcTransactionCIP64 export type CeloRpcTransactionReceiptOverrides = { feeCurrency: Address | null @@ -87,10 +88,12 @@ export type CeloRpcTransactionReceipt = RpcTransactionReceipt & export type CeloRpcTransactionRequest = | RpcTransactionRequest | RpcTransactionRequestCIP42 + | RpcTransactionRequestCIP64 export type CeloTransaction = | Transaction | TransactionCIP42 + | TransactionCIP64 export type CeloTransactionReceiptOverrides = { feeCurrency: Address | null @@ -112,9 +115,12 @@ export type CeloTransactionSerializable = export type CeloTransactionSerialized< TType extends CeloTransactionType = 'legacy', -> = TransactionSerialized | TransactionSerializedCIP42 +> = + | TransactionSerialized + | TransactionSerializedCIP42 + | TransactionSerializedCIP64 -export type CeloTransactionType = TransactionType | 'cip42' +export type CeloTransactionType = TransactionType | 'cip42' | 'cip64' type RpcTransaction = RpcTransaction_ & { @@ -138,6 +144,13 @@ export type RpcTransactionCIP42 = type: '0x7c' } +export type RpcTransactionCIP64 = + TransactionBase & + FeeValuesEIP1559 & { + feeCurrency: Address | null + type: '0x7b' + } + export type RpcTransactionRequestCIP42 = TransactionRequestBase< Quantity, Index @@ -150,6 +163,16 @@ export type RpcTransactionRequestCIP42 = TransactionRequestBase< type?: '0x7c' } +export type RpcTransactionRequestCIP64 = TransactionRequestBase< + Quantity, + Index +> & + Partial> & { + accessList?: AccessList + feeCurrency?: Address + type?: '0x7b' + } + type Transaction = Transaction_< bigint, number, @@ -169,6 +192,13 @@ export type TransactionCIP42 = type: 'cip42' } +export type TransactionCIP64 = + TransactionBase & + FeeValuesEIP1559 & { + feeCurrency: Address | null + type: 'cip64' + } + type TransactionRequest = TransactionRequest_ & { feeCurrency?: Address gatewayFee?: bigint From fe47e3ddb6bf5c55e5a59df2b8e56c2c40f2ee0d Mon Sep 17 00:00:00 2001 From: Audrey Penven Date: Mon, 2 Oct 2023 18:15:20 +0200 Subject: [PATCH 03/15] attempt to update formatter, with some wrong-handling of union types --- src/chains/celo/formatters.ts | 43 +++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/src/chains/celo/formatters.ts b/src/chains/celo/formatters.ts index 6f40c763ce..8c007c3ec7 100644 --- a/src/chains/celo/formatters.ts +++ b/src/chains/celo/formatters.ts @@ -35,10 +35,14 @@ export const formattersCelo = { return { ...formatTransaction(transaction as RpcTransaction), feeCurrency: transaction.feeCurrency, - gatewayFee: transaction.gatewayFee - ? hexToBigInt(transaction.gatewayFee) - : null, - gatewayFeeRecipient: transaction.gatewayFeeRecipient, + ...(transaction.type === '0x7b' + ? {} + : { + gatewayFee: transaction.gatewayFee + ? hexToBigInt(transaction.gatewayFee) + : null, + gatewayFeeRecipient: transaction.gatewayFeeRecipient, + }), } }) as Hash[] | CeloTransaction[] return { @@ -51,8 +55,12 @@ export const formattersCelo = { format(args: CeloRpcTransaction): CeloTransaction { return { feeCurrency: args.feeCurrency, - gatewayFee: args.gatewayFee ? hexToBigInt(args.gatewayFee) : null, - gatewayFeeRecipient: args.gatewayFeeRecipient, + ...(args.type === '0x7b' + ? {} + : { + gatewayFee: args.gatewayFee ? hexToBigInt(args.gatewayFee) : null, + gatewayFeeRecipient: args.gatewayFeeRecipient, + }), } as CeloTransaction }, }), @@ -67,18 +75,29 @@ export const formattersCelo = { } }, }), + + // TODO: Figure out how to deal with the error here. gateway fields aren't on all + // types in the union type. Also the type indicator can be blank, so.... this is super ambiguous transactionRequest: /*#__PURE__*/ defineTransactionRequest({ format(args: CeloTransactionRequest): CeloRpcTransactionRequest { const request = { feeCurrency: args.feeCurrency, - gatewayFee: - typeof args.gatewayFee !== 'undefined' - ? numberToHex(args.gatewayFee) - : undefined, - gatewayFeeRecipient: args.gatewayFeeRecipient, + // gatewayFee: + // typeof args.gatewayFee !== 'undefined' + // ? numberToHex(args.gatewayFee) + // : undefined, + // gatewayFeeRecipient: args.gatewayFeeRecipient, } as CeloRpcTransactionRequest - if (args.type === 'cip42') request.type = '0x7c' + if (args.type === 'cip64') request.type = '0x7b' + if (args.type === 'cip42') request.type = '0x7c' + + if (typeof args.gatewayFee !== 'undefined') { + request.gatewayFee = numberToHex(args.gatewayFee) + } + if (args.gatewayFeeRecipient) { + request.gatewayFeeRecipient = args.gatewayFeeRecipient + } return request }, }), From 7701a5983f8f2fbe4f34c755502e50535f93c1cf Mon Sep 17 00:00:00 2001 From: Audrey Penven Date: Wed, 18 Oct 2023 15:58:23 +0200 Subject: [PATCH 04/15] fix some types, get some tests working --- src/chains/celo/formatters.test-d.ts | 8 +- src/chains/celo/formatters.test.ts | 52 +-- src/chains/celo/formatters.ts | 66 ++-- src/chains/celo/parsers.test.ts | 459 ++++++++++++++++++--------- src/chains/celo/parsers.ts | 4 +- src/chains/celo/types.ts | 10 + 6 files changed, 399 insertions(+), 200 deletions(-) diff --git a/src/chains/celo/formatters.test-d.ts b/src/chains/celo/formatters.test-d.ts index 164a3f8c81..592d1e4913 100644 --- a/src/chains/celo/formatters.test-d.ts +++ b/src/chains/celo/formatters.test-d.ts @@ -57,9 +57,11 @@ describe('transaction', () => { expectTypeOf< ReturnType['feeCurrency'] >().toEqualTypeOf<`0x${string}` | null>() - expectTypeOf< - ReturnType['gatewayFee'] - >().toEqualTypeOf() + if ('gatewayFee' in formattersCelo.transaction.format) { + expectTypeOf< + ReturnType['gatewayFee'] + >().toEqualTypeOf() + } expectTypeOf< ReturnType['gatewayFeeRecipient'] >().toEqualTypeOf<`0x${string}` | null>() diff --git a/src/chains/celo/formatters.test.ts b/src/chains/celo/formatters.test.ts index 06e1e39a5e..fa0f817dec 100644 --- a/src/chains/celo/formatters.test.ts +++ b/src/chains/celo/formatters.test.ts @@ -237,30 +237,30 @@ describe('block', () => { const { extraData: _extraData, transactions, ...rest } = block expect(transactions[0]).toMatchInlineSnapshot(` - { - "blockHash": "0xac8c9bc3b84e103dc321bbe83b670e425ff68bfc9a333a4f1b1b204ad11c583d", - "blockNumber": 16645775n, - "chainId": undefined, - "ethCompatible": false, - "feeCurrency": null, - "from": "0x045d685d23e8aa34dc408a66fb408f20dc84d785", - "gas": 1527520n, - "gasPrice": 562129081n, - "gatewayFee": 0n, - "gatewayFeeRecipient": null, - "hash": "0x487efb864b308ee85afd7ed5954e968457cfe84e71726114b0a44f31fb876e85", - "input": "0x389ec778", - "nonce": 714820, - "r": "0x1c0c8776e2e9d97b9a95435d2c2439d5f634e1afc35a5a0f0bd02093dd4724e0", - "s": "0xde418ff749f2430a85e60a4b3f81af9f8e2117cffbe32c719b9b784c01be774", - "to": "0xb86d682b1b6bf20d8d54f55c48f848b9487dec37", - "transactionIndex": 0, - "type": "legacy", - "typeHex": "0x0", - "v": 84476n, - "value": 0n, - } - `) + { + "blockHash": "0xac8c9bc3b84e103dc321bbe83b670e425ff68bfc9a333a4f1b1b204ad11c583d", + "blockNumber": 16645775n, + "chainId": undefined, + "ethCompatible": false, + "feeCurrency": null, + "from": "0x045d685d23e8aa34dc408a66fb408f20dc84d785", + "gas": 1527520n, + "gasPrice": 562129081n, + "gatewayFee": "0x0", + "gatewayFeeRecipient": null, + "hash": "0x487efb864b308ee85afd7ed5954e968457cfe84e71726114b0a44f31fb876e85", + "input": "0x389ec778", + "nonce": 714820, + "r": "0x1c0c8776e2e9d97b9a95435d2c2439d5f634e1afc35a5a0f0bd02093dd4724e0", + "s": "0xde418ff749f2430a85e60a4b3f81af9f8e2117cffbe32c719b9b784c01be774", + "to": "0xb86d682b1b6bf20d8d54f55c48f848b9487dec37", + "transactionIndex": 0, + "type": "legacy", + "typeHex": "0x0", + "v": 84476n, + "value": 0n, + } + `) expect(rest).toMatchInlineSnapshot(` { "baseFeePerGas": null, @@ -777,7 +777,7 @@ describe('transactionRequest', () => { "maxFeePerGas": "0x2", "maxPriorityFeePerGas": "0x1", "nonce": "0x1", - "type": undefined, + "type": "0x7c", "value": "0x1", } `) @@ -804,7 +804,7 @@ describe('transactionRequest', () => { "maxFeePerGas": "0x2", "maxPriorityFeePerGas": "0x1", "nonce": "0x1", - "type": undefined, + "type": "0x7c", "value": "0x1", } `) diff --git a/src/chains/celo/formatters.ts b/src/chains/celo/formatters.ts index 8c007c3ec7..7628da254b 100644 --- a/src/chains/celo/formatters.ts +++ b/src/chains/celo/formatters.ts @@ -1,6 +1,6 @@ import { type ChainFormatters } from '../../types/chain.js' import type { Hash } from '../../types/misc.js' -import type { RpcTransaction } from '../../types/rpc.js' +import type { RpcTransaction, RpcTransactionRequest } from '../../types/rpc.js' import { hexToBigInt } from '../../utils/encoding/fromHex.js' import { numberToHex } from '../../utils/encoding/toHex.js' import { defineBlock } from '../../utils/formatters/block.js' @@ -18,6 +18,8 @@ import type { CeloTransaction, CeloTransactionReceiptOverrides, CeloTransactionRequest, + RpcTransactionRequestCIP42, + RpcTransactionRequestCIP64, } from './types.js' export const formattersCelo = { @@ -35,14 +37,15 @@ export const formattersCelo = { return { ...formatTransaction(transaction as RpcTransaction), feeCurrency: transaction.feeCurrency, - ...(transaction.type === '0x7b' - ? {} - : { + + ...(transaction.type === '0x7c' + ? { gatewayFee: transaction.gatewayFee ? hexToBigInt(transaction.gatewayFee) : null, - gatewayFeeRecipient: transaction.gatewayFeeRecipient, - }), + gatewayFeeRecipient: transaction.gatewayFeeRecipient || null, + } + : {}), } }) as Hash[] | CeloTransaction[] return { @@ -55,12 +58,12 @@ export const formattersCelo = { format(args: CeloRpcTransaction): CeloTransaction { return { feeCurrency: args.feeCurrency, - ...(args.type === '0x7b' - ? {} - : { + ...(args.type === '0x7c' + ? { gatewayFee: args.gatewayFee ? hexToBigInt(args.gatewayFee) : null, gatewayFeeRecipient: args.gatewayFeeRecipient, - }), + } + : {}), } as CeloTransaction }, }), @@ -76,29 +79,38 @@ export const formattersCelo = { }, }), - // TODO: Figure out how to deal with the error here. gateway fields aren't on all - // types in the union type. Also the type indicator can be blank, so.... this is super ambiguous transactionRequest: /*#__PURE__*/ defineTransactionRequest({ format(args: CeloTransactionRequest): CeloRpcTransactionRequest { + if ( + args.type === 'cip64' || + ('feeCurrency' in args && + !('gatewayFee' in args || 'gatewayFeeRecipient' in args)) + ) { + return { + type: '0x7b', + feeCurrency: args.feeCurrency, + } as RpcTransactionRequestCIP64 + } + const request = { feeCurrency: args.feeCurrency, - // gatewayFee: - // typeof args.gatewayFee !== 'undefined' - // ? numberToHex(args.gatewayFee) - // : undefined, - // gatewayFeeRecipient: args.gatewayFeeRecipient, - } as CeloRpcTransactionRequest - - if (args.type === 'cip64') request.type = '0x7b' - if (args.type === 'cip42') request.type = '0x7c' + } as Exclude - if (typeof args.gatewayFee !== 'undefined') { - request.gatewayFee = numberToHex(args.gatewayFee) - } - if (args.gatewayFeeRecipient) { - request.gatewayFeeRecipient = args.gatewayFeeRecipient + if ( + args.type === 'cip42' || + 'gatewayFee' in args || + 'gatewayFeeRecipient' in args + ) { + request.type = '0x7c' + request.gatewayFee = + 'gatewayFee' in args && typeof args.gatewayFee !== 'undefined' + ? numberToHex(args.gatewayFee) + : undefined + request.gatewayFeeRecipient = + 'gatewayFeeRecipient' in args ? args.gatewayFeeRecipient : undefined + return request as RpcTransactionRequestCIP42 } - return request + return request as RpcTransactionRequest }, }), } as const satisfies ChainFormatters diff --git a/src/chains/celo/parsers.test.ts b/src/chains/celo/parsers.test.ts index 51d3394a73..01cb6b110c 100644 --- a/src/chains/celo/parsers.test.ts +++ b/src/chains/celo/parsers.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from 'vitest' +import { describe, expect, test } from 'vitest' import { accounts } from '~test/src/constants.js' import { @@ -10,7 +10,10 @@ import { } from '../../index.js' import { parseTransactionCelo } from './parsers.js' import { serializeTransactionCelo } from './serializers.js' -import type { TransactionSerializableCIP42 } from './types.js' +import type { + TransactionSerializableCIP42, + TransactionSerializableCIP64, +} from './types.js' test('should be able to parse a cip42 transaction', () => { const signedTransaction = @@ -39,7 +42,7 @@ const transaction = { value: parseEther('1'), } -test('should return same result as standard parser when not CIP42', () => { +test('should return same result as standard parser when not CIP42 or CIP64', () => { const serialized = serializeTransaction(transaction) expect(parseTransactionCelo(serialized)).toEqual( @@ -47,7 +50,7 @@ test('should return same result as standard parser when not CIP42', () => { ) }) -test('should parse a CIP42 transaction with gatewayFee', () => { +describe('should parse a CIP42 transaction', () => { const transactionWithGatewayFee = { ...transaction, chainId: 42270, @@ -55,168 +58,340 @@ test('should parse a CIP42 transaction with gatewayFee', () => { gatewayFeeRecipient: accounts[1].address, } - const serialized = serializeTransactionCelo(transactionWithGatewayFee) + test('with gatewayFee', () => { + const serialized = serializeTransactionCelo(transactionWithGatewayFee) - expect(parseTransactionCelo(serialized)).toMatchInlineSnapshot(` - { - "chainId": 42270, - "gas": 21001n, - "gatewayFee": 100000000000000000n, - "gatewayFeeRecipient": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "maxFeePerGas": 2000000000n, - "maxPriorityFeePerGas": 2000000000n, - "nonce": 785, - "to": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "type": "cip42", - "value": 1000000000000000000n, - } - `) -}) + expect(parseTransactionCelo(serialized)).toMatchInlineSnapshot(` + { + "chainId": 42270, + "gas": 21001n, + "gatewayFee": 100000000000000000n, + "gatewayFeeRecipient": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "maxFeePerGas": 2000000000n, + "maxPriorityFeePerGas": 2000000000n, + "nonce": 785, + "to": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "type": "cip42", + "value": 1000000000000000000n, + } + `) + }) + test('with access list', () => { + const transactionWithAccessList: TransactionSerializableCIP42 = { + ...transactionWithGatewayFee, + accessList: [ + { + address: '0x0000000000000000000000000000000000000000', + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000001', + '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', + ], + }, + ], + } -test('should parse a CIP42 transaction with access list', () => { - const transactionWithAccessList: TransactionSerializableCIP42 = { - feeCurrency: '0x765de816845861e75a25fca122bb6898b8b1282a', - ...transaction, - chainId: 42270, - accessList: [ - { - address: '0x0000000000000000000000000000000000000000', - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000001', - '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', - ], - }, - ], - } + const serialized = serializeTransactionCelo(transactionWithAccessList) - const serialized = serializeTransactionCelo(transactionWithAccessList) + expect(parseTransactionCelo(serialized)).toMatchInlineSnapshot(` + { + "accessList": [ + { + "address": "0x0000000000000000000000000000000000000000", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe", + ], + }, + ], + "chainId": 42270, + "gas": 21001n, + "gatewayFee": 100000000000000000n, + "gatewayFeeRecipient": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "maxFeePerGas": 2000000000n, + "maxPriorityFeePerGas": 2000000000n, + "nonce": 785, + "to": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "type": "cip42", + "value": 1000000000000000000n, + } + `) + }) + test('with data as 0x', () => { + const transactionWithData: TransactionSerializableCIP42 = { + ...transactionWithGatewayFee, + data: '0x', + } - expect(parseTransactionCelo(serialized)).toMatchInlineSnapshot(` - { - "accessList": [ - { - "address": "0x0000000000000000000000000000000000000000", - "storageKeys": [ - "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe", - ], - }, - ], - "chainId": 42270, - "feeCurrency": "0x765de816845861e75a25fca122bb6898b8b1282a", - "gas": 21001n, - "maxFeePerGas": 2000000000n, - "maxPriorityFeePerGas": 2000000000n, - "nonce": 785, - "to": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "type": "cip42", - "value": 1000000000000000000n, - } - `) + const serialized = serializeTransactionCelo(transactionWithData) + + expect(parseTransactionCelo(serialized)).toMatchInlineSnapshot(` + { + "chainId": 42270, + "gas": 21001n, + "gatewayFee": 100000000000000000n, + "gatewayFeeRecipient": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "maxFeePerGas": 2000000000n, + "maxPriorityFeePerGas": 2000000000n, + "nonce": 785, + "to": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "type": "cip42", + "value": 1000000000000000000n, + } + `) + }) + test('with data', () => { + const transactionWithData: TransactionSerializableCIP42 = { + ...transactionWithGatewayFee, + data: '0x1234', + } + + const serialized = serializeTransactionCelo(transactionWithData) + + expect(parseTransactionCelo(serialized)).toMatchInlineSnapshot(` + { + "chainId": 42270, + "data": "0x1234", + "gas": 21001n, + "gatewayFee": 100000000000000000n, + "gatewayFeeRecipient": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "maxFeePerGas": 2000000000n, + "maxPriorityFeePerGas": 2000000000n, + "nonce": 785, + "to": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "type": "cip42", + "value": 1000000000000000000n, + } + `) + }) }) -test('should parse a CIP42 transaction with data as 0x', () => { - const transactionWithData: TransactionSerializableCIP42 = { - feeCurrency: '0x765de816845861e75a25fca122bb6898b8b1282a', - ...transaction, - chainId: 42270, - data: '0x', - } +describe('should throw an error for invalid cip42 transactions', () => { + test('when all fields are missing', () => { + expect(() => + parseTransactionCelo(`0x7c${toRlp([]).slice(2)}`), + ).toThrowErrorMatchingInlineSnapshot(` + "Invalid serialized transaction of type \\"cip42\\" was provided. - const serialized = serializeTransactionCelo(transactionWithData) + Serialized Transaction: \\"0x7cc0\\" + Missing Attributes: chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, feeCurrency, to, gatewayFeeRecipient, gatewayFee, value, data, accessList - expect(parseTransactionCelo(serialized)).toMatchInlineSnapshot(` - { - "chainId": 42270, - "feeCurrency": "0x765de816845861e75a25fca122bb6898b8b1282a", - "gas": 21001n, - "maxFeePerGas": 2000000000n, - "maxPriorityFeePerGas": 2000000000n, - "nonce": 785, - "to": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "type": "cip42", - "value": 1000000000000000000n, - } - `) + Version: viem@1.0.2" + `) + }) + + test('when some fields are missing', () => { + expect(() => + parseTransactionCelo(`0x7c${toRlp(['0x0', '0x1']).slice(2)}`), + ).toThrowErrorMatchingInlineSnapshot(` + "Invalid serialized transaction of type \\"cip42\\" was provided. + + Serialized Transaction: \\"0x7cc20001\\" + Missing Attributes: maxPriorityFeePerGas, maxFeePerGas, gas, feeCurrency, to, gatewayFeeRecipient, gatewayFee, value, data, accessList + + Version: viem@1.0.2" + `) + }) + + test('when the signature is missing', () => { + expect(() => + parseTransactionCelo( + `0x7c${toRlp([ + '0x', + '0x', + '0x', + '0x', + '0x', + '0x', + '0x', + '0x', + '0x', + '0x', + '0x', + '0x', + '0x', + ]).slice(2)}`, + ), + ).toThrowErrorMatchingInlineSnapshot(` + "Invalid serialized transaction of type \\"cip42\\" was provided. + + Serialized Transaction: \\"0x7ccd80808080808080808080808080\\" + Missing Attributes: r, s + + Version: viem@1.0.2" + `) + }) }) -test('should parse a CIP42 transaction with data', () => { - const transactionWithData: TransactionSerializableCIP42 = { +describe('should parse a CIP64 transaction', () => { + const transactionCip64 = { ...transaction, feeCurrency: '0x765de816845861e75a25fca122bb6898b8b1282a', chainId: 42270, - data: '0x1234', - } + type: 'cip64', + } as TransactionSerializableCIP64 - const serialized = serializeTransactionCelo(transactionWithData) + test('when type is not specified, but the fields match CIP64', () => { + const transactionWithoutType = { + ...transaction, + feeCurrency: '0x765de816845861e75a25fca122bb6898b8b1282a', + type: 'cip64', + chainId: 42270, + } as TransactionSerializableCIP64 - expect(parseTransactionCelo(serialized)).toMatchInlineSnapshot(` - { - "chainId": 42270, - "data": "0x1234", - "feeCurrency": "0x765de816845861e75a25fca122bb6898b8b1282a", - "gas": 21001n, - "maxFeePerGas": 2000000000n, - "maxPriorityFeePerGas": 2000000000n, - "nonce": 785, - "to": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "type": "cip42", - "value": 1000000000000000000n, - } + const serialized = serializeTransactionCelo(transactionWithoutType) + + expect(parseTransactionCelo(serialized)).toMatchInlineSnapshot(` + { + "chainId": 42270, + "feeCurrency": "0x765de816845861e75a25fca122bb6898b8b1282a", + "gas": 21001n, + "maxFeePerGas": 2000000000n, + "maxPriorityFeePerGas": 2000000000n, + "nonce": 785, + "to": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "type": "cip64", + "value": 1000000000000000000n, + } `) -}) + }) -test('invalid transaction (all missing)', () => { - expect(() => - parseTransactionCelo(`0x7c${toRlp([]).slice(2)}`), - ).toThrowErrorMatchingInlineSnapshot(` - "Invalid serialized transaction of type \\"cip42\\" was provided. + test('with access list', () => { + const transactionWithAccessList: TransactionSerializableCIP64 = { + ...transactionCip64, + accessList: [ + { + address: '0x0000000000000000000000000000000000000000', + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000001', + '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', + ], + }, + ], + } - Serialized Transaction: \\"0x7cc0\\" - Missing Attributes: chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, feeCurrency, to, gatewayFeeRecipient, gatewayFee, value, data, accessList + const serialized = serializeTransactionCelo(transactionWithAccessList) - Version: viem@1.0.2" + expect(parseTransactionCelo(serialized)).toMatchInlineSnapshot(` + { + "accessList": [ + { + "address": "0x0000000000000000000000000000000000000000", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe", + ], + }, + ], + "chainId": 42270, + "feeCurrency": "0x765de816845861e75a25fca122bb6898b8b1282a", + "gas": 21001n, + "maxFeePerGas": 2000000000n, + "maxPriorityFeePerGas": 2000000000n, + "nonce": 785, + "to": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "type": "cip64", + "value": 1000000000000000000n, + } `) -}) + }) + test('with data as 0x', () => { + const transactionWithData: TransactionSerializableCIP64 = { + ...transactionCip64, + data: '0x', + } + + const serialized = serializeTransactionCelo(transactionWithData) -test('invalid transaction (some missing)', () => { - expect(() => - parseTransactionCelo(`0x7c${toRlp(['0x0', '0x1']).slice(2)}`), - ).toThrowErrorMatchingInlineSnapshot(` - "Invalid serialized transaction of type \\"cip42\\" was provided. + expect(parseTransactionCelo(serialized)).toMatchInlineSnapshot(` + { + "chainId": 42270, + "feeCurrency": "0x765de816845861e75a25fca122bb6898b8b1282a", + "gas": 21001n, + "maxFeePerGas": 2000000000n, + "maxPriorityFeePerGas": 2000000000n, + "nonce": 785, + "to": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "type": "cip64", + "value": 1000000000000000000n, + } + `) + }) + test('with data', () => { + const transactionWithData: TransactionSerializableCIP64 = { + ...transactionCip64, + data: '0x1234', + } - Serialized Transaction: \\"0x7cc20001\\" - Missing Attributes: maxPriorityFeePerGas, maxFeePerGas, gas, feeCurrency, to, gatewayFeeRecipient, gatewayFee, value, data, accessList + const serialized = serializeTransactionCelo(transactionWithData) - Version: viem@1.0.2" + expect(parseTransactionCelo(serialized)).toMatchInlineSnapshot(` + { + "chainId": 42270, + "data": "0x1234", + "feeCurrency": "0x765de816845861e75a25fca122bb6898b8b1282a", + "gas": 21001n, + "maxFeePerGas": 2000000000n, + "maxPriorityFeePerGas": 2000000000n, + "nonce": 785, + "to": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "type": "cip64", + "value": 1000000000000000000n, + } `) + }) }) -test('invalid transaction (missing signature)', () => { - expect(() => - parseTransactionCelo( - `0x7c${toRlp([ - '0x', - '0x', - '0x', - '0x', - '0x', - '0x', - '0x', - '0x', - '0x', - '0x', - '0x', - '0x', - '0x', - ]).slice(2)}`, - ), - ).toThrowErrorMatchingInlineSnapshot(` - "Invalid serialized transaction of type \\"cip42\\" was provided. - - Serialized Transaction: \\"0x7ccd80808080808080808080808080\\" - Missing Attributes: r, s - - Version: viem@1.0.2" - `) +describe('should throw an error for invalid cip64 transactions', () => { + test('when all fields are missing', () => { + expect(() => + parseTransactionCelo(`0x7b${toRlp([]).slice(2)}`), + ).toThrowErrorMatchingInlineSnapshot(` + "Invalid serialized transaction of type \\"cip64\\" was provided. + + Serialized Transaction: \\"0x7bc0\\" + Missing Attributes: chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, feeCurrency, to, value, data, accessList + + Version: viem@1.0.2" + `) + }) + + test('when some fields are missing', () => { + expect(() => + parseTransactionCelo(`0x7b${toRlp(['0x0', '0x1']).slice(2)}`), + ).toThrowErrorMatchingInlineSnapshot(` + "Invalid serialized transaction of type \\"cip64\\" was provided. + + Serialized Transaction: \\"0x7bc20001\\" + Missing Attributes: maxPriorityFeePerGas, maxFeePerGas, gas, feeCurrency, to, value, data, accessList + + Version: viem@1.0.2" + `) + }) + + test('when the signature is missing', () => { + expect(() => + parseTransactionCelo( + `0x7b${toRlp([ + '0x', + '0x', + '0x', + '0x', + '0x', + '0x', + '0x', + '0x', + '0x', + '0x', + '0x', + ]).slice(2)}`, + ), + ).toThrowErrorMatchingInlineSnapshot(` + "Invalid serialized transaction of type \\"cip64\\" was provided. + + Serialized Transaction: \\"0x7bcb8080808080808080808080\\" + Missing Attributes: r, s + + Version: viem@1.0.2" + `) + }) }) diff --git a/src/chains/celo/parsers.ts b/src/chains/celo/parsers.ts index be26e02188..38687a6427 100644 --- a/src/chains/celo/parsers.ts +++ b/src/chains/celo/parsers.ts @@ -153,7 +153,7 @@ function parseTransactionCIP64( s, ] = transactionArray - if (transactionArray.length !== 13 && transactionArray.length !== 11) { + if (transactionArray.length !== 13 && transactionArray.length !== 10) { throw new InvalidSerializedTransactionError({ attributes: { chainId, @@ -166,7 +166,7 @@ function parseTransactionCIP64( value, data, accessList, - ...(transactionArray.length > 11 + ...(transactionArray.length > 10 ? { v, r, diff --git a/src/chains/celo/types.ts b/src/chains/celo/types.ts index ae2feab2e8..fb1d723712 100644 --- a/src/chains/celo/types.ts +++ b/src/chains/celo/types.ts @@ -148,6 +148,8 @@ export type RpcTransactionCIP64 = TransactionBase & FeeValuesEIP1559 & { feeCurrency: Address | null + gatewayFee: never + gatewayFeeRecipient: never type: '0x7b' } @@ -170,6 +172,8 @@ export type RpcTransactionRequestCIP64 = TransactionRequestBase< Partial> & { accessList?: AccessList feeCurrency?: Address + gatewayFee?: never + gatewayFeeRecipient?: never type?: '0x7b' } @@ -196,6 +200,8 @@ export type TransactionCIP64 = TransactionBase & FeeValuesEIP1559 & { feeCurrency: Address | null + gatewayFee: never + gatewayFeeRecipient: never type: 'cip64' } @@ -218,6 +224,8 @@ export type TransactionRequestCIP64 = TransactionRequestBase & Partial & { accessList?: AccessList feeCurrency?: Address + gatewayFee?: never + gatewayFeeRecipient?: never type?: 'cip64' } @@ -243,6 +251,8 @@ export type TransactionSerializableCIP64< accessList?: AccessList gasPrice?: never feeCurrency?: Address + gatewayFeeRecipient?: never + gatewayFee?: never chainId: number type?: 'cip64' } From 536cbfc193f331c5982b6447216b7809352bb6aa Mon Sep 17 00:00:00 2001 From: Audrey Penven Date: Wed, 18 Oct 2023 16:24:10 +0200 Subject: [PATCH 05/15] fix order of fields for CIP64 transactions --- src/chains/celo/parsers.test.ts | 4 ++-- src/chains/celo/parsers.ts | 4 ++-- src/chains/celo/serializers.test.ts | 28 ++++++++++++++-------------- src/chains/celo/serializers.ts | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/chains/celo/parsers.test.ts b/src/chains/celo/parsers.test.ts index 01cb6b110c..43083d0f4f 100644 --- a/src/chains/celo/parsers.test.ts +++ b/src/chains/celo/parsers.test.ts @@ -349,7 +349,7 @@ describe('should throw an error for invalid cip64 transactions', () => { "Invalid serialized transaction of type \\"cip64\\" was provided. Serialized Transaction: \\"0x7bc0\\" - Missing Attributes: chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, feeCurrency, to, value, data, accessList + Missing Attributes: chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, feeCurrency Version: viem@1.0.2" `) @@ -362,7 +362,7 @@ describe('should throw an error for invalid cip64 transactions', () => { "Invalid serialized transaction of type \\"cip64\\" was provided. Serialized Transaction: \\"0x7bc20001\\" - Missing Attributes: maxPriorityFeePerGas, maxFeePerGas, gas, feeCurrency, to, value, data, accessList + Missing Attributes: maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, feeCurrency Version: viem@1.0.2" `) diff --git a/src/chains/celo/parsers.ts b/src/chains/celo/parsers.ts index 38687a6427..16e6066464 100644 --- a/src/chains/celo/parsers.ts +++ b/src/chains/celo/parsers.ts @@ -143,11 +143,11 @@ function parseTransactionCIP64( maxPriorityFeePerGas, maxFeePerGas, gas, - feeCurrency, to, value, data, accessList, + feeCurrency, v, r, s, @@ -161,11 +161,11 @@ function parseTransactionCIP64( maxPriorityFeePerGas, maxFeePerGas, gas, - feeCurrency, to, value, data, accessList, + feeCurrency, ...(transactionArray.length > 10 ? { v, diff --git a/src/chains/celo/serializers.test.ts b/src/chains/celo/serializers.test.ts index 118f061d77..e49ee283fb 100644 --- a/src/chains/celo/serializers.test.ts +++ b/src/chains/celo/serializers.test.ts @@ -237,7 +237,7 @@ describe('cip64', () => { } expect(serializeTransactionCelo(transaction)).toEqual( - '0x7bf84482a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c0', + '0x7bf84482a4ec80847735940084773594008094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c094765de816845861e75a25fca122bb6898b8b1282a', ) }) @@ -256,7 +256,7 @@ describe('cip64', () => { } expect(serializeTransactionCelo(transaction)).toEqual( - '0x7bf8a082a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080f85bf859940000000000000000000000000000000000000000f842a00000000000000000000000000000000000000000000000000000000000000001a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', + '0x7bf8a082a4ec80847735940084773594008094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080f85bf859940000000000000000000000000000000000000000f842a00000000000000000000000000000000000000000000000000000000000000001a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe94765de816845861e75a25fca122bb6898b8b1282a', ) }) @@ -267,7 +267,7 @@ describe('cip64', () => { } const serialized = serializeTransactionCelo(args) expect(serialized).toEqual( - '0x7bf84682a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a7640000821234c0', + '0x7bf84682a4ec80847735940084773594008094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a7640000821234c094765de816845861e75a25fca122bb6898b8b1282a', ) }) @@ -277,7 +277,7 @@ describe('cip64', () => { gas: 69420n, } expect(serializeTransactionCelo(transaction)).toEqual( - '0x7bf84782a4ec808477359400847735940083010f2c94765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c0', + '0x7bf84782a4ec808477359400847735940083010f2c94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c094765de816845861e75a25fca122bb6898b8b1282a', ) }) @@ -287,7 +287,7 @@ describe('cip64', () => { gas: undefined, } expect(serializeTransactionCelo(transaction)).toEqual( - '0x7bf84482a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c0', + '0x7bf84482a4ec80847735940084773594008094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c094765de816845861e75a25fca122bb6898b8b1282a', ) }) @@ -298,7 +298,7 @@ describe('cip64', () => { maxFeePerGas: undefined, } expect(serializeTransactionCelo(transaction)).toEqual( - '0x7bf84082a4ec808477359400808094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c0', + '0x7bf84082a4ec808477359400808094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c094765de816845861e75a25fca122bb6898b8b1282a', ) }) @@ -309,7 +309,7 @@ describe('cip64', () => { maxPriorityFeePerGas: undefined, } expect(serializeTransactionCelo(transaction)).toEqual( - '0x7bf84082a4ec808084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c0', + '0x7bf84082a4ec808084773594008094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c094765de816845861e75a25fca122bb6898b8b1282a', ) }) @@ -319,7 +319,7 @@ describe('cip64', () => { nonce: 20, } expect(serializeTransactionCelo(transaction)).toEqual( - '0x7bf84482a4ec14847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c0', + '0x7bf84482a4ec14847735940084773594008094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c094765de816845861e75a25fca122bb6898b8b1282a', ) }) @@ -330,7 +330,7 @@ describe('cip64', () => { } const serialized = serializeTransactionCelo(args) expect(serialized).toEqual( - '0x7bf082a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a80880de0b6b3a764000080c0', + '0x7bf082a4ec80847735940084773594008080880de0b6b3a764000080c094765de816845861e75a25fca122bb6898b8b1282a', ) }) @@ -341,7 +341,7 @@ describe('cip64', () => { } const serialized = serializeTransactionCelo(args) expect(serialized).toEqual( - '0x7bf83c82a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb922668080c0', + '0x7bf83c82a4ec80847735940084773594008094f39fd6e51aad88f6f4ce6ab8827279cfffb922668080c094765de816845861e75a25fca122bb6898b8b1282a', ) }) @@ -353,7 +353,7 @@ describe('cip64', () => { } expect(serializeTransactionCelo(transaction)).toEqual( - '0x7bf84482a4ec80847735940084773594008094d8763cba276a3738e6de85b4b3bf5fded6d6ca7394f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c0', + '0x7bf84482a4ec80847735940084773594008094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c094d8763cba276a3738e6de85b4b3bf5fded6d6ca73', ) }) @@ -365,7 +365,7 @@ describe('cip64', () => { }) expect(signed).toEqual( - '0x7bf88782a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c001a0f147642519264184aa71cb5d0d3439b828fc933034e99161c5846882d08b3be2a067a99b5678a8ead52e3c0793378d1995c02354f2ed4440047cce900fe85f2c83', + '0x7bf88782a4ec80847735940084773594008094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c094765de816845861e75a25fca122bb6898b8b1282a80a07b5ef5199c55a6765782eeb966fe135ff34b39eadabf952dfc00b017924b356aa06425ed31cf71b817c064b669f89d819ee25affa1669270b9b8ac9638b53d7e7f', ) }) @@ -381,7 +381,7 @@ describe('cip64', () => { }, ), ).toEqual( - '0x7bf88782a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c001a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', + '0x7bf88782a4ec80847735940084773594008094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c094765de816845861e75a25fca122bb6898b8b1282a01a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', ) expect( serializeTransactionCelo( @@ -394,7 +394,7 @@ describe('cip64', () => { }, ), ).toEqual( - '0x7bf88782a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a94f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c080a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', + '0x7bf88782a4ec80847735940084773594008094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c094765de816845861e75a25fca122bb6898b8b1282a80a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', ) }) }) diff --git a/src/chains/celo/serializers.ts b/src/chains/celo/serializers.ts index 473b2a69e0..3e1313d6f1 100644 --- a/src/chains/celo/serializers.ts +++ b/src/chains/celo/serializers.ts @@ -133,11 +133,11 @@ function serializeTransactionCIP64( maxPriorityFeePerGas ? toHex(maxPriorityFeePerGas) : '0x', maxFeePerGas ? toHex(maxFeePerGas) : '0x', gas ? toHex(gas) : '0x', - feeCurrency ?? '0x', to ?? '0x', value ? toHex(value) : '0x', data ?? '0x', serializeAccessList(accessList), + feeCurrency ?? '0x', ] if (signature) { From 9cb4edffd226565fc49e74f4689069acefadf3d8 Mon Sep 17 00:00:00 2001 From: Audrey Penven Date: Thu, 19 Oct 2023 16:05:52 +0200 Subject: [PATCH 06/15] some types intentionally broken to demonstrate conflicting behavior with --- src/chains/celo/formatters.test-d.ts | 7 +++++++ src/chains/celo/formatters.test.ts | 25 +++++++++++++++++++++++++ src/chains/celo/formatters.ts | 2 -- src/chains/celo/types.ts | 8 ++++++-- src/chains/utils/index.ts | 7 ++++++- 5 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/chains/celo/formatters.test-d.ts b/src/chains/celo/formatters.test-d.ts index 592d1e4913..700ab328c2 100644 --- a/src/chains/celo/formatters.test-d.ts +++ b/src/chains/celo/formatters.test-d.ts @@ -230,6 +230,13 @@ describe('smoke', () => { type: 'cip42', }) + // @ts-expect-error `gasPrice` is not defined + prepareTransactionRequest(client, { + feeCurrency: '0x', + gasPrice: 0n, + type: 'cip64', + }) + // @ts-expect-error `gasPrice` is not defined prepareTransactionRequest(client, { feeCurrency: '0x', diff --git a/src/chains/celo/formatters.test.ts b/src/chains/celo/formatters.test.ts index fa0f817dec..38146527be 100644 --- a/src/chains/celo/formatters.test.ts +++ b/src/chains/celo/formatters.test.ts @@ -837,5 +837,30 @@ describe('transactionRequest', () => { "value": "0x1", } `) + + expect( + transactionRequest.format({ + feeCurrency: '0x0f16e9b0d03470827a95cdfd0cb8a8a3b46969b9', + from: '0x0f16e9b0d03470827a95cdfd0cb8a8a3b46969b9', + gas: 1n, + maxFeePerGas: 2n, + maxPriorityFeePerGas: 1n, + nonce: 1, + type: 'cip64', + value: 1n, + }), + ).toMatchInlineSnapshot(` + { + "feeCurrency": "0x0f16e9b0d03470827a95cdfd0cb8a8a3b46969b9", + "from": "0x0f16e9b0d03470827a95cdfd0cb8a8a3b46969b9", + "gas": "0x1", + "gasPrice": undefined, + "maxFeePerGas": "0x2", + "maxPriorityFeePerGas": "0x1", + "nonce": "0x1", + "type": "0x7b", + "value": "0x1", + } + `) }) }) diff --git a/src/chains/celo/formatters.ts b/src/chains/celo/formatters.ts index 7628da254b..cc1afce6fc 100644 --- a/src/chains/celo/formatters.ts +++ b/src/chains/celo/formatters.ts @@ -91,11 +91,9 @@ export const formattersCelo = { feeCurrency: args.feeCurrency, } as RpcTransactionRequestCIP64 } - const request = { feeCurrency: args.feeCurrency, } as Exclude - if ( args.type === 'cip42' || 'gatewayFee' in args || diff --git a/src/chains/celo/types.ts b/src/chains/celo/types.ts index fb1d723712..6ed7142c6d 100644 --- a/src/chains/celo/types.ts +++ b/src/chains/celo/types.ts @@ -165,6 +165,8 @@ export type RpcTransactionRequestCIP42 = TransactionRequestBase< type?: '0x7c' } +// when `never` declarations are OMITTED from this type, there are errors in +// formatters.test-d.ts on lines 103 - 109 export type RpcTransactionRequestCIP64 = TransactionRequestBase< Quantity, Index @@ -172,8 +174,8 @@ export type RpcTransactionRequestCIP64 = TransactionRequestBase< Partial> & { accessList?: AccessList feeCurrency?: Address - gatewayFee?: never - gatewayFeeRecipient?: never + // gatewayFee?: never + // gatewayFeeRecipient?: never type?: '0x7b' } @@ -220,6 +222,8 @@ export type TransactionRequestCIP42 = TransactionRequestBase & type?: 'cip42' } +// In this type, defining `gatewayFee` and `gatewayFeeRecipient` to `never` causes +// errors in formatters.test-d.ts, lines 214 - 382 export type TransactionRequestCIP64 = TransactionRequestBase & Partial & { accessList?: AccessList diff --git a/src/chains/utils/index.ts b/src/chains/utils/index.ts index d14375fc7e..502675b710 100644 --- a/src/chains/utils/index.ts +++ b/src/chains/utils/index.ts @@ -21,11 +21,16 @@ export type { CeloTransactionSerialized, CeloTransactionType, RpcTransactionCIP42, + RpcTransactionCIP64, RpcTransactionRequestCIP42, + RpcTransactionRequestCIP64, TransactionCIP42, + TransactionCIP64, TransactionRequestCIP42, + TransactionRequestCIP64, TransactionSerializableCIP42, - TransactionSerializedCIP42, + TransactionSerializableCIP64, + TransactionSerializedCIP64, } from '../celo/types.js' export { formattersOptimism } from '../optimism/formatters.js' From 7a940c35a2a9a56f06e4ad63c484f947a96e7237 Mon Sep 17 00:00:00 2001 From: Audrey Penven Date: Fri, 20 Oct 2023 17:18:26 +0200 Subject: [PATCH 07/15] simplify, remove superfluous nevers --- src/chains/celo/formatters.test-d.ts | 20 +++--- src/chains/celo/formatters.ts | 93 ++++++++++++++++++---------- src/chains/celo/types.ts | 16 ++--- 3 files changed, 73 insertions(+), 56 deletions(-) diff --git a/src/chains/celo/formatters.test-d.ts b/src/chains/celo/formatters.test-d.ts index 700ab328c2..4238b212fb 100644 --- a/src/chains/celo/formatters.test-d.ts +++ b/src/chains/celo/formatters.test-d.ts @@ -57,14 +57,12 @@ describe('transaction', () => { expectTypeOf< ReturnType['feeCurrency'] >().toEqualTypeOf<`0x${string}` | null>() - if ('gatewayFee' in formattersCelo.transaction.format) { - expectTypeOf< - ReturnType['gatewayFee'] - >().toEqualTypeOf() - } + expectTypeOf< + ReturnType['gatewayFee'] + >().toEqualTypeOf() expectTypeOf< ReturnType['gatewayFeeRecipient'] - >().toEqualTypeOf<`0x${string}` | null>() + >().toEqualTypeOf<`0x${string}` | null | undefined>() }) describe('transactionReceipt', () => { @@ -139,10 +137,10 @@ describe('smoke', () => { ).toEqualTypeOf<`0x${string}` | null>() expectTypeOf( block_includeTransactions.transactions[0].gatewayFee, - ).toEqualTypeOf() + ).toEqualTypeOf() expectTypeOf( block_includeTransactions.transactions[0].gatewayFeeRecipient, - ).toEqualTypeOf<`0x${string}` | null>() + ).toEqualTypeOf<`0x${string}` | null | undefined>() const block_pending = await getBlock(client, { blockTag: 'pending', @@ -172,9 +170,11 @@ describe('smoke', () => { }) expectTypeOf(transaction.feeCurrency).toEqualTypeOf<`0x${string}` | null>() - expectTypeOf(transaction.gatewayFee).toEqualTypeOf() + expectTypeOf(transaction.gatewayFee).toEqualTypeOf< + bigint | null | undefined + >() expectTypeOf(transaction.gatewayFeeRecipient).toEqualTypeOf< - `0x${string}` | null + `0x${string}` | null | undefined >() expectTypeOf(transaction.type).toEqualTypeOf< 'legacy' | 'eip2930' | 'eip1559' | 'cip42' | 'cip64' diff --git a/src/chains/celo/formatters.ts b/src/chains/celo/formatters.ts index cc1afce6fc..aba224a3cf 100644 --- a/src/chains/celo/formatters.ts +++ b/src/chains/celo/formatters.ts @@ -1,6 +1,6 @@ import { type ChainFormatters } from '../../types/chain.js' import type { Hash } from '../../types/misc.js' -import type { RpcTransaction, RpcTransactionRequest } from '../../types/rpc.js' +import type { RpcTransaction } from '../../types/rpc.js' import { hexToBigInt } from '../../utils/encoding/fromHex.js' import { numberToHex } from '../../utils/encoding/toHex.js' import { defineBlock } from '../../utils/formatters/block.js' @@ -18,10 +18,34 @@ import type { CeloTransaction, CeloTransactionReceiptOverrides, CeloTransactionRequest, - RpcTransactionRequestCIP42, RpcTransactionRequestCIP64, + TransactionCIP42, + TransactionCIP64, + TransactionRequestCIP42, + TransactionRequestCIP64, } from './types.js' +function isTransactionRequestCIP64(args: CeloTransactionRequest): boolean { + if (args.type === 'cip64') return true + if (args.type) return false + return ( + 'feeCurrency' in args && + // @ts-expect-error `gatewayFee` is not defined + args.gatewayFee === undefined && + // @ts-expect-error `gatewayFeeRecipient` is not defined + args.gatewayFeeRecipient === undefined + ) +} + +function isTransactionRequestCIP42(args: CeloTransactionRequest): boolean { + if (args.type === 'cip42') return true + if (args.type) return false + return ( + (args as TransactionRequestCIP42).gatewayFee !== undefined || + (args as TransactionRequestCIP42).gatewayFeeRecipient !== undefined + ) +} + export const formattersCelo = { block: /*#__PURE__*/ defineBlock({ exclude: ['difficulty', 'gasLimit', 'mixHash', 'nonce', 'uncles'], @@ -56,15 +80,24 @@ export const formattersCelo = { }), transaction: /*#__PURE__*/ defineTransaction({ format(args: CeloRpcTransaction): CeloTransaction { - return { - feeCurrency: args.feeCurrency, - ...(args.type === '0x7c' - ? { - gatewayFee: args.gatewayFee ? hexToBigInt(args.gatewayFee) : null, - gatewayFeeRecipient: args.gatewayFeeRecipient, - } - : {}), - } as CeloTransaction + switch (args.type) { + case '0x7c': + return { + feeCurrency: args.feeCurrency, + gatewayFee: args.gatewayFee ? hexToBigInt(args.gatewayFee) : null, + gatewayFeeRecipient: args.gatewayFeeRecipient, + } as TransactionCIP42 + case '0x7b': + return { + feeCurrency: args.feeCurrency, + } as TransactionCIP64 + default: + return { + feeCurrency: args.feeCurrency, + gatewayFee: args.gatewayFee ? hexToBigInt(args.gatewayFee) : null, + gatewayFeeRecipient: args.gatewayFeeRecipient, + } as CeloTransaction + } }, }), transactionReceipt: /*#__PURE__*/ defineTransactionReceipt({ @@ -80,35 +113,27 @@ export const formattersCelo = { }), transactionRequest: /*#__PURE__*/ defineTransactionRequest({ - format(args: CeloTransactionRequest): CeloRpcTransactionRequest { - if ( - args.type === 'cip64' || - ('feeCurrency' in args && - !('gatewayFee' in args || 'gatewayFeeRecipient' in args)) - ) { + format(args: CeloTransactionRequest) { + if (isTransactionRequestCIP64(args)) { return { type: '0x7b', feeCurrency: args.feeCurrency, } as RpcTransactionRequestCIP64 } + const _args = args as Exclude< + CeloTransactionRequest, + TransactionRequestCIP64 + > const request = { - feeCurrency: args.feeCurrency, - } as Exclude - if ( - args.type === 'cip42' || - 'gatewayFee' in args || - 'gatewayFeeRecipient' in args - ) { - request.type = '0x7c' - request.gatewayFee = - 'gatewayFee' in args && typeof args.gatewayFee !== 'undefined' - ? numberToHex(args.gatewayFee) - : undefined - request.gatewayFeeRecipient = - 'gatewayFeeRecipient' in args ? args.gatewayFeeRecipient : undefined - return request as RpcTransactionRequestCIP42 - } - return request as RpcTransactionRequest + feeCurrency: _args.feeCurrency, + gatewayFee: + typeof _args.gatewayFee !== 'undefined' + ? numberToHex(_args.gatewayFee) + : undefined, + gatewayFeeRecipient: _args.gatewayFeeRecipient, + } as CeloRpcTransactionRequest + if (isTransactionRequestCIP42(_args)) request.type = '0x7c' + return request }, }), } as const satisfies ChainFormatters diff --git a/src/chains/celo/types.ts b/src/chains/celo/types.ts index 6ed7142c6d..ce4dbc09f4 100644 --- a/src/chains/celo/types.ts +++ b/src/chains/celo/types.ts @@ -148,8 +148,6 @@ export type RpcTransactionCIP64 = TransactionBase & FeeValuesEIP1559 & { feeCurrency: Address | null - gatewayFee: never - gatewayFeeRecipient: never type: '0x7b' } @@ -165,8 +163,6 @@ export type RpcTransactionRequestCIP42 = TransactionRequestBase< type?: '0x7c' } -// when `never` declarations are OMITTED from this type, there are errors in -// formatters.test-d.ts on lines 103 - 109 export type RpcTransactionRequestCIP64 = TransactionRequestBase< Quantity, Index @@ -174,8 +170,8 @@ export type RpcTransactionRequestCIP64 = TransactionRequestBase< Partial> & { accessList?: AccessList feeCurrency?: Address - // gatewayFee?: never - // gatewayFeeRecipient?: never + gatewayFee?: undefined + gatewayFeeRecipient?: undefined type?: '0x7b' } @@ -202,8 +198,8 @@ export type TransactionCIP64 = TransactionBase & FeeValuesEIP1559 & { feeCurrency: Address | null - gatewayFee: never - gatewayFeeRecipient: never + gatewayFee?: undefined + gatewayFeeRecipient?: undefined type: 'cip64' } @@ -228,8 +224,6 @@ export type TransactionRequestCIP64 = TransactionRequestBase & Partial & { accessList?: AccessList feeCurrency?: Address - gatewayFee?: never - gatewayFeeRecipient?: never type?: 'cip64' } @@ -255,8 +249,6 @@ export type TransactionSerializableCIP64< accessList?: AccessList gasPrice?: never feeCurrency?: Address - gatewayFeeRecipient?: never - gatewayFee?: never chainId: number type?: 'cip64' } From d6bb71be00c7028739e9a32724992eb7b26b0b1b Mon Sep 17 00:00:00 2001 From: Audrey Penven Date: Fri, 20 Oct 2023 17:42:30 +0200 Subject: [PATCH 08/15] restore and extend helper functions in the serializer to check whether cip42 or cip64 --- src/chains/celo/serializers.ts | 61 ++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/src/chains/celo/serializers.ts b/src/chains/celo/serializers.ts index 3e1313d6f1..4414e8bae0 100644 --- a/src/chains/celo/serializers.ts +++ b/src/chains/celo/serializers.ts @@ -26,25 +26,19 @@ import type { export const serializeTransactionCelo: SerializeTransactionFn< CeloTransactionSerializable > = (tx, signature) => { - if ('maxFeePerGas' in tx && 'maxPriorityFeePerGas' in tx) { - // process as CIP42 if any of these fields are present. realistically gatewayfee is not used but is part of spec - if ( - 'gatewayFee' in tx || - 'gatewayFeeRecipient' in tx || - ('feeCurrency' in tx && tx.type && tx.type === 'cip42') - ) { - return serializeTransactionCIP42( - tx as TransactionSerializableCIP42, - signature, - ) - } else if ('feeCurrency' in tx) { - return serializeTransactionCIP64( - tx as TransactionSerializableCIP64, - signature, - ) - } + if (isCIP64(tx)) { + return serializeTransactionCIP64( + tx as TransactionSerializableCIP64, + signature, + ) + } else if (isCIP42(tx)) { + return serializeTransactionCIP42( + tx as TransactionSerializableCIP42, + signature, + ) + } else { + return serializeTransaction(tx as TransactionSerializable, signature) } - return serializeTransaction(tx as TransactionSerializable, signature) } export const serializersCelo = { @@ -157,6 +151,37 @@ function serializeTransactionCIP64( ////////////////////////////////////////////////////////////////////////////// // Utilities +// process as CIP42 if any of these fields are present. realistically gatewayfee is not used but is part of spec +function isCIP42(transaction: CeloTransactionSerializable): boolean { + if (transaction.type === 'cip42') return true + // if the type is defined as anything else, assume it is *not* cip42 + if (transaction.type) return false + + // if the type is undefined, check if the fields match the expectations for cip42 + return ( + 'maxFeePerGas' in transaction && + 'maxPriorityFeePerGas' in transaction && + ('feeCurrency' in transaction || + 'gatewayFee' in transaction || + 'gatewayFeeRecipient' in transaction) + ) +} + +function isCIP64(transaction: CeloTransactionSerializable): boolean { + if (transaction.type === 'cip64') return true + // if the type is defined as anything else, assume it is *not* cip64 + if (transaction.type) return false + + // if the type is undefined, check if the fields match the expectations for cip64 + return ( + 'maxFeePerGas' in transaction && + 'maxPriorityFeePerGas' in transaction && + 'feeCurrency' in transaction && + !('gatewayFee' in transaction) && + !('gatewayFeeRecipient' in transaction) + ) +} + // maxFeePerGas must be less than 2^256 - 1: however writing like that caused exceptions to be raised const MAX_MAX_FEE_PER_GAS = 115792089237316195423570985008687907853269984665640564039457584007913129639935n From 52a953b98560abebe2d2c0b7d1fac1debee52b10 Mon Sep 17 00:00:00 2001 From: Audrey Penven Date: Fri, 20 Oct 2023 17:48:53 +0200 Subject: [PATCH 09/15] fix accidental deletion of cip42 type --- src/chains/utils/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/chains/utils/index.ts b/src/chains/utils/index.ts index 502675b710..f2d6b6e62a 100644 --- a/src/chains/utils/index.ts +++ b/src/chains/utils/index.ts @@ -30,6 +30,7 @@ export type { TransactionRequestCIP64, TransactionSerializableCIP42, TransactionSerializableCIP64, + TransactionSerializedCIP42, TransactionSerializedCIP64, } from '../celo/types.js' From c3a5ab3c74cdc4210991c123be8c5974365580d6 Mon Sep 17 00:00:00 2001 From: Audrey Penven Date: Fri, 20 Oct 2023 18:00:49 +0200 Subject: [PATCH 10/15] fix failing test (but not 100% sure why this is needed --- src/chains/celo/formatters.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chains/celo/formatters.test.ts b/src/chains/celo/formatters.test.ts index 38146527be..d4aa83099f 100644 --- a/src/chains/celo/formatters.test.ts +++ b/src/chains/celo/formatters.test.ts @@ -177,7 +177,7 @@ describe('block', () => { "from": "0x045d685d23e8aa34dc408a66fb408f20dc84d785", "gas": 431136n, "gasPrice": undefined, - "gatewayFee": 0n, + "gatewayFee": "0x0", "gatewayFeeRecipient": null, "hash": "0x487efb864b308ee85afd7ed5954e968457cfe84e71726114b0a44f31fb876e85", "input": "0x389ec778", From 52f7c2097f0e1fbfb5a967216b4462f3768722f3 Mon Sep 17 00:00:00 2001 From: Audrey Penven Date: Fri, 20 Oct 2023 18:08:10 +0200 Subject: [PATCH 11/15] delete debugging comment --- src/chains/celo/types.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/chains/celo/types.ts b/src/chains/celo/types.ts index ce4dbc09f4..f1a7135d6e 100644 --- a/src/chains/celo/types.ts +++ b/src/chains/celo/types.ts @@ -218,8 +218,6 @@ export type TransactionRequestCIP42 = TransactionRequestBase & type?: 'cip42' } -// In this type, defining `gatewayFee` and `gatewayFeeRecipient` to `never` causes -// errors in formatters.test-d.ts, lines 214 - 382 export type TransactionRequestCIP64 = TransactionRequestBase & Partial & { accessList?: AccessList From e0149711da5894ac5f0719414b4ecc06ccaecb7b Mon Sep 17 00:00:00 2001 From: Omar Date: Sat, 21 Oct 2023 14:56:32 -0700 Subject: [PATCH 12/15] ci: auto-publish provenance statements on release (#1372) * auto-publish provenance statements on release * undo quotation accidental format --- .github/workflows/on-push-to-main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/on-push-to-main.yml b/.github/workflows/on-push-to-main.yml index 347a502946..20260c156c 100644 --- a/.github/workflows/on-push-to-main.yml +++ b/.github/workflows/on-push-to-main.yml @@ -58,3 +58,5 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + # https://docs.npmjs.com/generating-provenance-statements + NPM_CONFIG_PROVENANCE: true From bc9b8dd89855c9a4d6f4f21ce3ac36fa8e339689 Mon Sep 17 00:00:00 2001 From: Audrey Penven Date: Thu, 26 Oct 2023 14:27:22 +0200 Subject: [PATCH 13/15] restore original behavior for transactions that aren't CIP-42 or CIP-64 --- src/chains/celo/formatters.test.ts | 50 +++++++++++++++--------------- src/chains/celo/formatters.ts | 26 +++++++++++----- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/src/chains/celo/formatters.test.ts b/src/chains/celo/formatters.test.ts index d4aa83099f..408ae54fde 100644 --- a/src/chains/celo/formatters.test.ts +++ b/src/chains/celo/formatters.test.ts @@ -177,7 +177,7 @@ describe('block', () => { "from": "0x045d685d23e8aa34dc408a66fb408f20dc84d785", "gas": 431136n, "gasPrice": undefined, - "gatewayFee": "0x0", + "gatewayFee": 0n, "gatewayFeeRecipient": null, "hash": "0x487efb864b308ee85afd7ed5954e968457cfe84e71726114b0a44f31fb876e85", "input": "0x389ec778", @@ -237,30 +237,30 @@ describe('block', () => { const { extraData: _extraData, transactions, ...rest } = block expect(transactions[0]).toMatchInlineSnapshot(` - { - "blockHash": "0xac8c9bc3b84e103dc321bbe83b670e425ff68bfc9a333a4f1b1b204ad11c583d", - "blockNumber": 16645775n, - "chainId": undefined, - "ethCompatible": false, - "feeCurrency": null, - "from": "0x045d685d23e8aa34dc408a66fb408f20dc84d785", - "gas": 1527520n, - "gasPrice": 562129081n, - "gatewayFee": "0x0", - "gatewayFeeRecipient": null, - "hash": "0x487efb864b308ee85afd7ed5954e968457cfe84e71726114b0a44f31fb876e85", - "input": "0x389ec778", - "nonce": 714820, - "r": "0x1c0c8776e2e9d97b9a95435d2c2439d5f634e1afc35a5a0f0bd02093dd4724e0", - "s": "0xde418ff749f2430a85e60a4b3f81af9f8e2117cffbe32c719b9b784c01be774", - "to": "0xb86d682b1b6bf20d8d54f55c48f848b9487dec37", - "transactionIndex": 0, - "type": "legacy", - "typeHex": "0x0", - "v": 84476n, - "value": 0n, - } - `) + { + "blockHash": "0xac8c9bc3b84e103dc321bbe83b670e425ff68bfc9a333a4f1b1b204ad11c583d", + "blockNumber": 16645775n, + "chainId": undefined, + "ethCompatible": false, + "feeCurrency": null, + "from": "0x045d685d23e8aa34dc408a66fb408f20dc84d785", + "gas": 1527520n, + "gasPrice": 562129081n, + "gatewayFee": 0n, + "gatewayFeeRecipient": null, + "hash": "0x487efb864b308ee85afd7ed5954e968457cfe84e71726114b0a44f31fb876e85", + "input": "0x389ec778", + "nonce": 714820, + "r": "0x1c0c8776e2e9d97b9a95435d2c2439d5f634e1afc35a5a0f0bd02093dd4724e0", + "s": "0xde418ff749f2430a85e60a4b3f81af9f8e2117cffbe32c719b9b784c01be774", + "to": "0xb86d682b1b6bf20d8d54f55c48f848b9487dec37", + "transactionIndex": 0, + "type": "legacy", + "typeHex": "0x0", + "v": 84476n, + "value": 0n, + } + `) expect(rest).toMatchInlineSnapshot(` { "baseFeePerGas": null, diff --git a/src/chains/celo/formatters.ts b/src/chains/celo/formatters.ts index aba224a3cf..49a2c3c96c 100644 --- a/src/chains/celo/formatters.ts +++ b/src/chains/celo/formatters.ts @@ -25,8 +25,10 @@ import type { TransactionRequestCIP64, } from './types.js' -function isTransactionRequestCIP64(args: CeloTransactionRequest): boolean { - if (args.type === 'cip64') return true +function isTransactionOrRequestCIP64( + args: CeloTransactionRequest | CeloRpcTransaction, +): boolean { + if (args.type === 'cip64' || args.type === '0x7b') return true if (args.type) return false return ( 'feeCurrency' in args && @@ -37,8 +39,10 @@ function isTransactionRequestCIP64(args: CeloTransactionRequest): boolean { ) } -function isTransactionRequestCIP42(args: CeloTransactionRequest): boolean { - if (args.type === 'cip42') return true +function isTransactionOrRequestCIP42( + args: CeloTransactionRequest | CeloRpcTransaction, +): boolean { + if (args.type === 'cip42' || args.type === '0x7c') return true if (args.type) return false return ( (args as TransactionRequestCIP42).gatewayFee !== undefined || @@ -62,7 +66,7 @@ export const formattersCelo = { ...formatTransaction(transaction as RpcTransaction), feeCurrency: transaction.feeCurrency, - ...(transaction.type === '0x7c' + ...(transaction.type !== '0x7b' ? { gatewayFee: transaction.gatewayFee ? hexToBigInt(transaction.gatewayFee) @@ -114,16 +118,18 @@ export const formattersCelo = { transactionRequest: /*#__PURE__*/ defineTransactionRequest({ format(args: CeloTransactionRequest) { - if (isTransactionRequestCIP64(args)) { + if (isTransactionOrRequestCIP64(args)) { return { type: '0x7b', feeCurrency: args.feeCurrency, } as RpcTransactionRequestCIP64 } + const _args = args as Exclude< CeloTransactionRequest, TransactionRequestCIP64 > + const request = { feeCurrency: _args.feeCurrency, gatewayFee: @@ -132,8 +138,12 @@ export const formattersCelo = { : undefined, gatewayFeeRecipient: _args.gatewayFeeRecipient, } as CeloRpcTransactionRequest - if (isTransactionRequestCIP42(_args)) request.type = '0x7c' - return request + + if (isTransactionOrRequestCIP42(args)) { + request.type = '0x7c' + } + + return request as CeloRpcTransactionRequest }, }), } as const satisfies ChainFormatters From f9e175ccd17cc065d50a30ed7c36244ae19831ce Mon Sep 17 00:00:00 2001 From: Audrey Penven Date: Thu, 26 Oct 2023 15:31:49 +0200 Subject: [PATCH 14/15] simplify transaction request checker --- src/chains/celo/formatters.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/chains/celo/formatters.ts b/src/chains/celo/formatters.ts index 49a2c3c96c..1ac5a1c5f3 100644 --- a/src/chains/celo/formatters.ts +++ b/src/chains/celo/formatters.ts @@ -25,10 +25,8 @@ import type { TransactionRequestCIP64, } from './types.js' -function isTransactionOrRequestCIP64( - args: CeloTransactionRequest | CeloRpcTransaction, -): boolean { - if (args.type === 'cip64' || args.type === '0x7b') return true +function isTransactionRequestCIP64(args: CeloTransactionRequest): boolean { + if (args.type === 'cip64') return true if (args.type) return false return ( 'feeCurrency' in args && @@ -39,10 +37,8 @@ function isTransactionOrRequestCIP64( ) } -function isTransactionOrRequestCIP42( - args: CeloTransactionRequest | CeloRpcTransaction, -): boolean { - if (args.type === 'cip42' || args.type === '0x7c') return true +function isTransactionRequestCIP42(args: CeloTransactionRequest): boolean { + if (args.type === 'cip42') return true if (args.type) return false return ( (args as TransactionRequestCIP42).gatewayFee !== undefined || @@ -118,7 +114,7 @@ export const formattersCelo = { transactionRequest: /*#__PURE__*/ defineTransactionRequest({ format(args: CeloTransactionRequest) { - if (isTransactionOrRequestCIP64(args)) { + if (isTransactionRequestCIP64(args)) { return { type: '0x7b', feeCurrency: args.feeCurrency, @@ -139,7 +135,7 @@ export const formattersCelo = { gatewayFeeRecipient: _args.gatewayFeeRecipient, } as CeloRpcTransactionRequest - if (isTransactionOrRequestCIP42(args)) { + if (isTransactionRequestCIP42(args)) { request.type = '0x7c' } From 968a6fc5d840bc81220c50060cbdcf01ff87b42d Mon Sep 17 00:00:00 2001 From: Audrey Penven Date: Thu, 26 Oct 2023 15:43:44 +0200 Subject: [PATCH 15/15] added changeset --- .changeset/three-buses-kiss.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/three-buses-kiss.md diff --git a/.changeset/three-buses-kiss.md b/.changeset/three-buses-kiss.md new file mode 100644 index 0000000000..d89194156a --- /dev/null +++ b/.changeset/three-buses-kiss.md @@ -0,0 +1,5 @@ +--- +"viem": minor +--- + +Added support for Celo CIP-64 transactions