diff --git a/packages/tx/src/1559/tx.ts b/packages/tx/src/1559/tx.ts index 372f824e31..00423967c8 100644 --- a/packages/tx/src/1559/tx.ts +++ b/packages/tx/src/1559/tx.ts @@ -1,4 +1,3 @@ -import { Common } from '@ethereumjs/common' import { BIGINT_0, BIGINT_27, @@ -9,14 +8,13 @@ import { toBytes, } from '@ethereumjs/util' -import { BaseTransaction } from '../baseTransaction.js' import * as EIP1559 from '../capabilities/eip1559.js' import * as EIP2718 from '../capabilities/eip2718.js' import * as EIP2930 from '../capabilities/eip2930.js' import * as Legacy from '../capabilities/legacy.js' -import { paramsTx } from '../params.js' +import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../features/util.js' import { TransactionType } from '../types.js' -import { AccessLists, validateNotArray } from '../util.js' +import { AccessLists } from '../util.js' import { createFeeMarket1559Tx } from './constructors.js' @@ -25,9 +23,14 @@ import type { AccessListBytes, TxData as AllTypesTxData, TxValuesArray as AllTypesTxValuesArray, + Capability, JSONTx, + TransactionCache, + TransactionInterface, TxOptions, } from '../types.js' +import type { Common } from '@ethereumjs/common' +import type { Address } from '@ethereumjs/util' export type TxData = AllTypesTxData[TransactionType.FeeMarketEIP1559] export type TxValuesArray = AllTypesTxValuesArray[TransactionType.FeeMarketEIP1559] @@ -38,15 +41,42 @@ export type TxValuesArray = AllTypesTxValuesArray[TransactionType.FeeMarketEIP15 * - TransactionType: 2 * - EIP: [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) */ -export class FeeMarket1559Tx extends BaseTransaction { +export class FeeMarket1559Tx implements TransactionInterface { // implements EIP1559CompatibleTx - public readonly chainId: bigint + public type: number = TransactionType.FeeMarketEIP1559 // 1559 tx type + + // Tx data part (part of the RLP) + public readonly nonce!: bigint + public readonly gasLimit!: bigint + public readonly value!: bigint + public readonly data!: Uint8Array + public readonly to?: Address public readonly accessList: AccessListBytes - public readonly AccessListJSON: AccessList + public readonly chainId: bigint public readonly maxPriorityFeePerGas: bigint public readonly maxFeePerGas: bigint - public readonly common: Common + // Props only for signed txs + public readonly v?: bigint + public readonly r?: bigint + public readonly s?: bigint + + // End of Tx data part + + public readonly AccessListJSON: AccessList + + public readonly common!: Common + + readonly txOptions!: TxOptions + + readonly cache: TransactionCache = {} + + /** + * List of tx type defining EIPs, + * e.g. 1559 (fee market) and 2930 (access lists) + * for FeeMarket1559Tx objects + */ + protected activeCapabilities: number[] = [] /** * This constructor takes the values, validates them, assigns them and freezes the object. @@ -56,16 +86,14 @@ export class FeeMarket1559Tx extends BaseTransaction MAX_INTEGER) { - const msg = this._errorMsg('gasLimit * maxFeePerGas cannot exceed MAX_INTEGER (2^256-1)') + const msg = Legacy.errorMsg( + this, + 'gasLimit * maxFeePerGas cannot exceed MAX_INTEGER (2^256-1)', + ) throw new Error(msg) } if (this.maxFeePerGas < this.maxPriorityFeePerGas) { - const msg = this._errorMsg( + const msg = Legacy.errorMsg( + this, 'maxFeePerGas cannot be less than maxPriorityFeePerGas (The total must be the larger of the two)', ) throw new Error(msg) @@ -111,6 +141,26 @@ export class FeeMarket1559Tx extends BaseTransactionLegacy.sign(this, privateKey) + } + + public isSigned(): boolean { + const { v, r, s } = this + if (v === undefined || r === undefined || s === undefined) { + return false + } else { + return true + } + } + /** * Return a compact error string representation of the object */ public errorStr() { - let errorStr = this._getSharedErrorPostfix() + let errorStr = Legacy.getSharedErrorPostfix(this) errorStr += ` maxFeePerGas=${this.maxFeePerGas} maxPriorityFeePerGas=${this.maxPriorityFeePerGas}` return errorStr } - - /** - * Internal helper function to create an annotated error message - * - * @param msg Base error message - * @hidden - */ - protected _errorMsg(msg: string) { - return Legacy.errorMsg(this, msg) - } } diff --git a/packages/tx/src/2930/constructors.ts b/packages/tx/src/2930/constructors.ts index 7373239e97..06e9cda76c 100644 --- a/packages/tx/src/2930/constructors.ts +++ b/packages/tx/src/2930/constructors.ts @@ -4,7 +4,7 @@ import { bytesToBigInt, bytesToHex, equalsBytes, validateNoLeadingZeroes } from import { TransactionType } from '../types.js' import { txTypeBytes, validateNotArray } from '../util.js' -import { AccessList2930Transaction } from './tx.js' +import { AccessList2930Tx } from './tx.js' import type { AccessList, TxOptions } from '../types.js' import type { TxData, TxValuesArray } from './tx.js' @@ -20,7 +20,7 @@ import type { TxData, TxValuesArray } from './tx.js' * - All parameters are optional and have some basic default values */ export function createAccessList2930Tx(txData: TxData, opts: TxOptions = {}) { - return new AccessList2930Transaction(txData, opts) + return new AccessList2930Tx(txData, opts) } /** @@ -43,7 +43,7 @@ export function createAccessList2930TxFromBytesArray(values: TxValuesArray, opts const emptyAccessList: AccessList = [] - return new AccessList2930Transaction( + return new AccessList2930Tx( { chainId: bytesToBigInt(chainId), nonce, diff --git a/packages/tx/src/2930/index.ts b/packages/tx/src/2930/index.ts index 6fc2f7ab5c..9b289d641a 100644 --- a/packages/tx/src/2930/index.ts +++ b/packages/tx/src/2930/index.ts @@ -1,2 +1,2 @@ export * from './constructors.js' -export { AccessList2930Transaction } from './tx.js' +export { AccessList2930Tx } from './tx.js' diff --git a/packages/tx/src/2930/tx.ts b/packages/tx/src/2930/tx.ts index 10565728db..f827d2518f 100644 --- a/packages/tx/src/2930/tx.ts +++ b/packages/tx/src/2930/tx.ts @@ -1,4 +1,3 @@ -import { Common } from '@ethereumjs/common' import { BIGINT_27, MAX_INTEGER, @@ -8,13 +7,12 @@ import { toBytes, } from '@ethereumjs/util' -import { BaseTransaction } from '../baseTransaction.js' import * as EIP2718 from '../capabilities/eip2718.js' import * as EIP2930 from '../capabilities/eip2930.js' import * as Legacy from '../capabilities/legacy.js' -import { paramsTx } from '../index.js' +import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../features/util.js' import { TransactionType } from '../types.js' -import { AccessLists, validateNotArray } from '../util.js' +import { AccessLists } from '../util.js' import { createAccessList2930Tx } from './constructors.js' @@ -23,9 +21,14 @@ import type { AccessListBytes, TxData as AllTypesTxData, TxValuesArray as AllTypesTxValuesArray, + Capability, JSONTx, + TransactionCache, + TransactionInterface, TxOptions, } from '../types.js' +import type { Common } from '@ethereumjs/common' +import type { Address } from '@ethereumjs/util' export type TxData = AllTypesTxData[TransactionType.AccessListEIP2930] export type TxValuesArray = AllTypesTxValuesArray[TransactionType.AccessListEIP2930] @@ -36,13 +39,40 @@ export type TxValuesArray = AllTypesTxValuesArray[TransactionType.AccessListEIP2 * - TransactionType: 1 * - EIP: [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) */ -export class AccessList2930Transaction extends BaseTransaction { - public readonly chainId: bigint +export class AccessList2930Tx implements TransactionInterface { + public type: number = TransactionType.AccessListEIP2930 // 2930 tx type + + // Tx data part (part of the RLP) + public readonly gasPrice: bigint + public readonly nonce!: bigint + public readonly gasLimit!: bigint + public readonly value!: bigint + public readonly data!: Uint8Array + public readonly to?: Address public readonly accessList: AccessListBytes + public readonly chainId: bigint + + // Props only for signed txs + public readonly v?: bigint + public readonly r?: bigint + public readonly s?: bigint + + // End of Tx data part + public readonly AccessListJSON: AccessList - public readonly gasPrice: bigint - public readonly common: Common + public readonly common!: Common + + readonly txOptions!: TxOptions + + readonly cache: TransactionCache = {} + + /** + * List of tx type defining EIPs, + * e.g. 1559 (fee market) and 2930 (access lists) + * for FeeMarket1559Tx objects + */ + protected activeCapabilities: number[] = [] /** * This constructor takes the values, validates them, assigns them and freezes the object. @@ -52,16 +82,14 @@ export class AccessList2930Transaction extends BaseTransaction MAX_INTEGER) { - const msg = this._errorMsg('gasLimit * gasPrice cannot exceed MAX_INTEGER') + const msg = Legacy.errorMsg(this, 'gasLimit * gasPrice cannot exceed MAX_INTEGER') throw new Error(msg) } @@ -99,6 +123,26 @@ export class AccessList2930Transaction extends BaseTransactionLegacy.sign(this, privateKey) + } + + isSigned(): boolean { + return Legacy.isSigned(this) + } + /** * Return a compact error string representation of the object */ public errorStr() { - let errorStr = this._getSharedErrorPostfix() + let errorStr = Legacy.getSharedErrorPostfix(this) // Keep ? for this.accessList since this otherwise causes Hardhat E2E tests to fail errorStr += ` gasPrice=${this.gasPrice} accessListCount=${this.accessList?.length ?? 0}` return errorStr } - - /** - * Internal helper function to create an annotated error message - * - * @param msg Base error message - * @hidden - */ - protected _errorMsg(msg: string) { - return Legacy.errorMsg(this, msg) - } } diff --git a/packages/tx/src/4844/tx.ts b/packages/tx/src/4844/tx.ts index 909f462a55..f1af09dc11 100644 --- a/packages/tx/src/4844/tx.ts +++ b/packages/tx/src/4844/tx.ts @@ -1,4 +1,3 @@ -import { Common } from '@ethereumjs/common' import { BIGINT_0, BIGINT_27, @@ -12,13 +11,12 @@ import { toType, } from '@ethereumjs/util' -import { BaseTransaction } from '../baseTransaction.js' import * as EIP1559 from '../capabilities/eip1559.js' import * as EIP2718 from '../capabilities/eip2718.js' import * as EIP2930 from '../capabilities/eip2930.js' import * as Legacy from '../capabilities/legacy.js' import { LIMIT_BLOBS_PER_TX } from '../constants.js' -import { paramsTx } from '../index.js' +import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../features/util.js' import { TransactionType } from '../types.js' import { AccessLists, validateNotArray } from '../util.js' @@ -29,10 +27,14 @@ import type { AccessListBytes, TxData as AllTypesTxData, TxValuesArray as AllTypesTxValuesArray, + Capability, JSONTx, + TransactionCache, + TransactionInterface, TxOptions, } from '../types.js' -import type { PrefixedHexString } from '@ethereumjs/util' +import type { Common } from '@ethereumjs/common' +import type { Address, PrefixedHexString } from '@ethereumjs/util' export type TxData = AllTypesTxData[TransactionType.BlobEIP4844] export type TxValuesArray = AllTypesTxValuesArray[TransactionType.BlobEIP4844] @@ -43,20 +45,48 @@ export type TxValuesArray = AllTypesTxValuesArray[TransactionType.BlobEIP4844] * - TransactionType: 3 * - EIP: [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) */ -export class Blob4844Tx extends BaseTransaction { - public readonly chainId: bigint +export class Blob4844Tx implements TransactionInterface { + public type: number = TransactionType.BlobEIP4844 // 4844 tx type + + // Tx data part (part of the RLP) + public readonly nonce!: bigint + public readonly gasLimit!: bigint + public readonly value!: bigint + public readonly data!: Uint8Array + public readonly to?: Address public readonly accessList: AccessListBytes - public readonly AccessListJSON: AccessList + public readonly chainId: bigint public readonly maxPriorityFeePerGas: bigint public readonly maxFeePerGas: bigint public readonly maxFeePerBlobGas: bigint - - public readonly common: Common public blobVersionedHashes: PrefixedHexString[] + + // Props only for signed txs + public readonly v?: bigint + public readonly r?: bigint + public readonly s?: bigint + + // End of Tx data part + blobs?: PrefixedHexString[] // This property should only be populated when the transaction is in the "Network Wrapper" format kzgCommitments?: PrefixedHexString[] // This property should only be populated when the transaction is in the "Network Wrapper" format kzgProofs?: PrefixedHexString[] // This property should only be populated when the transaction is in the "Network Wrapper" format + public readonly AccessListJSON: AccessList + + public readonly common!: Common + + readonly txOptions!: TxOptions + + readonly cache: TransactionCache = {} + + /** + * List of tx type defining EIPs, + * e.g. 1559 (fee market) and 2930 (access lists) + * for FeeMarket1559Tx objects + */ + protected activeCapabilities: number[] = [] + /** * This constructor takes the values, validates them, assigns them and freezes the object. * @@ -65,16 +95,14 @@ export class Blob4844Tx extends BaseTransaction { * varying data types. */ constructor(txData: TxData, opts: TxOptions = {}) { - super({ ...txData, type: TransactionType.BlobEIP4844 }, opts) + sharedConstructor(this, { ...txData, type: TransactionType.BlobEIP4844 }, opts) const { chainId, accessList, maxFeePerGas, maxPriorityFeePerGas, maxFeePerBlobGas } = txData - this.common = opts.common?.copy() ?? new Common({ chain: this.DEFAULT_CHAIN }) if (chainId !== undefined && bytesToBigInt(toBytes(chainId)) !== this.common.chainId()) { throw new Error( `Common chain ID ${this.common.chainId} not matching the derived chain ID ${chainId}`, ) } - this.common.updateParams(opts.params ?? paramsTx) this.chainId = this.common.chainId() if (!this.common.isActivatedEIP(1559)) { @@ -96,7 +124,7 @@ export class Blob4844Tx extends BaseTransaction { this.maxFeePerGas = bytesToBigInt(toBytes(maxFeePerGas)) this.maxPriorityFeePerGas = bytesToBigInt(toBytes(maxPriorityFeePerGas)) - this._validateCannotExceedMaxInteger({ + valueBoundaryCheck({ maxFeePerGas: this.maxFeePerGas, maxPriorityFeePerGas: this.maxPriorityFeePerGas, }) @@ -104,12 +132,16 @@ export class Blob4844Tx extends BaseTransaction { validateNotArray(txData) if (this.gasLimit * this.maxFeePerGas > MAX_INTEGER) { - const msg = this._errorMsg('gasLimit * maxFeePerGas cannot exceed MAX_INTEGER (2^256-1)') + const msg = Legacy.errorMsg( + this, + 'gasLimit * maxFeePerGas cannot exceed MAX_INTEGER (2^256-1)', + ) throw new Error(msg) } if (this.maxFeePerGas < this.maxPriorityFeePerGas) { - const msg = this._errorMsg( + const msg = Legacy.errorMsg( + this, 'maxFeePerGas cannot be less than maxPriorityFeePerGas (The total must be the larger of the two)', ) throw new Error(msg) @@ -128,24 +160,28 @@ export class Blob4844Tx extends BaseTransaction { for (const hash of this.blobVersionedHashes) { if (hash.length !== 66) { // 66 is the length of a 32 byte hash as a PrefixedHexString - const msg = this._errorMsg('versioned hash is invalid length') + const msg = Legacy.errorMsg(this, 'versioned hash is invalid length') throw new Error(msg) } if (BigInt(parseInt(hash.slice(2, 4))) !== this.common.param('blobCommitmentVersionKzg')) { // We check the first "byte" of the hash (starts at position 2 since hash is a PrefixedHexString) - const msg = this._errorMsg('versioned hash does not start with KZG commitment version') + const msg = Legacy.errorMsg( + this, + 'versioned hash does not start with KZG commitment version', + ) throw new Error(msg) } } if (this.blobVersionedHashes.length > LIMIT_BLOBS_PER_TX) { - const msg = this._errorMsg(`tx can contain at most ${LIMIT_BLOBS_PER_TX} blobs`) + const msg = Legacy.errorMsg(this, `tx can contain at most ${LIMIT_BLOBS_PER_TX} blobs`) throw new Error(msg) } else if (this.blobVersionedHashes.length === 0) { - const msg = this._errorMsg(`tx should contain at least one blob`) + const msg = Legacy.errorMsg(this, `tx should contain at least one blob`) throw new Error(msg) } if (this.to === undefined) { - const msg = this._errorMsg( + const msg = Legacy.errorMsg( + this, `tx should have a "to" field and cannot be used to create contracts`, ) throw new Error(msg) @@ -162,6 +198,26 @@ export class Blob4844Tx extends BaseTransaction { } } + /** + * Checks if a tx type defining capability is active + * on a tx, for example the EIP-1559 fee market mechanism + * or the EIP-2930 access list feature. + * + * Note that this is different from the tx type itself, + * so EIP-2930 access lists can very well be active + * on an EIP-1559 tx for example. + * + * This method can be useful for feature checks if the + * tx type is unknown (e.g. when instantiated with + * the tx factory). + * + * See `Capabilities` in the `types` module for a reference + * on all supported capabilities. + */ + supports(capability: Capability) { + return this.activeCapabilities.includes(capability) + } + /** * Returns the minimum of calculated priority fee (from maxFeePerGas and baseFee) and maxPriorityFeePerGas * @param baseFee Base fee retrieved from block @@ -185,6 +241,25 @@ export class Blob4844Tx extends BaseTransaction { return EIP1559.getUpfrontCost(this, baseFee) } + // TODO figure out if this is necessary + // NOTE/TODO: this should DEFINITELY be removed from the `TransactionInterface`, since 4844/7702 can NEVER create contracts + /** + * If the tx's `to` is to the creation address + */ + toCreationAddress(): boolean { + return Legacy.toCreationAddress(this) + } + + /** + * The minimum gas limit which the tx to have to be valid. + * This covers costs as the standard fee (21000 gas), the data fee (paid for each calldata byte), + * the optional creation fee (if the transaction creates a contract), and if relevant the gas + * to be paid for access lists (EIP-2930) and authority lists (EIP-7702). + */ + getIntrinsicGas(): bigint { + return Legacy.getIntrinsicGas(this) + } + /** * Returns a Uint8Array Array of the raw Bytes of the EIP-4844 transaction, in order. * @@ -297,7 +372,7 @@ export class Blob4844Tx extends BaseTransaction { toJSON(): JSONTx { const accessListJSON = AccessLists.getAccessListJSON(this.accessList) - const baseJSON = super.toJSON() + const baseJSON = getBaseJSON(this) return { ...baseJSON, @@ -343,25 +418,45 @@ export class Blob4844Tx extends BaseTransaction { opts, ) } + + getValidationErrors(): string[] { + return Legacy.getValidationErrors(this) + } + + isValid(): boolean { + return Legacy.isValid(this) + } + + verifySignature(): boolean { + return Legacy.verifySignature(this) + } + + getSenderAddress(): Address { + return Legacy.getSenderAddress(this) + } + + sign(privateKey: Uint8Array): Blob4844Tx { + return Legacy.sign(this, privateKey) + } + + public isSigned(): boolean { + const { v, r, s } = this + if (v === undefined || r === undefined || s === undefined) { + return false + } else { + return true + } + } + /** * Return a compact error string representation of the object */ public errorStr() { - let errorStr = this._getSharedErrorPostfix() + let errorStr = Legacy.getSharedErrorPostfix(this) errorStr += ` maxFeePerGas=${this.maxFeePerGas} maxPriorityFeePerGas=${this.maxPriorityFeePerGas}` return errorStr } - /** - * Internal helper function to create an annotated error message - * - * @param msg Base error message - * @hidden - */ - protected _errorMsg(msg: string) { - return Legacy.errorMsg(this, msg) - } - /** * @returns the number of blobs included with this transaction */ diff --git a/packages/tx/src/7702/constructors.ts b/packages/tx/src/7702/constructors.ts index 5c8d0ab9ab..a29eca4047 100644 --- a/packages/tx/src/7702/constructors.ts +++ b/packages/tx/src/7702/constructors.ts @@ -4,7 +4,7 @@ import { bytesToBigInt, bytesToHex, equalsBytes, validateNoLeadingZeroes } from import { TransactionType } from '../types.js' import { txTypeBytes, validateNotArray } from '../util.js' -import { EOACode7702Transaction } from './tx.js' +import { EOACode7702Tx } from './tx.js' import type { TxOptions } from '../types.js' import type { TxData, TxValuesArray } from './tx.js' @@ -20,7 +20,7 @@ import type { TxData, TxValuesArray } from './tx.js' * - All parameters are optional and have some basic default values */ export function createEOACode7702Tx(txData: TxData, opts: TxOptions = {}) { - return new EOACode7702Transaction(txData, opts) + return new EOACode7702Tx(txData, opts) } /** @@ -55,7 +55,7 @@ export function createEOACode7702TxFromBytesArray(values: TxValuesArray, opts: T validateNotArray({ chainId, v }) validateNoLeadingZeroes({ nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, value, v, r, s }) - return new EOACode7702Transaction( + return new EOACode7702Tx( { chainId: bytesToBigInt(chainId), nonce, diff --git a/packages/tx/src/7702/index.ts b/packages/tx/src/7702/index.ts index 4130393815..ccb09c1209 100644 --- a/packages/tx/src/7702/index.ts +++ b/packages/tx/src/7702/index.ts @@ -1,2 +1,2 @@ export * from './constructors.js' -export { EOACode7702Transaction } from './tx.js' +export { EOACode7702Tx } from './tx.js' diff --git a/packages/tx/src/7702/tx.ts b/packages/tx/src/7702/tx.ts index d9ccc9086c..685293b88d 100644 --- a/packages/tx/src/7702/tx.ts +++ b/packages/tx/src/7702/tx.ts @@ -1,4 +1,3 @@ -import { Common } from '@ethereumjs/common' import { BIGINT_0, BIGINT_27, @@ -9,12 +8,11 @@ import { toBytes, } from '@ethereumjs/util' -import { BaseTransaction } from '../baseTransaction.js' import * as EIP1559 from '../capabilities/eip1559.js' import * as EIP2718 from '../capabilities/eip2718.js' import * as EIP7702 from '../capabilities/eip7702.js' import * as Legacy from '../capabilities/legacy.js' -import { paramsTx } from '../index.js' +import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../features/util.js' import { TransactionType } from '../types.js' import { AccessLists, AuthorizationLists, validateNotArray } from '../util.js' @@ -27,9 +25,14 @@ import type { TxValuesArray as AllTypesTxValuesArray, AuthorizationList, AuthorizationListBytes, + Capability, JSONTx, + TransactionCache, + TransactionInterface, TxOptions, } from '../types.js' +import type { Common } from '@ethereumjs/common' +import type { Address } from '@ethereumjs/util' export type TxData = AllTypesTxData[TransactionType.EOACodeEIP7702] export type TxValuesArray = AllTypesTxValuesArray[TransactionType.EOACodeEIP7702] @@ -40,16 +43,43 @@ export type TxValuesArray = AllTypesTxValuesArray[TransactionType.EOACodeEIP7702 * - TransactionType: 4 * - EIP: [EIP-7702](https://github.com/ethereum/EIPs/blob/62419ca3f45375db00b04a368ea37c0bfb05386a/EIPS/eip-7702.md) */ -export class EOACode7702Transaction extends BaseTransaction { - public readonly chainId: bigint +export class EOACode7702Tx implements TransactionInterface { + public type: number = TransactionType.EOACodeEIP7702 // 7702 tx type + + // Tx data part (part of the RLP) + public readonly nonce!: bigint + public readonly gasLimit!: bigint + public readonly value!: bigint + public readonly data!: Uint8Array + public readonly to?: Address public readonly accessList: AccessListBytes - public readonly AccessListJSON: AccessList public readonly authorizationList: AuthorizationListBytes - public readonly AuthorizationListJSON: AuthorizationList + public readonly chainId: bigint public readonly maxPriorityFeePerGas: bigint public readonly maxFeePerGas: bigint - public readonly common: Common + // Props only for signed txs + public readonly v?: bigint + public readonly r?: bigint + public readonly s?: bigint + + // End of Tx data part + + public readonly AccessListJSON: AccessList + public readonly AuthorizationListJSON: AuthorizationList + + public readonly common!: Common + + readonly txOptions!: TxOptions + + readonly cache: TransactionCache = {} + + /** + * List of tx type defining EIPs, + * e.g. 1559 (fee market) and 2930 (access lists) + * for FeeMarket1559Tx objects + */ + protected activeCapabilities: number[] = [] /** * This constructor takes the values, validates them, assigns them and freezes the object. @@ -59,16 +89,14 @@ export class EOACode7702Transaction extends BaseTransaction MAX_INTEGER) { - const msg = this._errorMsg('gasLimit * maxFeePerGas cannot exceed MAX_INTEGER (2^256-1)') + const msg = Legacy.errorMsg( + this, + 'gasLimit * maxFeePerGas cannot exceed MAX_INTEGER (2^256-1)', + ) throw new Error(msg) } if (this.maxFeePerGas < this.maxPriorityFeePerGas) { - const msg = this._errorMsg( + const msg = Legacy.errorMsg( + this, 'maxFeePerGas cannot be less than maxPriorityFeePerGas (The total must be the larger of the two)', ) throw new Error(msg) @@ -118,7 +150,8 @@ export class EOACode7702Transaction extends BaseTransactionLegacy.sign(this, privateKey) + } + + public isSigned(): boolean { + const { v, r, s } = this + if (v === undefined || r === undefined || s === undefined) { + return false + } else { + return true + } + } + /** * Return a compact error string representation of the object */ public errorStr() { - let errorStr = this._getSharedErrorPostfix() + let errorStr = Legacy.getSharedErrorPostfix(this) errorStr += ` maxFeePerGas=${this.maxFeePerGas} maxPriorityFeePerGas=${this.maxPriorityFeePerGas}` return errorStr } - - /** - * Internal helper function to create an annotated error message - * - * @param msg Base error message - * @hidden - */ - protected _errorMsg(msg: string) { - return Legacy.errorMsg(this, msg) - } } diff --git a/packages/tx/src/baseTransaction.ts b/packages/tx/src/baseTransaction.ts deleted file mode 100644 index 0f1b469f00..0000000000 --- a/packages/tx/src/baseTransaction.ts +++ /dev/null @@ -1,475 +0,0 @@ -import { Common, Mainnet } from '@ethereumjs/common' -import { - Address, - BIGINT_0, - MAX_INTEGER, - MAX_UINT64, - bigIntToHex, - bytesToBigInt, - bytesToHex, - ecsign, - publicToAddress, - toBytes, - unpadBytes, -} from '@ethereumjs/util' - -import { paramsTx } from './params.js' -import { Capability, TransactionType } from './types.js' -import { checkMaxInitCodeSize } from './util.js' - -import type { - JSONTx, - Transaction, - TransactionCache, - TransactionInterface, - TxData, - TxOptions, - TxValuesArray, -} from './types.js' - -/** - * This base class will likely be subject to further - * refactoring along the introduction of additional tx types - * on the Ethereum network. - * - * It is therefore not recommended to use directly. - */ -export abstract class BaseTransaction - implements TransactionInterface -{ - protected readonly _type: TransactionType - - public readonly nonce: bigint - public readonly gasLimit: bigint - public readonly to?: Address - public readonly value: bigint - public readonly data: Uint8Array - - public readonly v?: bigint - public readonly r?: bigint - public readonly s?: bigint - - public readonly common!: Common - - public cache: TransactionCache = { - hash: undefined, - dataFee: undefined, - senderPubKey: undefined, - } - - protected readonly txOptions: TxOptions - - /** - * List of tx type defining EIPs, - * e.g. 1559 (fee market) and 2930 (access lists) - * for FeeMarket1559Tx objects - */ - protected activeCapabilities: number[] = [] - - /** - * The default chain the tx falls back to if no Common - * is provided and if the chain can't be derived from - * a passed in chainId (only EIP-2718 typed txs) or - * EIP-155 signature (legacy txs). - * - * @hidden - */ - protected DEFAULT_CHAIN = Mainnet - - constructor(txData: TxData[T], opts: TxOptions) { - this.common = opts.common?.copy() ?? new Common({ chain: this.DEFAULT_CHAIN }) - - const { nonce, gasLimit, to, value, data, v, r, s, type } = txData - this._type = Number(bytesToBigInt(toBytes(type))) - - this.txOptions = opts - - const toB = toBytes(to === '' ? '0x' : to) - const vB = toBytes(v) - const rB = toBytes(r) - const sB = toBytes(s) - - this.nonce = bytesToBigInt(toBytes(nonce)) - this.gasLimit = bytesToBigInt(toBytes(gasLimit)) - this.to = toB.length > 0 ? new Address(toB) : undefined - this.value = bytesToBigInt(toBytes(value)) - this.data = toBytes(data === '' ? '0x' : data) - - this.v = vB.length > 0 ? bytesToBigInt(vB) : undefined - this.r = rB.length > 0 ? bytesToBigInt(rB) : undefined - this.s = sB.length > 0 ? bytesToBigInt(sB) : undefined - - this._validateCannotExceedMaxInteger({ value: this.value, r: this.r, s: this.s }) - - // geth limits gasLimit to 2^64-1 - this._validateCannotExceedMaxInteger({ gasLimit: this.gasLimit }, 64) - - // EIP-2681 limits nonce to 2^64-1 (cannot equal 2^64-1) - this._validateCannotExceedMaxInteger({ nonce: this.nonce }, 64, true) - - const createContract = this.to === undefined || this.to === null - const allowUnlimitedInitCodeSize = opts.allowUnlimitedInitCodeSize ?? false - - this.common.updateParams(opts.params ?? paramsTx) - if ( - createContract && - this.common.isActivatedEIP(3860) && - allowUnlimitedInitCodeSize === false - ) { - checkMaxInitCodeSize(this.common, this.data.length) - } - } - - /** - * Returns the transaction type. - * - * Note: legacy txs will return tx type `0`. - */ - get type() { - return this._type - } - - /** - * Checks if a tx type defining capability is active - * on a tx, for example the EIP-1559 fee market mechanism - * or the EIP-2930 access list feature. - * - * Note that this is different from the tx type itself, - * so EIP-2930 access lists can very well be active - * on an EIP-1559 tx for example. - * - * This method can be useful for feature checks if the - * tx type is unknown (e.g. when instantiated with - * the tx factory). - * - * See `Capabilities` in the `types` module for a reference - * on all supported capabilities. - */ - supports(capability: Capability) { - return this.activeCapabilities.includes(capability) - } - - /** - * Validates the transaction signature and minimum gas requirements. - * @returns {string[]} an array of error strings - */ - getValidationErrors(): string[] { - const errors = [] - - if (this.isSigned() && !this.verifySignature()) { - errors.push('Invalid Signature') - } - - if (this.getIntrinsicGas() > this.gasLimit) { - errors.push( - `gasLimit is too low. given ${this.gasLimit}, need at least ${this.getIntrinsicGas()}`, - ) - } - - return errors - } - - /** - * Validates the transaction signature and minimum gas requirements. - * @returns {boolean} true if the transaction is valid, false otherwise - */ - isValid(): boolean { - const errors = this.getValidationErrors() - - return errors.length === 0 - } - - /** - * The minimum gas limit which the tx to have to be valid. - * This covers costs as the standard fee (21000 gas), the data fee (paid for each calldata byte), - * the optional creation fee (if the transaction creates a contract), and if relevant the gas - * to be paid for access lists (EIP-2930) and authority lists (EIP-7702). - */ - getIntrinsicGas(): bigint { - const txFee = this.common.param('txGas') - let fee = this.getDataGas() - if (txFee) fee += txFee - if (this.common.gteHardfork('homestead') && this.toCreationAddress()) { - const txCreationFee = this.common.param('txCreationGas') - if (txCreationFee) fee += txCreationFee - } - return fee - } - - /** - * The amount of gas paid for the calldata in this tx - */ - getDataGas(): bigint { - const txDataZero = this.common.param('txDataZeroGas') - const txDataNonZero = this.common.param('txDataNonZeroGas') - - let cost = BIGINT_0 - for (let i = 0; i < this.data.length; i++) { - this.data[i] === 0 ? (cost += txDataZero) : (cost += txDataNonZero) - } - - if ((this.to === undefined || this.to === null) && this.common.isActivatedEIP(3860)) { - const dataLength = BigInt(Math.ceil(this.data.length / 32)) - const initCodeCost = this.common.param('initCodeWordGas') * dataLength - cost += initCodeCost - } - - return cost - } - - /** - * Returns the effective priority fee. This is the priority fee which the coinbase will receive - * once it is included in the block - * @param baseFee Optional baseFee of the block. Note for EIP1559 and EIP4844 this is required. - */ - abstract getEffectivePriorityFee(baseFee: bigint | undefined): bigint - - /** - * The upfront amount of wei to be paid in order for this tx to be valid and included in a block - */ - abstract getUpfrontCost(): bigint - - /** - * If the tx's `to` is to the creation address - */ - toCreationAddress(): boolean { - return this.to === undefined || this.to.bytes.length === 0 - } - - /** - * Returns a Uint8Array Array of the raw Bytes of this transaction, in order. - * - * Use {@link BaseTransaction.serialize} to add a transaction to a block - * with {@link createBlockFromBytesArray}. - * - * For an unsigned tx this method uses the empty Bytes values for the - * signature parameters `v`, `r` and `s` for encoding. For an EIP-155 compliant - * representation for external signing use {@link BaseTransaction.getMessageToSign}. - */ - abstract raw(): TxValuesArray[T] - - /** - * Returns the encoding of the transaction. - */ - abstract serialize(): Uint8Array - - // Returns the raw unsigned tx, which is used to sign the transaction. - abstract getMessageToSign(): Uint8Array | Uint8Array[] - - // Returns the hashed unsigned tx, which is used to sign the transaction. - abstract getHashedMessageToSign(): Uint8Array - - abstract hash(): Uint8Array - - abstract getMessageToVerifySignature(): Uint8Array - - public isSigned(): boolean { - const { v, r, s } = this - if (v === undefined || r === undefined || s === undefined) { - return false - } else { - return true - } - } - - /** - * Determines if the signature is valid - */ - verifySignature(): boolean { - try { - // Main signature verification is done in `getSenderPublicKey()` - const publicKey = this.getSenderPublicKey() - return unpadBytes(publicKey).length !== 0 - } catch (e: any) { - return false - } - } - - /** - * Returns the sender's address - */ - getSenderAddress(): Address { - return new Address(publicToAddress(this.getSenderPublicKey())) - } - - /** - * Returns the public key of the sender - */ - abstract getSenderPublicKey(): Uint8Array - - /** - * Signs a transaction. - * - * Note that the signed tx is returned as a new object, - * use as follows: - * ```javascript - * const signedTx = tx.sign(privateKey) - * ``` - */ - sign(privateKey: Uint8Array): Transaction[T] { - if (privateKey.length !== 32) { - const msg = this._errorMsg('Private key must be 32 bytes in length.') - throw new Error(msg) - } - - // Hack for the constellation that we have got a legacy tx after spuriousDragon with a non-EIP155 conforming signature - // and want to recreate a signature (where EIP155 should be applied) - // Leaving this hack lets the legacy.spec.ts -> sign(), verifySignature() test fail - // 2021-06-23 - let hackApplied = false - if ( - this.type === TransactionType.Legacy && - this.common.gteHardfork('spuriousDragon') && - !this.supports(Capability.EIP155ReplayProtection) - ) { - this.activeCapabilities.push(Capability.EIP155ReplayProtection) - hackApplied = true - } - - const msgHash = this.getHashedMessageToSign() - const ecSignFunction = this.common.customCrypto?.ecsign ?? ecsign - const { v, r, s } = ecSignFunction(msgHash, privateKey) - const tx = this.addSignature(v, r, s, true) - - // Hack part 2 - if (hackApplied) { - const index = this.activeCapabilities.indexOf(Capability.EIP155ReplayProtection) - if (index > -1) { - this.activeCapabilities.splice(index, 1) - } - } - - return tx - } - - /** - * Returns an object with the JSON representation of the transaction - */ - toJSON(): JSONTx { - return { - type: bigIntToHex(BigInt(this.type)), - nonce: bigIntToHex(this.nonce), - gasLimit: bigIntToHex(this.gasLimit), - to: this.to !== undefined ? this.to.toString() : undefined, - value: bigIntToHex(this.value), - data: bytesToHex(this.data), - v: this.v !== undefined ? bigIntToHex(this.v) : undefined, - r: this.r !== undefined ? bigIntToHex(this.r) : undefined, - s: this.s !== undefined ? bigIntToHex(this.s) : undefined, - chainId: bigIntToHex(this.common.chainId()), - yParity: this.v === 0n || this.v === 1n ? bigIntToHex(this.v) : undefined, - } - } - - /** - * Returns a new transaction with the same data fields as the current, but now signed - * @param v The `v` value of the signature - * @param r The `r` value of the signature - * @param s The `s` value of the signature - * @param convertV Set this to `true` if the raw output of `ecsign` is used. If this is `false` (default) - * then the raw value passed for `v` will be used for the signature. For legacy transactions, - * if this is set to `true`, it will also set the right `v` value for the chain id. - */ - abstract addSignature( - v: bigint, - r: Uint8Array | bigint, - s: Uint8Array | bigint, - convertV?: boolean, - ): Transaction[T] - - /** - * 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 the maximum value - */ - protected _validateCannotExceedMaxInteger( - 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) { - const msg = this._errorMsg( - `${key} cannot equal or exceed MAX_UINT64 (2^64-1), given ${value}`, - ) - throw new Error(msg) - } - } else { - if (value !== undefined && value > MAX_UINT64) { - const msg = this._errorMsg(`${key} cannot exceed MAX_UINT64 (2^64-1), given ${value}`) - throw new Error(msg) - } - } - break - case 256: - if (cannotEqual) { - if (value !== undefined && value >= MAX_INTEGER) { - const msg = this._errorMsg( - `${key} cannot equal or exceed MAX_INTEGER (2^256-1), given ${value}`, - ) - throw new Error(msg) - } - } else { - if (value !== undefined && value > MAX_INTEGER) { - const msg = this._errorMsg( - `${key} cannot exceed MAX_INTEGER (2^256-1), given ${value}`, - ) - throw new Error(msg) - } - } - break - default: { - const msg = this._errorMsg('unimplemented bits value') - throw new Error(msg) - } - } - } - } - - /** - * Return a compact error string representation of the object - */ - public abstract errorStr(): string - - /** - * Internal helper function to create an annotated error message - * - * @param msg Base error message - * @hidden - */ - protected abstract _errorMsg(msg: string): string - - /** - * Returns the shared error postfix part for _error() method - * tx type implementations. - */ - protected _getSharedErrorPostfix() { - let hash = '' - try { - hash = this.isSigned() ? bytesToHex(this.hash()) : 'not available (unsigned)' - } catch (e: any) { - hash = 'error' - } - let isSigned = '' - try { - isSigned = this.isSigned().toString() - } catch (e: any) { - hash = 'error' - } - let hf = '' - try { - hf = this.common.hardfork() - } catch (e: any) { - hf = 'error' - } - - let postfix = `tx type=${this.type} hash=${hash} nonce=${this.nonce} value=${this.value} ` - postfix += `signed=${isSigned} hf=${hf}` - - return postfix - } -} diff --git a/packages/tx/src/capabilities/legacy.ts b/packages/tx/src/capabilities/legacy.ts index 34ebd741dc..be56064168 100644 --- a/packages/tx/src/capabilities/legacy.ts +++ b/packages/tx/src/capabilities/legacy.ts @@ -1,10 +1,19 @@ -import { SECP256K1_ORDER_DIV_2, bigIntToUnpaddedBytes, ecrecover } from '@ethereumjs/util' +import { + Address, + BIGINT_0, + SECP256K1_ORDER_DIV_2, + bigIntToUnpaddedBytes, + bytesToHex, + ecrecover, + ecsign, + publicToAddress, + unpadBytes, +} from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak.js' -import { BaseTransaction } from '../baseTransaction.js' -import { Capability } from '../types.js' +import { Capability, TransactionType } from '../types.js' -import type { LegacyTxInterface } from '../types.js' +import type { LegacyTxInterface, Transaction } from '../types.js' export function errorMsg(tx: LegacyTxInterface, msg: string) { return `${msg} (${tx.errorStr()})` @@ -27,7 +36,19 @@ export function getDataGas(tx: LegacyTxInterface, extraCost?: bigint): bigint { return tx.cache.dataFee.value } - const cost = BaseTransaction.prototype.getDataGas.bind(tx)() + (extraCost ?? 0n) + const txDataZero = tx.common.param('txDataZeroGas') + const txDataNonZero = tx.common.param('txDataNonZeroGas') + + let cost = extraCost ?? BIGINT_0 + for (let i = 0; i < tx.data.length; i++) { + tx.data[i] === 0 ? (cost += txDataZero) : (cost += txDataNonZero) + } + + if ((tx.to === undefined || tx.to === null) && tx.common.isActivatedEIP(3860)) { + const dataLength = BigInt(Math.ceil(tx.data.length / 32)) + const initCodeCost = tx.common.param('initCodeWordGas') * dataLength + cost += initCodeCost + } if (Object.isFrozen(tx)) { tx.cache.dataFee = { @@ -39,6 +60,27 @@ export function getDataGas(tx: LegacyTxInterface, extraCost?: bigint): bigint { return cost } +/** + * The minimum gas limit which the tx to have to be valid. + * This covers costs as the standard fee (21000 gas), the data fee (paid for each calldata byte), + * the optional creation fee (if the transaction creates a contract), and if relevant the gas + * to be paid for access lists (EIP-2930) and authority lists (EIP-7702). + */ +export function getIntrinsicGas(tx: LegacyTxInterface): bigint { + const txFee = tx.common.param('txGas') + let fee = tx.getDataGas() + if (txFee) fee += txFee + if (tx.common.gteHardfork('homestead') && tx.toCreationAddress()) { + const txCreationFee = tx.common.param('txCreationGas') + if (txCreationFee) fee += txCreationFee + } + return fee +} + +export function toCreationAddress(tx: LegacyTxInterface): boolean { + return tx.to === undefined || tx.to.bytes.length === 0 +} + export function hash(tx: LegacyTxInterface): Uint8Array { if (!tx.isSigned()) { const msg = errorMsg(tx, 'Cannot call hash method if transaction is not signed') @@ -113,3 +155,129 @@ export function getEffectivePriorityFee(gasPrice: bigint, baseFee: bigint | unde return gasPrice - baseFee } + +/** + * Validates the transaction signature and minimum gas requirements. + * @returns {string[]} an array of error strings + */ +export function getValidationErrors(tx: LegacyTxInterface): string[] { + const errors = [] + + if (tx.isSigned() && !tx.verifySignature()) { + errors.push('Invalid Signature') + } + + if (tx.getIntrinsicGas() > tx.gasLimit) { + errors.push(`gasLimit is too low. given ${tx.gasLimit}, need at least ${tx.getIntrinsicGas()}`) + } + + return errors +} + +/** + * Validates the transaction signature and minimum gas requirements. + * @returns {boolean} true if the transaction is valid, false otherwise + */ +export function isValid(tx: LegacyTxInterface): boolean { + const errors = tx.getValidationErrors() + + return errors.length === 0 +} + +/** + * Determines if the signature is valid + */ +export function verifySignature(tx: LegacyTxInterface): boolean { + try { + // Main signature verification is done in `getSenderPublicKey()` + const publicKey = tx.getSenderPublicKey() + return unpadBytes(publicKey).length !== 0 + } catch (e: any) { + return false + } +} + +/** + * Returns the sender's address + */ +export function getSenderAddress(tx: LegacyTxInterface): Address { + return new Address(publicToAddress(tx.getSenderPublicKey())) +} + +/** + * Signs a transaction. + * + * Note that the signed tx is returned as a new object, + * use as follows: + * ```javascript + * const signedTx = tx.sign(privateKey) + * ``` + */ +export function sign(tx: LegacyTxInterface, privateKey: Uint8Array): Transaction[TransactionType] { + if (privateKey.length !== 32) { + // TODO figure out this errorMsg logic how this diverges on other txs + const msg = errorMsg(tx, 'Private key must be 32 bytes in length.') + throw new Error(msg) + } + + // TODO (Jochem, 05 nov 2024): figure out what this hack does and clean it up + + // Hack for the constellation that we have got a legacy tx after spuriousDragon with a non-EIP155 conforming signature + // and want to recreate a signature (where EIP155 should be applied) + // Leaving this hack lets the legacy.spec.ts -> sign(), verifySignature() test fail + // 2021-06-23 + let hackApplied = false + if ( + tx.type === TransactionType.Legacy && + tx.common.gteHardfork('spuriousDragon') && + !tx.supports(Capability.EIP155ReplayProtection) + ) { + // cast as any to edit the protected `activeCapabilities` + ;(tx as any).activeCapabilities.push(Capability.EIP155ReplayProtection) + hackApplied = true + } + + const msgHash = tx.getHashedMessageToSign() + const ecSignFunction = tx.common.customCrypto?.ecsign ?? ecsign + const { v, r, s } = ecSignFunction(msgHash, privateKey) + const signedTx = tx.addSignature(v, r, s, true) + + // Hack part 2 + if (hackApplied) { + // cast as any to edit the protected `activeCapabilities` + const index = (tx).activeCapabilities.indexOf(Capability.EIP155ReplayProtection) + if (index > -1) { + // cast as any to edit the protected `activeCapabilities` + ;(tx).activeCapabilities.splice(index, 1) + } + } + + return signedTx +} + +// TODO maybe move this to shared methods (util.ts in features) +export function getSharedErrorPostfix(tx: LegacyTxInterface) { + let hash = '' + try { + hash = tx.isSigned() ? bytesToHex(tx.hash()) : 'not available (unsigned)' + } catch (e: any) { + hash = 'error' + } + let isSigned = '' + try { + isSigned = tx.isSigned().toString() + } catch (e: any) { + hash = 'error' + } + let hf = '' + try { + hf = tx.common.hardfork() + } catch (e: any) { + hf = 'error' + } + + let postfix = `tx type=${tx.type} hash=${hash} nonce=${tx.nonce} value=${tx.value} ` + postfix += `signed=${isSigned} hf=${hf}` + + return postfix +} diff --git a/packages/tx/src/features/util.ts b/packages/tx/src/features/util.ts new file mode 100644 index 0000000000..4c4f5976d0 --- /dev/null +++ b/packages/tx/src/features/util.ts @@ -0,0 +1,138 @@ +import { Common, Mainnet } from '@ethereumjs/common' +import { + Address, + MAX_INTEGER, + MAX_UINT64, + bigIntToHex, + bytesToBigInt, + bytesToHex, + toBytes, +} from '@ethereumjs/util' + +import { paramsTx } from '../params.js' +import { checkMaxInitCodeSize, validateNotArray } from '../util.js' + +import type { TransactionInterface, TransactionType, TxData, TxOptions } from '../types.js' + +export function getCommon(common?: Common): Common { + return common?.copy() ?? new Common({ chain: Mainnet }) +} + +/** + * 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 the maximum value + */ +export function valueBoundaryCheck( // TODO: better method name + 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 new Error(`${key} cannot equal or exceed MAX_UINT64 (2^64-1), given ${value}`) + } + } else { + if (value !== undefined && value > MAX_UINT64) { + throw new Error(`${key} cannot exceed MAX_UINT64 (2^64-1), given ${value}`) + } + } + break + case 256: + if (cannotEqual) { + if (value !== undefined && value >= MAX_INTEGER) { + throw new Error(`${key} cannot equal or exceed MAX_INTEGER (2^256-1), given ${value}`) + } + } else { + if (value !== undefined && value > MAX_INTEGER) { + throw new Error(`${key} cannot exceed MAX_INTEGER (2^256-1), given ${value}`) + } + } + break + default: { + throw new Error('unimplemented bits value') + } + } + } +} + +type Mutable = { + -readonly [P in keyof T]: T[P] +} + +// This is (temp) a shared method which reflects `super` logic which were called from all txs and thus +// represents the constructor of baseTransaction +// Note: have to use `Mutable` to write to readonly props. Only call this in constructor of txs. +export function sharedConstructor( + tx: Mutable, + 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 + valueBoundaryCheck({ value: tx.value, r: tx.r, s: tx.s }) + + // geth limits gasLimit to 2^64-1 + valueBoundaryCheck({ gasLimit: tx.gasLimit }, 64) + + // EIP-2681 limits nonce to 2^64-1 (cannot equal 2^64-1) + valueBoundaryCheck({ nonce: tx.nonce }, 64, true) + + 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) + } +} + +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, + } +} diff --git a/packages/tx/src/legacy/tx.ts b/packages/tx/src/legacy/tx.ts index 2b02ec2e02..a576f4f7ff 100644 --- a/packages/tx/src/legacy/tx.ts +++ b/packages/tx/src/legacy/tx.ts @@ -1,4 +1,3 @@ -import { Common } from '@ethereumjs/common' import { RLP } from '@ethereumjs/rlp' import { BIGINT_2, @@ -12,11 +11,10 @@ import { } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak.js' -import { BaseTransaction } from '../baseTransaction.js' import * as Legacy from '../capabilities/legacy.js' +import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../features/util.js' import { paramsTx } from '../index.js' import { Capability, TransactionType } from '../types.js' -import { validateNotArray } from '../util.js' import { createLegacyTx } from './constructors.js' @@ -24,8 +22,12 @@ import type { TxData as AllTypesTxData, TxValuesArray as AllTypesTxValuesArray, JSONTx, + TransactionCache, + TransactionInterface, TxOptions, } from '../types.js' +import type { Common } from '@ethereumjs/common' +import type { Address } from '@ethereumjs/util' export type TxData = AllTypesTxData[TransactionType.Legacy] export type TxValuesArray = AllTypesTxValuesArray[TransactionType.Legacy] @@ -36,15 +38,80 @@ function meetsEIP155(_v: bigint, chainId: bigint) { return v === chainIdDoubled + 35 || v === chainIdDoubled + 36 } +/** + * Validates tx's `v` value and extracts the chain id + */ +function validateVAndExtractChainID(common: Common, _v?: bigint): BigInt | undefined { + let chainIdBigInt + const v = _v !== undefined ? Number(_v) : undefined + // Check for valid v values in the scope of a signed legacy tx + if (v !== undefined) { + // v is 1. not matching the EIP-155 chainId included case and... + // v is 2. not matching the classic v=27 or v=28 case + if (v < 37 && v !== 27 && v !== 28) { + throw new Error( + `Legacy txs need either v = 27/28 or v >= 37 (EIP-155 replay protection), got v = ${v}`, + ) + } + } + + // No unsigned tx and EIP-155 activated and chain ID included + if (v !== undefined && v !== 0 && common.gteHardfork('spuriousDragon') && v !== 27 && v !== 28) { + if (!meetsEIP155(BigInt(v), common.chainId())) { + throw new Error( + `Incompatible EIP155-based V ${v} and chain id ${common.chainId()}. See the Common parameter of the Transaction constructor to set the chain id.`, + ) + } + // Derive the original chain ID + let numSub + if ((v - 35) % 2 === 0) { + numSub = 35 + } else { + numSub = 36 + } + // Use derived chain ID to create a proper Common + chainIdBigInt = BigInt(v - numSub) / BIGINT_2 + } + return chainIdBigInt +} + /** * An Ethereum non-typed (legacy) transaction */ -export class LegacyTx extends BaseTransaction { +export class LegacyTx implements TransactionInterface { + /* Tx public data fields */ + public type: number = TransactionType.Legacy // Legacy tx type + + // Tx data part (part of the RLP) public readonly gasPrice: bigint + public readonly nonce!: bigint + public readonly gasLimit!: bigint + public readonly value!: bigint + public readonly data!: Uint8Array + public readonly to?: Address + + // Props only for signed txs + public readonly v?: bigint + public readonly r?: bigint + public readonly s?: bigint + + // End of Tx data part - public readonly common: Common + /* Other handy tx props */ + public readonly common!: Common private keccakFunction: (msg: Uint8Array) => Uint8Array + readonly txOptions!: TxOptions + + readonly cache: TransactionCache = {} + + /** + * List of tx type defining EIPs, + * e.g. 1559 (fee market) and 2930 (access lists) + * for FeeMarket1559Tx objects + */ + protected activeCapabilities: number[] = [] + /** * This constructor takes the values, validates them, assigns them and freezes the object. * @@ -53,28 +120,27 @@ export class LegacyTx extends BaseTransaction { * varying data types. */ public constructor(txData: TxData, opts: TxOptions = {}) { - super({ ...txData, type: TransactionType.Legacy }, opts) + sharedConstructor(this, txData, opts) - this.common = opts.common?.copy() ?? new Common({ chain: this.DEFAULT_CHAIN }) - const chainId = this._validateTxV(this.common, this.v) + this.gasPrice = bytesToBigInt(toBytes(txData.gasPrice)) + valueBoundaryCheck({ gasPrice: this.gasPrice }) + + // Everything from BaseTransaction done here + this.common.updateParams(opts.params ?? paramsTx) // TODO should this move higher? + + const chainId = validateVAndExtractChainID(this.common, this.v) if (chainId !== undefined && chainId !== this.common.chainId()) { throw new Error( `Common chain ID ${this.common.chainId} not matching the derived chain ID ${chainId}`, ) } - this.common.updateParams(opts.params ?? paramsTx) this.keccakFunction = this.common.customCrypto.keccak256 ?? keccak256 - this.gasPrice = bytesToBigInt(toBytes(txData.gasPrice)) if (this.gasPrice * this.gasLimit > MAX_INTEGER) { - const msg = this._errorMsg('gas limit * gasPrice cannot exceed MAX_INTEGER (2^256-1)') - throw new Error(msg) + throw new Error('gas limit * gasPrice cannot exceed MAX_INTEGER (2^256-1)') } - this._validateCannotExceedMaxInteger({ gasPrice: this.gasPrice }) - validateNotArray(txData) - if (this.common.gteHardfork('spuriousDragon')) { if (!this.isSigned()) { this.activeCapabilities.push(Capability.EIP155ReplayProtection) @@ -97,6 +163,30 @@ export class LegacyTx extends BaseTransaction { } } + /** + * Checks if a tx type defining capability is active + * on a tx, for example the EIP-1559 fee market mechanism + * or the EIP-2930 access list feature. + * + * Note that this is different from the tx type itself, + * so EIP-2930 access lists can very well be active + * on an EIP-1559 tx for example. + * + * This method can be useful for feature checks if the + * tx type is unknown (e.g. when instantiated with + * the tx factory). + * + * See `Capabilities` in the `types` module for a reference + * on all supported capabilities. + */ + supports(capability: Capability) { + return this.activeCapabilities.includes(capability) + } + + isSigned(): boolean { + return Legacy.isSigned(this) + } + getEffectivePriorityFee(baseFee?: bigint): bigint { return Legacy.getEffectivePriorityFee(this.gasPrice, baseFee) } @@ -189,6 +279,23 @@ export class LegacyTx extends BaseTransaction { return Legacy.getDataGas(this) } + // TODO figure out if this is necessary + /** + * If the tx's `to` is to the creation address + */ + toCreationAddress(): boolean { + return Legacy.toCreationAddress(this) + } + + /** + * The minimum gas limit which the tx to have to be valid. + * This covers costs as the standard fee (21000 gas), the data fee (paid for each calldata byte), + * the optional creation fee (if the transaction creates a contract), and if relevant the gas + * to be paid for access lists (EIP-2930) and authority lists (EIP-7702). + */ + getIntrinsicGas(): bigint { + return Legacy.getIntrinsicGas(this) + } /** * The up front amount that an account must have for this transaction to be valid */ @@ -211,7 +318,7 @@ export class LegacyTx extends BaseTransaction { */ getMessageToVerifySignature() { if (!this.isSigned()) { - const msg = this._errorMsg('This transaction is not signed') + const msg = Legacy.errorMsg(this, 'This transaction is not signed') throw new Error(msg) } return this.getHashedMessageToSign() @@ -258,72 +365,40 @@ export class LegacyTx extends BaseTransaction { * Returns an object with the JSON representation of the transaction. */ toJSON(): JSONTx { - const baseJSON = super.toJSON() - return { - ...baseJSON, - gasPrice: bigIntToHex(this.gasPrice), - } + // TODO this is just copied. Make this execution-api compliant + + const baseJSON = getBaseJSON(this) as JSONTx + baseJSON.gasPrice = bigIntToHex(this.gasPrice) + + return baseJSON } - /** - * Validates tx's `v` value - */ - protected _validateTxV(common: Common, _v?: bigint): BigInt | undefined { - let chainIdBigInt - const v = _v !== undefined ? Number(_v) : undefined - // Check for valid v values in the scope of a signed legacy tx - if (v !== undefined) { - // v is 1. not matching the EIP-155 chainId included case and... - // v is 2. not matching the classic v=27 or v=28 case - if (v < 37 && v !== 27 && v !== 28) { - throw new Error( - `Legacy txs need either v = 27/28 or v >= 37 (EIP-155 replay protection), got v = ${v}`, - ) - } - } + getValidationErrors(): string[] { + return Legacy.getValidationErrors(this) + } - // No unsigned tx and EIP-155 activated and chain ID included - if ( - v !== undefined && - v !== 0 && - common.gteHardfork('spuriousDragon') && - v !== 27 && - v !== 28 - ) { - if (!meetsEIP155(BigInt(v), common.chainId())) { - throw new Error( - `Incompatible EIP155-based V ${v} and chain id ${common.chainId()}. See the Common parameter of the Transaction constructor to set the chain id.`, - ) - } - // Derive the original chain ID - let numSub - if ((v - 35) % 2 === 0) { - numSub = 35 - } else { - numSub = 36 - } - // Use derived chain ID to create a proper Common - chainIdBigInt = BigInt(v - numSub) / BIGINT_2 - } - return chainIdBigInt + isValid(): boolean { + return Legacy.isValid(this) + } + + verifySignature(): boolean { + return Legacy.verifySignature(this) + } + + getSenderAddress(): Address { + return Legacy.getSenderAddress(this) + } + + sign(privateKey: Uint8Array): LegacyTx { + return Legacy.sign(this, privateKey) } /** * Return a compact error string representation of the object */ public errorStr() { - let errorStr = this._getSharedErrorPostfix() + let errorStr = Legacy.getSharedErrorPostfix(this) errorStr += ` gasPrice=${this.gasPrice}` return errorStr } - - /** - * Internal helper function to create an annotated error message - * - * @param msg Base error message - * @hidden - */ - protected _errorMsg(msg: string) { - return Legacy.errorMsg(this, msg) - } } diff --git a/packages/tx/src/types.ts b/packages/tx/src/types.ts index 118a5f6791..16f442da16 100644 --- a/packages/tx/src/types.ts +++ b/packages/tx/src/types.ts @@ -1,9 +1,9 @@ import { bytesToBigInt, toBytes } from '@ethereumjs/util' import type { FeeMarket1559Tx } from './1559/tx.js' -import type { AccessList2930Transaction } from './2930/tx.js' +import type { AccessList2930Tx } from './2930/tx.js' import type { Blob4844Tx } from './4844/tx.js' -import type { EOACode7702Transaction } from './7702/tx.js' +import type { EOACode7702Tx } from './7702/tx.js' import type { LegacyTx } from './legacy/tx.js' import type { Common, Hardfork, ParamsDict } from '@ethereumjs/common' import type { @@ -158,9 +158,9 @@ export enum TransactionType { export interface Transaction { [TransactionType.Legacy]: LegacyTx [TransactionType.FeeMarketEIP1559]: FeeMarket1559Tx - [TransactionType.AccessListEIP2930]: AccessList2930Transaction + [TransactionType.AccessListEIP2930]: AccessList2930Tx [TransactionType.BlobEIP4844]: Blob4844Tx - [TransactionType.EOACodeEIP7702]: EOACode7702Transaction + [TransactionType.EOACodeEIP7702]: EOACode7702Tx } export type TypedTransaction = Transaction[TransactionType] @@ -169,7 +169,7 @@ export function isLegacyTx(tx: TypedTransaction): tx is LegacyTx { return tx.type === TransactionType.Legacy } -export function isAccessList2930Tx(tx: TypedTransaction): tx is AccessList2930Transaction { +export function isAccessList2930Tx(tx: TypedTransaction): tx is AccessList2930Tx { return tx.type === TransactionType.AccessListEIP2930 } @@ -181,7 +181,7 @@ export function isBlob4844Tx(tx: TypedTransaction): tx is Blob4844Tx { return tx.type === TransactionType.BlobEIP4844 } -export function isEOACode7702Tx(tx: TypedTransaction): tx is EOACode7702Transaction { +export function isEOACode7702Tx(tx: TypedTransaction): tx is EOACode7702Tx { return tx.type === TransactionType.EOACodeEIP7702 } @@ -198,6 +198,7 @@ export interface TransactionInterface @@ -349,7 +357,7 @@ export type LegacyTxData = { } /** - * {@link AccessList2930Transaction} data. + * {@link AccessList2930Tx} data. */ export interface AccessList2930TxData extends LegacyTxData { /** @@ -433,7 +441,7 @@ export interface TxValuesArray { type LegacyTxValuesArray = Uint8Array[] /** - * Bytes values array for an {@link AccessList2930Transaction} + * Bytes values array for an {@link AccessList2930Tx} */ type AccessList2930TxValuesArray = [ Uint8Array, diff --git a/packages/tx/test/base.spec.ts b/packages/tx/test/base.spec.ts index a2117f10e5..42dac3fc99 100644 --- a/packages/tx/test/base.spec.ts +++ b/packages/tx/test/base.spec.ts @@ -12,8 +12,9 @@ import { } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' +import { valueBoundaryCheck } from '../src/features/util.js' import { - AccessList2930Transaction, + AccessList2930Tx, Capability, FeeMarket1559Tx, LegacyTx, @@ -34,24 +35,23 @@ import { eip1559TxsData } from './testData/eip1559txs.js' import { eip2930TxsData } from './testData/eip2930txs.js' import { txsData } from './testData/txs.js' -import type { BaseTransaction } from '../src/baseTransaction.js' import type { AccessList2930TxData, FeeMarketEIP1559TxData, LegacyTxData } from '../src/index.js' describe('[BaseTransaction]', () => { // EIP-2930 is not enabled in Common by default (2021-03-06) const common = new Common({ chain: Mainnet, hardfork: Hardfork.London }) - const legacyTxs: BaseTransaction[] = [] + const legacyTxs: LegacyTx[] = [] for (const tx of txsData.slice(0, 4)) { legacyTxs.push(createLegacyTx(tx.data as LegacyTxData, { common })) } - const eip2930Txs: BaseTransaction[] = [] + const eip2930Txs: AccessList2930Tx[] = [] for (const tx of eip2930TxsData) { eip2930Txs.push(createAccessList2930Tx(tx.data as AccessList2930TxData, { common })) } - const eip1559Txs: BaseTransaction[] = [] + const eip1559Txs: FeeMarket1559Tx[] = [] for (const tx of eip1559TxsData) { eip1559Txs.push(createFeeMarket1559Tx(tx.data as FeeMarketEIP1559TxData, { common })) } @@ -79,8 +79,8 @@ describe('[BaseTransaction]', () => { ], }, { - class: AccessList2930Transaction, - name: 'AccessList2930Transaction', + class: AccessList2930Tx, + name: 'AccessList2930Tx', type: TransactionType.AccessListEIP2930, values: [new Uint8Array([1])].concat(Array(7).fill(zero)), txs: eip2930Txs, @@ -324,6 +324,7 @@ describe('[BaseTransaction]', () => { ...txType.txs, // add unsigned variants ...txType.txs.map((tx) => + // @ts-ignore Not sure why this is now throwing txType.create.txData({ ...tx, v: undefined, @@ -434,10 +435,10 @@ describe('[BaseTransaction]', () => { assert.equal(tx.nonce, bytesToBigInt(bufferZero)) }) - it('_validateCannotExceedMaxInteger()', () => { - const tx = createFeeMarket1559Tx(eip1559Txs[0]) + // TODO: move this to a different file (not part of base transaction anymore) + it('valueBoundaryCheck()', () => { try { - ;(tx as any)._validateCannotExceedMaxInteger({ a: MAX_INTEGER }, 256, true) + valueBoundaryCheck({ a: MAX_INTEGER }, 256, true) } catch (err: any) { assert.ok( err.message.includes('equal or exceed MAX_INTEGER'), @@ -445,12 +446,12 @@ describe('[BaseTransaction]', () => { ) } try { - ;(tx as any)._validateCannotExceedMaxInteger({ a: MAX_INTEGER + BigInt(1) }, 256, false) + valueBoundaryCheck({ a: MAX_INTEGER + BigInt(1) }, 256, false) } catch (err: any) { assert.ok(err.message.includes('exceed MAX_INTEGER'), 'throws when value exceeds MAX_INTEGER') } try { - ;(tx as any)._validateCannotExceedMaxInteger({ a: BigInt(0) }, 100, false) + valueBoundaryCheck({ a: BigInt(0) }, 100, false) } catch (err: any) { assert.ok( err.message.includes('unimplemented bits value'), @@ -458,12 +459,12 @@ describe('[BaseTransaction]', () => { ) } try { - ;(tx as any)._validateCannotExceedMaxInteger({ a: MAX_UINT64 + BigInt(1) }, 64, false) + valueBoundaryCheck({ a: MAX_UINT64 + BigInt(1) }, 64, false) } catch (err: any) { assert.ok(err.message.includes('2^64'), 'throws when 64 bit integer exceeds MAX_UINT64') } try { - ;(tx as any)._validateCannotExceedMaxInteger({ a: MAX_UINT64 }, 64, true) + valueBoundaryCheck({ a: MAX_UINT64 }, 64, true) } catch (err: any) { assert.ok( err.message.includes('2^64'), diff --git a/packages/tx/test/transactionFactory.spec.ts b/packages/tx/test/transactionFactory.spec.ts index e2dbe5ddd9..7d06a27192 100644 --- a/packages/tx/test/transactionFactory.spec.ts +++ b/packages/tx/test/transactionFactory.spec.ts @@ -3,7 +3,7 @@ import { hexToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' import { - AccessList2930Transaction, + AccessList2930Tx, FeeMarket1559Tx, LegacyTx, TransactionType, @@ -40,8 +40,8 @@ const txTypes = [ type: TransactionType.Legacy, }, { - class: AccessList2930Transaction, - name: 'AccessList2930Transaction', + class: AccessList2930Tx, + name: 'AccessList2930Tx', unsigned: unsignedEIP2930Tx, signed: signedEIP2930Tx, eip2718: true, diff --git a/packages/tx/test/typedTxsAndEIP2930.spec.ts b/packages/tx/test/typedTxsAndEIP2930.spec.ts index 968bac39c2..b7d49166d9 100644 --- a/packages/tx/test/typedTxsAndEIP2930.spec.ts +++ b/packages/tx/test/typedTxsAndEIP2930.spec.ts @@ -15,7 +15,7 @@ import { import { assert, describe, it } from 'vitest' import { - AccessList2930Transaction, + AccessList2930Tx, FeeMarket1559Tx, TransactionType, createAccessList2930Tx, @@ -39,8 +39,8 @@ const common = new Common({ const txTypes = [ { - class: AccessList2930Transaction, - name: 'AccessList2930Transaction', + class: AccessList2930Tx, + name: 'AccessList2930Tx', type: TransactionType.AccessListEIP2930, create: { txData: createAccessList2930Tx, @@ -62,7 +62,7 @@ const validAddress = hexToBytes(`0x${'01'.repeat(20)}`) const validSlot = hexToBytes(`0x${'01'.repeat(32)}`) const chainId = 1 -describe('[AccessList2930Transaction / FeeMarket1559Tx] -> EIP-2930 Compatibility', () => { +describe('[AccessList2930Tx / FeeMarket1559Tx] -> EIP-2930 Compatibility', () => { it('Initialization / Getter -> fromTxData()', () => { for (const txType of txTypes) { let tx = txType.create.txData({}, { common }) @@ -426,7 +426,7 @@ describe('[AccessList2930Transaction / FeeMarket1559Tx] -> EIP-2930 Compatibilit }) }) -describe('[AccessList2930Transaction] -> Class Specific Tests', () => { +describe('[AccessList2930Tx] -> Class Specific Tests', () => { it(`Initialization`, () => { const tx = createAccessList2930Tx({}, { common }) assert.ok( diff --git a/packages/vm/src/runTx.ts b/packages/vm/src/runTx.ts index 365acf747b..3697f810ad 100644 --- a/packages/vm/src/runTx.ts +++ b/packages/vm/src/runTx.ts @@ -43,7 +43,7 @@ import type { Block } from '@ethereumjs/block' import type { Common, VerkleAccessWitnessInterface } from '@ethereumjs/common' import type { AccessList, - AccessList2930Transaction, + AccessList2930Tx, AccessListItem, EIP7702CompatibleTx, FeeMarket1559Tx, @@ -147,7 +147,7 @@ export async function runTx(vm: VM, opts: RunTxOpts): Promise { throw new Error(msg) } - const castedTx = opts.tx + const castedTx = opts.tx for (const accessListItem of castedTx.AccessListJSON) { vm.evm.journal.addAlwaysWarmAddress(accessListItem.address, true) diff --git a/packages/vm/test/api/EIPs/eip-1559-FeeMarket.spec.ts b/packages/vm/test/api/EIPs/eip-1559-FeeMarket.spec.ts index 2bda52068f..f921372100 100644 --- a/packages/vm/test/api/EIPs/eip-1559-FeeMarket.spec.ts +++ b/packages/vm/test/api/EIPs/eip-1559-FeeMarket.spec.ts @@ -1,6 +1,6 @@ import { createBlock } from '@ethereumjs/block' import { Common, Hardfork, Mainnet } from '@ethereumjs/common' -import { AccessList2930Transaction, FeeMarket1559Tx, LegacyTx } from '@ethereumjs/tx' +import { AccessList2930Tx, FeeMarket1559Tx, LegacyTx } from '@ethereumjs/tx' import { Account, Address, @@ -106,7 +106,7 @@ describe('EIP1559 tests', () => { assert.equal(account!.balance, expectedAccountBalance, 'account balance correct') assert.equal(results.amountSpent, expectedCost, 'reported cost correct') - const tx2 = new AccessList2930Transaction( + const tx2 = new AccessList2930Tx( { gasLimit: 21000, gasPrice: Units.gwei(5), diff --git a/packages/vm/test/api/runTx.spec.ts b/packages/vm/test/api/runTx.spec.ts index fb2e8de1af..17a9b963eb 100644 --- a/packages/vm/test/api/runTx.spec.ts +++ b/packages/vm/test/api/runTx.spec.ts @@ -3,7 +3,7 @@ import { Blockchain, createBlockchain } from '@ethereumjs/blockchain' import { Common, Goerli, Hardfork, Mainnet, createCommonFromGethGenesis } from '@ethereumjs/common' import { Blob4844Tx, - EOACode7702Transaction, + EOACode7702Tx, FeeMarket1559Tx, TransactionType, createFeeMarket1559Tx, @@ -231,9 +231,7 @@ describe('runTx() -> successful API parameter usage', async () => { // calculate expected coinbase balance const baseFee = block.header.baseFeePerGas! const inclusionFeePerGas = - tx instanceof FeeMarket1559Tx || - tx instanceof Blob4844Tx || - tx instanceof EOACode7702Transaction + tx instanceof FeeMarket1559Tx || tx instanceof Blob4844Tx || tx instanceof EOACode7702Tx ? tx.maxPriorityFeePerGas < tx.maxFeePerGas - baseFee ? tx.maxPriorityFeePerGas : tx.maxFeePerGas - baseFee diff --git a/packages/vm/test/util.ts b/packages/vm/test/util.ts index a478879cef..0e014203f6 100644 --- a/packages/vm/test/util.ts +++ b/packages/vm/test/util.ts @@ -30,9 +30,9 @@ import { keccak256 } from 'ethereum-cryptography/keccak' import type { BlockOptions } from '@ethereumjs/block' import type { StateManagerInterface } from '@ethereumjs/common' import type { - AccessList2930Transaction, + AccessList2930Tx, Blob4844Tx, - EOACode7702Transaction, + EOACode7702Tx, FeeMarket1559Tx, LegacyTx, TxOptions, @@ -128,7 +128,7 @@ export function format(a: any, toZero: boolean = false, isHex: boolean = false): export function makeTx( txData: any, opts?: TxOptions, -): EOACode7702Transaction | Blob4844Tx | FeeMarket1559Tx | AccessList2930Transaction | LegacyTx { +): EOACode7702Tx | Blob4844Tx | FeeMarket1559Tx | AccessList2930Tx | LegacyTx { let tx if (txData.authorizationList !== undefined) { // Convert `v` keys to `yParity`