Skip to content

Commit

Permalink
add cip64 support
Browse files Browse the repository at this point in the history
fix signing test that was supposed to be testing eip1559
dont include fields that are not part of spec in the EncodedTransaction
  • Loading branch information
aaronmgdr committed Sep 21, 2023
1 parent 0ee42a8 commit d765a34
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 30 deletions.
9 changes: 7 additions & 2 deletions packages/sdk/connect/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export { BlockNumber, EventLog, Log, PromiEvent, Sign } from 'web3-core'
export { Block, BlockHeader, Syncing } from 'web3-eth'
export { Contract, ContractSendMethod, PastEventOptions } from 'web3-eth-contract'

export type TransactionTypes = 'eip1559' | 'celo-legacy' | 'cip42'
export type TransactionTypes = 'eip1559' | 'celo-legacy' | 'cip42' | 'cip64'

interface CommonTXProperties {
nonce: string
Expand All @@ -90,6 +90,11 @@ export interface EIP1559TXProperties extends FeeMarketAndAccessListTXProperties
type: 'eip1559'
}

export interface CIP64TXProperties extends FeeMarketAndAccessListTXProperties {
feeCurrency: string
type: 'cip64'
}

export interface CIP42TXProperties extends FeeMarketAndAccessListTXProperties {
feeCurrency: string
gatewayFeeRecipient?: string
Expand All @@ -110,7 +115,7 @@ export interface LegacyTXProperties extends CommonTXProperties {

export interface EncodedTransaction {
raw: Hex
tx: LegacyTXProperties | CIP42TXProperties | EIP1559TXProperties
tx: LegacyTXProperties | CIP42TXProperties | EIP1559TXProperties | CIP64TXProperties
}

export type CeloTxPending = Transaction & Partial<CeloParams>
Expand Down
23 changes: 23 additions & 0 deletions packages/sdk/connect/src/utils/formatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,29 @@ describe('inputCeloTxFormatter', () => {
`)
})
})
describe('valid cip64 tx', () => {
const cip64 = {
...base,
maxFeePerGas: '0x3e8',
maxPriorityFeePerGas: '0x3e8',
feeCurrency: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
}
it('formats', () => {
expect(inputCeloTxFormatter(cip64)).toMatchInlineSnapshot(`
{
"data": "0x",
"feeCurrency": "0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae",
"from": "0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae",
"gas": "0xf4240",
"maxFeePerGas": "0x3e8",
"maxPriorityFeePerGas": "0x3e8",
"nonce": "0x1",
"to": "0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae",
"value": "0x241",
}
`)
})
})
describe('valid cip42 tx', () => {
const cip42 = {
...base,
Expand Down
31 changes: 10 additions & 21 deletions packages/sdk/wallets/wallet-base/src/signing-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { celo } from 'viem/chains'
import Web3 from 'web3'
import {
extractSignature,
getSignerFromTxCIP42,
getSignerFromTxEIP2718TX,
isPriceToLow,
recoverTransaction,
rlpEncodedTx,
Expand Down Expand Up @@ -139,62 +139,51 @@ describe('rlpEncodedTx', () => {
})

describe('when maxFeePerGas and maxPriorityFeePerGas and feeCurrency are provided', () => {
it('orders fields in RLP as specified by CIP42', () => {
const CIP42Transaction = {
it('orders fields in RLP as specified by CIP64', () => {
const CIP64Transaction = {
...eip1559Transaction,
feeCurrency: '0x5409ED021D9299bf6814279A6A1411A7e866A631',
}
const result = rlpEncodedTx(CIP42Transaction)
const result = rlpEncodedTx(CIP64Transaction)
expect(result).toMatchInlineSnapshot(`
{
"rlpEncode": "0x7cf8400280630a63945409ed021d9299bf6814279a6a1411a7e866a6318080941be31a94361a391bbafb2a4ccd704f57dc04d4bb893635c9adc5dea0000083abcdefc0",
"rlpEncode": "0x7bf83e0280630a63941be31a94361a391bbafb2a4ccd704f57dc04d4bb893635c9adc5dea0000083abcdefc0945409ed021d9299bf6814279a6a1411a7e866a631",
"transaction": {
"chainId": 2,
"data": "0xabcdef",
"feeCurrency": "0x5409ed021d9299bf6814279a6a1411a7e866a631",
"from": "0x1be31a94361a391bbafb2a4ccd704f57dc04d4bb",
"gas": "0x63",
"gasPrice": "0x",
"gatewayFee": "0x",
"gatewayFeeRecipient": "0x",
"maxFeePerGas": "0x0a",
"maxPriorityFeePerGas": "0x63",
"nonce": 0,
"to": "0x1be31a94361a391bbafb2a4ccd704f57dc04d4bb",
"value": "0x3635c9adc5dea00000",
},
"type": "cip42",
"type": "cip64",
}
`)
})
})

describe('when maxFeePerGas and maxPriorityFeePerGas are provided', () => {
it('orders fields in RLP as specified by EIP1559', () => {
const CIP42Transaction = {
...eip1559Transaction,
feeCurrency: '0x5409ED021D9299bf6814279A6A1411A7e866A631',
}
const result = rlpEncodedTx(CIP42Transaction)
const result = rlpEncodedTx(eip1559Transaction)
expect(result).toMatchInlineSnapshot(`
{
"rlpEncode": "0x7cf8400280630a63945409ed021d9299bf6814279a6a1411a7e866a6318080941be31a94361a391bbafb2a4ccd704f57dc04d4bb893635c9adc5dea0000083abcdefc0",
"rlpEncode": "0x02e90280630a63941be31a94361a391bbafb2a4ccd704f57dc04d4bb893635c9adc5dea0000083abcdefc0",
"transaction": {
"chainId": 2,
"data": "0xabcdef",
"feeCurrency": "0x5409ed021d9299bf6814279a6a1411a7e866a631",
"from": "0x1be31a94361a391bbafb2a4ccd704f57dc04d4bb",
"gas": "0x63",
"gasPrice": "0x",
"gatewayFee": "0x",
"gatewayFeeRecipient": "0x",
"maxFeePerGas": "0x0a",
"maxPriorityFeePerGas": "0x63",
"nonce": 0,
"to": "0x1be31a94361a391bbafb2a4ccd704f57dc04d4bb",
"value": "0x3635c9adc5dea00000",
},
"type": "cip42",
"type": "eip1559",
}
`)
})
Expand Down Expand Up @@ -579,7 +568,7 @@ describe('getSignerFromTx', () => {
},
{ serializer: celo.serializers?.transaction }
)
expect(getSignerFromTxCIP42(signed)).toEqual(account.address)
expect(getSignerFromTxEIP2718TX(signed)).toEqual(account.address)
})
})

Expand Down
102 changes: 95 additions & 7 deletions packages/sdk/wallets/wallet-base/src/signing-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,26 @@ export function rlpEncodedTx(tx: CeloTx): RLPEncodedTx {
transaction.maxPriorityFeePerGas = stringNumberOrBNToHex(tx.maxPriorityFeePerGas)

let rlpEncode: Hex
if (isCIP42(tx)) {
if (isCIP64(tx)) {
// https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0064.md
// 0x7b || rlp([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList, feeCurrency, signatureYParity, signatureR, signatureS]).
rlpEncode = RLP.encode([
stringNumberToHex(transaction.chainId),
stringNumberToHex(transaction.nonce),
transaction.maxPriorityFeePerGas || '0x',
transaction.maxFeePerGas || '0x',
transaction.gas || '0x',
transaction.to || '0x',
transaction.value || '0x',
transaction.data || '0x',
transaction.accessList || [],
transaction.feeCurrency || '0x',
])
delete transaction.gatewayFee
delete transaction.gatewayFeeRecipient
delete transaction.gasPrice
return { transaction, rlpEncode: concatHex([TxTypeToPrefix.cip64, rlpEncode]), type: 'cip64' }
} else if (isCIP42(tx)) {
// 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]).
// This will be in addition to the type 0x02 transaction as specified in EIP-1559.
Expand All @@ -147,7 +166,8 @@ export function rlpEncodedTx(tx: CeloTx): RLPEncodedTx {
transaction.data || '0x',
transaction.accessList || [],
])
return { transaction, rlpEncode: concatHex(['0x7c', rlpEncode]), type: 'cip42' }
delete transaction.gasPrice
return { transaction, rlpEncode: concatHex([TxTypeToPrefix.cip42, rlpEncode]), type: 'cip42' }
} else if (isEIP1559(tx)) {
// https://eips.ethereum.org/EIPS/eip-1559
// 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list, signature_y_parity, signature_r, signature_s]).
Expand All @@ -162,7 +182,15 @@ export function rlpEncodedTx(tx: CeloTx): RLPEncodedTx {
transaction.data || '0x',
transaction.accessList || [],
])
return { transaction, rlpEncode: concatHex(['0x02', rlpEncode]), type: 'eip1559' }
delete transaction.feeCurrency
delete transaction.gatewayFee
delete transaction.gatewayFeeRecipient
delete transaction.gasPrice
return {
transaction,
rlpEncode: concatHex([TxTypeToPrefix.eip1559, rlpEncode]),
type: 'eip1559',
}
} else {
// This order should match the order in Geth.
// https://github.com/celo-org/celo-blockchain/blob/027dba2e4584936cc5a8e8993e4e27d28d5247b8/core/types/transaction.go#L65
Expand All @@ -187,6 +215,7 @@ export function rlpEncodedTx(tx: CeloTx): RLPEncodedTx {
enum TxTypeToPrefix {
'celo-legacy' = '',
cip42 = '0x7c',
cip64 = '0x7b',
eip1559 = '0x02',
}

Expand Down Expand Up @@ -255,6 +284,15 @@ function isEIP1559(tx: CeloTx): boolean {
return isPresent(tx.maxFeePerGas) && isPresent(tx.maxPriorityFeePerGas)
}

function isCIP64(tx: CeloTx) {
return (
isEIP1559(tx) &&
isPresent(tx.feeCurrency) &&
!isPresent(tx.gatewayFeeRecipient) &&
!isPresent(tx.gatewayFeeRecipient)
)
}

function isCIP42(tx: CeloTx): boolean {
return (
isEIP1559(tx) &&
Expand Down Expand Up @@ -399,6 +437,8 @@ export function recoverTransaction(rawTx: string): [CeloTx, string] {
}

switch (determineTXType(rawTx)) {
case 'cip64':
return recoverTransactionCIP64(rawTx as Hex)
case 'cip42':
return recoverTransactionCIP42(rawTx as Hex)
case 'eip1559':
Expand Down Expand Up @@ -434,6 +474,7 @@ export function recoverTransaction(rawTx: string): [CeloTx, string] {

// inspired by @ethereumjs/tx
function getPublicKeyofSignerFromTx(transactionArray: string[]) {
// TODO this needs to be 10 for cip64
const base = transactionArray.slice(0, 12) // 12 is length of cip42 without vrs fields
const message = concatHex([TxTypeToPrefix.cip42, RLP.encode(base).slice(2)])
const msgHash = keccak256(hexToBytes(message))
Expand All @@ -451,7 +492,7 @@ function getPublicKeyofSignerFromTx(transactionArray: string[]) {
}
}

export function getSignerFromTxCIP42(serializedTransaction: string): string {
export function getSignerFromTxEIP2718TX(serializedTransaction: string): string {
const transactionArray: any[] = RLP.decode(`0x${serializedTransaction.slice(4)}`)
const signer = getPublicKeyofSignerFromTx(transactionArray)
return toChecksumAddress(Address.fromPublicKey(signer).toString())
Expand All @@ -460,10 +501,12 @@ export function getSignerFromTxCIP42(serializedTransaction: string): string {
function determineTXType(serializedTransaction: string): TransactionTypes {
const prefix = serializedTransaction.slice(0, 4)

if (prefix === '0x02') {
if (prefix === TxTypeToPrefix.eip1559) {
return 'eip1559'
} else if (prefix === '0x7c') {
} else if (prefix === TxTypeToPrefix.cip42) {
return 'cip42'
} else if (prefix === TxTypeToPrefix.cip64) {
return 'cip64'
}
return 'celo-legacy'
}
Expand Down Expand Up @@ -523,7 +566,52 @@ function recoverTransactionCIP42(serializedTransaction: Hex): [CeloTxWithSig, st
}

const signer =
transactionArray.length === 15 ? getSignerFromTxCIP42(serializedTransaction) : 'unsigned'
transactionArray.length === 15 ? getSignerFromTxEIP2718TX(serializedTransaction) : 'unsigned'
return [celoTX, signer]
}

function recoverTransactionCIP64(serializedTransaction: Hex): [CeloTxWithSig, string] {
const transactionArray: any[] = prefixAwareRLPDecode(serializedTransaction, 'cip64')
debug('signing-utils@recoverTransactionCIP64: values are %s', transactionArray)
if (transactionArray.length !== 13 && transactionArray.length !== 10) {
throw new Error(
`Invalid transaction length for type CIP64: ${transactionArray.length} instead of 13 or 10. array: ${transactionArray}`
)
}
const [
chainId,
nonce,
maxPriorityFeePerGas,
maxFeePerGas,
gas,
to,
value,
data,
accessList,
feeCurrency,
vRaw,
r,
s,
] = transactionArray

const celoTX: CeloTxWithSig = {
type: 'cip64',
nonce: nonce.toLowerCase() === '0x' ? 0 : parseInt(nonce, 16),
maxPriorityFeePerGas:
maxPriorityFeePerGas.toLowerCase() === '0x' ? 0 : parseInt(maxPriorityFeePerGas, 16),
maxFeePerGas: maxFeePerGas.toLowerCase() === '0x' ? 0 : parseInt(maxFeePerGas, 16),
gas: gas.toLowerCase() === '0x' ? 0 : parseInt(gas, 16),
feeCurrency,
to,
value: value.toLowerCase() === '0x' ? 0 : parseInt(value, 16),
data,
chainId: chainId.toLowerCase() === '0x' ? 0 : parseInt(chainId, 16),
accessList: parseAccessList(accessList),
...vrsForRecovery(vRaw, r, s),
}

const signer =
transactionArray.length === 13 ? getSignerFromTxEIP2718TX(serializedTransaction) : 'unsigned'
return [celoTX, signer]
}

Expand Down

0 comments on commit d765a34

Please sign in to comment.