UNPKG

@ethereumjs/tx

Version:
227 lines (202 loc) 7.36 kB
import { Common, Mainnet } from '@ethereumjs/common' import { Address, EthereumJSErrorWithoutCode, MAX_INTEGER, MAX_UINT64, bigIntToHex, bytesToBigInt, bytesToHex, hexToBytes, toBytes, } from '@ethereumjs/util' import { paramsTx } from '../params.ts' import type { TransactionInterface, TransactionType, TxData, TxOptions } from '../types.ts' /** * Gets a Common instance, creating a new one if none provided * @param common - Optional Common instance * @returns Common instance (copied if provided, new Mainnet instance if not) */ export function getCommon(common?: Common): Common { return common?.copy() ?? new Common({ chain: Mainnet }) } /** * Converts a transaction type to its byte representation * @param txType - The transaction type * @returns Uint8Array representation of the transaction type */ export function txTypeBytes(txType: TransactionType): Uint8Array { return hexToBytes(`0x${txType.toString(16).padStart(2, '0')}`) } /** * Validates that transaction data fields are not arrays * @param values - Object containing transaction data fields * @throws EthereumJSErrorWithoutCode if any transaction field is an array */ export function validateNotArray(values: { [key: string]: any }) { const txDataKeys = [ 'nonce', 'gasPrice', 'gasLimit', 'to', 'value', 'data', 'v', 'r', 's', 'type', 'baseFee', 'maxFeePerGas', 'chainId', ] for (const [key, value] of Object.entries(values)) { if (txDataKeys.includes(key)) { if (Array.isArray(value)) { throw EthereumJSErrorWithoutCode(`${key} cannot be an array`) } } } } function checkMaxInitCodeSize(common: Common, length: number) { const maxInitCodeSize = common.param('maxInitCodeSize') if (maxInitCodeSize && BigInt(length) > maxInitCodeSize) { throw EthereumJSErrorWithoutCode( `the initcode size of this transaction is too large: it is ${length} while the max is ${common.param( 'maxInitCodeSize', )}`, ) } } /** * Validates that an object with BigInt values cannot exceed the specified bit limit. * @param values Object containing string keys and BigInt values * @param bits Number of bits to check (64 or 256) * @param cannotEqual Pass true if the number also cannot equal one less than the maximum value */ export function valueOverflowCheck( values: { [key: string]: bigint | undefined }, bits = 256, cannotEqual = false, ) { for (const [key, value] of Object.entries(values)) { switch (bits) { case 64: if (cannotEqual) { if (value !== undefined && value >= MAX_UINT64) { // TODO: error msgs got raised to a error string handler first, now throws "generic" error throw EthereumJSErrorWithoutCode( `${key} cannot equal or exceed MAX_UINT64 (2^64-1), given ${value}`, ) } } else { if (value !== undefined && value > MAX_UINT64) { throw EthereumJSErrorWithoutCode( `${key} cannot exceed MAX_UINT64 (2^64-1), given ${value}`, ) } } break case 256: if (cannotEqual) { if (value !== undefined && value >= MAX_INTEGER) { throw EthereumJSErrorWithoutCode( `${key} cannot equal or exceed MAX_INTEGER (2^256-1), given ${value}`, ) } } else { if (value !== undefined && value > MAX_INTEGER) { throw EthereumJSErrorWithoutCode( `${key} cannot exceed MAX_INTEGER (2^256-1), given ${value}`, ) } } break default: { throw EthereumJSErrorWithoutCode('unimplemented bits value') } } } } type Mutable<T> = { -readonly [P in keyof T]: T[P] } /** * Shared constructor logic for all transaction types * Note: Uses Mutable type to write to readonly properties. Only call this in transaction constructors. * @param tx - Mutable transaction interface to initialize * @param txData - Transaction data * @param opts - Transaction options */ export function sharedConstructor( tx: Mutable<TransactionInterface>, txData: TxData[TransactionType], opts: TxOptions = {}, ) { // LOAD base tx super({ ...txData, type: TransactionType.Legacy }, opts) tx.common = getCommon(opts.common) tx.common.updateParams(opts.params ?? paramsTx) validateNotArray(txData) // is this necessary? const { nonce, gasLimit, to, value, data, v, r, s } = txData tx.txOptions = opts // TODO: freeze? // Set the tx properties const toB = toBytes(to === '' ? '0x' : to) tx.to = toB.length > 0 ? new Address(toB) : undefined // TODO mark this explicitly as null if create-contract-tx? const vB = toBytes(v) const rB = toBytes(r) const sB = toBytes(s) tx.nonce = bytesToBigInt(toBytes(nonce)) tx.gasLimit = bytesToBigInt(toBytes(gasLimit)) tx.to = toB.length > 0 ? new Address(toB) : undefined tx.value = bytesToBigInt(toBytes(value)) tx.data = toBytes(data === '' ? '0x' : data) // Set signature values (if the tx is signed) tx.v = vB.length > 0 ? bytesToBigInt(vB) : undefined tx.r = rB.length > 0 ? bytesToBigInt(rB) : undefined tx.s = sB.length > 0 ? bytesToBigInt(sB) : undefined // Start validating the data // Validate value/r/s valueOverflowCheck({ value: tx.value, r: tx.r, s: tx.s }) // geth limits gasLimit to 2^64-1 valueOverflowCheck({ gasLimit: tx.gasLimit }, 64) // EIP-2681 limits nonce to 2^64-1 (cannot equal 2^64-1) valueOverflowCheck({ nonce: tx.nonce }, 64, true) // EIP-7825: Transaction Gas Limit Cap. // Under EIP-8037 the cap applies to the regular-gas dimension only (state-gas // is capped solely by tx.gas), so the tx-level total-gas-limit cap is lifted // here. The runTx-level validation enforces // `max(intrinsic_regular_gas, calldata_floor_gas_cost) <= TX_MAX_GAS_LIMIT` // instead. if (tx.common.isActivatedEIP(7825) && !tx.common.isActivatedEIP(8037)) { const maxGasLimit = tx.common.param('maxTransactionGasLimit') if (tx.gasLimit > maxGasLimit) { throw EthereumJSErrorWithoutCode( `Transaction gas limit ${tx.gasLimit} exceeds the maximum allowed by EIP-7825 (${maxGasLimit})`, ) } } const createContract = tx.to === undefined || tx.to === null const allowUnlimitedInitCodeSize = opts.allowUnlimitedInitCodeSize ?? false if (createContract && tx.common.isActivatedEIP(3860) && allowUnlimitedInitCodeSize === false) { checkMaxInitCodeSize(tx.common, tx.data.length) } } /** * Converts a transaction to its base JSON representation * @param tx - The transaction interface * @returns JSON object with base transaction fields */ export function getBaseJSON(tx: TransactionInterface) { return { type: bigIntToHex(BigInt(tx.type)), nonce: bigIntToHex(tx.nonce), gasLimit: bigIntToHex(tx.gasLimit), to: tx.to !== undefined ? tx.to.toString() : undefined, value: bigIntToHex(tx.value), data: bytesToHex(tx.data), v: tx.v !== undefined ? bigIntToHex(tx.v) : undefined, r: tx.r !== undefined ? bigIntToHex(tx.r) : undefined, s: tx.s !== undefined ? bigIntToHex(tx.s) : undefined, chainId: bigIntToHex(tx.common.chainId()), yParity: tx.v === 0n || tx.v === 1n ? bigIntToHex(tx.v) : undefined, } }