Skip to content

Commit

Permalink
add parseTransactionCelo for parsing out serialized transaction types…
Browse files Browse the repository at this point in the history
… specific to celo chain.
  • Loading branch information
aaronmgdr authored and jxom committed Aug 21, 2023
1 parent 2c4012b commit 0be63c7
Show file tree
Hide file tree
Showing 6 changed files with 361 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/cyan-bears-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": minor
---

Added `parseTransactionCelo` function for parsing serialized transactions that could be CIP42 tx or other serialized transaction types exported at viem/chains/utils
226 changes: 226 additions & 0 deletions src/chains/celo/parsers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import { accounts } from '../../_test/constants.js'
import {
parseEther,
parseGwei,
parseTransaction as parseTransaction_,
serializeTransaction,
toRlp,
} from '../../index.js'

import {
type TransactionSerializableCIP42,
serializeTransactionCelo,
} from './serializers.js'

import { parseTransactionCelo } from './parsers.js'
import { describe, expect, test } from 'vitest'

describe('parseTransaction', () => {
test('should be able to parse a cip42 transaction', () => {
const signedTransaction =
'0x7cf84682a4ec80847735940084773594008094765de816845861e75a25fca122bb6898b8b1282a808094f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c0'

expect(parseTransactionCelo(signedTransaction)).toMatchInlineSnapshot(`
{
"chainId": 42220,
"feeCurrency": "0x765de816845861e75a25fca122bb6898b8b1282a",
"maxFeePerGas": 2000000000n,
"maxPriorityFeePerGas": 2000000000n,
"to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"type": "cip42",
"value": 1000000000000000000n,
}
`)
})

const transaction = {
chainId: 1,
gas: 21001n,
maxFeePerGas: parseGwei('2'),
maxPriorityFeePerGas: parseGwei('2'),
to: accounts[3].address,
nonce: 785,
value: parseEther('1'),
}

test('should return same result as standard parser when not CIP42', () => {
const serialized = serializeTransaction(transaction)

expect(parseTransactionCelo(serialized)).toEqual(
parseTransaction_(serialized),
)
})

test('should parse a CIP42 transaction with gatewayFee', () => {
const transactionWithGatewayFee = {
...transaction,
chainId: 42270,
gatewayFee: parseEther('0.1'),
gatewayFeeRecipient: accounts[1].address,
}

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,
}
`)
})

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)

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,
}
`)
})

test('should parse a CIP42 transaction with data as 0x', () => {
const transactionWithData: TransactionSerializableCIP42 = {
feeCurrency: '0x765de816845861e75a25fca122bb6898b8b1282a',
...transaction,
chainId: 42270,
data: '0x',
}

const serialized = serializeTransactionCelo(transactionWithData)

expect(parseTransactionCelo(serialized)).toMatchInlineSnapshot(`
{
"chainId": 42270,
"feeCurrency": "0x765de816845861e75a25fca122bb6898b8b1282a",
"gas": 21001n,
"maxFeePerGas": 2000000000n,
"maxPriorityFeePerGas": 2000000000n,
"nonce": 785,
"to": "0x90f79bf6eb2c4f870365e785982e1f101e93b906",
"type": "cip42",
"value": 1000000000000000000n,
}
`)
})
test('should parse a CIP42 transaction with data', () => {
const transactionWithData: TransactionSerializableCIP42 = {
...transaction,
feeCurrency: '0x765de816845861e75a25fca122bb6898b8b1282a',
chainId: 42270,
data: '0x1234',
}

const serialized = serializeTransactionCelo(transactionWithData)

expect(parseTransactionCelo(serialized)).toMatchInlineSnapshot(`
{
"chainId": 42270,
"data": "0x1234",
"feeCurrency": "0x765de816845861e75a25fca122bb6898b8b1282a",
"gas": 21001n,
"maxFeePerGas": 2000000000n,
"maxPriorityFeePerGas": 2000000000n,
"nonce": 785,
"to": "0x90f79bf6eb2c4f870365e785982e1f101e93b906",
"type": "cip42",
"value": 1000000000000000000n,
}
`)
})

test('invalid transaction (all missing)', () => {
expect(() =>
parseTransactionCelo(`0x7c${toRlp([]).slice(2)}`),
).toThrowErrorMatchingInlineSnapshot(`
"Invalid serialized transaction of type \\"cip42\\" was provided.
Serialized Transaction: \\"0x7cc0\\"
Missing Attributes: chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, feeCurrency, to, gatewayFeeRecipient, gatewayFee, value, data, accessList
Version: viem@1.0.2"
`)
})

test('invalid transaction (some 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('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"
`)
})
})
117 changes: 117 additions & 0 deletions src/chains/celo/parsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { InvalidSerializedTransactionError } from '../../errors/transaction.js'
import {
type Hex,
type TransactionSerialized,
hexToBigInt,
hexToNumber,
isHex,
parseTransaction,
sliceHex,
} from '../../index.js'
import {
parseAccessList,
toTransactionArray,
} from '../../utils/transaction/parseTransaction.js'
import {
type CeloTransactionSerializable,
type SerializedCIP42TransactionReturnType,
type TransactionSerializableCIP42,
assertTransactionCIP42,
} from './serializers.js'

import type { RecursiveArray } from '../../utils/encoding/toRlp.js'

export function parseTransactionCelo(
serializedTransaction:
| TransactionSerialized
| SerializedCIP42TransactionReturnType,
): CeloTransactionSerializable {
const serializedType = sliceHex(serializedTransaction, 0, 1)

if (serializedType === '0x7c') {
return parseTransactionCIP42(
serializedTransaction as SerializedCIP42TransactionReturnType,
)
}

return parseTransaction(serializedTransaction)
}

function parseTransactionCIP42(
serializedTransaction: SerializedCIP42TransactionReturnType,
): TransactionSerializableCIP42 {
const transactionArray = toTransactionArray(serializedTransaction)

const [
chainId,
nonce,
maxPriorityFeePerGas,
maxFeePerGas,
gas,
feeCurrency,
gatewayFeeRecipient,
gatewayFee,
to,
value,
data,
accessList,
v,
r,
s,
] = transactionArray

if (transactionArray.length !== 15 && transactionArray.length !== 12) {
throw new InvalidSerializedTransactionError({
attributes: {
chainId,
nonce,
maxPriorityFeePerGas,
maxFeePerGas,
gas,
feeCurrency,
to,
gatewayFeeRecipient,
gatewayFee,
value,
data,
accessList,
...(transactionArray.length > 12
? {
v,
r,
s,
}
: {}),
},
serializedTransaction,
type: 'cip42',
})
}

const transaction: Partial<TransactionSerializableCIP42> = {
chainId: hexToNumber(chainId as Hex),
type: 'cip42',
}

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(gatewayFeeRecipient) && gatewayFeeRecipient !== '0x')
transaction.gatewayFeeRecipient = gatewayFeeRecipient
if (isHex(gatewayFee) && gatewayFee !== '0x')
transaction.gatewayFee = hexToBigInt(gatewayFee)
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<Hex>)

assertTransactionCIP42(transaction as TransactionSerializableCIP42)

return transaction as TransactionSerializableCIP42
}
4 changes: 3 additions & 1 deletion src/chains/celo/serializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ function isCIP42(transaction: CeloTransactionSerializable) {
const MAX_MAX_FEE_PER_GAS =
115792089237316195423570985008687907853269984665640564039457584007913129639935n

function assertTransactionCIP42(transaction: TransactionSerializableCIP42) {
export function assertTransactionCIP42(
transaction: TransactionSerializableCIP42,
) {
const {
chainId,
maxPriorityFeePerGas,
Expand Down
3 changes: 3 additions & 0 deletions src/chains/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export {
serializeTransactionCelo,
serializersCelo,
} from './celo/serializers.js'

export { parseTransactionCelo } from './celo/parsers.js'

export type {
CeloBlock,
CeloBlockOverrides,
Expand Down
Loading

0 comments on commit 0be63c7

Please sign in to comment.