diff --git a/packages/block/src/block/constructors.ts b/packages/block/src/block/constructors.ts index 2a50ee08d0..bab7a78495 100644 --- a/packages/block/src/block/constructors.ts +++ b/packages/block/src/block/constructors.ts @@ -5,12 +5,12 @@ import { createTxFromBlockBodyData, createTxFromSerializedData, createTxFromTxData, + normalizeTxParams, } from '@ethereumjs/tx' import { CLRequestFactory, ConsolidationRequest, DepositRequest, - TypeOutput, Withdrawal, WithdrawalRequest, bigIntToHex, @@ -22,9 +22,6 @@ import { hexToBytes, intToHex, isHexString, - setLengthLeft, - toBytes, - toType, } from '@ethereumjs/util' import { generateCliqueBlockExtraData } from '../consensus/clique.js' @@ -59,27 +56,6 @@ import type { WithdrawalBytes, } from '@ethereumjs/util' -export function normalizeTxParams(_txParams: any) { - const txParams = Object.assign({}, _txParams) - - txParams.gasLimit = toType(txParams.gasLimit ?? txParams.gas, TypeOutput.BigInt) - txParams.data = txParams.data === undefined ? txParams.input : txParams.data - - // check and convert gasPrice and value params - txParams.gasPrice = txParams.gasPrice !== undefined ? BigInt(txParams.gasPrice) : undefined - txParams.value = txParams.value !== undefined ? BigInt(txParams.value) : undefined - - // strict byte length checking - txParams.to = - txParams.to !== null && txParams.to !== undefined - ? setLengthLeft(toBytes(txParams.to), 20) - : null - - txParams.v = toType(txParams.v, TypeOutput.BigInt) - - return txParams -} - /** * Static constructor to create a block from a block data dictionary * diff --git a/packages/tx/src/fromRpc.ts b/packages/tx/src/fromRpc.ts deleted file mode 100644 index 6b69bf9957..0000000000 --- a/packages/tx/src/fromRpc.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { TypeOutput, setLengthLeft, toBytes, toType } from '@ethereumjs/util' - -import type { TypedTxData } from './types.js' - -export const normalizeTxParams = (_txParams: any): TypedTxData => { - const txParams = Object.assign({}, _txParams) - - txParams.gasLimit = toType(txParams.gasLimit ?? txParams.gas, TypeOutput.BigInt) - txParams.data = txParams.data === undefined ? txParams.input : txParams.data - - // check and convert gasPrice and value params - txParams.gasPrice = txParams.gasPrice !== undefined ? BigInt(txParams.gasPrice) : undefined - txParams.value = txParams.value !== undefined ? BigInt(txParams.value) : undefined - - // strict byte length checking - txParams.to = - txParams.to !== null && txParams.to !== undefined - ? setLengthLeft(toBytes(txParams.to), 20) - : null - - // Normalize the v/r/s values. If RPC returns '0x0', ensure v/r/s are set to `undefined` in the tx. - // If this is not done, then the transaction creation will throw, because `v` is `0`. - // Note: this still means that `isSigned` will return `false`. - // v/r/s values are `0x0` on networks like Optimism, where the tx is a system tx. - // For instance: https://optimistic.etherscan.io/tx/0xf4304cb09b3f58a8e5d20fec5f393c96ccffe0269aaf632cb2be7a8a0f0c91cc - - txParams.v = txParams.v === '0x0' ? '0x' : txParams.v - txParams.r = txParams.r === '0x0' ? '0x' : txParams.r - txParams.s = txParams.s === '0x0' ? '0x' : txParams.s - - if (txParams.v !== '0x' || txParams.r !== '0x' || txParams.s !== '0x') { - txParams.v = toType(txParams.v, TypeOutput.BigInt) - } - - return txParams -} diff --git a/packages/tx/src/index.ts b/packages/tx/src/index.ts index d8fc984275..efadb29f92 100644 --- a/packages/tx/src/index.ts +++ b/packages/tx/src/index.ts @@ -18,3 +18,6 @@ export { // Types export * from './types.js' + +// Utils +export * from './util.js' diff --git a/packages/tx/src/transactionFactory.ts b/packages/tx/src/transactionFactory.ts index e543320f1d..f821d32b50 100644 --- a/packages/tx/src/transactionFactory.ts +++ b/packages/tx/src/transactionFactory.ts @@ -4,7 +4,6 @@ import { create1559FeeMarketTx, create1559FeeMarketTxFromRLP } from './1559/cons import { create2930AccessListTx, create2930AccessListTxFromRLP } from './2930/constructors.js' import { create4844BlobTx, create4844BlobTxFromRLP } from './4844/constructors.js' import { create7702EOACodeTx, create7702EOACodeTxFromRLP } from './7702/constructors.js' -import { normalizeTxParams } from './fromRpc.js' import { createLegacyTx, createLegacyTxFromBytesArray, @@ -18,6 +17,7 @@ import { isFeeMarketEIP1559TxData, isLegacyTxData, } from './types.js' +import { normalizeTxParams } from './util.js' import type { Transaction, TxData, TxOptions, TypedTxData } from './types.js' import type { EthersProvider } from '@ethereumjs/util' diff --git a/packages/tx/src/util.ts b/packages/tx/src/util.ts index 591c0aa853..3a9e1ead72 100644 --- a/packages/tx/src/util.ts +++ b/packages/tx/src/util.ts @@ -1,8 +1,11 @@ import { type PrefixedHexString, + TypeOutput, bytesToHex, hexToBytes, setLengthLeft, + toBytes, + toType, validateNoLeadingZeroes, } from '@ethereumjs/util' @@ -16,6 +19,7 @@ import type { AuthorizationListBytes, AuthorizationListItem, TransactionType, + TypedTxData, } from './types.js' import type { Common } from '@ethereumjs/common' @@ -249,3 +253,42 @@ export function validateNotArray(values: { [key: string]: any }) { } } } + +/** + * Normalizes values for transactions that are received from an RPC provider to be properly usable within + * the ethereumjs context + * @param txParamsFromRPC a transaction in the standard JSON-RPC format + * @returns a normalized {@link TypedTxData} object with valid values + */ +export const normalizeTxParams = (txParamsFromRPC: any): TypedTxData => { + const txParams = Object.assign({}, txParamsFromRPC) + + txParams.gasLimit = toType(txParams.gasLimit ?? txParams.gas, TypeOutput.BigInt) + txParams.data = txParams.data === undefined ? txParams.input : txParams.data + + // check and convert gasPrice and value params + txParams.gasPrice = txParams.gasPrice !== undefined ? BigInt(txParams.gasPrice) : undefined + txParams.value = txParams.value !== undefined ? BigInt(txParams.value) : undefined + + // strict byte length checking + txParams.to = + txParams.to !== null && txParams.to !== undefined + ? setLengthLeft(toBytes(txParams.to), 20) + : null + + // Normalize the v/r/s values. If RPC returns '0x0', ensure v/r/s are set to `undefined` in the tx. + // If this is not done, then the transaction creation will throw, because `v` is `0`. + // Note: this still means that `isSigned` will return `false`. + // v/r/s values are `0x0` on networks like Optimism, where the tx is a system tx. + // For instance: https://optimistic.etherscan.io/tx/0xf4304cb09b3f58a8e5d20fec5f393c96ccffe0269aaf632cb2be7a8a0f0c91cc + + txParams.v = txParams.v === '0x0' ? '0x' : txParams.v + txParams.r = txParams.r === '0x0' ? '0x' : txParams.r + txParams.s = txParams.s === '0x0' ? '0x' : txParams.s + + if (txParams.v !== '0x' || txParams.r !== '0x' || txParams.s !== '0x') { + txParams.v = toType(txParams.v, TypeOutput.BigInt) + } + + return txParams +} diff --git a/packages/tx/test/fromRpc.spec.ts b/packages/tx/test/fromRpc.spec.ts index ded73b5524..ae4c1c3fbd 100644 --- a/packages/tx/test/fromRpc.spec.ts +++ b/packages/tx/test/fromRpc.spec.ts @@ -2,13 +2,13 @@ import { Common, Hardfork, Mainnet, createCustomCommon } from '@ethereumjs/commo import { bytesToHex, randomBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' -import { normalizeTxParams } from '../src/fromRpc.js' import { TransactionType, createTxFromJsonRpcProvider, createTxFromRPC, createTxFromTxData, } from '../src/index.js' +import { normalizeTxParams } from '../src/util.js' import optimismTx from './json/optimismTx.json' import rpcTx from './json/rpcTx.json'