From 9cf7a918129142858cbfe68d3a735313d2944694 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 24 Feb 2025 05:57:39 +0100 Subject: [PATCH 1/7] lint: add rule to disallow `new Error` throwing --- config/eslint.cjs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/config/eslint.cjs b/config/eslint.cjs index 050d7d2af89..410bef1dbc1 100644 --- a/config/eslint.cjs +++ b/config/eslint.cjs @@ -116,7 +116,13 @@ module.exports = { 'simple-import-sort/exports': 'error', 'sort-imports': ['error', { ignoreDeclarationSort: true }], 'ethereumjs/noBuffer': 'error', - 'no-restricted-syntax': 'off', + 'no-restricted-syntax': [ + 'error', + { + selector: "ThrowStatement > NewExpression[callee.name='Error']", + message: "Throwing default JS Errors is not allowed. It is only possible to throw `EthereumJSError` (see the util package)", + } + ] }, parserOptions: { extraFileExtensions: ['.json'], From be421b3984d4702dca493bb8f1fdef5429aea3c3 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 24 Feb 2025 06:05:17 +0100 Subject: [PATCH 2/7] util: introduce EthereumJSError --- packages/util/src/errors.ts | 70 +++++++++++++++++++++++++++++++++++++ packages/util/src/index.ts | 5 +++ 2 files changed, 75 insertions(+) create mode 100644 packages/util/src/errors.ts diff --git a/packages/util/src/errors.ts b/packages/util/src/errors.ts new file mode 100644 index 00000000000..62df158e4b6 --- /dev/null +++ b/packages/util/src/errors.ts @@ -0,0 +1,70 @@ +/** + * Generic EthereumJS error class with metadata attached + * + * Kudos to https://github.com/ChainSafe/lodestar monorepo + * for the inspiration :-) + * See: https://github.com/ChainSafe/lodestar/blob/unstable/packages/utils/src/errors.ts + */ +export type EthereumJSErrorMetaData = Record +export type EthereumJSErrorObject = { + message: string + stack: string + className: string + type: EthereumJSErrorMetaData +} + +// In order to update all our errors to use `EthereumJSError`, temporarily include the +// unset error code. All errors throwing this code should be updated to use the relevant +// error code. +export const UNSET_ERROR_CODE = 'ETHEREUMJS_UNSET_ERROR_CODE' + +/** + * Generic EthereumJS error with attached metadata + */ +export class EthereumJSError extends Error { + type: T + constructor(type: T, message?: string, stack?: string) { + super(message ?? type.code) + this.type = type + if (stack !== undefined) this.stack = stack + } + + getMetadata(): EthereumJSErrorMetaData { + return this.type + } + + /** + * Get the metadata and the stacktrace for the error. + */ + toObject(): EthereumJSErrorObject { + return { + type: this.getMetadata(), + message: this.message ?? '', + stack: this.stack ?? '', + className: this.constructor.name, + } + } +} + +/** + * @deprecated Use `EthereumJSError` with a set error code instead + * @param message Optional error message + * @param stack Optional stack trace + * @returns + */ +export function EthereumJSErrorUnsetCode(message?: string, stack?: string) { + return new EthereumJSError({ code: UNSET_ERROR_CODE }, message, stack) +} + +// Below here: specific monorepo-wide errors (examples and commented out) + +/*export enum UsageErrorType { + UNSUPPORTED_FEATURE = 'unsupported feature', +}* + +/** + * Error along API Usage + * + * Use directly or in a subclassed context for error comparison (`e instanceof UsageError`) + */ +//export class UsageError extends EthereumJSError<{ code: UsageErrorType }> {} diff --git a/packages/util/src/index.ts b/packages/util/src/index.ts index 50316890362..234667b00ca 100644 --- a/packages/util/src/index.ts +++ b/packages/util/src/index.ts @@ -3,6 +3,11 @@ */ export * from './constants.js' +/** + * Errors + */ +export * from './errors.js' + /** * Units helpers */ From 8585be68da06ead6f8be5ce33f5d7aee6919feda Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 24 Feb 2025 06:58:46 +0100 Subject: [PATCH 3/7] monorepo: throw EthereumJSErrors, not default js errors --- config/eslint.cjs | 22 +++++- packages/binarytree/src/binaryTree.ts | 37 +++++---- packages/binarytree/src/node/internalNode.ts | 12 +-- packages/binarytree/src/node/stemNode.ts | 5 +- packages/binarytree/src/node/util.ts | 5 +- packages/block/src/block/block.ts | 41 +++++----- packages/block/src/block/constructors.ts | 15 ++-- packages/block/src/consensus/clique.ts | 5 +- packages/block/src/header/constructors.ts | 14 ++-- packages/block/src/header/header.ts | 75 ++++++++++--------- packages/block/src/helpers.ts | 18 +++-- packages/blockchain/src/blockchain.ts | 59 ++++++++------- packages/blockchain/src/consensus/casper.ts | 4 +- packages/blockchain/src/consensus/clique.ts | 21 +++--- packages/blockchain/src/consensus/ethash.ts | 8 +- packages/blockchain/src/constructors.ts | 4 +- packages/blockchain/src/db/manager.ts | 15 ++-- packages/blockchain/src/db/operation.ts | 4 +- packages/client/bin/cli.ts | 26 ++++--- packages/client/bin/startRPC.ts | 9 ++- packages/client/bin/utils.ts | 13 ++-- packages/client/src/blockchain/chain.ts | 22 +++--- packages/client/src/execution/receipt.ts | 7 +- packages/client/src/execution/vmexecution.ts | 13 +++- packages/client/src/net/peer/peer.ts | 4 +- .../client/src/net/protocol/boundprotocol.ts | 10 +-- .../client/src/net/protocol/ethprotocol.ts | 3 +- packages/client/src/net/protocol/protocol.ts | 12 +-- packages/client/src/net/protocol/sender.ts | 5 +- packages/client/src/rpc/modules/debug.ts | 11 ++- .../client/src/rpc/modules/engine/engine.ts | 5 +- .../src/rpc/modules/engine/util/newPayload.ts | 18 +++-- packages/client/src/rpc/modules/eth.ts | 27 ++++--- packages/client/src/rpc/validation.ts | 4 +- packages/client/src/service/skeleton.ts | 7 +- packages/client/src/service/txpool.ts | 29 +++---- packages/client/src/util/parse.ts | 6 +- packages/common/src/common.ts | 24 +++--- packages/common/src/utils.ts | 14 ++-- packages/devp2p/src/dns/dns.ts | 7 +- packages/devp2p/src/dns/enr.ts | 32 ++++---- packages/devp2p/src/dpt/dpt.ts | 9 ++- packages/devp2p/src/dpt/message.ts | 17 +++-- packages/devp2p/src/dpt/server.ts | 8 +- packages/devp2p/src/protocol/eth.ts | 21 +++--- packages/devp2p/src/protocol/snap.ts | 4 +- packages/devp2p/src/rlpx/ecies.ts | 10 ++- packages/devp2p/src/rlpx/peer.ts | 5 +- packages/devp2p/src/rlpx/rlpx.ts | 12 ++- packages/devp2p/src/util.ts | 12 ++- packages/era/src/e2store.ts | 16 +++- packages/era/src/era1.ts | 10 ++- packages/ethash/src/index.ts | 7 +- packages/evm/src/eof/container.ts | 10 ++- packages/evm/src/eof/errors.ts | 48 ++++++------ packages/evm/src/evm.ts | 11 +-- packages/evm/src/interpreter.ts | 15 ++-- packages/evm/src/journal.ts | 3 +- packages/evm/src/logger.ts | 10 ++- packages/evm/src/memory.ts | 7 +- packages/evm/src/message.ts | 6 +- packages/evm/src/opcodes/codes.ts | 5 +- .../precompiles/0a-kzg-point-evaluation.ts | 3 +- packages/evm/src/transientStorage.ts | 10 +-- packages/evm/src/verkleAccessWitness.ts | 3 +- packages/mpt/src/mpt.ts | 27 ++++--- packages/mpt/src/node/util.ts | 7 +- packages/mpt/src/proof/proof.ts | 10 +-- packages/mpt/src/proof/range.ts | 44 ++++++----- packages/mpt/src/util/walkController.ts | 6 +- packages/statemanager/src/cache/storage.ts | 6 +- .../statemanager/src/merkleStateManager.ts | 19 ++--- packages/statemanager/src/proof/merkle.ts | 21 +++--- packages/statemanager/src/proof/verkle.ts | 4 +- packages/statemanager/src/rpcStateManager.ts | 10 ++- .../statemanager/src/simpleStateManager.ts | 8 +- .../src/statefulVerkleStateManager.ts | 18 +++-- .../src/statelessVerkleStateManager.ts | 7 +- packages/tx/src/1559/constructors.ts | 14 +++- packages/tx/src/1559/tx.ts | 9 ++- packages/tx/src/2930/constructors.ts | 14 +++- packages/tx/src/2930/tx.ts | 7 +- packages/tx/src/4844/constructors.ts | 51 ++++++++----- packages/tx/src/4844/tx.ts | 23 +++--- packages/tx/src/7702/constructors.ts | 14 +++- packages/tx/src/7702/tx.ts | 11 +-- packages/tx/src/capabilities/eip1559.ts | 4 +- packages/tx/src/capabilities/eip2718.ts | 4 +- packages/tx/src/capabilities/legacy.ts | 11 +-- packages/tx/src/features/util.ts | 19 +++-- packages/tx/src/legacy/constructors.ts | 6 +- packages/tx/src/legacy/tx.ts | 11 +-- packages/tx/src/transactionFactory.ts | 12 +-- packages/tx/src/util.ts | 35 +++++---- packages/util/src/account.ts | 47 ++++++------ packages/util/src/address.ts | 17 +++-- packages/util/src/bytes.ts | 17 +++-- packages/util/src/helpers.ts | 9 ++- packages/util/src/internal.ts | 25 +++++-- packages/util/src/provider.ts | 6 +- packages/util/src/signature.ts | 13 ++-- packages/util/src/types.ts | 9 ++- packages/util/src/units.ts | 5 +- packages/verkle/src/node/internalNode.ts | 6 +- packages/verkle/src/node/leafNode.ts | 6 +- packages/verkle/src/node/util.ts | 8 +- packages/verkle/src/verkleTree.ts | 48 +++++++----- packages/vm/src/bloom/index.ts | 4 +- packages/vm/src/buildBlock.ts | 13 ++-- packages/vm/src/constructors.ts | 12 ++- packages/vm/src/requests.ts | 3 +- packages/vm/src/runBlock.ts | 33 +++++--- packages/vm/src/runTx.ts | 27 +++---- 113 files changed, 993 insertions(+), 681 deletions(-) diff --git a/config/eslint.cjs b/config/eslint.cjs index 410bef1dbc1..3a5e84136fb 100644 --- a/config/eslint.cjs +++ b/config/eslint.cjs @@ -135,6 +135,7 @@ module.exports = { rules: { 'implicit-dependencies/no-implicit': 'off', 'import/no-extraneous-dependencies': 'off', + 'no-restricted-syntax': 'off', }, }, { @@ -144,6 +145,7 @@ module.exports = { 'import/no-extraneous-dependencies': 'off', 'no-console': 'off', '@typescript-eslint/no-unused-vars': 'off', + 'no-restricted-syntax': 'off' }, }, { @@ -151,7 +153,6 @@ module.exports = { rules: { '@typescript-eslint/no-use-before-define': 'off', 'no-invalid-this': 'off', - 'no-restricted-syntax': 'off', }, }, { @@ -163,11 +164,30 @@ module.exports = { '@typescript-eslint/no-unused-vars': 'off', }, }, + { + files: ['packages/devp2p/src/ext/**'], + rules: { + 'no-restricted-syntax': 'off' + }, + }, + { + files: ['packages/client/src/ext/**'], + rules: { + 'no-restricted-syntax': 'off' + }, + }, { files: ['packages/wallet/**'], rules: { 'github/array-foreach': 'warn', 'no-prototype-builtins': 'warn', + 'no-restricted-syntax': 'off' + }, + }, + { + files: ['packages/rlp/**'], + rules: { + 'no-restricted-syntax': 'off' }, }, ], diff --git a/packages/binarytree/src/binaryTree.ts b/packages/binarytree/src/binaryTree.ts index ceb148003bd..f96037dc912 100644 --- a/packages/binarytree/src/binaryTree.ts +++ b/packages/binarytree/src/binaryTree.ts @@ -1,4 +1,5 @@ import { + EthereumJSErrorUnsetCode, Lock, bitsToBytes, bytesToBits, @@ -55,7 +56,7 @@ export class BinaryTree { this._opts = opts if (opts.db instanceof CheckpointDB) { - throw new Error('Cannot pass in an instance of CheckpointDB') + throw EthereumJSErrorUnsetCode('Cannot pass in an instance of CheckpointDB') } this._db = new CheckpointDB({ db: opts.db, cacheSize: opts.cacheSize }) @@ -97,7 +98,7 @@ export class BinaryTree { } if (value.length !== this._hashLen) { - throw new Error(`Invalid root length. Roots are ${this._hashLen} bytes`) + throw EthereumJSErrorUnsetCode(`Invalid root length. Roots are ${this._hashLen} bytes`) } this._root = value @@ -130,7 +131,8 @@ export class BinaryTree { * If the stem is not found, will return an empty array. */ async get(stem: Uint8Array, suffixes: number[]): Promise<(Uint8Array | null)[]> { - if (stem.length !== 31) throw new Error(`expected stem with length 31; got ${stem.length}`) + if (stem.length !== 31) + throw EthereumJSErrorUnsetCode(`expected stem with length 31; got ${stem.length}`) this.DEBUG && this.debug(`Stem: ${bytesToHex(stem)}; Suffix: ${suffixes}`, ['get']) const stemPath = await this.findPath(stem) if (stemPath.node instanceof StemBinaryNode) { @@ -159,9 +161,10 @@ export class BinaryTree { * @returns A Promise that resolves once the value is stored. */ async put(stem: Uint8Array, suffixes: number[], values: (Uint8Array | null)[]): Promise { - if (stem.length !== 31) throw new Error(`expected stem with length 31, got ${stem.length}`) + if (stem.length !== 31) + throw EthereumJSErrorUnsetCode(`expected stem with length 31, got ${stem.length}`) if (values.length > 0 && values.length !== suffixes.length) - throw new Error( + throw EthereumJSErrorUnsetCode( `expected number of values (${values.length}) to equal number of suffixes (${suffixes.length})`, ) @@ -178,7 +181,7 @@ export class BinaryTree { const foundPath = await this.findPath(stem) // We should always at least get the root node back - if (foundPath.stack.length === 0) throw new Error(`Root node not found in trie`) + if (foundPath.stack.length === 0) throw EthereumJSErrorUnsetCode(`Root node not found in trie`) // Step 1) Create or update the stem node let stemNode: StemBinaryNode @@ -259,7 +262,9 @@ export class BinaryTree { this.DEBUG && this.debug(`Updated parent internal node hash for path ${path.join(',')}`, ['put']) } else { - throw new Error(`Expected internal node at path ${path.join(',')}, got ${node}`) + throw EthereumJSErrorUnsetCode( + `Expected internal node at path ${path.join(',')}, got ${node}`, + ) } } @@ -419,7 +424,7 @@ export class BinaryTree { // Get the root node. let rawNode = await this._db.get(this.root()) - if (rawNode === undefined) throw new Error('root node should exist') + if (rawNode === undefined) throw EthereumJSErrorUnsetCode('root node should exist') const rootNode = decodeBinaryNode(rawNode) this.DEBUG && this.debug(`Starting with Root Node: [${bytesToHex(this.root())}]`, ['find_path']) @@ -450,7 +455,7 @@ export class BinaryTree { // Look up child node by its node hash. rawNode = await this._db.get(childNode.hash) - if (rawNode === undefined) throw new Error(`missing node at ${childNode.path}`) + if (rawNode === undefined) throw EthereumJSErrorUnsetCode(`missing node at ${childNode.path}`) const decodedNode = decodeBinaryNode(rawNode) // Determine how many bits match between keyInBits and the stored path in childNode. @@ -577,7 +582,7 @@ export class BinaryTree { * @param proof */ async fromProof(_proof: any): Promise { - throw new Error('Not implemented') + throw EthereumJSErrorUnsetCode('Not implemented') } /** @@ -585,7 +590,7 @@ export class BinaryTree { * @param key */ async createBinaryProof(_key: Uint8Array): Promise { - throw new Error('Not implemented') + throw EthereumJSErrorUnsetCode('Not implemented') } /** @@ -601,7 +606,7 @@ export class BinaryTree { _key: Uint8Array, _proof: any, ): Promise { - throw new Error('Not implemented') + throw EthereumJSErrorUnsetCode('Not implemented') } /** @@ -609,7 +614,7 @@ export class BinaryTree { * @return Returns a [stream](https://nodejs.org/dist/latest-v12.x/docs/api/stream.html#stream_class_stream_readable) of the contents of the `tree` */ createReadStream(): any { - throw new Error('Not implemented') + throw EthereumJSErrorUnsetCode('Not implemented') } /** @@ -668,7 +673,7 @@ export class BinaryTree { */ async commit(): Promise { if (!this.hasCheckpoints()) { - throw new Error('trying to commit when not checkpointed') + throw EthereumJSErrorUnsetCode('trying to commit when not checkpointed') } await this._lock.acquire() @@ -684,7 +689,7 @@ export class BinaryTree { */ async revert(): Promise { if (!this.hasCheckpoints()) { - throw new Error('trying to revert when not checkpointed') + throw EthereumJSErrorUnsetCode('trying to revert when not checkpointed') } await this._lock.acquire() @@ -707,7 +712,7 @@ export class BinaryTree { } if (msg.length !== 32 && msg.length !== 64) { - throw new Error('Data must be 32 or 64 bytes') + throw EthereumJSErrorUnsetCode('Data must be 32 or 64 bytes') } return Uint8Array.from(this._opts.hashFunction.call(undefined, msg)) diff --git a/packages/binarytree/src/node/internalNode.ts b/packages/binarytree/src/node/internalNode.ts index fe46741f7dd..a2a370ed3ac 100644 --- a/packages/binarytree/src/node/internalNode.ts +++ b/packages/binarytree/src/node/internalNode.ts @@ -1,5 +1,5 @@ import { RLP } from '@ethereumjs/rlp' -import { bitsToBytes, bytesToBits } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, bitsToBytes, bytesToBits } from '@ethereumjs/util' import { BinaryNodeType } from './types.js' @@ -17,13 +17,13 @@ export class InternalBinaryNode { static fromRawNode(rawNode: Uint8Array[]): InternalBinaryNode { const nodeType = rawNode[0][0] if (nodeType !== BinaryNodeType.Internal) { - throw new Error('Invalid node type') + throw EthereumJSErrorUnsetCode('Invalid node type') } // The length of the rawNode should be the # of children * 2 (for hash and path) + 1 for the node type if (rawNode.length !== 2 * 2 + 1) { - throw new Error('Invalid node length') + throw EthereumJSErrorUnsetCode('Invalid node length') } const [, leftChildHash, rightChildHash, leftChildRawPath, rightChildRawPath] = rawNode @@ -32,13 +32,13 @@ export class InternalBinaryNode { const decoded = RLP.decode(rawPath) if (!Array.isArray(decoded) || decoded.length !== 2) { - throw new Error('Invalid RLP encoding for child path') + throw EthereumJSErrorUnsetCode('Invalid RLP encoding for child path') } const [encodedLength, encodedPath] = decoded as Uint8Array[] if (encodedLength.length !== 1) { - throw new Error('Invalid path length encoding') + throw EthereumJSErrorUnsetCode('Invalid path length encoding') } const pathLength = encodedLength[0] @@ -62,7 +62,7 @@ export class InternalBinaryNode { */ static create(children?: (ChildBinaryNode | null)[]): InternalBinaryNode { if (children !== undefined && children.length !== 2) { - throw new Error('Internal node must have 2 children') + throw EthereumJSErrorUnsetCode('Internal node must have 2 children') } return new InternalBinaryNode({ children }) } diff --git a/packages/binarytree/src/node/stemNode.ts b/packages/binarytree/src/node/stemNode.ts index a8a23918d1b..d4eb7589bef 100644 --- a/packages/binarytree/src/node/stemNode.ts +++ b/packages/binarytree/src/node/stemNode.ts @@ -1,4 +1,5 @@ import { RLP } from '@ethereumjs/rlp' +import { EthereumJSErrorUnsetCode } from '@ethereumjs/util' import { BinaryNodeType, NODE_WIDTH } from './types.js' @@ -18,12 +19,12 @@ export class StemBinaryNode { static fromRawNode(rawNode: Uint8Array[]): StemBinaryNode { const nodeType = rawNode[0][0] if (nodeType !== BinaryNodeType.Stem) { - throw new Error('Invalid node type') + throw EthereumJSErrorUnsetCode('Invalid node type') } // The length of the rawNode should be the # of values (node width) + 2 for the node type and the stem if (rawNode.length !== NODE_WIDTH + 2) { - throw new Error('Invalid node length') + throw EthereumJSErrorUnsetCode('Invalid node length') } const stem = rawNode[1] diff --git a/packages/binarytree/src/node/util.ts b/packages/binarytree/src/node/util.ts index 20b5e6ec058..a1dc82b7ca6 100644 --- a/packages/binarytree/src/node/util.ts +++ b/packages/binarytree/src/node/util.ts @@ -1,4 +1,5 @@ import { RLP } from '@ethereumjs/rlp' +import { EthereumJSErrorUnsetCode } from '@ethereumjs/util' import { InternalBinaryNode } from './internalNode.js' import { StemBinaryNode } from './stemNode.js' @@ -12,14 +13,14 @@ export function decodeRawBinaryNode(raw: Uint8Array[]): BinaryNode { case BinaryNodeType.Stem: return StemBinaryNode.fromRawNode(raw) default: - throw new Error('Invalid node type') + throw EthereumJSErrorUnsetCode('Invalid node type') } } export function decodeBinaryNode(raw: Uint8Array) { const decoded = RLP.decode(Uint8Array.from(raw)) as Uint8Array[] if (!Array.isArray(decoded)) { - throw new Error('Invalid node') + throw EthereumJSErrorUnsetCode('Invalid node') } return decodeRawBinaryNode(decoded) } diff --git a/packages/block/src/block/block.ts b/packages/block/src/block/block.ts index 454e2c40227..7437a60db40 100644 --- a/packages/block/src/block/block.ts +++ b/packages/block/src/block/block.ts @@ -4,6 +4,7 @@ import { RLP } from '@ethereumjs/rlp' import { Blob4844Tx, Capability } from '@ethereumjs/tx' import { BIGINT_0, + EthereumJSErrorUnsetCode, KECCAK256_RLP, KECCAK256_RLP_ARRAY, bytesToHex, @@ -123,18 +124,18 @@ export class Block { const msg = this._errorMsg( 'Block initialization with uncleHeaders on a PoA network is not allowed', ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } if (this.common.consensusType() === ConsensusType.ProofOfStake) { const msg = this._errorMsg( 'Block initialization with uncleHeaders on a PoS network is not allowed', ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } if (!this.common.isActivatedEIP(4895) && withdrawals !== undefined) { - throw new Error('Cannot have a withdrawals field if EIP 4895 is not active') + throw EthereumJSErrorUnsetCode('Cannot have a withdrawals field if EIP 4895 is not active') } if ( @@ -142,7 +143,9 @@ export class Block { executionWitness !== undefined && executionWitness !== null ) { - throw new Error(`Cannot have executionWitness field if EIP 6800 is not active `) + throw EthereumJSErrorUnsetCode( + `Cannot have executionWitness field if EIP 6800 is not active `, + ) } const freeze = opts?.freeze ?? true @@ -299,7 +302,7 @@ export class Block { const txErrors = this.getTransactionsValidationErrors() if (txErrors.length > 0) { const msg = this._errorMsg(`invalid transactions: ${txErrors.join(' ')}`) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } @@ -313,24 +316,24 @@ export class Block { const msg = this._errorMsg( `invalid transactions: transaction at index ${index} is unsigned`, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } } if (!(await this.transactionsTrieIsValid())) { const msg = this._errorMsg('invalid transaction trie') - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } if (!this.uncleHashIsValid()) { const msg = this._errorMsg('invalid uncle hash') - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } if (this.common.isActivatedEIP(4895) && !(await this.withdrawalsTrieIsValid())) { const msg = this._errorMsg('invalid withdrawals trie') - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } // Validation for Verkle blocks @@ -338,10 +341,12 @@ export class Block { // TODO: Decide if we should actually require this or not if (this.common.isActivatedEIP(6800)) { if (this.executionWitness === undefined) { - throw new Error(`Invalid block: missing executionWitness`) + throw EthereumJSErrorUnsetCode(`Invalid block: missing executionWitness`) } if (this.executionWitness === null) { - throw new Error(`Invalid block: ethereumjs stateless client needs executionWitness`) + throw EthereumJSErrorUnsetCode( + `Invalid block: ethereumjs stateless client needs executionWitness`, + ) } } } @@ -360,7 +365,7 @@ export class Block { const expectedExcessBlobGas = parentHeader.calcNextExcessBlobGas(this.common) if (this.header.excessBlobGas !== expectedExcessBlobGas) { - throw new Error( + throw EthereumJSErrorUnsetCode( `block excessBlobGas mismatch: have ${this.header.excessBlobGas}, want ${expectedExcessBlobGas}`, ) } @@ -371,7 +376,7 @@ export class Block { if (tx instanceof Blob4844Tx) { blobGasPrice = blobGasPrice ?? this.header.getBlobGasPrice() if (tx.maxFeePerBlobGas < blobGasPrice) { - throw new Error( + throw EthereumJSErrorUnsetCode( `blob transaction maxFeePerBlobGas ${ tx.maxFeePerBlobGas } < than block blob gas price ${blobGasPrice} - ${this.errorStr()}`, @@ -381,7 +386,7 @@ export class Block { blobGasUsed += BigInt(tx.blobVersionedHashes.length) * blobGasPerBlob if (blobGasUsed > blobGasLimit) { - throw new Error( + throw EthereumJSErrorUnsetCode( `tx causes total blob gas of ${blobGasUsed} to exceed maximum blob gas per block of ${blobGasLimit}`, ) } @@ -389,7 +394,7 @@ export class Block { } if (this.header.blobGasUsed !== blobGasUsed) { - throw new Error( + throw EthereumJSErrorUnsetCode( `block blobGasUsed mismatch: have ${this.header.blobGasUsed}, want ${blobGasUsed}`, ) } @@ -415,7 +420,7 @@ export class Block { */ async withdrawalsTrieIsValid(): Promise { if (!this.common.isActivatedEIP(4895)) { - throw new Error('EIP 4895 is not activated') + throw EthereumJSErrorUnsetCode('EIP 4895 is not activated') } let result @@ -451,14 +456,14 @@ export class Block { // Header has at most 2 uncles if (this.uncleHeaders.length > 2) { const msg = this._errorMsg('too many uncle headers') - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } // Header does not count an uncle twice. const uncleHashes = this.uncleHeaders.map((header) => bytesToHex(header.hash())) if (!(new Set(uncleHashes).size === uncleHashes.length)) { const msg = this._errorMsg('duplicate uncles') - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } diff --git a/packages/block/src/block/constructors.ts b/packages/block/src/block/constructors.ts index 86920243db5..263412d228a 100644 --- a/packages/block/src/block/constructors.ts +++ b/packages/block/src/block/constructors.ts @@ -8,6 +8,7 @@ import { normalizeTxParams, } from '@ethereumjs/tx' import { + EthereumJSErrorUnsetCode, bigIntToHex, bytesToHex, bytesToUtf8, @@ -118,7 +119,9 @@ export function createEmptyBlock(headerData: HeaderData, opts?: BlockOptions) { */ export function createBlockFromBytesArray(values: BlockBytes, opts?: BlockOptions) { if (values.length > 5) { - throw new Error(`invalid More values=${values.length} than expected were received (at most 5)`) + throw EthereumJSErrorUnsetCode( + `invalid More values=${values.length} than expected were received (at most 5)`, + ) } // First try to load header so that we can use its common (in case of setHardfork being activated) @@ -140,13 +143,13 @@ export function createBlockFromBytesArray(values: BlockBytes, opts?: BlockOption header.common.isActivatedEIP(4895) && (withdrawalBytes === undefined || !Array.isArray(withdrawalBytes)) ) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'Invalid serialized block input: EIP-4895 is active, and no withdrawals were provided as array', ) } if (header.common.isActivatedEIP(6800) && executionWitnessBytes === undefined) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'Invalid serialized block input: EIP-6800 is active, and execution witness is undefined', ) } @@ -217,7 +220,7 @@ export function createBlockFromRLP(serialized: Uint8Array, opts?: BlockOptions) const values = RLP.decode(Uint8Array.from(serialized)) as BlockBytes if (!Array.isArray(values)) { - throw new Error('Invalid serialized block input. Must be array') + throw EthereumJSErrorUnsetCode('Invalid serialized block input. Must be array') } return createBlockFromBytesArray(values, opts) @@ -291,13 +294,13 @@ export const createBlockFromJSONRPCProvider = async ( params: [blockTag, true], }) } else { - throw new Error( + throw EthereumJSErrorUnsetCode( `expected blockTag to be block hash, bigint, hex prefixed string, or earliest/latest/pending; got ${blockTag}`, ) } if (blockData === null) { - throw new Error('No block data returned from provider') + throw EthereumJSErrorUnsetCode('No block data returned from provider') } const uncleHeaders = [] diff --git a/packages/block/src/consensus/clique.ts b/packages/block/src/consensus/clique.ts index 3a89a463192..d9f8f59d596 100644 --- a/packages/block/src/consensus/clique.ts +++ b/packages/block/src/consensus/clique.ts @@ -4,6 +4,7 @@ import { Address, BIGINT_0, BIGINT_27, + EthereumJSErrorUnsetCode, bigIntToBytes, bytesToBigInt, concatBytes, @@ -28,7 +29,7 @@ export function requireClique(header: BlockHeader, name: string) { const msg = header['_errorMsg']( `BlockHeader.${name}() call only supported for clique PoA networks`, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } @@ -84,7 +85,7 @@ export function cliqueEpochTransitionSigners(header: BlockHeader): Address[] { requireClique(header, 'cliqueEpochTransitionSigners') if (!cliqueIsEpochTransition(header)) { const msg = header['_errorMsg']('Signers are only included in epoch transition blocks (clique)') - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } const start = CLIQUE_EXTRA_VANITY diff --git a/packages/block/src/header/constructors.ts b/packages/block/src/header/constructors.ts index 2cf6891e53d..3b97f02f44a 100644 --- a/packages/block/src/header/constructors.ts +++ b/packages/block/src/header/constructors.ts @@ -1,5 +1,5 @@ import { RLP } from '@ethereumjs/rlp' -import { bigIntToBytes, equalsBytes } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, bigIntToBytes, equalsBytes } from '@ethereumjs/util' import { generateCliqueBlockExtraData } from '../consensus/clique.js' import { numberToHex, valuesArrayToHeaderData } from '../helpers.js' @@ -34,22 +34,22 @@ export function createBlockHeaderFromBytesArray(values: BlockHeaderBytes, opts: eip1559ActivationBlock !== undefined && equalsBytes(eip1559ActivationBlock, number as Uint8Array) ) { - throw new Error('invalid header. baseFeePerGas should be provided') + throw EthereumJSErrorUnsetCode('invalid header. baseFeePerGas should be provided') } } if (header.common.isActivatedEIP(4844)) { if (excessBlobGas === undefined) { - throw new Error('invalid header. excessBlobGas should be provided') + throw EthereumJSErrorUnsetCode('invalid header. excessBlobGas should be provided') } else if (blobGasUsed === undefined) { - throw new Error('invalid header. blobGasUsed should be provided') + throw EthereumJSErrorUnsetCode('invalid header. blobGasUsed should be provided') } } if (header.common.isActivatedEIP(4788) && parentBeaconBlockRoot === undefined) { - throw new Error('invalid header. parentBeaconBlockRoot should be provided') + throw EthereumJSErrorUnsetCode('invalid header. parentBeaconBlockRoot should be provided') } if (header.common.isActivatedEIP(7685) && requestsHash === undefined) { - throw new Error('invalid header. requestsHash should be provided') + throw EthereumJSErrorUnsetCode('invalid header. requestsHash should be provided') } return header } @@ -66,7 +66,7 @@ export function createBlockHeaderFromRLP( ) { const values = RLP.decode(serializedHeaderData) if (!Array.isArray(values)) { - throw new Error('Invalid serialized header input. Must be array') + throw EthereumJSErrorUnsetCode('Invalid serialized header input. Must be array') } return createBlockHeaderFromBytesArray(values as Uint8Array[], opts) } diff --git a/packages/block/src/header/header.ts b/packages/block/src/header/header.ts index e0fe7f88337..b416e89a8fb 100644 --- a/packages/block/src/header/header.ts +++ b/packages/block/src/header/header.ts @@ -6,6 +6,7 @@ import { BIGINT_1, BIGINT_2, BIGINT_7, + EthereumJSErrorUnsetCode, KECCAK256_RLP, KECCAK256_RLP_ARRAY, SHA256_NULL, @@ -79,7 +80,7 @@ export class BlockHeader { const msg = this._errorMsg( 'The prevRandao parameter can only be accessed when EIP-4399 is activated', ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } return this.mixHash } @@ -182,33 +183,37 @@ export class BlockHeader { toType(headerData.requestsHash, TypeOutput.Uint8Array) ?? hardforkDefaults.requestsHash if (!this.common.isActivatedEIP(1559) && baseFeePerGas !== undefined) { - throw new Error('A base fee for a block can only be set with EIP1559 being activated') + throw EthereumJSErrorUnsetCode( + 'A base fee for a block can only be set with EIP1559 being activated', + ) } if (!this.common.isActivatedEIP(4895) && withdrawalsRoot !== undefined) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'A withdrawalsRoot for a header can only be provided with EIP4895 being activated', ) } if (!this.common.isActivatedEIP(4844)) { if (blobGasUsed !== undefined) { - throw new Error('blob gas used can only be provided with EIP4844 activated') + throw EthereumJSErrorUnsetCode('blob gas used can only be provided with EIP4844 activated') } if (excessBlobGas !== undefined) { - throw new Error('excess blob gas can only be provided with EIP4844 activated') + throw EthereumJSErrorUnsetCode( + 'excess blob gas can only be provided with EIP4844 activated', + ) } } if (!this.common.isActivatedEIP(4788) && parentBeaconBlockRoot !== undefined) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'A parentBeaconBlockRoot for a header can only be provided with EIP4788 being activated', ) } if (!this.common.isActivatedEIP(7685) && requestsHash !== undefined) { - throw new Error('requestsHash can only be provided with EIP 7685 activated') + throw EthereumJSErrorUnsetCode('requestsHash can only be provided with EIP 7685 activated') } this.parentHash = parentHash @@ -262,32 +267,32 @@ export class BlockHeader { if (parentHash.length !== 32) { const msg = this._errorMsg(`parentHash must be 32 bytes, received ${parentHash.length} bytes`) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } if (stateRoot.length !== 32) { const msg = this._errorMsg(`stateRoot must be 32 bytes, received ${stateRoot.length} bytes`) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } if (transactionsTrie.length !== 32) { const msg = this._errorMsg( `transactionsTrie must be 32 bytes, received ${transactionsTrie.length} bytes`, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } if (receiptTrie.length !== 32) { const msg = this._errorMsg( `receiptTrie must be 32 bytes, received ${receiptTrie.length} bytes`, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } if (mixHash.length !== 32) { const msg = this._errorMsg(`mixHash must be 32 bytes, received ${mixHash.length} bytes`) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } if (nonce.length !== 8) { const msg = this._errorMsg(`nonce must be 8 bytes, received ${nonce.length} bytes`) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } // check if the block used too much gas @@ -295,14 +300,14 @@ export class BlockHeader { const msg = this._errorMsg( `Invalid block: too much gas used. Used: ${this.gasUsed}, gas limit: ${this.gasLimit}`, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } // Validation for EIP-1559 blocks if (this.common.isActivatedEIP(1559)) { if (typeof this.baseFeePerGas !== 'bigint') { const msg = this._errorMsg('EIP1559 block has no base fee field') - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } const londonHfBlock = this.common.hardforkBlock(Hardfork.London) if ( @@ -313,7 +318,7 @@ export class BlockHeader { const initialBaseFee = this.common.param('initialBaseFee') if (this.baseFeePerGas !== initialBaseFee) { const msg = this._errorMsg('Initial EIP1559 block does not have initial base fee') - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } } @@ -321,20 +326,20 @@ export class BlockHeader { if (this.common.isActivatedEIP(4895)) { if (this.withdrawalsRoot === undefined) { const msg = this._errorMsg('EIP4895 block has no withdrawalsRoot field') - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } if (this.withdrawalsRoot?.length !== 32) { const msg = this._errorMsg( `withdrawalsRoot must be 32 bytes, received ${this.withdrawalsRoot!.length} bytes`, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } if (this.common.isActivatedEIP(4788)) { if (this.parentBeaconBlockRoot === undefined) { const msg = this._errorMsg('EIP4788 block has no parentBeaconBlockRoot field') - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } if (this.parentBeaconBlockRoot?.length !== 32) { const msg = this._errorMsg( @@ -342,14 +347,14 @@ export class BlockHeader { this.parentBeaconBlockRoot!.length } bytes`, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } if (this.common.isActivatedEIP(7685)) { if (this.requestsHash === undefined) { const msg = this._errorMsg('EIP7685 block has no requestsHash field') - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } } @@ -367,7 +372,7 @@ export class BlockHeader { if (number > BIGINT_0 && this.extraData.length > this.common.param('maxExtraDataSize')) { // Check length of data on all post-genesis blocks const msg = this._errorMsg('invalid amount of extra data') - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } if (this.common.consensusAlgorithm() === ConsensusAlgorithm.Clique) { @@ -379,7 +384,7 @@ export class BlockHeader { const msg = this._errorMsg( `extraData must be ${minLength} bytes on non-epoch transition blocks, received ${this.extraData.length} bytes`, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } else { const signerLength = this.extraData.length - minLength @@ -387,20 +392,20 @@ export class BlockHeader { const msg = this._errorMsg( `invalid signer list length in extraData, received signer length of ${signerLength} (not divisible by 20)`, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } // coinbase (beneficiary) on epoch transition if (!this.coinbase.isZero()) { const msg = this._errorMsg( `coinbase must be filled with zeros on epoch transition blocks, received ${this.coinbase}`, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } // MixHash format if (!equalsBytes(this.mixHash, new Uint8Array(32))) { const msg = this._errorMsg(`mixHash must be filled with zeros, received ${this.mixHash}`) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } // Validation for PoS blocks (EIP-3675) @@ -433,7 +438,7 @@ export class BlockHeader { } if (error) { const msg = this._errorMsg(`Invalid PoS block: ${errorMsg}`) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } } @@ -467,14 +472,14 @@ export class BlockHeader { const msg = this._errorMsg( `gas limit increased too much. Gas limit: ${gasLimit}, max gas limit: ${maxGasLimit}`, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } if (gasLimit <= minGasLimit) { const msg = this._errorMsg( `gas limit decreased too much. Gas limit: ${gasLimit}, min gas limit: ${minGasLimit}`, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } if (gasLimit < this.common.param('minGasLimit')) { @@ -483,7 +488,7 @@ export class BlockHeader { 'minGasLimit', )}`, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } @@ -495,7 +500,7 @@ export class BlockHeader { const msg = this._errorMsg( 'calcNextBaseFee() can only be called with EIP1559 being activated', ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } let nextBaseFee: bigint const elasticity = this.common.param('elasticityMultiplier') @@ -530,7 +535,7 @@ export class BlockHeader { */ getBlobGasPrice(): bigint { if (this.excessBlobGas === undefined) { - throw new Error('header must have excessBlobGas field populated') + throw EthereumJSErrorUnsetCode('header must have excessBlobGas field populated') } return computeBlobGasPrice(this.excessBlobGas, this.common) } @@ -651,13 +656,13 @@ export class BlockHeader { ethashCanonicalDifficulty(parentBlockHeader: BlockHeader): bigint { if (this.common.consensusType() !== ConsensusType.ProofOfWork) { const msg = this._errorMsg('difficulty calculation is only supported on PoW chains') - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } if (this.common.consensusAlgorithm() !== ConsensusAlgorithm.Ethash) { const msg = this._errorMsg( 'difficulty calculation currently only supports the ethash algorithm', ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } const blockTs = this.timestamp const { timestamp: parentTs, difficulty: parentDif } = parentBlockHeader @@ -785,7 +790,7 @@ export class BlockHeader { this.extraData, )})`, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } diff --git a/packages/block/src/helpers.ts b/packages/block/src/helpers.ts index bd200f03d36..39124b6a276 100644 --- a/packages/block/src/helpers.ts +++ b/packages/block/src/helpers.ts @@ -1,7 +1,15 @@ import { MerklePatriciaTrie } from '@ethereumjs/mpt' import { RLP } from '@ethereumjs/rlp' import { Blob4844Tx } from '@ethereumjs/tx' -import { BIGINT_0, BIGINT_1, TypeOutput, concatBytes, isHexString, toType } from '@ethereumjs/util' +import { + BIGINT_0, + BIGINT_1, + EthereumJSErrorUnsetCode, + TypeOutput, + concatBytes, + isHexString, + toType, +} from '@ethereumjs/util' import type { BlockHeaderBytes, HeaderData } from './types.js' import type { Common } from '@ethereumjs/common' @@ -17,7 +25,7 @@ export const numberToHex = function (input?: string): PrefixedHexString | undefi const regex = new RegExp(/^\d+$/) // test to make sure input contains only digits if (!regex.test(input)) { const msg = `Cannot convert string to hex string. numberToHex only supports 0x-prefixed hex or integer strings but the given string was: ${input}` - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } return `0x${parseInt(input, 10).toString(16)}` } @@ -50,12 +58,12 @@ export function valuesArrayToHeaderData(values: BlockHeaderBytes): HeaderData { ] = values if (values.length > 21) { - throw new Error( + throw EthereumJSErrorUnsetCode( `invalid header. More values than expected were received. Max: 20, got: ${values.length}`, ) } if (values.length < 15) { - throw new Error( + throw EthereumJSErrorUnsetCode( `invalid header. Less values than expected were received. Min: 15, got: ${values.length}`, ) } @@ -176,7 +184,7 @@ export function genRequestsRoot( if (requests.length > 1) { for (let x = 1; x < requests.length; x++) { if (requests[x].type < requests[x - 1].type) - throw new Error('requests are not sorted in ascending order') + throw EthereumJSErrorUnsetCode('requests are not sorted in ascending order') } } diff --git a/packages/blockchain/src/blockchain.ts b/packages/blockchain/src/blockchain.ts index 0613940013d..c3521b429bf 100644 --- a/packages/blockchain/src/blockchain.ts +++ b/packages/blockchain/src/blockchain.ts @@ -4,6 +4,7 @@ import { BIGINT_0, BIGINT_1, BIGINT_8, + EthereumJSErrorUnsetCode, KECCAK256_RLP, Lock, MapDB, @@ -157,7 +158,7 @@ export class Blockchain implements BlockchainInterface { private _consensusCheck() { if (this._validateConsensus && this.consensus === undefined) { - throw new Error( + throw EthereumJSErrorUnsetCode( `Consensus object for ${this.common.consensusAlgorithm()} must be passed (see consensusDict option) if consensus validation is activated`, ) } @@ -250,7 +251,7 @@ export class Blockchain implements BlockchainInterface { */ async getCanonicalHeadHeader(): Promise { return this.runWithLock(async () => { - if (!this._headHeaderHash) throw new Error('No head header set') + if (!this._headHeaderHash) throw EthereumJSErrorUnsetCode('No head header set') const header = await this._getHeader(this._headHeaderHash) return header }) @@ -261,7 +262,7 @@ export class Blockchain implements BlockchainInterface { */ async getCanonicalHeadBlock(): Promise { return this.runWithLock(async () => { - if (!this._headBlockHash) throw new Error('No head block set') + if (!this._headBlockHash) throw EthereumJSErrorUnsetCode('No head block set') return this.getBlock(this._headBlockHash) }) } @@ -341,7 +342,7 @@ export class Blockchain implements BlockchainInterface { await this.runWithLock(async () => { hash = await this.dbManager.numberToHash(canonicalHead) if (hash === undefined) { - throw new Error(`no block for ${canonicalHead} found in DB`) + throw EthereumJSErrorUnsetCode(`no block for ${canonicalHead} found in DB`) } const header = await this._getHeader(hash, canonicalHead) @@ -401,7 +402,7 @@ export class Blockchain implements BlockchainInterface { // Try to re-put the existing genesis block, accept this return } - throw new Error( + throw EthereumJSErrorUnsetCode( 'Cannot put a different genesis block than current blockchain genesis: create a new Blockchain', ) } @@ -414,7 +415,7 @@ export class Blockchain implements BlockchainInterface { let dbOps: DBOp[] = [] if (block.common.chainId() !== this.common.chainId()) { - throw new Error( + throw EthereumJSErrorUnsetCode( `Chain mismatch while trying to put block or header. Chain ID of block: ${block.common.chainId}, chain ID of blockchain : ${this.common.chainId}`, ) } @@ -534,11 +535,11 @@ export class Blockchain implements BlockchainInterface { const { number } = header if (number !== parentHeader.number + BIGINT_1) { - throw new Error(`invalid number ${header.errorStr()}`) + throw EthereumJSErrorUnsetCode(`invalid number ${header.errorStr()}`) } if (header.timestamp <= parentHeader.timestamp) { - throw new Error(`invalid timestamp ${header.errorStr()}`) + throw EthereumJSErrorUnsetCode(`invalid timestamp ${header.errorStr()}`) } if (!(header.common.consensusType() === 'pos')) await this.consensus?.validateDifficulty(header) @@ -547,7 +548,9 @@ export class Blockchain implements BlockchainInterface { const period = (this.common.consensusConfig() as CliqueConfig).period // Timestamp diff between blocks is lower than PERIOD (clique) if (parentHeader.timestamp + BigInt(period) > header.timestamp) { - throw new Error(`invalid timestamp diff (lower than period) ${header.errorStr()}`) + throw EthereumJSErrorUnsetCode( + `invalid timestamp diff (lower than period) ${header.errorStr()}`, + ) } } @@ -557,7 +560,7 @@ export class Blockchain implements BlockchainInterface { const dif = height - parentHeader.number if (!(dif < BIGINT_8 && dif > BIGINT_1)) { - throw new Error( + throw EthereumJSErrorUnsetCode( `uncle block has a parent that is too old or too young ${header.errorStr()}`, ) } @@ -576,20 +579,22 @@ export class Blockchain implements BlockchainInterface { } if (header.baseFeePerGas! !== expectedBaseFee) { - throw new Error(`Invalid block: base fee not correct ${header.errorStr()}`) + throw EthereumJSErrorUnsetCode(`Invalid block: base fee not correct ${header.errorStr()}`) } } if (header.common.isActivatedEIP(4844)) { const expectedExcessBlobGas = parentHeader.calcNextExcessBlobGas(header.common) if (header.excessBlobGas !== expectedExcessBlobGas) { - throw new Error(`expected blob gas: ${expectedExcessBlobGas}, got: ${header.excessBlobGas}`) + throw EthereumJSErrorUnsetCode( + `expected blob gas: ${expectedExcessBlobGas}, got: ${header.excessBlobGas}`, + ) } } if (header.common.isActivatedEIP(7685)) { if (header.requestsHash === undefined) { - throw new Error(`requestsHash must be provided when EIP-7685 is active`) + throw EthereumJSErrorUnsetCode(`requestsHash must be provided when EIP-7685 is active`) } } } @@ -673,17 +678,19 @@ export class Blockchain implements BlockchainInterface { const parentHash = bytesToUnprefixedHex(uh.parentHash) if (!canonicalChainHashes[parentHash]) { - throw new Error( + throw EthereumJSErrorUnsetCode( `The parent hash of the uncle header is not part of the canonical chain ${block.errorStr()}`, ) } if (includedUncles[uncleHash]) { - throw new Error(`The uncle is already included in the canonical chain ${block.errorStr()}`) + throw EthereumJSErrorUnsetCode( + `The uncle is already included in the canonical chain ${block.errorStr()}`, + ) } if (canonicalChainHashes[uncleHash]) { - throw new Error(`The uncle is a canonical block ${block.errorStr()}`) + throw EthereumJSErrorUnsetCode(`The uncle is a canonical block ${block.errorStr()}`) } }) } @@ -706,9 +713,9 @@ export class Blockchain implements BlockchainInterface { if (block === undefined) { if (typeof blockId === 'object') { - throw new Error(`Block with hash ${bytesToHex(blockId)} not found in DB`) + throw EthereumJSErrorUnsetCode(`Block with hash ${bytesToHex(blockId)} not found in DB`) } else { - throw new Error(`Block number ${blockId} not found in DB`) + throw EthereumJSErrorUnsetCode(`Block number ${blockId} not found in DB`) } } return block @@ -721,7 +728,7 @@ export class Blockchain implements BlockchainInterface { if (number === undefined) { number = await this.dbManager.hashToNumber(hash) if (number === undefined) { - throw new Error(`Block with hash ${bytesToHex(hash)} not found in DB`) + throw EthereumJSErrorUnsetCode(`Block with hash ${bytesToHex(hash)} not found in DB`) } } return this.dbManager.getTotalDifficulty(hash, number) @@ -1036,7 +1043,7 @@ export class Blockchain implements BlockchainInterface { * @param newHeader - the new block header */ private async findCommonAncestor(newHeader: BlockHeader) { - if (!this._headHeaderHash) throw new Error('No head header set') + if (!this._headHeaderHash) throw EthereumJSErrorUnsetCode('No head header set') const ancestorHeaders = new Set() let header = await this._getHeader(this._headHeaderHash) @@ -1050,7 +1057,7 @@ export class Blockchain implements BlockchainInterface { } } if (header.number !== newHeader.number) { - throw new Error('Failed to find ancient header') + throw EthereumJSErrorUnsetCode('Failed to find ancient header') } while (!equalsBytes(header.hash(), newHeader.hash()) && header.number > BIGINT_0) { header = await this.getCanonicalHeader(header.number - BIGINT_1) @@ -1059,7 +1066,7 @@ export class Blockchain implements BlockchainInterface { ancestorHeaders.add(newHeader) } if (!equalsBytes(header.hash(), newHeader.hash())) { - throw new Error('Failed to find ancient header') + throw EthereumJSErrorUnsetCode('Failed to find ancient header') } this.DEBUG && this._debug(`found common ancestor with hash=${bytesToHex(header.hash())}`) @@ -1255,7 +1262,8 @@ export class Blockchain implements BlockchainInterface { private async _getHeader(hash: Uint8Array, number?: bigint) { if (number === undefined) { number = await this.dbManager.hashToNumber(hash) - if (number === undefined) throw new Error(`no header for ${bytesToHex(hash)} found in DB`) + if (number === undefined) + throw EthereumJSErrorUnsetCode(`no header for ${bytesToHex(hash)} found in DB`) } return this.dbManager.getHeader(hash, number) } @@ -1280,7 +1288,7 @@ export class Blockchain implements BlockchainInterface { async getCanonicalHeader(number: bigint) { const hash = await this.dbManager.numberToHash(number) if (hash === undefined) { - throw new Error(`header with number ${number} not found in canonical chain`) + throw EthereumJSErrorUnsetCode(`header with number ${number} not found in canonical chain`) } return this._getHeader(hash, number) } @@ -1300,7 +1308,8 @@ export class Blockchain implements BlockchainInterface { * The genesis {@link Block} for the blockchain. */ get genesisBlock(): Block { - if (!this._genesisBlock) throw new Error('genesis block not set (init may not be finished)') + if (!this._genesisBlock) + throw EthereumJSErrorUnsetCode('genesis block not set (init may not be finished)') return this._genesisBlock } diff --git a/packages/blockchain/src/consensus/casper.ts b/packages/blockchain/src/consensus/casper.ts index 8d006e73675..dc389fcd0df 100644 --- a/packages/blockchain/src/consensus/casper.ts +++ b/packages/blockchain/src/consensus/casper.ts @@ -1,5 +1,5 @@ import { ConsensusAlgorithm } from '@ethereumjs/common' -import { BIGINT_0 } from '@ethereumjs/util' +import { BIGINT_0, EthereumJSErrorUnsetCode } from '@ethereumjs/util' import type { Consensus } from '../types.js' import type { BlockHeader } from '@ethereumjs/block' @@ -26,7 +26,7 @@ export class CasperConsensus implements Consensus { // blockchain along adding new blocks or headers if (header.difficulty !== BIGINT_0) { const msg = 'invalid difficulty. PoS blocks must have difficulty 0' - throw new Error(`${msg} ${header.errorStr()}`) + throw EthereumJSErrorUnsetCode(`${msg} ${header.errorStr()}`) } } public async newBlock(): Promise {} diff --git a/packages/blockchain/src/consensus/clique.ts b/packages/blockchain/src/consensus/clique.ts index 1e0ffa35a86..326888f8019 100644 --- a/packages/blockchain/src/consensus/clique.ts +++ b/packages/blockchain/src/consensus/clique.ts @@ -11,6 +11,7 @@ import { BIGINT_0, BIGINT_1, BIGINT_2, + EthereumJSErrorUnsetCode, TypeOutput, bigIntToBytes, bytesToBigInt, @@ -144,16 +145,16 @@ export class CliqueConsensus implements Consensus { async validateConsensus(block: Block): Promise { if (!this.blockchain) { - throw new Error('blockchain not provided') + throw EthereumJSErrorUnsetCode('blockchain not provided') } const { header } = block const valid = cliqueVerifySignature(header, this.cliqueActiveSigners(header.number)) if (!valid) { - throw new Error('invalid PoA block signature (clique)') + throw EthereumJSErrorUnsetCode('invalid PoA block signature (clique)') } if (this.cliqueCheckRecentlySigned(header)) { - throw new Error('recently signed') + throw EthereumJSErrorUnsetCode('recently signed') } // validate checkpoint signers towards active signers on epoch transition blocks @@ -165,7 +166,7 @@ export class CliqueConsensus implements Consensus { const activeSigners = this.cliqueActiveSigners(header.number) for (const [i, cSigner] of checkpointSigners.entries()) { if (activeSigners[i]?.equals(cSigner) !== true) { - throw new Error( + throw EthereumJSErrorUnsetCode( `checkpoint signer not found in active signers list at index ${i}: ${cSigner}`, ) } @@ -175,19 +176,19 @@ export class CliqueConsensus implements Consensus { async validateDifficulty(header: BlockHeader): Promise { if (!this.blockchain) { - throw new Error('blockchain not provided') + throw EthereumJSErrorUnsetCode('blockchain not provided') } if (header.difficulty !== CLIQUE_DIFF_INTURN && header.difficulty !== CLIQUE_DIFF_NOTURN) { const msg = `difficulty for clique block must be INTURN (2) or NOTURN (1), received: ${header.difficulty}` - throw new Error(`${msg} ${header.errorStr()}`) + throw EthereumJSErrorUnsetCode(`${msg} ${header.errorStr()}`) } const signers = this.cliqueActiveSigners(header.number) if (signers.length === 0) { // abort if signers are unavailable const msg = 'no signers available' - throw new Error(`${msg} ${header.errorStr()}`) + throw EthereumJSErrorUnsetCode(`${msg} ${header.errorStr()}`) } const signerIndex = signers.findIndex((address: Address) => address.equals(cliqueSigner(header)), @@ -199,7 +200,7 @@ export class CliqueConsensus implements Consensus { ) { return } - throw new Error(`'invalid clique difficulty ${header.errorStr()}`) + throw EthereumJSErrorUnsetCode(`'invalid clique difficulty ${header.errorStr()}`) } async newBlock(block: Block, commonAncestor: BlockHeader | undefined): Promise { @@ -452,7 +453,7 @@ export class CliqueConsensus implements Consensus { return signers[i][1] } } - throw new Error(`Could not load signers for block ${blockNum}`) + throw EthereumJSErrorUnsetCode(`Could not load signers for block ${blockNum}`) } /** @@ -618,7 +619,7 @@ export class CliqueConsensus implements Consensus { const signers = this.cliqueActiveSigners(blockNum) const signerIndex = signers.findIndex((address) => address.equals(signer)) if (signerIndex === -1) { - throw new Error('Signer not found') + throw EthereumJSErrorUnsetCode('Signer not found') } const { number } = await this.blockchain!.getCanonicalHeadHeader() //eslint-disable-next-line diff --git a/packages/blockchain/src/consensus/ethash.ts b/packages/blockchain/src/consensus/ethash.ts index 64717722d87..bd6e1405b4e 100644 --- a/packages/blockchain/src/consensus/ethash.ts +++ b/packages/blockchain/src/consensus/ethash.ts @@ -1,5 +1,5 @@ import { ConsensusAlgorithm } from '@ethereumjs/common' -import { bytesToHex } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, bytesToHex } from '@ethereumjs/util' import debugDefault from 'debug' import type { Blockchain } from '../index.js' @@ -35,7 +35,7 @@ export class EthashConsensus implements Consensus { async validateConsensus(block: Block): Promise { const valid = await this._ethash.verifyPOW(block) if (!valid) { - throw new Error('invalid POW') + throw EthereumJSErrorUnsetCode('invalid POW') } this.DEBUG && this._debug( @@ -49,11 +49,11 @@ export class EthashConsensus implements Consensus { */ async validateDifficulty(header: BlockHeader) { if (!this.blockchain) { - throw new Error('blockchain not provided') + throw EthereumJSErrorUnsetCode('blockchain not provided') } const parentHeader = await this.blockchain['_getHeader'](header.parentHash) if (header.ethashCanonicalDifficulty(parentHeader) !== header.difficulty) { - throw new Error(`invalid difficulty ${header.errorStr()}`) + throw EthereumJSErrorUnsetCode(`invalid difficulty ${header.errorStr()}`) } this.DEBUG && this._debug( diff --git a/packages/blockchain/src/constructors.ts b/packages/blockchain/src/constructors.ts index 615c501e967..83c8c80de2a 100644 --- a/packages/blockchain/src/constructors.ts +++ b/packages/blockchain/src/constructors.ts @@ -1,5 +1,5 @@ import { createBlock } from '@ethereumjs/block' -import { BIGINT_0, bytesToHex, equalsBytes } from '@ethereumjs/util' +import { BIGINT_0, EthereumJSErrorUnsetCode, bytesToHex, equalsBytes } from '@ethereumjs/util' import debugDefault from 'debug' import { @@ -46,7 +46,7 @@ export async function createBlockchain(opts: BlockchainOptions = {}) { // If the DB has a genesis block, then verify that the genesis block in the // DB is indeed the Genesis block generated or assigned. if (dbGenesisBlock !== undefined && !equalsBytes(genesisBlock.hash(), dbGenesisBlock.hash())) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'The genesis block in the DB has a different hash than the provided genesis block.', ) } diff --git a/packages/blockchain/src/db/manager.ts b/packages/blockchain/src/db/manager.ts index 82338a18855..e1977e414e5 100644 --- a/packages/blockchain/src/db/manager.ts +++ b/packages/blockchain/src/db/manager.ts @@ -1,6 +1,7 @@ import { createBlockFromBytesArray, createBlockHeaderFromBytesArray } from '@ethereumjs/block' import { RLP } from '@ethereumjs/rlp' import { + EthereumJSErrorUnsetCode, KECCAK256_RLP, KECCAK256_RLP_ARRAY, bytesToBigInt, @@ -99,7 +100,7 @@ export class DBManager { number = blockId hash = await this.numberToHash(blockId) } else { - throw new Error('Unknown blockId type') + throw EthereumJSErrorUnsetCode('Unknown blockId type') } if (hash === undefined || number === undefined) return undefined @@ -112,18 +113,20 @@ export class DBManager { body = [[], []] as BlockBodyBytes // Do extra validations on the header since we are assuming empty transactions and uncles if (!equalsBytes(header.transactionsTrie, KECCAK256_RLP)) { - throw new Error('transactionsTrie root should be equal to hash of null') + throw EthereumJSErrorUnsetCode('transactionsTrie root should be equal to hash of null') } if (!equalsBytes(header.uncleHash, KECCAK256_RLP_ARRAY)) { - throw new Error('uncle hash should be equal to hash of empty array') + throw EthereumJSErrorUnsetCode('uncle hash should be equal to hash of empty array') } // If this block had empty withdrawals push an empty array in body if (header.withdrawalsRoot !== undefined) { // Do extra validations for withdrawal before assuming empty withdrawals if (!equalsBytes(header.withdrawalsRoot, KECCAK256_RLP)) { - throw new Error('withdrawals root shoot be equal to hash of null when no withdrawals') + throw EthereumJSErrorUnsetCode( + 'withdrawals root shoot be equal to hash of null when no withdrawals', + ) } else { body.push([]) } @@ -168,7 +171,7 @@ export class DBManager { async hashToNumber(blockHash: Uint8Array): Promise { const value = await this.get(DBTarget.HashToNumber, { blockHash }) if (value === undefined) { - throw new Error(`value for ${bytesToHex(blockHash)} not found in DB`) + throw EthereumJSErrorUnsetCode(`value for ${bytesToHex(blockHash)} not found in DB`) } return value !== undefined ? bytesToBigInt(value) : undefined } @@ -194,7 +197,7 @@ export class DBManager { if (cacheString !== undefined) { if (this._cache[cacheString] === undefined) { - throw new Error(`Invalid cache: ${cacheString}`) + throw EthereumJSErrorUnsetCode(`Invalid cache: ${cacheString}`) } let value = this._cache[cacheString].get(dbKey) if (value === undefined) { diff --git a/packages/blockchain/src/db/operation.ts b/packages/blockchain/src/db/operation.ts index a861b9605fc..4be25ff6711 100644 --- a/packages/blockchain/src/db/operation.ts +++ b/packages/blockchain/src/db/operation.ts @@ -1,4 +1,4 @@ -import { KeyEncoding, ValueEncoding } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, KeyEncoding, ValueEncoding } from '@ethereumjs/util' import { HEADS_KEY, @@ -143,7 +143,7 @@ export class DBOp { } else if (this.baseDBOp.type === 'del') { cacheMap[this.cacheString].del(this.baseDBOp.key) } else { - throw new Error('unsupported db operation on cache') + throw EthereumJSErrorUnsetCode('unsupported db operation on cache') } } } diff --git a/packages/client/bin/cli.ts b/packages/client/bin/cli.ts index 4150bdd0a9f..064b253d3d9 100755 --- a/packages/client/bin/cli.ts +++ b/packages/client/bin/cli.ts @@ -4,7 +4,7 @@ import { createBlockFromBytesArray } from '@ethereumjs/block' import { CliqueConsensus, createBlockchain } from '@ethereumjs/blockchain' import { ConsensusAlgorithm, Hardfork } from '@ethereumjs/common' import { RLP } from '@ethereumjs/rlp' -import { bytesToHex, short } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, bytesToHex, short } from '@ethereumjs/util' import { mkdirSync, readFileSync } from 'fs' import { Level } from 'level' @@ -84,15 +84,15 @@ async function executeBlocks(client: EthereumClient) { txHashes = blockRange[0][1] as string[] if ((blockRange[0][1] as string[]).length > 0 && blockRange.length === 2) { - throw new Error('wrong input') + throw EthereumJSErrorUnsetCode('wrong input') } } catch (e: any) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'Wrong input format for block execution, allowed format types: 5, 5-10, 5[0xba4b5fd92a26badad3cad22eb6f7c7e745053739b5f5d1e8a3afb00f8fb2a280,[TX_HASH_2],...], 5[*] (all txs in verbose mode)', ) } const { execution } = client.service - if (execution === undefined) throw new Error('executeBlocks requires execution') + if (execution === undefined) throw EthereumJSErrorUnsetCode('executeBlocks requires execution') await execution.executeBlocks(first, last, txHashes) } @@ -105,13 +105,13 @@ async function startBlock(client: EthereumClient) { const startBlock = BigInt(args.startBlock) const height = client.chain.headers.height if (height < startBlock) { - throw new Error(`Cannot start chain higher than current height ${height}`) + throw EthereumJSErrorUnsetCode(`Cannot start chain higher than current height ${height}`) } try { await client.chain.resetCanonicalHead(startBlock) client.config.logger.info(`Chain height reset to ${client.chain.headers.height}`) } catch (err: any) { - throw new Error(`Error setting back chain in startBlock: ${err}`) + throw EthereumJSErrorUnsetCode(`Error setting back chain in startBlock: ${err}`) } } @@ -121,7 +121,7 @@ async function startExecutionFrom(client: EthereumClient) { const height = client.chain.headers.height if (height < startExecutionFrom) { - throw new Error(`Cannot start merkle chain higher than current height ${height}`) + throw EthereumJSErrorUnsetCode(`Cannot start merkle chain higher than current height ${height}`) } const startExecutionBlock = await client.chain.getBlock(startExecutionFrom) @@ -143,7 +143,9 @@ async function startExecutionFrom(client: EthereumClient) { `vmHead set to ${client.chain.headers.height} for starting stateless execution at hardfork=${startExecutionHardfork}`, ) } catch (err: any) { - throw new Error(`Error setting vmHead for starting stateless execution: ${err}`) + throw EthereumJSErrorUnsetCode( + `Error setting vmHead for starting stateless execution: ${err}`, + ) } } else if (client.config.statefulVerkle) { try { @@ -153,11 +155,13 @@ async function startExecutionFrom(client: EthereumClient) { `vmHead set to ${client.chain.headers.height} for starting stateful execution at hardfork=${startExecutionHardfork}`, ) } catch (err: any) { - throw new Error(`Error setting vmHead for starting stateful execution: ${err}`) + throw EthereumJSErrorUnsetCode( + `Error setting vmHead for starting stateful execution: ${err}`, + ) } } else { // we need parent state availability to set the vmHead to the parent - throw new Error( + throw EthereumJSErrorUnsetCode( `Stateful execution reset not implemented at hardfork=${startExecutionHardfork}`, ) } @@ -187,7 +191,7 @@ async function startClient( let stateRoot if (config.statefulVerkle) { if (genesisMeta.genesisState === undefined) { - throw new Error('genesisState is required to compute stateRoot') + throw EthereumJSErrorUnsetCode('genesisState is required to compute stateRoot') } stateRoot = await generateVKTStateRoot(genesisMeta.genesisState, config.chainCommon) } diff --git a/packages/client/bin/startRPC.ts b/packages/client/bin/startRPC.ts index 507a5553ff9..4cf8dc3d1c4 100644 --- a/packages/client/bin/startRPC.ts +++ b/packages/client/bin/startRPC.ts @@ -1,4 +1,9 @@ -import { bytesToUnprefixedHex, hexToBytes, randomBytes } from '@ethereumjs/util' +import { + EthereumJSErrorUnsetCode, + bytesToUnprefixedHex, + hexToBytes, + randomBytes, +} from '@ethereumjs/util' import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs' import { RPCManager, saveReceiptsMethods } from '../src/rpc/index.js' @@ -44,7 +49,7 @@ function parseJwtSecret(config: Config, jwtFilePath?: string): Uint8Array { // If jwtFilePath is provided, it should exist if (jwtFilePath !== undefined && !existsSync(jwtFilePath)) { - throw new Error(`No file exists at provided jwt secret path=${jwtFilePath}`) + throw EthereumJSErrorUnsetCode(`No file exists at provided jwt secret path=${jwtFilePath}`) } if (jwtFilePath !== undefined || existsSync(defaultJwtPath)) { diff --git a/packages/client/bin/utils.ts b/packages/client/bin/utils.ts index d34c67b078d..deef0ce49bc 100644 --- a/packages/client/bin/utils.ts +++ b/packages/client/bin/utils.ts @@ -9,6 +9,7 @@ import { } from '@ethereumjs/common' import { BIGINT_2, + EthereumJSErrorUnsetCode, bytesToHex, calculateSigRecovery, concatBytes, @@ -473,7 +474,7 @@ export function getArgs(): ClientOpts { if (argv.rpc === true && usedPorts.has(argv.rpcPort)) collision = true if (argv.rpcEngine === true && usedPorts.has(argv.rpcEnginePort)) collision = true - if (collision) throw new Error('cannot reuse ports between RPC instances') + if (collision) throw EthereumJSErrorUnsetCode('cannot reuse ports between RPC instances') return true }) .parseSync() @@ -585,7 +586,7 @@ async function inputAccounts(args: ClientOpts) { if (address.equals(derivedAddress) === true) { accounts.push([address, privKey]) } else { - throw new Error( + throw EthereumJSErrorUnsetCode( `Private key does not match for ${address} (address derived: ${derivedAddress})`, ) } @@ -597,7 +598,7 @@ async function inputAccounts(args: ClientOpts) { accounts.push([derivedAddress, privKey]) } } catch (e: any) { - throw new Error(`Encountered error unlocking account:\n${e.message}`) + throw EthereumJSErrorUnsetCode(`Encountered error unlocking account:\n${e.message}`) } rl.close() return accounts @@ -654,7 +655,7 @@ export async function generateClientConfig(args: ClientOpts) { ) => { if (msg.length < 32) { // WASM errors with `unreachable` if we try to pass in less than 32 bytes in the message - throw new Error('message length must be 32 bytes or greater') + throw EthereumJSErrorUnsetCode('message length must be 32 bytes or greater') } const { chainId } = ecSignOpts const buf = secp256k1Sign(msg, pk) @@ -719,7 +720,7 @@ export async function generateClientConfig(args: ClientOpts) { customCrypto: cryptoFunctions, }) } catch (err: any) { - throw new Error(`invalid chain parameters: ${err.message}`) + throw EthereumJSErrorUnsetCode(`invalid chain parameters: ${err.message}`) } } else if (typeof args.gethGenesis === 'string') { // Use geth genesis parameters file if specified @@ -733,7 +734,7 @@ export async function generateClientConfig(args: ClientOpts) { } if (args.mine === true && accounts.length === 0) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'Please provide an account to mine blocks with `--unlock [address]` or use `--dev` to generate', ) } diff --git a/packages/client/src/blockchain/chain.ts b/packages/client/src/blockchain/chain.ts index af99f4aa631..520a201f554 100644 --- a/packages/client/src/blockchain/chain.ts +++ b/packages/client/src/blockchain/chain.ts @@ -1,7 +1,7 @@ import { createBlockFromBytesArray, createBlockHeaderFromBytesArray } from '@ethereumjs/block' import { CliqueConsensus, createBlockchain } from '@ethereumjs/blockchain' import { ConsensusAlgorithm, Hardfork } from '@ethereumjs/common' -import { BIGINT_0, equalsBytes } from '@ethereumjs/util' +import { BIGINT_0, EthereumJSErrorUnsetCode, equalsBytes } from '@ethereumjs/util' import { LevelDB } from '../execution/level.js' import { Event } from '../types.js' @@ -361,7 +361,7 @@ export class Chain { skip = 0, reverse = false, ): Promise { - if (!this.opened) throw new Error('Chain closed') + if (!this.opened) throw EthereumJSErrorUnsetCode('Chain closed') return this.blockchain.getBlocks(block, max, skip, reverse) } @@ -371,7 +371,7 @@ export class Chain { * @throws if block is not found */ async getBlock(block: Uint8Array | bigint): Promise { - if (!this.opened) throw new Error('Chain closed') + if (!this.opened) throw EthereumJSErrorUnsetCode('Chain closed') return this.blockchain.getBlock(block) } @@ -382,7 +382,7 @@ export class Chain { * @returns number of blocks added */ async putBlocks(blocks: Block[], fromEngine = false, skipUpdateEmit = false): Promise { - if (!this.opened) throw new Error('Chain closed') + if (!this.opened) throw EthereumJSErrorUnsetCode('Chain closed') if (blocks.length === 0) return 0 let numAdded = 0 @@ -456,7 +456,7 @@ export class Chain { * @returns number of headers added */ async putHeaders(headers: BlockHeader[], mergeIncludes = false): Promise { - if (!this.opened) throw new Error('Chain closed') + if (!this.opened) throw EthereumJSErrorUnsetCode('Chain closed') if (headers.length === 0) return 0 let numAdded = 0 @@ -484,7 +484,7 @@ export class Chain { * Gets the latest header in the canonical chain */ async getCanonicalHeadHeader(): Promise { - if (!this.opened) throw new Error('Chain closed') + if (!this.opened) throw EthereumJSErrorUnsetCode('Chain closed') return this.blockchain.getCanonicalHeadHeader() } @@ -492,7 +492,7 @@ export class Chain { * Gets the latest block in the canonical chain */ async getCanonicalHeadBlock(): Promise { - if (!this.opened) throw new Error('Chain closed') + if (!this.opened) throw EthereumJSErrorUnsetCode('Chain closed') return this.blockchain.getCanonicalHeadBlock() } @@ -500,7 +500,7 @@ export class Chain { * Gets the latest block in the canonical chain */ async getCanonicalSafeBlock(): Promise { - if (!this.opened) throw new Error('Chain closed') + if (!this.opened) throw EthereumJSErrorUnsetCode('Chain closed') return this.blockchain.getIteratorHeadSafe('safe') } @@ -508,7 +508,7 @@ export class Chain { * Gets the latest block in the canonical chain */ async getCanonicalFinalizedBlock(): Promise { - if (!this.opened) throw new Error('Chain closed') + if (!this.opened) throw EthereumJSErrorUnsetCode('Chain closed') return this.blockchain.getIteratorHeadSafe('finalized') } @@ -516,7 +516,7 @@ export class Chain { * Gets the latest block in the canonical chain */ async getCanonicalVmHead(): Promise { - if (!this.opened) throw new Error('Chain closed') + if (!this.opened) throw EthereumJSErrorUnsetCode('Chain closed') return this.blockchain.getIteratorHead() } @@ -527,7 +527,7 @@ export class Chain { * @returns the td */ async getTd(hash: Uint8Array, num: bigint): Promise { - if (!this.opened) throw new Error('Chain closed') + if (!this.opened) throw EthereumJSErrorUnsetCode('Chain closed') return this.blockchain.getTotalDifficulty(hash, num) } } diff --git a/packages/client/src/execution/receipt.ts b/packages/client/src/execution/receipt.ts index b560b60c0f0..00a52392767 100644 --- a/packages/client/src/execution/receipt.ts +++ b/packages/client/src/execution/receipt.ts @@ -1,6 +1,7 @@ import { RLP } from '@ethereumjs/rlp' import { BIGINT_0, + EthereumJSErrorUnsetCode, bigIntToBytes, bytesToBigInt, bytesToInt, @@ -277,7 +278,7 @@ export class ReceiptsManager extends MetaDBManager { break } default: - throw new Error('Unsupported index type') + throw EthereumJSErrorUnsetCode('Unsupported index type') } } @@ -295,7 +296,7 @@ export class ReceiptsManager extends MetaDBManager { return this.rlp(RlpConvert.Decode, RlpType.TxHash, encoded) } default: - throw new Error('Unsupported index type') + throw EthereumJSErrorUnsetCode('Unsupported index type') } } @@ -367,7 +368,7 @@ export class ReceiptsManager extends MetaDBManager { return [blockHash, bytesToInt(txIndex)] as TxHashIndex } default: - throw new Error('Unknown rlp conversion') + throw EthereumJSErrorUnsetCode('Unknown rlp conversion') } } diff --git a/packages/client/src/execution/vmexecution.ts b/packages/client/src/execution/vmexecution.ts index b921e547346..2899b7d1966 100644 --- a/packages/client/src/execution/vmexecution.ts +++ b/packages/client/src/execution/vmexecution.ts @@ -18,6 +18,7 @@ import { import { BIGINT_0, BIGINT_1, + EthereumJSErrorUnsetCode, Lock, ValueEncoding, bytesToHex, @@ -209,7 +210,7 @@ export class VMExecution extends Execution { } else if (this.config.statefulVerkle) { this.config.logger.info(`Setting up verkleVM for stateful verkle execution`) stateManager = new StatefulVerkleStateManager({ common: this.config.execCommon }) - } else throw new Error('EIP-6800 active and no verkle execution mode specified') + } else throw EthereumJSErrorUnsetCode('EIP-6800 active and no verkle execution mode specified') await mcl.init(mcl.BLS12_381) const rustBN = await initRustBN() this.verkleVM = await createVM({ @@ -270,7 +271,9 @@ export class VMExecution extends Execution { const blockchain = this.chain.blockchain if (typeof blockchain.getIteratorHead !== 'function') { - throw new Error('cannot get iterator head: blockchain has no getIteratorHead function') + throw EthereumJSErrorUnsetCode( + 'cannot get iterator head: blockchain has no getIteratorHead function', + ) } const headBlock = await blockchain.getIteratorHead() const { number, timestamp, stateRoot } = headBlock.header @@ -282,7 +285,9 @@ export class VMExecution extends Execution { } if (typeof blockchain.getTotalDifficulty !== 'function') { - throw new Error('cannot get iterator head: blockchain has no getTotalDifficulty function') + throw EthereumJSErrorUnsetCode( + 'cannot get iterator head: blockchain has no getTotalDifficulty function', + ) } this.config.execCommon.setHardforkBy({ blockNumber: number, timestamp }) this.hardfork = this.config.execCommon.hardfork() @@ -305,7 +310,7 @@ export class VMExecution extends Execution { !genesisState && (!('generateCanonicalGenesis' in this.vm.stateManager) || !this.config.statelessVerkle) ) { - throw new Error('genesisState not available') + throw EthereumJSErrorUnsetCode('genesisState not available') } else { await this.vm.stateManager.generateCanonicalGenesis!(genesisState) } diff --git a/packages/client/src/net/peer/peer.ts b/packages/client/src/net/peer/peer.ts index 99c5e46a151..c929389706b 100644 --- a/packages/client/src/net/peer/peer.ts +++ b/packages/client/src/net/peer/peer.ts @@ -1,4 +1,4 @@ -import { BIGINT_0, short } from '@ethereumjs/util' +import { BIGINT_0, EthereumJSErrorUnsetCode, short } from '@ethereumjs/util' import { EventEmitter } from 'eventemitter3' import { BoundEthProtocol, BoundSnapProtocol } from '../protocol/index.js' @@ -177,7 +177,7 @@ export abstract class Peer extends EventEmitter { this.snap = bound } else { - throw new Error(`addProtocol: ${protocol.name} protocol not supported`) + throw EthereumJSErrorUnsetCode(`addProtocol: ${protocol.name} protocol not supported`) } this.boundProtocols.push(bound) diff --git a/packages/client/src/net/protocol/boundprotocol.ts b/packages/client/src/net/protocol/boundprotocol.ts index 7930af26529..6af51b464cb 100644 --- a/packages/client/src/net/protocol/boundprotocol.ts +++ b/packages/client/src/net/protocol/boundprotocol.ts @@ -1,4 +1,4 @@ -import { Lock } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, Lock } from '@ethereumjs/util' import { Event } from '../../types.js' @@ -74,7 +74,7 @@ export class BoundProtocol { // Expected message queue growth is in the single digits // so this adds a guard here if something goes wrong if (this.messageQueue.length >= 50) { - const error = new Error('unexpected message queue growth for peer') + const error = EthereumJSErrorUnsetCode('unexpected message queue growth for peer') this.config.events.emit(Event.PROTOCOL_ERROR, error, this.peer) } } @@ -117,7 +117,7 @@ export class BoundProtocol { try { data = this.protocol.decode(message, incoming.payload) } catch (e: any) { - error = new Error(`Could not decode message ${message.name}: ${e}`) + error = EthereumJSErrorUnsetCode(`Could not decode message ${message.name}: ${e}`) } const resolver = this.resolvers.get(incoming.code) if (resolver !== undefined) { @@ -163,7 +163,7 @@ export class BoundProtocol { const encoded = this.protocol.encode(message, args) this.sender.sendMessage(message.code, encoded) } else { - throw new Error(`Unknown message: ${name}`) + throw EthereumJSErrorUnsetCode(`Unknown message: ${name}`) } return message } @@ -207,7 +207,7 @@ export class BoundProtocol { resolver.timeout = setTimeout(() => { resolver.timeout = null this.resolvers.delete(message.response!) - resolver.reject(new Error(`Request timed out after ${this.timeout}ms`)) + resolver.reject(EthereumJSErrorUnsetCode(`Request timed out after ${this.timeout}ms`)) }, this.timeout) }) } diff --git a/packages/client/src/net/protocol/ethprotocol.ts b/packages/client/src/net/protocol/ethprotocol.ts index da542c632a8..5b2d40b1501 100644 --- a/packages/client/src/net/protocol/ethprotocol.ts +++ b/packages/client/src/net/protocol/ethprotocol.ts @@ -13,6 +13,7 @@ import { } from '@ethereumjs/tx' import { BIGINT_0, + EthereumJSErrorUnsetCode, bigIntToUnpaddedBytes, bytesToBigInt, bytesToHex, @@ -91,7 +92,7 @@ export interface EthProtocolMethods { } function exhaustiveTypeGuard(_value: never, errorMsg: string): never { - throw new Error(errorMsg) + throw EthereumJSErrorUnsetCode(errorMsg) } /** diff --git a/packages/client/src/net/protocol/protocol.ts b/packages/client/src/net/protocol/protocol.ts index 285063079b8..41c107cb315 100644 --- a/packages/client/src/net/protocol/protocol.ts +++ b/packages/client/src/net/protocol/protocol.ts @@ -1,3 +1,5 @@ +import { EthereumJSErrorUnsetCode } from '@ethereumjs/util' + import type { Config } from '../../config.js' import type { Sender } from './sender.js' @@ -66,7 +68,7 @@ export class Protocol { return new Promise((resolve, reject) => { let timeout: any = setTimeout(() => { timeout = null - reject(new Error(`Handshake timed out after ${this.timeout}ms`)) + reject(EthereumJSErrorUnsetCode(`Handshake timed out after ${this.timeout}ms`)) }, this.timeout) const handleStatus = (status: any) => { if (timeout !== null && timeout !== 0) { @@ -93,21 +95,21 @@ export class Protocol { * Protocol versions supported */ get versions(): number[] { - throw new Error('Unimplemented') + throw EthereumJSErrorUnsetCode('Unimplemented') } /** * Messages defined by this protocol */ get messages(): Message[] { - throw new Error('Unimplemented') + throw EthereumJSErrorUnsetCode('Unimplemented') } /** * Encodes status into status message payload. Must be implemented by subclass. */ encodeStatus(): any { - throw new Error('Unimplemented') + throw EthereumJSErrorUnsetCode('Unimplemented') } /** @@ -116,7 +118,7 @@ export class Protocol { * @param _status status message payload */ decodeStatus(_status: any): Object { - throw new Error('Unimplemented') + throw EthereumJSErrorUnsetCode('Unimplemented') } /** diff --git a/packages/client/src/net/protocol/sender.ts b/packages/client/src/net/protocol/sender.ts index 571c8a65433..78467336149 100644 --- a/packages/client/src/net/protocol/sender.ts +++ b/packages/client/src/net/protocol/sender.ts @@ -1,3 +1,4 @@ +import { EthereumJSErrorUnsetCode } from '@ethereumjs/util' import { EventEmitter } from 'eventemitter3' /** @@ -30,7 +31,7 @@ export class Sender extends EventEmitter { * @param status */ sendStatus(_status: any) { - throw new Error('Unimplemented') + throw EthereumJSErrorUnsetCode('Unimplemented') } /** @@ -39,6 +40,6 @@ export class Sender extends EventEmitter { * @param rlpEncodedData rlp encoded message payload */ sendMessage(_code: number, _rlpEncodedData: any[] | Uint8Array) { - throw new Error('Unimplemented') + throw EthereumJSErrorUnsetCode('Unimplemented') } } diff --git a/packages/client/src/rpc/modules/debug.ts b/packages/client/src/rpc/modules/debug.ts index 9562c5cf3fb..41303a58d5b 100644 --- a/packages/client/src/rpc/modules/debug.ts +++ b/packages/client/src/rpc/modules/debug.ts @@ -1,4 +1,5 @@ import { + EthereumJSErrorUnsetCode, TypeOutput, bigIntToHex, bytesToHex, @@ -267,7 +268,7 @@ export class Debug { } if (this.vm === undefined) { - throw new Error('missing vm') + throw EthereumJSErrorUnsetCode('missing vm') } const opts = validateTracerConfig(tracerOpts) @@ -358,7 +359,7 @@ export class Debug { const [blockHash, txIndex, account, startKey, limit] = params if (this.vm === undefined) { - throw new Error('Missing VM.') + throw EthereumJSErrorUnsetCode('Missing VM.') } let block: Block @@ -426,7 +427,8 @@ export class Debug { */ async getRawReceipts(params: [string]) { const [blockOpt] = params - if (!this.service.execution.receiptsManager) throw new Error('missing receiptsManager') + if (!this.service.execution.receiptsManager) + throw EthereumJSErrorUnsetCode('missing receiptsManager') const block = await getBlockByOption(blockOpt, this.chain) const receipts = await this.service.execution.receiptsManager.getReceipts( block.hash(), @@ -441,7 +443,8 @@ export class Debug { */ async getRawTransaction(params: [PrefixedHexString]) { const [txHash] = params - if (!this.service.execution.receiptsManager) throw new Error('missing receiptsManager') + if (!this.service.execution.receiptsManager) + throw EthereumJSErrorUnsetCode('missing receiptsManager') const result = await this.service.execution.receiptsManager.getReceiptByTxHash( hexToBytes(txHash), ) diff --git a/packages/client/src/rpc/modules/engine/engine.ts b/packages/client/src/rpc/modules/engine/engine.ts index 31796bcad5f..0af46de1a20 100644 --- a/packages/client/src/rpc/modules/engine/engine.ts +++ b/packages/client/src/rpc/modules/engine/engine.ts @@ -1,6 +1,7 @@ import { Hardfork } from '@ethereumjs/common' import { BIGINT_1, + EthereumJSErrorUnsetCode, bytesToHex, bytesToUnprefixedHex, equalsBytes, @@ -444,7 +445,9 @@ export class Engine { (await validExecutedChainBlock(hexToBytes(parentHash as PrefixedHexString), this.chain)) // If the parent is not executed throw an error, it will be caught and return SYNCING or ACCEPTED. if (!executedParentExists) { - throw new Error(`Parent block not yet executed number=${parent.header.number}`) + throw EthereumJSErrorUnsetCode( + `Parent block not yet executed number=${parent.header.number}`, + ) } } catch (error: any) { // Stash the block for a potential forced forkchoice update to it later. diff --git a/packages/client/src/rpc/modules/engine/util/newPayload.ts b/packages/client/src/rpc/modules/engine/util/newPayload.ts index b387274027c..956e045ced6 100644 --- a/packages/client/src/rpc/modules/engine/util/newPayload.ts +++ b/packages/client/src/rpc/modules/engine/util/newPayload.ts @@ -1,6 +1,12 @@ import { createBlockFromExecutionPayload, genRequestsRoot } from '@ethereumjs/block' import { Blob4844Tx } from '@ethereumjs/tx' -import { CLRequest, CLRequestType, bytesToHex, hexToBytes } from '@ethereumjs/util' +import { + CLRequest, + CLRequestType, + EthereumJSErrorUnsetCode, + bytesToHex, + hexToBytes, +} from '@ethereumjs/util' import { sha256 } from 'ethereum-cryptography/sha256' import { short } from '../../../../util/index.js' @@ -62,29 +68,29 @@ export const validateAndGen7685RequestsHash = ( for (const request of executionRequests) { const bytes = hexToBytes(request) if (bytes.length === 0) { - throw new Error('Got a request without a request-identifier') + throw EthereumJSErrorUnsetCode('Got a request without a request-identifier') } switch (bytes[0]) { case CLRequestType.Deposit: if (!common.isActivatedEIP(6110)) { - throw new Error(`Deposit requests are not active`) + throw EthereumJSErrorUnsetCode(`Deposit requests are not active`) } requests.push(new CLRequest(CLRequestType.Deposit, bytes.slice(1))) break case CLRequestType.Withdrawal: if (!common.isActivatedEIP(7002)) { - throw new Error(`Withdrawal requests are not active`) + throw EthereumJSErrorUnsetCode(`Withdrawal requests are not active`) } requests.push(new CLRequest(CLRequestType.Withdrawal, bytes.slice(1))) break case CLRequestType.Consolidation: if (!common.isActivatedEIP(7251)) { - throw new Error(`Consolidation requests are not active`) + throw EthereumJSErrorUnsetCode(`Consolidation requests are not active`) } requests.push(new CLRequest(CLRequestType.Consolidation, bytes.slice(1))) break default: - throw new Error(`Unknown request identifier: got ${bytes[0]}`) + throw EthereumJSErrorUnsetCode(`Unknown request identifier: got ${bytes[0]}`) } } diff --git a/packages/client/src/rpc/modules/eth.ts b/packages/client/src/rpc/modules/eth.ts index 5eb5070897e..eb7cbb27293 100644 --- a/packages/client/src/rpc/modules/eth.ts +++ b/packages/client/src/rpc/modules/eth.ts @@ -17,6 +17,7 @@ import { BIGINT_1, BIGINT_100, BIGINT_NEG1, + EthereumJSErrorUnsetCode, TypeOutput, bigIntMax, bigIntToHex, @@ -496,7 +497,7 @@ export class Eth { const block = await getBlockByOption(blockOpt, this._chain) if (this._vm === undefined) { - throw new Error('missing vm') + throw EthereumJSErrorUnsetCode('missing vm') } const vm = await this._vm.shallowCopy() @@ -556,7 +557,7 @@ export class Eth { const block = await getBlockByOption(blockOpt ?? 'latest', this._chain) if (this._vm === undefined) { - throw new Error('missing vm') + throw EthereumJSErrorUnsetCode('missing vm') } const vm = await this._vm.shallowCopy() await vm.stateManager.setStateRoot(block.header.stateRoot) @@ -633,7 +634,7 @@ export class Eth { const block = await getBlockByOption(blockOpt, this._chain) if (this._vm === undefined) { - throw new Error('missing vm') + throw EthereumJSErrorUnsetCode('missing vm') } const vm = await this._vm.shallowCopy() @@ -728,7 +729,7 @@ export class Eth { const block = await getBlockByOption(blockOpt, this._chain) if (this._vm === undefined) { - throw new Error('missing vm') + throw EthereumJSErrorUnsetCode('missing vm') } const vm = await this._vm.shallowCopy() @@ -767,7 +768,7 @@ export class Eth { } } if (this._vm === undefined) { - throw new Error('missing vm') + throw EthereumJSErrorUnsetCode('missing vm') } const vm = await this._vm.shallowCopy() @@ -844,7 +845,7 @@ export class Eth { */ async getTransactionByHash(params: [PrefixedHexString]) { const [txHash] = params - if (!this.receiptsManager) throw new Error('missing receiptsManager') + if (!this.receiptsManager) throw EthereumJSErrorUnsetCode('missing receiptsManager') const result = await this.receiptsManager.getReceiptByTxHash(hexToBytes(txHash)) if (!result) return null const [_receipt, blockHash, txIndex] = result @@ -866,7 +867,7 @@ export class Eth { else block = await getBlockByOption('latest', this._chain) if (this._vm === undefined) { - throw new Error('missing vm') + throw EthereumJSErrorUnsetCode('missing vm') } const vm = await this._vm.shallowCopy() @@ -931,7 +932,7 @@ export class Eth { return null } const blockHash = block.hash() - if (!this.receiptsManager) throw new Error('missing receiptsManager') + if (!this.receiptsManager) throw EthereumJSErrorUnsetCode('missing receiptsManager') const result = await this.receiptsManager.getReceipts(blockHash, true, true) if (result.length === 0) return [] const parentBlock = await this._chain.getBlock(block.header.parentHash) @@ -988,7 +989,7 @@ export class Eth { async getTransactionReceipt(params: [PrefixedHexString]) { const [txHash] = params - if (!this.receiptsManager) throw new Error('missing receiptsManager') + if (!this.receiptsManager) throw EthereumJSErrorUnsetCode('missing receiptsManager') const result = await this.receiptsManager.getReceiptByTxHash(hexToBytes(txHash)) if (!result) return null const [receipt, blockHash, txIndex, logIndex] = result @@ -1042,7 +1043,7 @@ export class Eth { */ async getLogs(params: [GetLogsParams]) { const { fromBlock, toBlock, blockHash, address, topics } = params[0] - if (!this.receiptsManager) throw new Error('missing receiptsManager') + if (!this.receiptsManager) throw EthereumJSErrorUnsetCode('missing receiptsManager') if (blockHash !== undefined && (fromBlock !== undefined || toBlock !== undefined)) { throw { code: INVALID_PARAMS, @@ -1231,7 +1232,7 @@ export class Eth { const block = await getBlockByOption(blockOpt, this._chain) if (this._vm === undefined) { - throw new Error('missing vm') + throw EthereumJSErrorUnsetCode('missing vm') } const vm = await this._vm.shallowCopy() @@ -1246,7 +1247,9 @@ export class Eth { } else if (vm.stateManager instanceof StatelessVerkleStateManager) { proof = await getVerkleStateProof(vm.stateManager, address, slots) } else { - throw new Error('getProof RPC method not supported with the StateManager provided') + throw EthereumJSErrorUnsetCode( + 'getProof RPC method not supported with the StateManager provided', + ) } for (const p of proof.storageProof) { diff --git a/packages/client/src/rpc/validation.ts b/packages/client/src/rpc/validation.ts index dcde5ebb7c4..da35c05c1cb 100644 --- a/packages/client/src/rpc/validation.ts +++ b/packages/client/src/rpc/validation.ts @@ -1,3 +1,5 @@ +import { EthereumJSErrorUnsetCode } from '@ethereumjs/util' + import { INVALID_PARAMS } from './error-code.js' import type { RPCMethod } from './types.js' @@ -75,7 +77,7 @@ function bytes(bytes: number, params: any[], index: number) { function uint(uint: number, params: any[], index: number) { if (uint % 8 !== 0) { // Sanity check - throw new Error(`Uint should be a multiple of 8, got: ${uint}`) + throw EthereumJSErrorUnsetCode(`Uint should be a multiple of 8, got: ${uint}`) } if (typeof params[index] !== 'string') { return { diff --git a/packages/client/src/service/skeleton.ts b/packages/client/src/service/skeleton.ts index 76ff29ef606..95e22a36c19 100644 --- a/packages/client/src/service/skeleton.ts +++ b/packages/client/src/service/skeleton.ts @@ -5,6 +5,7 @@ import { BIGINT_1, BIGINT_100, BIGINT_2EXP256, + EthereumJSErrorUnsetCode, Lock, bigIntToBytes, bytesToBigInt, @@ -74,20 +75,20 @@ type SkeletonSubchainRLP = [head: Uint8Array, tail: Uint8Array, next: Uint8Array * the current sync cycle was (partially) reorged, thus the skeleton syncer * should abort and restart with the new state. */ -export const errSyncReorged = new Error('sync reorged') +export const errSyncReorged = EthereumJSErrorUnsetCode('sync reorged') /** * errReorgDenied is returned if an attempt is made to extend the beacon chain * with a new header, but it does not link up to the existing sync. */ -export const errReorgDenied = new Error('non-forced head reorg denied') +export const errReorgDenied = EthereumJSErrorUnsetCode('non-forced head reorg denied') /** * errSyncMerged is an internal helper error to signal that the current sync * cycle merged with a previously aborted subchain, thus the skeleton syncer * should abort and restart with the new state. */ -export const errSyncMerged = new Error('sync merged') +export const errSyncMerged = EthereumJSErrorUnsetCode('sync merged') const zeroBlockHash = new Uint8Array(32) /** diff --git a/packages/client/src/service/txpool.ts b/packages/client/src/service/txpool.ts index fe2d36c176f..08712bd8470 100644 --- a/packages/client/src/service/txpool.ts +++ b/packages/client/src/service/txpool.ts @@ -11,6 +11,7 @@ import { Address, BIGINT_0, BIGINT_2, + EthereumJSErrorUnsetCode, bytesToHex, bytesToUnprefixedHex, equalsBytes, @@ -246,7 +247,7 @@ export class TxPool { existingTxGasPrice.maxFee + (existingTxGasPrice.maxFee * BigInt(MIN_GAS_PRICE_BUMP_PERCENT)) / BigInt(100) if (newGasPrice.tip < minTipCap || newGasPrice.maxFee < minFeeCap) { - throw new Error( + throw EthereumJSErrorUnsetCode( `replacement gas too low, got tip ${newGasPrice.tip}, min: ${minTipCap}, got fee ${newGasPrice.maxFee}, min: ${minFeeCap}`, ) } @@ -256,7 +257,7 @@ export class TxPool { existingTx.maxFeePerBlobGas + (existingTx.maxFeePerBlobGas * BigInt(MIN_GAS_PRICE_BUMP_PERCENT)) / BigInt(100) if (addedTx.maxFeePerBlobGas < minblobGasFee) { - throw new Error( + throw EthereumJSErrorUnsetCode( `replacement blob gas too low, got: ${addedTx.maxFeePerBlobGas}, min: ${minblobGasFee}`, ) } @@ -269,10 +270,10 @@ export class TxPool { */ private async validate(tx: TypedTransaction, isLocalTransaction: boolean = false) { if (!tx.isSigned()) { - throw new Error('Attempting to add tx to txpool which is not signed') + throw EthereumJSErrorUnsetCode('Attempting to add tx to txpool which is not signed') } if (tx.data.length > TX_MAX_DATA_SIZE) { - throw new Error( + throw EthereumJSErrorUnsetCode( `Tx is too large (${tx.data.length} bytes) and exceeds the max data size of ${TX_MAX_DATA_SIZE} bytes`, ) } @@ -283,11 +284,11 @@ export class TxPool { if (!isLocalTransaction) { const txsInPool = this.txsInPool if (txsInPool >= MAX_POOL_SIZE) { - throw new Error('Cannot add tx: pool is full') + throw EthereumJSErrorUnsetCode('Cannot add tx: pool is full') } // Local txs are not checked against MIN_GAS_PRICE if (currentTip < MIN_GAS_PRICE) { - throw new Error(`Tx does not pay the minimum gas price of ${MIN_GAS_PRICE}`) + throw EthereumJSErrorUnsetCode(`Tx does not pay the minimum gas price of ${MIN_GAS_PRICE}`) } } const senderAddress = tx.getSenderAddress() @@ -295,7 +296,7 @@ export class TxPool { const inPool = this.pool.get(sender) if (inPool) { if (!isLocalTransaction && inPool.length >= MAX_TXS_PER_ACCOUNT) { - throw new Error( + throw EthereumJSErrorUnsetCode( `Cannot add tx for ${senderAddress}: already have max amount of txs for this account`, ) } @@ -303,7 +304,9 @@ export class TxPool { const existingTxn = inPool.find((poolObj) => poolObj.tx.nonce === tx.nonce) if (existingTxn) { if (equalsBytes(existingTxn.tx.hash(), tx.hash())) { - throw new Error(`${bytesToHex(tx.hash())}: this transaction is already in the TxPool`) + throw EthereumJSErrorUnsetCode( + `${bytesToHex(tx.hash())}: this transaction is already in the TxPool`, + ) } this.validateTxGasBump(existingTxn.tx, tx) } @@ -311,13 +314,13 @@ export class TxPool { const block = await this.service.chain.getCanonicalHeadHeader() if (typeof block.baseFeePerGas === 'bigint' && block.baseFeePerGas !== BIGINT_0) { if (currentGasPrice.maxFee < block.baseFeePerGas / BIGINT_2 && !isLocalTransaction) { - throw new Error( + throw EthereumJSErrorUnsetCode( `Tx cannot pay basefee of ${block.baseFeePerGas}, have ${currentGasPrice.maxFee} (not within 50% range of current basefee)`, ) } } if (tx.gasLimit > block.gasLimit) { - throw new Error( + throw EthereumJSErrorUnsetCode( `Tx gaslimit of ${tx.gasLimit} exceeds block gas limit of ${block.gasLimit} (exceeds last block gas limit)`, ) } @@ -331,13 +334,13 @@ export class TxPool { account = new Account() } if (account.nonce > tx.nonce) { - throw new Error( + throw EthereumJSErrorUnsetCode( `0x${sender} tries to send a tx with nonce ${tx.nonce}, but account has nonce ${account.nonce} (tx nonce too low)`, ) } const minimumBalance = tx.value + currentGasPrice.maxFee * tx.gasLimit if (account.balance < minimumBalance) { - throw new Error( + throw EthereumJSErrorUnsetCode( `0x${sender} does not have enough balance to cover transaction costs, need ${minimumBalance}, but have ${account.balance} (insufficient balance)`, ) } @@ -778,7 +781,7 @@ export class TxPool { tip: tx.maxPriorityFeePerGas, } } else { - throw new Error(`tx of type ${(tx as TypedTransaction).type} unknown`) + throw EthereumJSErrorUnsetCode(`tx of type ${(tx as TypedTransaction).type} unknown`) } } diff --git a/packages/client/src/util/parse.ts b/packages/client/src/util/parse.ts index a62f8c26d87..c2c03436f36 100644 --- a/packages/client/src/util/parse.ts +++ b/packages/client/src/util/parse.ts @@ -1,4 +1,4 @@ -import { hexToBytes } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, hexToBytes } from '@ethereumjs/util' import { isMultiaddr, multiaddr } from '@multiformats/multiaddr' import { URL } from 'url' @@ -64,10 +64,10 @@ export function parseMultiaddrs(input: MultiaddrLike): Multiaddr[] { if (ip && port) { return multiaddr(`/ip4/${ip}/tcp/${port}`) } - throw new Error(`Unable to parse bootnode URL: ${s}`) + throw EthereumJSErrorUnsetCode(`Unable to parse bootnode URL: ${s}`) }) } catch (e: any) { - throw new Error(`Invalid bootnode URLs: ${e.message}`) + throw EthereumJSErrorUnsetCode(`Invalid bootnode URLs: ${e.message}`) } } diff --git a/packages/common/src/common.ts b/packages/common/src/common.ts index 7603f65eb84..d7b9fbff88d 100644 --- a/packages/common/src/common.ts +++ b/packages/common/src/common.ts @@ -1,5 +1,6 @@ import { BIGINT_0, + EthereumJSErrorUnsetCode, TypeOutput, bytesToHex, concatBytes, @@ -154,7 +155,7 @@ export class Common { } } if (!existing) { - throw new Error(`Hardfork with name ${hardfork} not supported`) + throw EthereumJSErrorUnsetCode(`Hardfork with name ${hardfork} not supported`) } } @@ -274,11 +275,11 @@ export class Common { setEIPs(eips: number[] = []) { for (const eip of eips) { if (!(eip in eipsDict)) { - throw new Error(`${eip} not supported`) + throw EthereumJSErrorUnsetCode(`${eip} not supported`) } const minHF = this.gteHardfork(eipsDict[eip]['minimumHardfork']) if (!minHF) { - throw new Error( + throw EthereumJSErrorUnsetCode( `${eip} cannot be activated on hardfork ${this.hardfork()}, minimumHardfork: ${minHF}`, ) } @@ -291,7 +292,9 @@ export class Common { if (eipsDict[eip].requiredEIPs !== undefined) { for (const elem of eipsDict[eip].requiredEIPs!) { if (!(eips.includes(elem) || this.isActivatedEIP(elem))) { - throw new Error(`${eip} requires EIP ${elem}, but is not included in the EIP list`) + throw EthereumJSErrorUnsetCode( + `${eip} requires EIP ${elem}, but is not included in the EIP list`, + ) } } } @@ -358,7 +361,7 @@ export class Common { // TODO: consider the case that different active EIPs // can change the same parameter if (!(name in this._paramsCache)) { - throw new Error(`Missing parameter value for ${name}`) + throw EthereumJSErrorUnsetCode(`Missing parameter value for ${name}`) } const value = this._paramsCache[name] return BigInt(value ?? 0) @@ -393,7 +396,7 @@ export class Common { if (hfChanges[0] === hardfork) break } if (value === undefined) { - throw new Error(`Missing parameter value for ${name}`) + throw EthereumJSErrorUnsetCode(`Missing parameter value for ${name}`) } return BigInt(value ?? 0) } @@ -406,12 +409,12 @@ export class Common { */ paramByEIP(name: string, eip: number): bigint | undefined { if (!(eip in eipsDict)) { - throw new Error(`${eip} not supported`) + throw EthereumJSErrorUnsetCode(`${eip} not supported`) } const eipParams = this._params[eip] if (eipParams?.[name] === undefined) { - throw new Error(`Missing parameter value for ${name}`) + throw EthereumJSErrorUnsetCode(`Missing parameter value for ${name}`) } const value = eipParams![name] return BigInt(value ?? 0) @@ -659,12 +662,13 @@ export class Common { const data = this._getHardfork(hardfork) if (data === null || (data?.block === null && data?.timestamp === undefined)) { const msg = 'No fork hash calculation possible for future hardfork' - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } if (data?.forkHash !== null && data?.forkHash !== undefined) { return data.forkHash } - if (!genesisHash) throw new Error('genesisHash required for forkHash calculation') + if (!genesisHash) + throw EthereumJSErrorUnsetCode('genesisHash required for forkHash calculation') return this._calcForkHash(hardfork, genesisHash) } diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index 270f3292c67..4df0910dc65 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -1,4 +1,4 @@ -import { intToHex, isHexString, stripHexPrefix } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, intToHex, isHexString, stripHexPrefix } from '@ethereumjs/util' import { Holesky, Kaustinen6, Mainnet, Sepolia } from './chains.js' import { Hardfork } from './enums.js' @@ -80,7 +80,7 @@ function parseGethParams(json: any) { // EIP155 and EIP158 are both part of Spurious Dragon hardfork and must occur at the same time // but have different configuration parameters in geth genesis parameters if (config.eip155Block !== config.eip158Block) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'EIP155 block number must equal EIP 158 block number since both are part of SpuriousDragon hardfork and the client only supports activating the full hardfork', ) } @@ -92,7 +92,7 @@ function parseGethParams(json: any) { for (const [hfKey, hfSchedule] of Object.entries(config.blobSchedule)) { const hfConfig = hardforksDict[hfKey] if (hfConfig === undefined) { - throw new Error(`unknown hardfork=${hfKey} specified in blobSchedule`) + throw EthereumJSErrorUnsetCode(`unknown hardfork=${hfKey} specified in blobSchedule`) } const { target, @@ -100,7 +100,7 @@ function parseGethParams(json: any) { baseFeeUpdateFraction: blobGasPriceUpdateFraction, } = hfSchedule as { target?: number; max?: number; baseFeeUpdateFraction?: undefined } if (target === undefined || max === undefined || blobGasPriceUpdateFraction === undefined) { - throw new Error( + throw EthereumJSErrorUnsetCode( `undefined target, max or baseFeeUpdateFraction specified in blobSchedule for hardfork=${hfKey}`, ) } @@ -292,7 +292,9 @@ export function parseGethGenesis(json: any, name?: string) { const required = ['config', 'difficulty', 'gasLimit', 'nonce', 'alloc'] if (required.some((field) => !(field in json))) { const missingField = required.filter((field) => !(field in json)) - throw new Error(`Invalid format, expected geth genesis field "${missingField}" missing`) + throw EthereumJSErrorUnsetCode( + `Invalid format, expected geth genesis field "${missingField}" missing`, + ) } // We copy the JSON object here because it's frozen in browser and properties can't be modified @@ -303,7 +305,7 @@ export function parseGethGenesis(json: any, name?: string) { } return parseGethParams(finalJSON) } catch (e: any) { - throw new Error(`Error parsing parameters file: ${e.message}`) + throw EthereumJSErrorUnsetCode(`Error parsing parameters file: ${e.message}`) } } diff --git a/packages/devp2p/src/dns/dns.ts b/packages/devp2p/src/dns/dns.ts index 2b0ad7226a4..5148e5cb5fa 100644 --- a/packages/devp2p/src/dns/dns.ts +++ b/packages/devp2p/src/dns/dns.ts @@ -1,3 +1,4 @@ +import { EthereumJSErrorUnsetCode } from '@ethereumjs/util' import debugDefault from 'debug' import * as dns from 'dns' @@ -142,7 +143,7 @@ export class DNS { } // If all possible paths are circular... if (Object.keys(circularRefs).length === branches.length) { - throw new Error('Unresolvable circular path detected') + throw EthereumJSErrorUnsetCode('Unresolvable circular path detected') } // Randomly select a viable path @@ -174,8 +175,8 @@ export class DNS { const response = await dns.promises.resolve(location, 'TXT') if (response.length === 0) - throw new Error('Received empty result array while fetching TXT record') - if (response[0].length === 0) throw new Error('Received empty TXT record') + throw EthereumJSErrorUnsetCode('Received empty result array while fetching TXT record') + if (response[0].length === 0) throw EthereumJSErrorUnsetCode('Received empty TXT record') // Branch entries can be an array of strings of comma delimited subdomains, with // some subdomain strings split across the array elements // (e.g btw end of arr[0] and beginning of arr[1]) diff --git a/packages/devp2p/src/dns/enr.ts b/packages/devp2p/src/dns/enr.ts index 52bea14b9e9..1e249f53f9b 100644 --- a/packages/devp2p/src/dns/enr.ts +++ b/packages/devp2p/src/dns/enr.ts @@ -1,5 +1,5 @@ import { RLP } from '@ethereumjs/rlp' -import { bytesToUtf8, utf8ToBytes } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, bytesToUtf8, utf8ToBytes } from '@ethereumjs/util' import { base32, base64url } from '@scure/base' import { keccak256 } from 'ethereum-cryptography/keccak.js' import { ecdsaVerify } from 'ethereum-cryptography/secp256k1-compat.js' @@ -49,7 +49,7 @@ export class ENR { */ static parseAndVerifyRecord(enr: string, common?: Common): PeerInfo { if (!enr.startsWith(this.RECORD_PREFIX)) - throw new Error(`String encoded ENR must start with '${this.RECORD_PREFIX}'`) + throw EthereumJSErrorUnsetCode(`String encoded ENR must start with '${this.RECORD_PREFIX}'`) // ENRs are RLP encoded and written to DNS TXT entries as base64 url-safe strings respectively // RawURLEncoding, which is the unpadded alternate base64 encoding defined in RFC 4648 @@ -77,7 +77,7 @@ export class ENR { obj.secp256k1, ) - if (!isVerified) throw new Error('Unable to verify ENR signature') + if (!isVerified) throw EthereumJSErrorUnsetCode('Unable to verify ENR signature') const peerInfo: PeerInfo = { address: ipToString(obj.ip), @@ -98,7 +98,7 @@ export class ENR { */ static parseAndVerifyRoot(root: string, publicKey: string, common?: Common): string { if (!root.startsWith(this.ROOT_PREFIX)) - throw new Error(`ENR root entry must start with '${this.ROOT_PREFIX}'`) + throw EthereumJSErrorUnsetCode(`ENR root entry must start with '${this.ROOT_PREFIX}'`) const rootValues = sscanf( root, @@ -109,10 +109,14 @@ export class ENR { 'signature', ) as ENRRootValues - if (!rootValues.eRoot) throw new Error("Could not parse 'e' value from ENR root entry") - if (!rootValues.lRoot) throw new Error("Could not parse 'l' value from ENR root entry") - if (!rootValues.seq) throw new Error("Could not parse 'seq' value from ENR root entry") - if (!rootValues.signature) throw new Error("Could not parse 'sig' value from ENR root entry") + if (!rootValues.eRoot) + throw EthereumJSErrorUnsetCode("Could not parse 'e' value from ENR root entry") + if (!rootValues.lRoot) + throw EthereumJSErrorUnsetCode("Could not parse 'l' value from ENR root entry") + if (!rootValues.seq) + throw EthereumJSErrorUnsetCode("Could not parse 'seq' value from ENR root entry") + if (!rootValues.signature) + throw EthereumJSErrorUnsetCode("Could not parse 'sig' value from ENR root entry") const decodedPublicKey = [...base32.decode(publicKey + '===').values()] @@ -133,7 +137,7 @@ export class ENR { keyBytes, ) - if (!isVerified) throw new Error('Unable to verify ENR root signature') + if (!isVerified) throw EthereumJSErrorUnsetCode('Unable to verify ENR root signature') return rootValues.eRoot } @@ -148,7 +152,7 @@ export class ENR { */ static parseTree(tree: string): ENRTreeValues { if (!tree.startsWith(this.TREE_PREFIX)) - throw new Error(`ENR tree entry must start with '${this.TREE_PREFIX}'`) + throw EthereumJSErrorUnsetCode(`ENR tree entry must start with '${this.TREE_PREFIX}'`) const treeValues = sscanf( tree, @@ -157,8 +161,10 @@ export class ENR { 'domain', ) as ENRTreeValues - if (!treeValues.publicKey) throw new Error('Could not parse public key from ENR tree entry') - if (!treeValues.domain) throw new Error('Could not parse domain from ENR tree entry') + if (!treeValues.publicKey) + throw EthereumJSErrorUnsetCode('Could not parse public key from ENR tree entry') + if (!treeValues.domain) + throw EthereumJSErrorUnsetCode('Could not parse domain from ENR tree entry') return treeValues } @@ -171,7 +177,7 @@ export class ENR { */ static parseBranch(branch: string): string[] { if (!branch.startsWith(this.BRANCH_PREFIX)) - throw new Error(`ENR branch entry must start with '${this.BRANCH_PREFIX}'`) + throw EthereumJSErrorUnsetCode(`ENR branch entry must start with '${this.BRANCH_PREFIX}'`) return branch.split(this.BRANCH_PREFIX)[1].split(',') } diff --git a/packages/devp2p/src/dpt/dpt.ts b/packages/devp2p/src/dpt/dpt.ts index d736f07d6fc..8d3d8d7d0b6 100644 --- a/packages/devp2p/src/dpt/dpt.ts +++ b/packages/devp2p/src/dpt/dpt.ts @@ -1,4 +1,9 @@ -import { bytesToInt, bytesToUnprefixedHex, randomBytes } from '@ethereumjs/util' +import { + EthereumJSErrorUnsetCode, + bytesToInt, + bytesToUnprefixedHex, + randomBytes, +} from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak.js' import { secp256k1 } from 'ethereum-cryptography/secp256k1.js' import { EventEmitter } from 'eventemitter3' @@ -150,7 +155,7 @@ export class DPT { } async addPeer(obj: PeerInfo): Promise { - if (this._banlist.has(obj)) throw new Error('Peer is banned') + if (this._banlist.has(obj)) throw EthereumJSErrorUnsetCode('Peer is banned') if (this.DEBUG) { this._debug(`attempt adding peer ${obj.address}:${obj.udpPort}`) } diff --git a/packages/devp2p/src/dpt/message.ts b/packages/devp2p/src/dpt/message.ts index 2ef08c84495..d01375ff07c 100644 --- a/packages/devp2p/src/dpt/message.ts +++ b/packages/devp2p/src/dpt/message.ts @@ -1,5 +1,12 @@ import { RLP } from '@ethereumjs/rlp' -import { bytesToHex, bytesToInt, bytesToUtf8, concatBytes, intToBytes } from '@ethereumjs/util' +import { + EthereumJSErrorUnsetCode, + bytesToHex, + bytesToInt, + bytesToUtf8, + concatBytes, + intToBytes, +} from '@ethereumjs/util' import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' import { ecdsaRecover, ecdsaSign } from 'ethereum-cryptography/secp256k1-compat.js' @@ -31,7 +38,7 @@ const address = { encode(value: string) { if (isV4Format(value)) return ipToBytes(value) if (isV6Format(value)) return ipToBytes(value) - throw new Error(`Invalid address: ${value}`) + throw EthereumJSErrorUnsetCode(`Invalid address: ${value}`) }, decode(bytes: Uint8Array) { if (bytes.length === 4) return ipToString(bytes) @@ -41,7 +48,7 @@ const address = { if (isV4Format(str) || isV6Format(str)) return str // also can be host, but skip it right now (because need async function for resolve) - throw new Error(`Invalid address bytes: ${bytesToHex(bytes)}`) + throw EthereumJSErrorUnsetCode(`Invalid address bytes: ${bytesToHex(bytes)}`) }, } @@ -174,7 +181,7 @@ const types: Types = { export function encode(typename: string, data: T, privateKey: Uint8Array, common?: Common) { const type: number = types.byName[typename] as number - if (type === undefined) throw new Error(`Invalid typename: ${typename}`) + if (type === undefined) throw EthereumJSErrorUnsetCode(`Invalid typename: ${typename}`) const encodedMsg = messages[typename].encode(data) const typedata = concatBytes(Uint8Array.from([type]), RLP.encode(encodedMsg)) @@ -192,7 +199,7 @@ export function decode(bytes: Uint8Array, common?: Common) { const typedata = bytes.subarray(97) const type = typedata[0] const typename = types.byType[type] - if (typename === undefined) throw new Error(`Invalid type: ${type}`) + if (typename === undefined) throw EthereumJSErrorUnsetCode(`Invalid type: ${type}`) const data = messages[typename].decode(unstrictDecode(typedata.subarray(1))) const sighash = (common?.customCrypto.keccak256 ?? keccak256)(typedata) diff --git a/packages/devp2p/src/dpt/server.ts b/packages/devp2p/src/dpt/server.ts index 15b3b8654d3..d28549c77e4 100644 --- a/packages/devp2p/src/dpt/server.ts +++ b/packages/devp2p/src/dpt/server.ts @@ -1,4 +1,4 @@ -import { bytesToHex, bytesToUnprefixedHex } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, bytesToHex, bytesToUnprefixedHex } from '@ethereumjs/util' import debugDefault from 'debug' import * as dgram from 'dgram' import { EventEmitter } from 'eventemitter3' @@ -115,7 +115,9 @@ export class Server { ) } this._requests.delete(rKey) - deferred.reject(new Error(`Timeout error: ping ${peer.address}:${peer.udpPort}`)) + deferred.reject( + EthereumJSErrorUnsetCode(`Timeout error: ping ${peer.address}:${peer.udpPort}`), + ) } else { return deferred.promise } @@ -131,7 +133,7 @@ export class Server { } _isAliveCheck() { - if (this._socket === null) throw new Error('Server already destroyed') + if (this._socket === null) throw EthereumJSErrorUnsetCode('Server already destroyed') } _send(peer: PeerInfo, typename: string, data: any) { diff --git a/packages/devp2p/src/protocol/eth.ts b/packages/devp2p/src/protocol/eth.ts index bb35c932358..5e1fc0a6edd 100644 --- a/packages/devp2p/src/protocol/eth.ts +++ b/packages/devp2p/src/protocol/eth.ts @@ -1,6 +1,7 @@ import { RLP } from '@ethereumjs/rlp' import { BIGINT_0, + EthereumJSErrorUnsetCode, bigIntToBytes, bytesToBigInt, bytesToHex, @@ -147,7 +148,7 @@ export class ETH extends Protocol { if (this.DEBUG) { this.debug('STATUS', msg) } - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } } @@ -158,7 +159,7 @@ export class ETH extends Protocol { if (this.DEBUG) { this.debug('STATUS', msg) } - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } if (!c.hardforkGteHardfork(peerFork.name, this._hardfork)) { @@ -172,7 +173,7 @@ export class ETH extends Protocol { if (this.DEBUG) { this.debug('STATUS', msg) } - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } } @@ -280,7 +281,7 @@ export class ETH extends Protocol { if (status.latestBlock) { const latestBlock = bytesToBigInt(status.latestBlock) if (latestBlock < this._latestBlock) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'latest block provided is not matching the HF setting of the Common instance (Rlpx)', ) } @@ -328,7 +329,7 @@ export class ETH extends Protocol { switch (code) { case ETH.MESSAGE_CODES.STATUS: - throw new Error('Please send status message through .sendStatus') + throw EthereumJSErrorUnsetCode('Please send status message through .sendStatus') case ETH.MESSAGE_CODES.NEW_BLOCK_HASHES: case ETH.MESSAGE_CODES.TX: @@ -338,26 +339,26 @@ export class ETH extends Protocol { case ETH.MESSAGE_CODES.BLOCK_BODIES: case ETH.MESSAGE_CODES.NEW_BLOCK: if (this._version >= ETH.eth62.version) break - throw new Error(`Code ${code} not allowed with version ${this._version}`) + throw EthereumJSErrorUnsetCode(`Code ${code} not allowed with version ${this._version}`) case ETH.MESSAGE_CODES.GET_RECEIPTS: case ETH.MESSAGE_CODES.RECEIPTS: if (this._version >= ETH.eth63.version) break - throw new Error(`Code ${code} not allowed with version ${this._version}`) + throw EthereumJSErrorUnsetCode(`Code ${code} not allowed with version ${this._version}`) case ETH.MESSAGE_CODES.NEW_POOLED_TRANSACTION_HASHES: case ETH.MESSAGE_CODES.GET_POOLED_TRANSACTIONS: case ETH.MESSAGE_CODES.POOLED_TRANSACTIONS: if (this._version >= ETH.eth65.version) break - throw new Error(`Code ${code} not allowed with version ${this._version}`) + throw EthereumJSErrorUnsetCode(`Code ${code} not allowed with version ${this._version}`) case ETH.MESSAGE_CODES.GET_NODE_DATA: case ETH.MESSAGE_CODES.NODE_DATA: if (this._version >= ETH.eth63.version && this._version <= ETH.eth66.version) break - throw new Error(`Code ${code} not allowed with version ${this._version}`) + throw EthereumJSErrorUnsetCode(`Code ${code} not allowed with version ${this._version}`) default: - throw new Error(`Unknown code ${code}`) + throw EthereumJSErrorUnsetCode(`Unknown code ${code}`) } payload = RLP.encode(payload) diff --git a/packages/devp2p/src/protocol/snap.ts b/packages/devp2p/src/protocol/snap.ts index 3fa8da2d448..ecd2b41c78f 100644 --- a/packages/devp2p/src/protocol/snap.ts +++ b/packages/devp2p/src/protocol/snap.ts @@ -1,5 +1,5 @@ import { RLP, utils } from '@ethereumjs/rlp' -import { bytesToHex } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, bytesToHex } from '@ethereumjs/util' import * as snappy from 'snappyjs' import { ProtocolType } from '../types.js' @@ -81,7 +81,7 @@ export class SNAP extends Protocol { case SNAP.MESSAGE_CODES.TRIE_NODES: break default: - throw new Error(`Unknown code ${code}`) + throw EthereumJSErrorUnsetCode(`Unknown code ${code}`) } payload = RLP.encode(payload) diff --git a/packages/devp2p/src/rlpx/ecies.ts b/packages/devp2p/src/rlpx/ecies.ts index c688cbbead8..50e68125608 100644 --- a/packages/devp2p/src/rlpx/ecies.ts +++ b/packages/devp2p/src/rlpx/ecies.ts @@ -1,5 +1,11 @@ import { RLP } from '@ethereumjs/rlp' -import { bytesToInt, concatBytes, hexToBytes, intToBytes } from '@ethereumjs/util' +import { + EthereumJSErrorUnsetCode, + bytesToInt, + concatBytes, + hexToBytes, + intToBytes, +} from '@ethereumjs/util' import * as crypto from 'crypto' import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' @@ -399,7 +405,7 @@ export class ECIES { } parseBody(data: Uint8Array): Uint8Array | undefined { - if (this._bodySize === null) throw new Error('need to parse header first') + if (this._bodySize === null) throw EthereumJSErrorUnsetCode('need to parse header first') const body = data.subarray(0, -16) const mac = data.subarray(-16) diff --git a/packages/devp2p/src/rlpx/peer.ts b/packages/devp2p/src/rlpx/peer.ts index 73335724b09..89d16e96753 100644 --- a/packages/devp2p/src/rlpx/peer.ts +++ b/packages/devp2p/src/rlpx/peer.ts @@ -1,5 +1,6 @@ import { RLP } from '@ethereumjs/rlp' import { + EthereumJSErrorUnsetCode, bytesToHex, bytesToInt, bytesToUtf8, @@ -423,7 +424,7 @@ export class Peer { // The subprotocol is then calling into the lower level method // (e.g. `ETH` calling into `Peer._sendMessage()`). const sendMethod = (code: number, data: Uint8Array) => { - if (code > obj.length) throw new Error('Code out of range') + if (code > obj.length) throw EthereumJSErrorUnsetCode('Code out of range') this._sendMessage(_offset + code, data) } // Dynamically instantiate the subprotocol object @@ -606,7 +607,7 @@ export class Peer { payload = RLP.decode(snappy.uncompress(payload)) } } else { - throw new Error(e) + throw EthereumJSErrorUnsetCode(e) } } } diff --git a/packages/devp2p/src/rlpx/rlpx.ts b/packages/devp2p/src/rlpx/rlpx.ts index 867f5485243..b299569a86d 100644 --- a/packages/devp2p/src/rlpx/rlpx.ts +++ b/packages/devp2p/src/rlpx/rlpx.ts @@ -1,4 +1,5 @@ import { + EthereumJSErrorUnsetCode, bytesToInt, bytesToUnprefixedHex, equalsBytes, @@ -152,8 +153,9 @@ export class RLPx { if (!(peer.id instanceof Uint8Array)) throw new TypeError('Expected peer.id as Uint8Array') const peerKey = bytesToUnprefixedHex(peer.id) - if (this._peers.has(peerKey)) throw new Error('Already connected') - if (this._getOpenSlots() === 0) throw new Error('Too many peers already connected') + if (this._peers.has(peerKey)) throw EthereumJSErrorUnsetCode('Already connected') + if (this._getOpenSlots() === 0) + throw EthereumJSErrorUnsetCode('Too many peers already connected') if (this.DEBUG) { this._debug( @@ -170,7 +172,9 @@ export class RLPx { }) socket.once('error', deferred.reject) - socket.setTimeout(this._timeout, () => deferred.reject(new Error('Connection timeout'))) + socket.setTimeout(this._timeout, () => + deferred.reject(EthereumJSErrorUnsetCode('Connection timeout')), + ) socket.connect(peer.tcpPort, peer.address, deferred.resolve) await deferred.promise @@ -193,7 +197,7 @@ export class RLPx { } _isAliveCheck() { - if (!this._isAlive()) throw new Error('Server already destroyed') + if (!this._isAlive()) throw EthereumJSErrorUnsetCode('Server already destroyed') } _getOpenSlots() { diff --git a/packages/devp2p/src/util.ts b/packages/devp2p/src/util.ts index 01f66cd4ef9..ecd76460aa1 100644 --- a/packages/devp2p/src/util.ts +++ b/packages/devp2p/src/util.ts @@ -1,5 +1,11 @@ import { RLP } from '@ethereumjs/rlp' -import { bytesToHex, bytesToUnprefixedHex, concatBytes, equalsBytes } from '@ethereumjs/util' +import { + EthereumJSErrorUnsetCode, + bytesToHex, + bytesToUnprefixedHex, + concatBytes, + equalsBytes, +} from '@ethereumjs/util' import debug from 'debug' import { publicKeyConvert } from 'ethereum-cryptography/secp256k1-compat.js' import { secp256k1 } from 'ethereum-cryptography/secp256k1.js' @@ -58,7 +64,7 @@ export function assertEq( } else { debug(debugMsg) } - throw new Error(fullMsg) + throw EthereumJSErrorUnsetCode(fullMsg) } if (expected === actual) return @@ -68,7 +74,7 @@ export function assertEq( } else { debug(fullMsg) } - throw new Error(fullMsg) + throw EthereumJSErrorUnsetCode(fullMsg) } export function formatLogId(id: string, verbose: boolean): string { diff --git a/packages/era/src/e2store.ts b/packages/era/src/e2store.ts index 39a3a7d3262..d39dedfcaa2 100644 --- a/packages/era/src/e2store.ts +++ b/packages/era/src/e2store.ts @@ -1,5 +1,11 @@ import { RLP } from '@ethereumjs/rlp' -import { bigInt64ToBytes, bytesToHex, concatBytes, equalsBytes } from '@ethereumjs/util' +import { + EthereumJSErrorUnsetCode, + bigInt64ToBytes, + bytesToHex, + concatBytes, + equalsBytes, +} from '@ethereumjs/util' import { uint256 } from 'micro-eth-signer/ssz' import { compressData, decompressData } from './snappy.js' @@ -30,7 +36,7 @@ export async function parseEntry(entry: e2StoreEntry) { data = decompressed break default: - throw new Error(`unknown entry type - ${bytesToHex(entry.type)}`) + throw EthereumJSErrorUnsetCode(`unknown entry type - ${bytesToHex(entry.type)}`) } return { type: entry.type, data } } @@ -43,7 +49,7 @@ export async function parseEntry(entry: e2StoreEntry) { */ export const readEntry = (bytes: Uint8Array): e2StoreEntry => { if (bytes.length < 8) - throw new Error(`invalid data length, got ${bytes.length}, expected at least 8`) + throw EthereumJSErrorUnsetCode(`invalid data length, got ${bytes.length}, expected at least 8`) const type = bytes.slice(0, 2) const lengthBytes = concatBytes(bytes.subarray(2, 8), new Uint8Array([0, 0])) const length = Number( @@ -51,7 +57,9 @@ export const readEntry = (bytes: Uint8Array): e2StoreEntry => { ) if (length > bytes.length) { // Check for overflow - throw new Error(`invalid data length, got ${length}, expected max of ${bytes.length - 8}`) + throw EthereumJSErrorUnsetCode( + `invalid data length, got ${length}, expected max of ${bytes.length - 8}`, + ) } const data = length > 0 ? bytes.subarray(8, 8 + length) : new Uint8Array() diff --git a/packages/era/src/era1.ts b/packages/era/src/era1.ts index ef778db91d7..e2865ef4c2b 100644 --- a/packages/era/src/era1.ts +++ b/packages/era/src/era1.ts @@ -1,4 +1,10 @@ -import { bigInt64ToBytes, bytesToBigInt64, concatBytes, equalsBytes } from '@ethereumjs/util' +import { + EthereumJSErrorUnsetCode, + bigInt64ToBytes, + bytesToBigInt64, + concatBytes, + equalsBytes, +} from '@ethereumjs/util' import * as ssz from 'micro-eth-signer/ssz' import { blockFromTuple, parseBlockTuple, readBlockTupleAtOffset } from './blockTuple.js' @@ -100,7 +106,7 @@ export function getBlockIndex(bytes: Uint8Array) { const recordStart = recordEnd - recordLength const { data, type } = readEntry(bytes.subarray(recordStart, recordEnd)) if (!equalsBytes(type, Era1Types.BlockIndex)) { - throw new Error('not a valid block index') + throw EthereumJSErrorUnsetCode('not a valid block index') } return { data, type, count, recordStart } } diff --git a/packages/ethash/src/index.ts b/packages/ethash/src/index.ts index db4d594c0b3..921e4101c01 100644 --- a/packages/ethash/src/index.ts +++ b/packages/ethash/src/index.ts @@ -2,6 +2,7 @@ import { Block, BlockHeader, createBlock, createBlockHeader } from '@ethereumjs/ import { RLP } from '@ethereumjs/rlp' import { BIGINT_0, + EthereumJSErrorUnsetCode, KeyEncoding, TWO_POW256, ValueEncoding, @@ -67,7 +68,7 @@ export class Miner { this.block = mineObject this.blockHeader = mineObject.header } else { - throw new Error('unsupported mineObject') + throw EthereumJSErrorUnsetCode('unsupported mineObject') } this.currentNonce = BIGINT_0 this.ethash = ethash @@ -210,7 +211,7 @@ export class Ethash { run(val: Uint8Array, nonce: Uint8Array, fullSize?: number) { if (fullSize === undefined) { if (this.fullSize === undefined) { - throw new Error('fullSize needed') + throw EthereumJSErrorUnsetCode('fullSize needed') } else { fullSize = this.fullSize } @@ -285,7 +286,7 @@ export class Ethash { this.epoc = epoc if (!this.cacheDB) { - throw new Error('cacheDB needed') + throw EthereumJSErrorUnsetCode('cacheDB needed') } // gives the seed the first epoc found diff --git a/packages/evm/src/eof/container.ts b/packages/evm/src/eof/container.ts index 3f8a1cddeb2..cbf2cfc2e69 100644 --- a/packages/evm/src/eof/container.ts +++ b/packages/evm/src/eof/container.ts @@ -1,3 +1,5 @@ +import { EthereumJSErrorUnsetCode } from '@ethereumjs/util' + import { CODE_MIN, CODE_SIZE_MIN, @@ -147,7 +149,7 @@ class EOFHeader { */ constructor(input: Uint8Array) { if (input.length > MAX_HEADER_SIZE) { - throw new Error('err: container size more than maximum valid size') + throw EthereumJSErrorUnsetCode('err: container size more than maximum valid size') } const stream = new StreamReader(input) // Verify that the header starts with 0xEF0001 @@ -155,7 +157,7 @@ class EOFHeader { stream.verifyUint(MAGIC, EOFError.MAGIC) stream.verifyUint(VERSION, EOFError.VERSION) if (input.length < 15) { - throw new Error('err: container size less than minimum valid size') + throw EthereumJSErrorUnsetCode('err: container size less than minimum valid size') } // Verify that the types section is present and its length is valid stream.verifyUint(KIND_TYPE, EOFError.KIND_TYPE) @@ -167,7 +169,9 @@ class EOFHeader { validationError(EOFError.InvalidTypeSize, typeSize) } if (typeSize > TYPE_MAX) { - throw new Error(`err: number of code sections must not exceed 1024 (got ${typeSize})`) + throw EthereumJSErrorUnsetCode( + `err: number of code sections must not exceed 1024 (got ${typeSize})`, + ) } // Verify that the code section is present and its size is valid stream.verifyUint(KIND_CODE, EOFError.KIND_CODE) diff --git a/packages/evm/src/eof/errors.ts b/packages/evm/src/eof/errors.ts index 7b7cf9798af..4c7ac5d8ce2 100644 --- a/packages/evm/src/eof/errors.ts +++ b/packages/evm/src/eof/errors.ts @@ -1,3 +1,5 @@ +import { EthereumJSErrorUnsetCode } from '@ethereumjs/util' + export enum EOFError { // Stream Reader OutOfBounds = 'Trying to read out of bounds', @@ -185,71 +187,73 @@ export function validationError(type: EOFError, ...args: any): never { case EOFError.OutOfBounds: { const pos = args[0] if (pos === 0 || pos === 2 || pos === 3 || pos === 6) { - throw new Error(args[1]) + throw EthereumJSErrorUnsetCode(args[1]) } - throw new Error(EOFError.OutOfBounds + ` `) + throw EthereumJSErrorUnsetCode(EOFError.OutOfBounds + ` `) } case EOFError.VerifyBytes: { const pos = args[0] if (pos === 0 || pos === 2 || pos === 3 || pos === 6) { - throw new Error(args[1]) + throw EthereumJSErrorUnsetCode(args[1]) } - throw new Error(EOFError.VerifyBytes + ` at pos: ${args[0]}: ${args[1]}`) + throw EthereumJSErrorUnsetCode(EOFError.VerifyBytes + ` at pos: ${args[0]}: ${args[1]}`) } case EOFError.VerifyUint: { const pos = args[0] if (pos === 0 || pos === 2 || pos === 3 || pos === 6 || pos === 18) { - throw new Error(args[1]) + throw EthereumJSErrorUnsetCode(args[1]) } - throw new Error(EOFError.VerifyUint + `at pos: ${args[0]}: ${args[1]}`) + throw EthereumJSErrorUnsetCode(EOFError.VerifyUint + `at pos: ${args[0]}: ${args[1]}`) } case EOFError.TypeSize: { - throw new Error(EOFError.TypeSize + args[0]) + throw EthereumJSErrorUnsetCode(EOFError.TypeSize + args[0]) } case EOFError.TypeSections: { - throw new Error(`${EOFError.TypeSections} (types ${args[0]} code ${args[1]})`) + throw EthereumJSErrorUnsetCode(`${EOFError.TypeSections} (types ${args[0]} code ${args[1]})`) } case EOFError.InvalidTypeSize: { - throw new Error(EOFError.InvalidTypeSize) + throw EthereumJSErrorUnsetCode(EOFError.InvalidTypeSize) } case EOFError.InvalidCodeSize: { - throw new Error(EOFError.InvalidCodeSize + args[0]) + throw EthereumJSErrorUnsetCode(EOFError.InvalidCodeSize + args[0]) } case EOFError.Inputs: { - throw new Error(`${EOFError.Inputs} - typeSection ${args[0]}`) + throw EthereumJSErrorUnsetCode(`${EOFError.Inputs} - typeSection ${args[0]}`) } case EOFError.Outputs: { - throw new Error(`${EOFError.Outputs} - typeSection ${args[0]}`) + throw EthereumJSErrorUnsetCode(`${EOFError.Outputs} - typeSection ${args[0]}`) } case EOFError.Code0Inputs: { - throw new Error(`first code section should have 0 inputs`) + throw EthereumJSErrorUnsetCode(`first code section should have 0 inputs`) } case EOFError.Code0Outputs: { - throw new Error(`first code section should have 0 outputs`) + throw EthereumJSErrorUnsetCode(`first code section should have 0 outputs`) } case EOFError.MaxInputs: { - throw new Error(EOFError.MaxInputs + `${args[1]} - code section ${args[0]}`) + throw EthereumJSErrorUnsetCode(EOFError.MaxInputs + `${args[1]} - code section ${args[0]}`) } case EOFError.MaxOutputs: { - throw new Error(EOFError.MaxOutputs + `${args[1]} - code section ${args[0]}`) + throw EthereumJSErrorUnsetCode(EOFError.MaxOutputs + `${args[1]} - code section ${args[0]}`) } case EOFError.CodeSection: { - throw new Error(`expected code: codeSection ${args[0]}: `) + throw EthereumJSErrorUnsetCode(`expected code: codeSection ${args[0]}: `) } case EOFError.DataSection: { - throw new Error(EOFError.DataSection) + throw EthereumJSErrorUnsetCode(EOFError.DataSection) } case EOFError.MaxStackHeight: { - throw new Error(`${EOFError.MaxStackHeight} - typeSection ${args[0]}: `) + throw EthereumJSErrorUnsetCode(`${EOFError.MaxStackHeight} - typeSection ${args[0]}: `) } case EOFError.MaxStackHeightLimit: { - throw new Error(`${EOFError.MaxStackHeightLimit}, got: ${args[1]} - typeSection ${args[0]}`) + throw EthereumJSErrorUnsetCode( + `${EOFError.MaxStackHeightLimit}, got: ${args[1]} - typeSection ${args[0]}`, + ) } case EOFError.DanglingBytes: { - throw new Error(EOFError.DanglingBytes) + throw EthereumJSErrorUnsetCode(EOFError.DanglingBytes) } default: { - throw new Error(type) + throw EthereumJSErrorUnsetCode(type) } } } diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index a07ea4dade4..5132480c6b7 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -5,6 +5,7 @@ import { Address, BIGINT_0, BIGINT_1, + EthereumJSErrorUnsetCode, KECCAK256_NULL, KECCAK256_RLP, MAX_INTEGER, @@ -170,7 +171,7 @@ export class EVM implements EVMInterface { const mandatory = ['checkChunkWitnessPresent'] for (const m of mandatory) { if (!(m in this.stateManager)) { - throw new Error( + throw EthereumJSErrorUnsetCode( `State manager used must implement ${m} if Verkle (EIP-6800) is activated`, ) } @@ -189,12 +190,12 @@ export class EVM implements EVMInterface { for (const eip of this.common.eips()) { if (!supportedEIPs.includes(eip)) { - throw new Error(`EIP-${eip} is not supported by the EVM`) + throw EthereumJSErrorUnsetCode(`EIP-${eip} is not supported by the EVM`) } } if (!EVM.supportedHardforks.includes(this.common.hardfork() as Hardfork)) { - throw new Error( + throw EthereumJSErrorUnsetCode( `Hardfork ${this.common.hardfork()} not set as supported in supportedHardforks`, ) } @@ -266,7 +267,7 @@ export class EVM implements EVMInterface { if (this.common.isActivatedEIP(6800)) { if (message.accessWitness === undefined) { - throw new Error('accessWitness is required for EIP-6800') + throw EthereumJSErrorUnsetCode('accessWitness is required for EIP-6800') } const sendsValue = message.value !== BIGINT_0 if (message.depth === 0) { @@ -1027,7 +1028,7 @@ export class EVM implements EVMInterface { gasLimit: bigint, ): Promise | ExecResult { if (typeof code !== 'function') { - throw new Error('Invalid precompile') + throw EthereumJSErrorUnsetCode('Invalid precompile') } const opts = { diff --git a/packages/evm/src/interpreter.ts b/packages/evm/src/interpreter.ts index eda6ae09d39..61568e13ea5 100644 --- a/packages/evm/src/interpreter.ts +++ b/packages/evm/src/interpreter.ts @@ -4,6 +4,7 @@ import { BIGINT_0, BIGINT_1, BIGINT_2, + EthereumJSErrorUnsetCode, MAX_UINT64, bigIntToHex, bytesToBigInt, @@ -174,7 +175,7 @@ export class Interpreter { this.common.consensusType() === 'poa' && this._evm['_optsCached'].cliqueSigner === undefined ) - throw new Error( + throw EthereumJSErrorUnsetCode( 'Must include cliqueSigner function if clique/poa is being used for consensus type', ) @@ -263,7 +264,7 @@ export class Interpreter { // Check that the programCounter is in range const pc = this._runState.programCounter if (pc !== 0 && (pc < 0 || pc >= this._runState.code.length)) { - throw new Error('Internal error: program counter not in range') + throw EthereumJSErrorUnsetCode('Internal error: program counter not in range') } let err @@ -633,7 +634,7 @@ export class Interpreter { await this._stateManager.putStorage(this._env.address, key, value) const account = await this._stateManager.getAccount(this._env.address) if (!account) { - throw new Error('could not read account while persisting memory') + throw EthereumJSErrorUnsetCode('could not read account while persisting memory') } this._env.contract = account } @@ -852,7 +853,7 @@ export class Interpreter { const baseFee = this._env.block.header.baseFeePerGas if (baseFee === undefined) { // Sanity check - throw new Error('Block has no Base Fee') + throw EthereumJSErrorUnsetCode('Block has no Base Fee') } return baseFee } @@ -864,7 +865,7 @@ export class Interpreter { const blobBaseFee = this._env.block.header.getBlobGasPrice() if (blobBaseFee === undefined) { // Sanity check - throw new Error('Block has no Blob Base Fee') + throw EthereumJSErrorUnsetCode('Block has no Blob Base Fee') } return blobBaseFee } @@ -1029,7 +1030,7 @@ export class Interpreter { // update stateRoot on current contract const account = await this._stateManager.getAccount(this._env.address) if (!account) { - throw new Error('could not read contract account') + throw EthereumJSErrorUnsetCode('could not read contract account') } this._env.contract = account this._runState.gasRefund = results.execResult.gasRefund ?? BIGINT_0 @@ -1133,7 +1134,7 @@ export class Interpreter { // update stateRoot on current contract const account = await this._stateManager.getAccount(this._env.address) if (!account) { - throw new Error('could not read contract account') + throw EthereumJSErrorUnsetCode('could not read contract account') } this._env.contract = account this._runState.gasRefund = results.execResult.gasRefund ?? BIGINT_0 diff --git a/packages/evm/src/journal.ts b/packages/evm/src/journal.ts index 2b57555d903..002ba9976d4 100644 --- a/packages/evm/src/journal.ts +++ b/packages/evm/src/journal.ts @@ -1,6 +1,7 @@ import { Hardfork } from '@ethereumjs/common' import { Address, + EthereumJSErrorUnsetCode, RIPEMD160_ADDRESS_STRING, bytesToHex, bytesToUnprefixedHex, @@ -99,7 +100,7 @@ export class Journal { if (this.preimages !== undefined) { const bytesAddress = unprefixedHexToBytes(address) if (this.stateManager.getAppliedKey === undefined) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'touchAccount: stateManager.getAppliedKey can not be undefined if preimage storing is enabled', ) } diff --git a/packages/evm/src/logger.ts b/packages/evm/src/logger.ts index 13f383e0098..2c84d5396c3 100644 --- a/packages/evm/src/logger.ts +++ b/packages/evm/src/logger.ts @@ -1,3 +1,5 @@ +import { EthereumJSErrorUnsetCode } from '@ethereumjs/util' + type EVMPerformanceLogEntry = { calls: number time: number @@ -113,7 +115,7 @@ export class EVMPerformanceLogger { // Only one timer can be timing at the same time startTimer(tag: string) { if (this.currentTimer !== undefined) { - throw new Error('Cannot have two timers running at the same time') + throw EthereumJSErrorUnsetCode('Cannot have two timers running at the same time') } this.currentTimer = new Timer(tag) @@ -124,7 +126,7 @@ export class EVMPerformanceLogger { pauseTimer() { const timer = this.currentTimer if (timer === undefined) { - throw new Error('No timer to pause') + throw EthereumJSErrorUnsetCode('No timer to pause') } timer.pause() this.currentTimer = undefined @@ -134,7 +136,7 @@ export class EVMPerformanceLogger { // Unpauses current timer and returns that timer unpauseTimer(timer: Timer) { if (this.currentTimer !== undefined) { - throw new Error('Cannot unpause timer: another timer is already running') + throw EthereumJSErrorUnsetCode('Cannot unpause timer: another timer is already running') } timer.unpause() this.currentTimer = timer @@ -149,7 +151,7 @@ export class EVMPerformanceLogger { dynamicGas?: number, ) { if (this.currentTimer === undefined || this.currentTimer !== timer) { - throw new Error('Cannot stop timer: another timer is already running') + throw EthereumJSErrorUnsetCode('Cannot stop timer: another timer is already running') } const time = timer.time() const tag = timer.tag diff --git a/packages/evm/src/memory.ts b/packages/evm/src/memory.ts index 7ddb6b4cc2e..73b252dcadf 100644 --- a/packages/evm/src/memory.ts +++ b/packages/evm/src/memory.ts @@ -1,4 +1,4 @@ -import { concatBytes } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, concatBytes } from '@ethereumjs/util' const ceil = (value: number, ceiling: number): number => { const r = value % ceiling @@ -52,8 +52,9 @@ export class Memory { this.extend(offset, size) - if (value.length !== size) throw new Error('Invalid value size') - if (offset + size > this._store.length) throw new Error('Value exceeds memory capacity') + if (value.length !== size) throw EthereumJSErrorUnsetCode('Invalid value size') + if (offset + size > this._store.length) + throw EthereumJSErrorUnsetCode('Value exceeds memory capacity') this._store.set(value, offset) } diff --git a/packages/evm/src/message.ts b/packages/evm/src/message.ts index 193b99940a8..149be7a614b 100644 --- a/packages/evm/src/message.ts +++ b/packages/evm/src/message.ts @@ -1,4 +1,4 @@ -import { BIGINT_0, createZeroAddress } from '@ethereumjs/util' +import { BIGINT_0, EthereumJSErrorUnsetCode, createZeroAddress } from '@ethereumjs/util' import type { PrecompileFunc } from './precompiles/index.js' import type { EOFEnv } from './types.js' @@ -95,7 +95,7 @@ export class Message { this.blobVersionedHashes = opts.blobVersionedHashes this.accessWitness = opts.accessWitness if (this.value < 0) { - throw new Error(`value field cannot be negative, received ${this.value}`) + throw EthereumJSErrorUnsetCode(`value field cannot be negative, received ${this.value}`) } } @@ -105,7 +105,7 @@ export class Message { get codeAddress(): Address { const codeAddress = this._codeAddress ?? this.to if (!codeAddress) { - throw new Error('Missing codeAddress') + throw EthereumJSErrorUnsetCode('Missing codeAddress') } return codeAddress } diff --git a/packages/evm/src/opcodes/codes.ts b/packages/evm/src/opcodes/codes.ts index 42993e7853e..ecdc7768ecf 100644 --- a/packages/evm/src/opcodes/codes.ts +++ b/packages/evm/src/opcodes/codes.ts @@ -1,4 +1,5 @@ import { Hardfork } from '@ethereumjs/common' +import { EthereumJSErrorUnsetCode } from '@ethereumjs/util' import { handlers } from './functions.js' import { dynamicGasHandlers } from './gas.js' @@ -439,7 +440,7 @@ export function getOpcodesForHF(common: Common, customOpcodes?: CustomOpcode[]): const baseFee = Number(common.param(`${opcodeBuilder[key].name.toLowerCase()}Gas`)) // explicitly verify that we have defined a base fee if (baseFee === undefined) { - throw new Error(`base fee not defined for: ${opcodeBuilder[key].name}`) + throw EthereumJSErrorUnsetCode(`base fee not defined for: ${opcodeBuilder[key].name}`) } opcodeBuilder[key].fee = baseFee } @@ -454,7 +455,7 @@ export function getOpcodesForHF(common: Common, customOpcodes?: CustomOpcode[]): // Sanity checks if (code.opcodeName === undefined || code.baseFee === undefined) { - throw new Error( + throw EthereumJSErrorUnsetCode( `Custom opcode ${code.opcode} does not have the required values: opcodeName and baseFee are required`, ) } diff --git a/packages/evm/src/precompiles/0a-kzg-point-evaluation.ts b/packages/evm/src/precompiles/0a-kzg-point-evaluation.ts index 990fe37b2e3..ff35a1033ae 100644 --- a/packages/evm/src/precompiles/0a-kzg-point-evaluation.ts +++ b/packages/evm/src/precompiles/0a-kzg-point-evaluation.ts @@ -1,4 +1,5 @@ import { + EthereumJSErrorUnsetCode, bigIntToBytes, bytesToHex, computeVersionedHash, @@ -25,7 +26,7 @@ const modulusBuffer = setLengthLeft(bigIntToBytes(BLS_MODULUS), 32) export async function precompile0a(opts: PrecompileInput): Promise { const pName = getPrecompileName('0a') if (opts.common.customCrypto?.kzg === undefined) { - throw new Error('kzg not initialized') + throw EthereumJSErrorUnsetCode('kzg not initialized') } const gasUsed = opts.common.param('kzgPointEvaluationPrecompileGas') if (!gasLimitCheck(opts, gasUsed, pName)) { diff --git a/packages/evm/src/transientStorage.ts b/packages/evm/src/transientStorage.ts index a98d84468dd..6013328d2d8 100644 --- a/packages/evm/src/transientStorage.ts +++ b/packages/evm/src/transientStorage.ts @@ -1,4 +1,4 @@ -import { bytesToHex } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, bytesToHex } from '@ethereumjs/util' import type { TransientStorageInterface } from './types.js' import type { Address } from '@ethereumjs/util' @@ -52,11 +52,11 @@ export class TransientStorage implements TransientStorageInterface { */ public put(addr: Address, key: Uint8Array, value: Uint8Array) { if (key.length !== 32) { - throw new Error('Transient storage key must be 32 bytes long') + throw EthereumJSErrorUnsetCode('Transient storage key must be 32 bytes long') } if (value.length > 32) { - throw new Error('Transient storage value cannot be longer than 32 bytes') + throw EthereumJSErrorUnsetCode('Transient storage value cannot be longer than 32 bytes') } const addrString = addr.toString() @@ -81,7 +81,7 @@ export class TransientStorage implements TransientStorageInterface { * Commit all the changes since the last checkpoint */ public commit(): void { - if (this._indices.length === 0) throw new Error('Nothing to commit') + if (this._indices.length === 0) throw EthereumJSErrorUnsetCode('Nothing to commit') // by discarding the length of the array from the last time checkpoint was called, all changes are included in the last stack this._indices.pop() } @@ -98,7 +98,7 @@ export class TransientStorage implements TransientStorageInterface { */ public revert() { const lastCheckpoint = this._indices.pop() - if (typeof lastCheckpoint === 'undefined') throw new Error('Nothing to revert') + if (typeof lastCheckpoint === 'undefined') throw EthereumJSErrorUnsetCode('Nothing to revert') for (let i = this._changeJournal.length - 1; i >= lastCheckpoint; i--) { const { key, prevValue, addr } = this._changeJournal[i] diff --git a/packages/evm/src/verkleAccessWitness.ts b/packages/evm/src/verkleAccessWitness.ts index ba5ce07345e..62915afbbf3 100644 --- a/packages/evm/src/verkleAccessWitness.ts +++ b/packages/evm/src/verkleAccessWitness.ts @@ -1,6 +1,7 @@ import { VerkleAccessedStateType } from '@ethereumjs/common' import { BIGINT_0, + EthereumJSErrorUnsetCode, VERKLE_BASIC_DATA_LEAF_KEY, VERKLE_CODE_HASH_LEAF_KEY, VERKLE_CODE_OFFSET, @@ -104,7 +105,7 @@ export class VerkleAccessWitness implements VerkleAccessWitnessInterface { chunks?: Map }) { if (opts.verkleCrypto === undefined) { - throw new Error('verkle crypto required') + throw EthereumJSErrorUnsetCode('verkle crypto required') } this.verkleCrypto = opts.verkleCrypto this.stems = opts.stems ?? new Map() diff --git a/packages/mpt/src/mpt.ts b/packages/mpt/src/mpt.ts index 5622b6276ab..5eb2ce417f9 100644 --- a/packages/mpt/src/mpt.ts +++ b/packages/mpt/src/mpt.ts @@ -4,6 +4,7 @@ import { RLP } from '@ethereumjs/rlp' import { BIGINT_0, + EthereumJSErrorUnsetCode, KeyEncoding, Lock, MapDB, @@ -88,7 +89,7 @@ export class MerklePatriciaTrie { // Sanity check: can only set valueEncoding if a db is provided // The valueEncoding defaults to `Bytes` if no DB is provided (use a MapDB in memory) if (opts?.valueEncoding !== undefined && opts.db === undefined) { - throw new Error('`valueEncoding` can only be set if a `db` is provided') + throw EthereumJSErrorUnsetCode('`valueEncoding` can only be set if a `db` is provided') } this._opts = { ...this._opts, ...opts } this._opts.useKeyHashingFunction = @@ -136,7 +137,7 @@ export class MerklePatriciaTrie { database(db?: DB, valueEncoding?: ValueEncoding) { if (db !== undefined) { if (db instanceof CheckpointDB) { - throw new Error('Cannot pass in an instance of CheckpointDB') + throw EthereumJSErrorUnsetCode('Cannot pass in an instance of CheckpointDB') } this._db = new CheckpointDB({ db, cacheSize: this._opts.cacheSize, valueEncoding }) @@ -155,7 +156,7 @@ export class MerklePatriciaTrie { } this.DEBUG && this.debug(`Setting root to ${bytesToHex(value)}`) if (value.length !== this._hashLen) { - throw new Error( + throw EthereumJSErrorUnsetCode( `Invalid root length. Roots are ${this._hashLen} bytes, got ${value.length} bytes`, ) } @@ -213,7 +214,9 @@ export class MerklePatriciaTrie { this.DEBUG && this.debug(`Key: ${bytesToHex(key)}`, ['put']) this.DEBUG && this.debug(`Value: ${value === null ? 'null' : bytesToHex(key)}`, ['put']) if (this._opts.useRootPersistence && equalsBytes(key, ROOT_DB_KEY) === true) { - throw new Error(`Attempted to set '${bytesToUtf8(ROOT_DB_KEY)}' key but it is not allowed.`) + throw EthereumJSErrorUnsetCode( + `Attempted to set '${bytesToUtf8(ROOT_DB_KEY)}' key but it is not allowed.`, + ) } // If value is empty, delete @@ -521,7 +524,7 @@ export class MerklePatriciaTrie { if (value === null) { // Dev note: this error message text is used for error checking in `checkRoot`, `verifyMPTWithMerkleProof`, and `findPath` - throw new Error('Missing node in DB') + throw EthereumJSErrorUnsetCode('Missing node in DB') } const decoded = decodeMPTNode(value) @@ -546,7 +549,7 @@ export class MerklePatriciaTrie { const toSave: BatchDBOp[] = [] const lastNode = stack.pop() if (!lastNode) { - throw new Error('Stack underflow') + throw EthereumJSErrorUnsetCode('Stack underflow') } // add the new nodes @@ -704,7 +707,7 @@ export class MerklePatriciaTrie { } let lastNode = stack.pop() - if (lastNode === undefined) throw new Error('missing last node') + if (lastNode === undefined) throw EthereumJSErrorUnsetCode('missing last node') let parentNode = stack.pop() const opStack: BatchDBOp[] = [] @@ -722,7 +725,7 @@ export class MerklePatriciaTrie { // the lastNode has to be a leaf if it's not a branch. // And a leaf's parent, if it has one, must be a branch. if (!(parentNode instanceof BranchMPTNode)) { - throw new Error('Expected branch node') + throw EthereumJSErrorUnsetCode('Expected branch node') } const lastNodeKey = lastNode.key() key.splice(key.length - lastNodeKey.length) @@ -787,7 +790,7 @@ export class MerklePatriciaTrie { while (stack.length) { const node = stack.pop() if (node === undefined) { - throw new Error('saveStack: missing node') + throw EthereumJSErrorUnsetCode('saveStack: missing node') } if (node instanceof LeafMPTNode || node instanceof ExtensionMPTNode) { key.splice(key.length - node.key().length) @@ -870,7 +873,7 @@ export class MerklePatriciaTrie { for (const op of ops) { if (op.type === 'put') { if (op.value === null || op.value === undefined) { - throw new Error('Invalid batch db operation') + throw EthereumJSErrorUnsetCode('Invalid batch db operation') } await this.put(op.key, op.value, skipKeyTransform) } else if (op.type === 'del') { @@ -1042,7 +1045,7 @@ export class MerklePatriciaTrie { */ async commit(): Promise { if (!this.hasCheckpoints()) { - throw new Error('trying to commit when not checkpointed') + throw EthereumJSErrorUnsetCode('trying to commit when not checkpointed') } this.DEBUG && this.debug(`${bytesToHex(this.root())}`, ['commit']) await this._lock.acquire() @@ -1058,7 +1061,7 @@ export class MerklePatriciaTrie { */ async revert(): Promise { if (!this.hasCheckpoints()) { - throw new Error('trying to revert when not checkpointed') + throw EthereumJSErrorUnsetCode('trying to revert when not checkpointed') } this.DEBUG && this.debug(`${bytesToHex(this.root())}`, ['revert', 'before']) diff --git a/packages/mpt/src/node/util.ts b/packages/mpt/src/node/util.ts index dd4d1c5421f..6e9a143b6be 100644 --- a/packages/mpt/src/node/util.ts +++ b/packages/mpt/src/node/util.ts @@ -1,4 +1,5 @@ import { RLP } from '@ethereumjs/rlp' +import { EthereumJSErrorUnsetCode, type NestedUint8Array } from '@ethereumjs/util' import { isTerminator } from '../util/hex.js' import { bytesToNibbles } from '../util/nibbles.js' @@ -7,8 +8,6 @@ import { BranchMPTNode } from './branch.js' import { ExtensionMPTNode } from './extension.js' import { LeafMPTNode } from './leaf.js' -import type { NestedUint8Array } from '@ethereumjs/util' - export function decodeRawMPTNode(raw: Uint8Array[]) { if (raw.length === 17) { return BranchMPTNode.fromArray(raw) @@ -19,7 +18,7 @@ export function decodeRawMPTNode(raw: Uint8Array[]) { } return new ExtensionMPTNode(ExtensionMPTNode.decodeKey(nibbles), raw[1]) } else { - throw new Error('Invalid node') + throw EthereumJSErrorUnsetCode('Invalid node') } } @@ -30,7 +29,7 @@ export function isRawMPTNode(n: Uint8Array | NestedUint8Array): n is Uint8Array[ export function decodeMPTNode(node: Uint8Array) { const decodedNode = RLP.decode(Uint8Array.from(node)) if (!isRawMPTNode(decodedNode)) { - throw new Error('Invalid node') + throw EthereumJSErrorUnsetCode('Invalid node') } return decodeRawMPTNode(decodedNode) } diff --git a/packages/mpt/src/proof/proof.ts b/packages/mpt/src/proof/proof.ts index e9be20c785a..2f07f6061a5 100644 --- a/packages/mpt/src/proof/proof.ts +++ b/packages/mpt/src/proof/proof.ts @@ -1,4 +1,4 @@ -import { bytesToHex, concatBytes, equalsBytes } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, bytesToHex, concatBytes, equalsBytes } from '@ethereumjs/util' import { createMPTFromProof } from '../constructors.js' import { MerklePatriciaTrie } from '../index.js' @@ -26,7 +26,7 @@ export async function verifyMerkleProof( const value = await proofTrie.get(key, true) return value } catch (err: any) { - throw new Error('Invalid proof provided') + throw EthereumJSErrorUnsetCode('Invalid proof provided') } } @@ -74,7 +74,7 @@ export async function updateMPTFromMerkleProof( if (shouldVerifyRoot) { if (opStack[0] !== undefined && opStack[0] !== null) { if (!equalsBytes(trie.root(), opStack[0].key)) { - throw new Error('The provided proof does not have the expected trie root') + throw EthereumJSErrorUnsetCode('The provided proof does not have the expected trie root') } } } @@ -117,7 +117,7 @@ export async function verifyMPTWithMerkleProof( try { await updateMPTFromMerkleProof(proofTrie, proof, true) } catch (e: any) { - throw new Error('Invalid proof nodes given') + throw EthereumJSErrorUnsetCode('Invalid proof nodes given') } try { trie['DEBUG'] && @@ -129,7 +129,7 @@ export async function verifyMPTWithMerkleProof( return value } catch (err: any) { if (err.message === 'Missing node in DB') { - throw new Error('Invalid proof provided') + throw EthereumJSErrorUnsetCode('Invalid proof provided') } else { throw err } diff --git a/packages/mpt/src/proof/range.ts b/packages/mpt/src/proof/range.ts index a15c668245b..ae7b69a03ff 100644 --- a/packages/mpt/src/proof/range.ts +++ b/packages/mpt/src/proof/range.ts @@ -1,4 +1,4 @@ -import { equalsBytes } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, equalsBytes } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak' import { createMPTFromProof } from '../index.js' @@ -95,7 +95,7 @@ async function unset( } else if (child === null) { return pos - 1 } else { - throw new Error('invalid node') + throw EthereumJSErrorUnsetCode('invalid node') } } @@ -149,7 +149,7 @@ async function unsetInternal( if (node instanceof LeafMPTNode) { // it shouldn't happen - throw new Error('invalid node') + throw EthereumJSErrorUnsetCode('invalid node') } // continue to the next node @@ -203,7 +203,7 @@ async function unsetInternal( node = await trie.lookupNode(leftNode) pos += 1 } else { - throw new Error('invalid node') + throw EthereumJSErrorUnsetCode('invalid node') } } @@ -234,11 +234,11 @@ async function unsetInternal( } if (shortForkLeft === -1 && shortForkRight === -1) { - throw new Error('invalid range') + throw EthereumJSErrorUnsetCode('invalid range') } if (shortForkLeft === 1 && shortForkRight === 1) { - throw new Error('invalid range') + throw EthereumJSErrorUnsetCode('invalid range') } if (shortForkLeft !== 0 && shortForkRight !== 0) { @@ -310,7 +310,7 @@ async function unsetInternal( return false } else { - throw new Error('invalid node') + throw EthereumJSErrorUnsetCode('invalid node') } } @@ -340,7 +340,7 @@ async function verifyMPTWithMerkleProof( } } catch (err: any) { if (err.message === 'Missing node in DB') { - throw new Error('Invalid proof provided') + throw EthereumJSErrorUnsetCode('Invalid proof provided') } else { throw err } @@ -380,7 +380,7 @@ async function hasRightElement(trie: MerklePatriciaTrie, key: Nibbles): Promise< } else if (node instanceof LeafMPTNode) { return false } else { - throw new Error('invalid node') + throw EthereumJSErrorUnsetCode('invalid node') } } return false @@ -433,19 +433,19 @@ export async function verifyMerkleRangeProof( const keys = keysRaw.map(bytesToNibbles) if (keys.length !== values.length) { - throw new Error('invalid keys length or values length') + throw EthereumJSErrorUnsetCode('invalid keys length or values length') } // Make sure the keys are in order for (let i = 0; i < keys.length - 1; i++) { if (nibblesCompare(keys[i], keys[i + 1]) >= 0) { - throw new Error('invalid keys order') + throw EthereumJSErrorUnsetCode('invalid keys order') } } // Make sure all values are present for (const value of values) { if (value.length === 0) { - throw new Error('invalid values') + throw EthereumJSErrorUnsetCode('invalid values') } } @@ -456,7 +456,7 @@ export async function verifyMerkleRangeProof( await trie.put(nibblesTypeToPackedBytes(keys[i]), values[i]) } if (!equalsBytes(rootHash, trie.root())) { - throw new Error('invalid all elements proof: root mismatch') + throw EthereumJSErrorUnsetCode('invalid all elements proof: root mismatch') } return false } @@ -472,7 +472,7 @@ export async function verifyMerkleRangeProof( ) if (value !== null || (await hasRightElement(trie, firstKey))) { - throw new Error('invalid zero element proof: value mismatch') + throw EthereumJSErrorUnsetCode('invalid zero element proof: value mismatch') } return false @@ -480,7 +480,7 @@ export async function verifyMerkleRangeProof( } if (proof === null || firstKey === null || lastKey === null) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'invalid all elements proof: proof, firstKey, lastKey must be null at the same time', ) } @@ -495,10 +495,12 @@ export async function verifyMerkleRangeProof( ) if (nibblesCompare(firstKey, keys[0]) !== 0) { - throw new Error('invalid one element proof: firstKey should be equal to keys[0]') + throw EthereumJSErrorUnsetCode( + 'invalid one element proof: firstKey should be equal to keys[0]', + ) } if (value === null || !equalsBytes(value, values[0])) { - throw new Error('invalid one element proof: value mismatch') + throw EthereumJSErrorUnsetCode('invalid one element proof: value mismatch') } return hasRightElement(trie, firstKey) @@ -506,10 +508,12 @@ export async function verifyMerkleRangeProof( // Two edge elements proof if (nibblesCompare(firstKey, lastKey) >= 0) { - throw new Error('invalid two edge elements proof: firstKey should be less than lastKey') + throw EthereumJSErrorUnsetCode( + 'invalid two edge elements proof: firstKey should be less than lastKey', + ) } if (firstKey.length !== lastKey.length) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'invalid two edge elements proof: the length of firstKey should be equal to the length of lastKey', ) } @@ -532,7 +536,7 @@ export async function verifyMerkleRangeProof( // Compare rootHash if (!equalsBytes(trie.root(), rootHash)) { - throw new Error('invalid two edge elements proof: root mismatch') + throw EthereumJSErrorUnsetCode('invalid two edge elements proof: root mismatch') } return hasRightElement(trie, keys[keys.length - 1]) diff --git a/packages/mpt/src/util/walkController.ts b/packages/mpt/src/util/walkController.ts index 81c6dd0239a..76a95e62451 100644 --- a/packages/mpt/src/util/walkController.ts +++ b/packages/mpt/src/util/walkController.ts @@ -1,4 +1,4 @@ -import { PrioritizedTaskExecutor } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, PrioritizedTaskExecutor } from '@ethereumjs/util' import { BranchMPTNode, ExtensionMPTNode, LeafMPTNode } from '../node/index.js' @@ -119,11 +119,11 @@ export class WalkController { */ onlyBranchIndex(node: BranchMPTNode, key: Nibbles = [], childIndex: number, priority?: number) { if (!(node instanceof BranchMPTNode)) { - throw new Error('Expected branch node') + throw EthereumJSErrorUnsetCode('Expected branch node') } const childRef = node.getBranch(childIndex) if (!childRef) { - throw new Error('Could not get branch of childIndex') + throw EthereumJSErrorUnsetCode('Could not get branch of childIndex') } const childKey = key.slice() // This copies the key to a new array. childKey.push(childIndex) diff --git a/packages/statemanager/src/cache/storage.ts b/packages/statemanager/src/cache/storage.ts index d8dcf1759e7..ea19bc4ba3c 100644 --- a/packages/statemanager/src/cache/storage.ts +++ b/packages/statemanager/src/cache/storage.ts @@ -1,4 +1,4 @@ -import { bytesToUnprefixedHex, hexToBytes } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, bytesToUnprefixedHex, hexToBytes } from '@ethereumjs/util' import { OrderedMap } from '@js-sdsl/ordered-map' import debugDefault from 'debug' import { LRUCache } from 'lru-cache' @@ -211,7 +211,9 @@ export class StorageCache extends Cache { items.push([addressHex, keyHex, value]) } } else { - throw new Error('internal error: storage cache map for account should be defined') + throw EthereumJSErrorUnsetCode( + 'internal error: storage cache map for account should be defined', + ) } } this._diffCache[this._checkpoints] = new Map() diff --git a/packages/statemanager/src/merkleStateManager.ts b/packages/statemanager/src/merkleStateManager.ts index d7aca0bd890..7b83ab9cb18 100644 --- a/packages/statemanager/src/merkleStateManager.ts +++ b/packages/statemanager/src/merkleStateManager.ts @@ -3,6 +3,7 @@ import { MerklePatriciaTrie } from '@ethereumjs/mpt' import { RLP } from '@ethereumjs/rlp' import { Account, + EthereumJSErrorUnsetCode, bytesToUnprefixedHex, concatBytes, createAccount, @@ -319,7 +320,7 @@ export class MerkleStateManager implements StateManagerInterface { */ async getStorage(address: Address, key: Uint8Array): Promise { if (key.length !== 32) { - throw new Error('Storage key must be 32 bytes long') + throw EthereumJSErrorUnsetCode('Storage key must be 32 bytes long') } const cachedValue = this._caches?.storage?.get(address, key) if (cachedValue !== undefined) { @@ -402,16 +403,16 @@ export class MerkleStateManager implements StateManagerInterface { */ async putStorage(address: Address, key: Uint8Array, value: Uint8Array): Promise { if (key.length !== 32) { - throw new Error('Storage key must be 32 bytes long') + throw EthereumJSErrorUnsetCode('Storage key must be 32 bytes long') } if (value.length > 32) { - throw new Error('Storage value cannot be longer than 32 bytes') + throw EthereumJSErrorUnsetCode('Storage value cannot be longer than 32 bytes') } const account = await this.getAccount(address) if (!account) { - throw new Error('putStorage() called on non-existing account') + throw EthereumJSErrorUnsetCode('putStorage() called on non-existing account') } value = unpadBytes(value) @@ -562,7 +563,7 @@ export class MerkleStateManager implements StateManagerInterface { if (!equalsBytes(stateRoot, this._trie.EMPTY_TRIE_ROOT)) { const hasRoot = await this._trie.checkRoot(stateRoot) if (!hasRoot) { - throw new Error('State trie does not contain state root') + throw EthereumJSErrorUnsetCode('State trie does not contain state root') } } @@ -584,7 +585,7 @@ export class MerkleStateManager implements StateManagerInterface { await this.flush() const account = await this.getAccount(address) if (!account) { - throw new Error(`dumpStorage f() can only be called for an existing account`) + throw EthereumJSErrorUnsetCode(`dumpStorage f() can only be called for an existing account`) } const trie = this._getStorageTrie(address, account) @@ -604,13 +605,13 @@ export class MerkleStateManager implements StateManagerInterface { */ async dumpStorageRange(address: Address, startKey: bigint, limit: number): Promise { if (!Number.isSafeInteger(limit) || limit < 0) { - throw new Error(`Limit is not a proper uint.`) + throw EthereumJSErrorUnsetCode(`Limit is not a proper uint.`) } await this.flush() const account = await this.getAccount(address) if (!account) { - throw new Error(`Account does not exist.`) + throw EthereumJSErrorUnsetCode(`Account does not exist.`) } const trie = this._getStorageTrie(address, account) @@ -640,7 +641,7 @@ export class MerkleStateManager implements StateManagerInterface { */ async generateCanonicalGenesis(initState: any): Promise { if (this._checkpointCount !== 0) { - throw new Error('Cannot create genesis state with uncommitted checkpoints') + throw EthereumJSErrorUnsetCode('Cannot create genesis state with uncommitted checkpoints') } if (this.DEBUG) { this._debug(`Save genesis state into the state trie`) diff --git a/packages/statemanager/src/proof/merkle.ts b/packages/statemanager/src/proof/merkle.ts index 06611373300..d8c7e21a73b 100644 --- a/packages/statemanager/src/proof/merkle.ts +++ b/packages/statemanager/src/proof/merkle.ts @@ -6,6 +6,7 @@ import { } from '@ethereumjs/mpt' import { RLP } from '@ethereumjs/rlp' import { + EthereumJSErrorUnsetCode, KECCAK256_NULL, KECCAK256_NULL_S, KECCAK256_RLP, @@ -204,35 +205,37 @@ export async function verifyMerkleStateProof( const notEmptyErrorMsg = 'Invalid proof provided: account is not empty' const nonce = unpadBytes(hexToBytes(proof.nonce)) if (!equalsBytes(nonce, emptyBytes)) { - throw new Error(`${notEmptyErrorMsg} (nonce is not zero)`) + throw EthereumJSErrorUnsetCode(`${notEmptyErrorMsg} (nonce is not zero)`) } const balance = unpadBytes(hexToBytes(proof.balance)) if (!equalsBytes(balance, emptyBytes)) { - throw new Error(`${notEmptyErrorMsg} (balance is not zero)`) + throw EthereumJSErrorUnsetCode(`${notEmptyErrorMsg} (balance is not zero)`) } const storageHash = hexToBytes(proof.storageHash) if (!equalsBytes(storageHash, KECCAK256_RLP)) { - throw new Error(`${notEmptyErrorMsg} (storageHash does not equal KECCAK256_RLP)`) + throw EthereumJSErrorUnsetCode( + `${notEmptyErrorMsg} (storageHash does not equal KECCAK256_RLP)`, + ) } const codeHash = hexToBytes(proof.codeHash) if (!equalsBytes(codeHash, KECCAK256_NULL)) { - throw new Error(`${notEmptyErrorMsg} (codeHash does not equal KECCAK256_NULL)`) + throw EthereumJSErrorUnsetCode(`${notEmptyErrorMsg} (codeHash does not equal KECCAK256_NULL)`) } } else { const account = createAccountFromRLP(value) const { nonce, balance, storageRoot, codeHash } = account const invalidErrorMsg = 'Invalid proof provided:' if (nonce !== BigInt(proof.nonce)) { - throw new Error(`${invalidErrorMsg} nonce does not match`) + throw EthereumJSErrorUnsetCode(`${invalidErrorMsg} nonce does not match`) } if (balance !== BigInt(proof.balance)) { - throw new Error(`${invalidErrorMsg} balance does not match`) + throw EthereumJSErrorUnsetCode(`${invalidErrorMsg} balance does not match`) } if (!equalsBytes(storageRoot, hexToBytes(proof.storageHash))) { - throw new Error(`${invalidErrorMsg} storageHash does not match`) + throw EthereumJSErrorUnsetCode(`${invalidErrorMsg} storageHash does not match`) } if (!equalsBytes(codeHash, hexToBytes(proof.codeHash))) { - throw new Error(`${invalidErrorMsg} codeHash does not match`) + throw EthereumJSErrorUnsetCode(`${invalidErrorMsg} codeHash does not match`) } } @@ -248,7 +251,7 @@ export async function verifyMerkleStateProof( 32, ) if (!equalsBytes(reportedValue, storageValue)) { - throw new Error( + throw EthereumJSErrorUnsetCode( `Reported trie value does not match storage, key: ${stProof.key}, reported: ${bytesToHex( reportedValue, )}, actual: ${bytesToHex(storageValue)}`, diff --git a/packages/statemanager/src/proof/verkle.ts b/packages/statemanager/src/proof/verkle.ts index 89bc7334342..677a0b12275 100644 --- a/packages/statemanager/src/proof/verkle.ts +++ b/packages/statemanager/src/proof/verkle.ts @@ -1,4 +1,4 @@ -import { verifyVerkleProof } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, verifyVerkleProof } from '@ethereumjs/util' import type { Proof } from '../index.js' import type { StatelessVerkleStateManager } from '../statelessVerkleStateManager.js' @@ -9,7 +9,7 @@ export function getVerkleStateProof( _: Address, __: Uint8Array[] = [], ): Promise { - throw new Error('Not implemented yet') + throw EthereumJSErrorUnsetCode('Not implemented yet') } /** * Verifies whether the execution witness matches the stateRoot diff --git a/packages/statemanager/src/rpcStateManager.ts b/packages/statemanager/src/rpcStateManager.ts index 1144cbdf378..86fae1d02aa 100644 --- a/packages/statemanager/src/rpcStateManager.ts +++ b/packages/statemanager/src/rpcStateManager.ts @@ -2,6 +2,7 @@ import { Common, Mainnet } from '@ethereumjs/common' import { RLP } from '@ethereumjs/rlp' import { Account, + EthereumJSErrorUnsetCode, bigIntToHex, bytesToHex, createAccount, @@ -45,7 +46,7 @@ export class RPCStateManager implements StateManagerInterface { if (typeof opts.provider === 'string' && opts.provider.startsWith('http')) { this._provider = opts.provider } else { - throw new Error(`valid RPC provider url required; got ${opts.provider}`) + throw EthereumJSErrorUnsetCode(`valid RPC provider url required; got ${opts.provider}`) } this._blockTag = opts.blockTag === 'earliest' ? opts.blockTag : bigIntToHex(opts.blockTag) @@ -137,7 +138,7 @@ export class RPCStateManager implements StateManagerInterface { async getStorage(address: Address, key: Uint8Array): Promise { // Check storage slot in cache if (key.length !== 32) { - throw new Error('Storage key must be 32 bytes long') + throw EthereumJSErrorUnsetCode('Storage key must be 32 bytes long') } let value = this._caches.storage?.get(address, key) @@ -354,14 +355,15 @@ export class RPCStateManager implements StateManagerInterface { * @deprecated This method is not used by the RPC State Manager and is a stub required by the State Manager interface */ hasStateRoot = () => { - throw new Error('function not implemented') + throw EthereumJSErrorUnsetCode('function not implemented') } } export class RPCBlockChain { readonly provider: string constructor(provider: string) { - if (provider === undefined || provider === '') throw new Error('provider URL is required') + if (provider === undefined || provider === '') + throw EthereumJSErrorUnsetCode('provider URL is required') this.provider = provider } async getBlock(blockId: number) { diff --git a/packages/statemanager/src/simpleStateManager.ts b/packages/statemanager/src/simpleStateManager.ts index 30bf0096414..bd98a7b9466 100644 --- a/packages/statemanager/src/simpleStateManager.ts +++ b/packages/statemanager/src/simpleStateManager.ts @@ -1,4 +1,4 @@ -import { Account, bytesToHex } from '@ethereumjs/util' +import { Account, EthereumJSErrorUnsetCode, bytesToHex } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak.js' import { OriginalStorageCache } from './cache/originalStorageCache.js' @@ -143,12 +143,12 @@ export class SimpleStateManager implements StateManagerInterface { // State root functionality not implemented getStateRoot(): Promise { - throw new Error('Method not implemented.') + throw EthereumJSErrorUnsetCode('Method not implemented.') } setStateRoot(): Promise { - throw new Error('Method not implemented.') + throw EthereumJSErrorUnsetCode('Method not implemented.') } hasStateRoot(): Promise { - throw new Error('Method not implemented.') + throw EthereumJSErrorUnsetCode('Method not implemented.') } } diff --git a/packages/statemanager/src/statefulVerkleStateManager.ts b/packages/statemanager/src/statefulVerkleStateManager.ts index 61f456a5678..ea0f507349d 100644 --- a/packages/statemanager/src/statefulVerkleStateManager.ts +++ b/packages/statemanager/src/statefulVerkleStateManager.ts @@ -3,6 +3,7 @@ import { RLP } from '@ethereumjs/rlp' import { Account, type Address, + EthereumJSErrorUnsetCode, KECCAK256_NULL, MapDB, VERKLE_CODE_CHUNK_SIZE, @@ -98,11 +99,11 @@ export class StatefulVerkleStateManager implements StateManagerInterface { this._checkpointCount = 0 if (opts.common.isActivatedEIP(6800) === false) { - throw new Error('EIP-6800 required for verkle state management') + throw EthereumJSErrorUnsetCode('EIP-6800 required for verkle state management') } if (opts.common.customCrypto.verkle === undefined) { - throw new Error('verkle crypto required') + throw EthereumJSErrorUnsetCode('verkle crypto required') } this.common = opts.common @@ -383,7 +384,8 @@ export class StatefulVerkleStateManager implements StateManagerInterface { const code = new Uint8Array(codeSize) // Insert code chunks into final array (skipping PUSHDATA overflow indicator byte) for (let x = 0; x < chunks.length; x++) { - if (chunks[x] === undefined) throw new Error(`expected code chunk at ID ${x}, got undefined`) + if (chunks[x] === undefined) + throw EthereumJSErrorUnsetCode(`expected code chunk at ID ${x}, got undefined`) let lastChunkByteIndex = VERKLE_CODE_CHUNK_SIZE // Determine code ending byte (if we're on the last chunk) @@ -410,7 +412,7 @@ export class StatefulVerkleStateManager implements StateManagerInterface { } getStorage = async (address: Address, key: Uint8Array): Promise => { if (key.length !== 32) { - throw new Error('Storage key must be 32 bytes long') + throw EthereumJSErrorUnsetCode('Storage key must be 32 bytes long') } const cachedValue = this._caches?.storage?.get(address, key) if (cachedValue !== undefined) { @@ -727,19 +729,19 @@ export class StatefulVerkleStateManager implements StateManagerInterface { return this._trie.checkRoot(root) } dumpStorage?(_address: Address): Promise { - throw new Error('Method not implemented.') + throw EthereumJSErrorUnsetCode('Method not implemented.') } dumpStorageRange?(_address: Address, _startKey: bigint, _limit: number): Promise { - throw new Error('Method not implemented.') + throw EthereumJSErrorUnsetCode('Method not implemented.') } clearCaches(): void { this._caches?.clear() } shallowCopy(_downlevelCaches?: boolean): StateManagerInterface { - throw new Error('Method not implemented.') + throw EthereumJSErrorUnsetCode('Method not implemented.') } async checkChunkWitnessPresent(_address: Address, _codeOffset: number): Promise { - throw new Error('Method not implemented.') + throw EthereumJSErrorUnsetCode('Method not implemented.') } async generateCanonicalGenesis(genesisState: GenesisState) { await this._trie.createRootNode() diff --git a/packages/statemanager/src/statelessVerkleStateManager.ts b/packages/statemanager/src/statelessVerkleStateManager.ts index 18c8bb6fbb0..737ea866d3c 100644 --- a/packages/statemanager/src/statelessVerkleStateManager.ts +++ b/packages/statemanager/src/statelessVerkleStateManager.ts @@ -1,6 +1,7 @@ import { VerkleAccessedStateType } from '@ethereumjs/common' import { Account, + EthereumJSErrorUnsetCode, KECCAK256_NULL, KECCAK256_NULL_S, VERKLE_CODE_CHUNK_SIZE, @@ -116,11 +117,11 @@ export class StatelessVerkleStateManager implements StateManagerInterface { this._caches = opts.caches if (opts.common.isActivatedEIP(6800) === false) { - throw new Error('EIP-6800 required for stateless verkle state management') + throw EthereumJSErrorUnsetCode('EIP-6800 required for stateless verkle state management') } if (opts.common.customCrypto.verkle === undefined) { - throw new Error('verkle crypto required') + throw EthereumJSErrorUnsetCode('verkle crypto required') } this.common = opts.common @@ -676,7 +677,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { */ async getStateRoot(): Promise { if (this._cachedStateRoot === undefined) { - throw new Error('Cache state root missing') + throw EthereumJSErrorUnsetCode('Cache state root missing') } return this._cachedStateRoot } diff --git a/packages/tx/src/1559/constructors.ts b/packages/tx/src/1559/constructors.ts index 623659832e9..a2a078ac276 100644 --- a/packages/tx/src/1559/constructors.ts +++ b/packages/tx/src/1559/constructors.ts @@ -1,5 +1,11 @@ import { RLP } from '@ethereumjs/rlp' -import { bytesToBigInt, bytesToHex, equalsBytes, validateNoLeadingZeroes } from '@ethereumjs/util' +import { + EthereumJSErrorUnsetCode, + bytesToBigInt, + bytesToHex, + equalsBytes, + validateNoLeadingZeroes, +} from '@ethereumjs/util' import { TransactionType } from '../types.js' import { txTypeBytes, validateNotArray } from '../util.js' @@ -31,7 +37,7 @@ export function createFeeMarket1559Tx(txData: TxData, opts: TxOptions = {}) { */ export function create1559FeeMarketTxFromBytesArray(values: TxValuesArray, opts: TxOptions = {}) { if (values.length !== 9 && values.length !== 12) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'Invalid EIP-1559 transaction. Only expecting 9 values (for unsigned tx) or 12 values (for signed tx).', ) } @@ -83,7 +89,7 @@ export function createFeeMarket1559TxFromRLP(serialized: Uint8Array, opts: TxOpt if ( equalsBytes(serialized.subarray(0, 1), txTypeBytes(TransactionType.FeeMarketEIP1559)) === false ) { - throw new Error( + throw EthereumJSErrorUnsetCode( `Invalid serialized tx input: not an EIP-1559 transaction (wrong tx type, expected: ${ TransactionType.FeeMarketEIP1559 }, received: ${bytesToHex(serialized.subarray(0, 1))}`, @@ -93,7 +99,7 @@ export function createFeeMarket1559TxFromRLP(serialized: Uint8Array, opts: TxOpt const values = RLP.decode(serialized.subarray(1)) if (!Array.isArray(values)) { - throw new Error('Invalid serialized tx input: must be array') + throw EthereumJSErrorUnsetCode('Invalid serialized tx input: must be array') } return create1559FeeMarketTxFromBytesArray(values as TxValuesArray, opts) diff --git a/packages/tx/src/1559/tx.ts b/packages/tx/src/1559/tx.ts index 24cc472e58e..90c774d936d 100644 --- a/packages/tx/src/1559/tx.ts +++ b/packages/tx/src/1559/tx.ts @@ -1,6 +1,7 @@ import { BIGINT_0, BIGINT_27, + EthereumJSErrorUnsetCode, MAX_INTEGER, bigIntToHex, bigIntToUnpaddedBytes, @@ -90,14 +91,14 @@ export class FeeMarket1559Tx implements TransactionInterface MAX_INTEGER) { const msg = Legacy.errorMsg(this, 'gasLimit * gasPrice cannot exceed MAX_INTEGER') - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } EIP2718.validateYParity(this) diff --git a/packages/tx/src/4844/constructors.ts b/packages/tx/src/4844/constructors.ts index ec2ff0ecec5..567afee05f6 100644 --- a/packages/tx/src/4844/constructors.ts +++ b/packages/tx/src/4844/constructors.ts @@ -1,5 +1,6 @@ import { RLP } from '@ethereumjs/rlp' import { + EthereumJSErrorUnsetCode, bigIntToHex, blobsToCommitments, blobsToProofs, @@ -35,26 +36,30 @@ const validateBlobTransactionNetworkWrapper = ( kzg: KZG, ) => { if (!(blobVersionedHashes.length === blobs.length && blobs.length === commitments.length)) { - throw new Error('Number of blobVersionedHashes, blobs, and commitments not all equal') + throw EthereumJSErrorUnsetCode( + 'Number of blobVersionedHashes, blobs, and commitments not all equal', + ) } if (blobVersionedHashes.length === 0) { - throw new Error('Invalid transaction with empty blobs') + throw EthereumJSErrorUnsetCode('Invalid transaction with empty blobs') } let isValid try { isValid = kzg.verifyBlobProofBatch(blobs, commitments, kzgProofs) } catch (error) { - throw new Error(`KZG verification of blobs fail with error=${error}`) + throw EthereumJSErrorUnsetCode(`KZG verification of blobs fail with error=${error}`) } if (!isValid) { - throw new Error('KZG proof cannot be verified from blobs/commitments') + throw EthereumJSErrorUnsetCode('KZG proof cannot be verified from blobs/commitments') } for (let x = 0; x < blobVersionedHashes.length; x++) { const computedVersionedHash = computeVersionedHash(commitments[x], version) if (computedVersionedHash !== blobVersionedHashes[x]) { - throw new Error(`commitment for blob at index ${x} does not match versionedHash`) + throw EthereumJSErrorUnsetCode( + `commitment for blob at index ${x} does not match versionedHash`, + ) } } } @@ -73,23 +78,31 @@ const validateBlobTransactionNetworkWrapper = ( */ export function createBlob4844Tx(txData: TxData, opts?: TxOptions) { if (opts?.common?.customCrypto?.kzg === undefined) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'A common object with customCrypto.kzg initialized required to instantiate a 4844 blob tx', ) } const kzg = opts!.common!.customCrypto!.kzg! if (txData.blobsData !== undefined) { if (txData.blobs !== undefined) { - throw new Error('cannot have both raw blobs data and encoded blobs in constructor') + throw EthereumJSErrorUnsetCode( + 'cannot have both raw blobs data and encoded blobs in constructor', + ) } if (txData.kzgCommitments !== undefined) { - throw new Error('cannot have both raw blobs data and KZG commitments in constructor') + throw EthereumJSErrorUnsetCode( + 'cannot have both raw blobs data and KZG commitments in constructor', + ) } if (txData.blobVersionedHashes !== undefined) { - throw new Error('cannot have both raw blobs data and versioned hashes in constructor') + throw EthereumJSErrorUnsetCode( + 'cannot have both raw blobs data and versioned hashes in constructor', + ) } if (txData.kzgProofs !== undefined) { - throw new Error('cannot have both raw blobs data and KZG proofs in constructor') + throw EthereumJSErrorUnsetCode( + 'cannot have both raw blobs data and KZG proofs in constructor', + ) } txData.blobs = getBlobs(txData.blobsData.reduce((acc, cur) => acc + cur)) as PrefixedHexString[] txData.kzgCommitments = blobsToCommitments(kzg, txData.blobs as PrefixedHexString[]) @@ -114,13 +127,13 @@ export function createBlob4844Tx(txData: TxData, opts?: TxOptions) { */ export function createBlob4844TxFromBytesArray(values: TxValuesArray, opts: TxOptions = {}) { if (opts.common?.customCrypto?.kzg === undefined) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'A common object with customCrypto.kzg initialized required to instantiate a 4844 blob tx', ) } if (values.length !== 11 && values.length !== 14) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'Invalid EIP-4844 transaction. Only expecting 11 values (for unsigned tx) or 14 values (for signed tx).', ) } @@ -184,13 +197,13 @@ export function createBlob4844TxFromBytesArray(values: TxValuesArray, opts: TxOp */ export function createBlob4844TxFromRLP(serialized: Uint8Array, opts: TxOptions = {}) { if (opts.common?.customCrypto?.kzg === undefined) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'A common object with customCrypto.kzg initialized required to instantiate a 4844 blob tx', ) } if (equalsBytes(serialized.subarray(0, 1), txTypeBytes(TransactionType.BlobEIP4844)) === false) { - throw new Error( + throw EthereumJSErrorUnsetCode( `Invalid serialized tx input: not an EIP-4844 transaction (wrong tx type, expected: ${ TransactionType.BlobEIP4844 }, received: ${bytesToHex(serialized.subarray(0, 1))}`, @@ -200,7 +213,7 @@ export function createBlob4844TxFromRLP(serialized: Uint8Array, opts: TxOptions const values = RLP.decode(serialized.subarray(1)) if (!Array.isArray(values)) { - throw new Error('Invalid serialized tx input: must be array') + throw EthereumJSErrorUnsetCode('Invalid serialized tx input: must be array') } return createBlob4844TxFromBytesArray(values as TxValuesArray, opts) @@ -217,17 +230,17 @@ export function createBlob4844TxFromSerializedNetworkWrapper( opts?: TxOptions, ): Blob4844Tx { if (!opts || !opts.common) { - throw new Error('common instance required to validate versioned hashes') + throw EthereumJSErrorUnsetCode('common instance required to validate versioned hashes') } if (opts.common?.customCrypto?.kzg === undefined) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'A common object with customCrypto.kzg initialized required to instantiate a 4844 blob tx', ) } if (equalsBytes(serialized.subarray(0, 1), txTypeBytes(TransactionType.BlobEIP4844)) === false) { - throw new Error( + throw EthereumJSErrorUnsetCode( `Invalid serialized tx input: not an EIP-4844 transaction (wrong tx type, expected: ${ TransactionType.BlobEIP4844 }, received: ${bytesToHex(serialized.subarray(0, 1))}`, @@ -290,7 +303,7 @@ export function createMinimal4844TxFromNetworkWrapper( opts?: TxOptions, ): Blob4844Tx { if (opts?.common?.customCrypto?.kzg === undefined) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'A common object with customCrypto.kzg initialized required to instantiate a 4844 blob tx', ) } diff --git a/packages/tx/src/4844/tx.ts b/packages/tx/src/4844/tx.ts index 069d18ce115..90ecca15818 100644 --- a/packages/tx/src/4844/tx.ts +++ b/packages/tx/src/4844/tx.ts @@ -1,6 +1,7 @@ import { BIGINT_0, BIGINT_27, + EthereumJSErrorUnsetCode, MAX_INTEGER, TypeOutput, bigIntToHex, @@ -98,18 +99,18 @@ export class Blob4844Tx implements TransactionInterface limitBlobsPerTx) { const msg = Legacy.errorMsg(this, `tx can contain at most ${limitBlobsPerTx} blobs`) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } else if (this.blobVersionedHashes.length === 0) { const msg = Legacy.errorMsg(this, `tx should contain at least one blob`) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } if (this.to === undefined) { @@ -187,7 +188,7 @@ export class Blob4844Tx implements TransactionInterface toType(blob, TypeOutput.PrefixedHexString)) @@ -318,7 +319,7 @@ export class Blob4844Tx implements TransactionInterface tx.maxFeePerGas) { - throw new Error('Tx cannot pay baseFee') + throw EthereumJSErrorUnsetCode('Tx cannot pay baseFee') } // The remaining fee for the coinbase, which can take up to this value, capped at `maxPriorityFeePerGas` diff --git a/packages/tx/src/capabilities/eip2718.ts b/packages/tx/src/capabilities/eip2718.ts index 6aeb3739634..de02be703fc 100644 --- a/packages/tx/src/capabilities/eip2718.ts +++ b/packages/tx/src/capabilities/eip2718.ts @@ -1,5 +1,5 @@ import { RLP } from '@ethereumjs/rlp' -import { BIGINT_0, BIGINT_1, concatBytes } from '@ethereumjs/util' +import { BIGINT_0, BIGINT_1, EthereumJSErrorUnsetCode, concatBytes } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak.js' import { txTypeBytes } from '../util.js' @@ -22,6 +22,6 @@ export function validateYParity(tx: EIP2718CompatibleTx) { const { v } = tx if (v !== undefined && v !== BIGINT_0 && v !== BIGINT_1) { const msg = errorMsg(tx, 'The y-parity of the transaction should either be 0 or 1') - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } diff --git a/packages/tx/src/capabilities/legacy.ts b/packages/tx/src/capabilities/legacy.ts index 6c8d66fd0fc..ad83541327d 100644 --- a/packages/tx/src/capabilities/legacy.ts +++ b/packages/tx/src/capabilities/legacy.ts @@ -1,6 +1,7 @@ import { Address, BIGINT_0, + EthereumJSErrorUnsetCode, SECP256K1_ORDER_DIV_2, bigIntMax, bigIntToUnpaddedBytes, @@ -85,7 +86,7 @@ export function toCreationAddress(tx: LegacyTxInterface): boolean { export function hash(tx: LegacyTxInterface): Uint8Array { if (!tx.isSigned()) { const msg = errorMsg(tx, 'Cannot call hash method if transaction is not signed') - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } const keccakFunction = tx.common.customCrypto.keccak256 ?? keccak256 @@ -111,7 +112,7 @@ export function validateHighS(tx: LegacyTxInterface): void { tx, 'Invalid Signature: s-values greater than secp256k1n/2 are considered invalid', ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } @@ -141,13 +142,13 @@ export function getSenderPublicKey(tx: LegacyTxInterface): Uint8Array { return sender } catch (e: any) { const msg = errorMsg(tx, 'Invalid Signature') - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } export function getEffectivePriorityFee(gasPrice: bigint, baseFee: bigint | undefined): bigint { if (baseFee !== undefined && baseFee > gasPrice) { - throw new Error('Tx cannot pay baseFee') + throw EthereumJSErrorUnsetCode('Tx cannot pay baseFee') } if (baseFee === undefined) { @@ -234,7 +235,7 @@ export function sign( 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) + throw EthereumJSErrorUnsetCode(msg) } // TODO (Jochem, 05 nov 2024): figure out what this hack does and clean it up diff --git a/packages/tx/src/features/util.ts b/packages/tx/src/features/util.ts index 36bd9546187..37dc7636a4e 100644 --- a/packages/tx/src/features/util.ts +++ b/packages/tx/src/features/util.ts @@ -1,6 +1,7 @@ import { Common, Mainnet } from '@ethereumjs/common' import { Address, + EthereumJSErrorUnsetCode, MAX_INTEGER, MAX_UINT64, bigIntToHex, @@ -36,27 +37,35 @@ export function valueBoundaryCheck( 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}`) + throw EthereumJSErrorUnsetCode( + `${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}`) + throw EthereumJSErrorUnsetCode( + `${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}`) + throw EthereumJSErrorUnsetCode( + `${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}`) + throw EthereumJSErrorUnsetCode( + `${key} cannot exceed MAX_INTEGER (2^256-1), given ${value}`, + ) } } break default: { - throw new Error('unimplemented bits value') + throw EthereumJSErrorUnsetCode('unimplemented bits value') } } } diff --git a/packages/tx/src/legacy/constructors.ts b/packages/tx/src/legacy/constructors.ts index f69acedb255..1e2747be0c5 100644 --- a/packages/tx/src/legacy/constructors.ts +++ b/packages/tx/src/legacy/constructors.ts @@ -1,5 +1,5 @@ import { RLP } from '@ethereumjs/rlp' -import { validateNoLeadingZeroes } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, validateNoLeadingZeroes } from '@ethereumjs/util' import { LegacyTx } from './tx.js' @@ -27,7 +27,7 @@ export function createLegacyTxFromBytesArray(values: TxValuesArray, opts: TxOpti // If length is not 6, it has length 9. If v/r/s are empty Uint8Arrays, it is still an unsigned transaction // This happens if you get the RLP data from `raw()` if (values.length !== 6 && values.length !== 9) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'Invalid transaction. Only expecting 6 values (for unsigned tx) or 9 values (for signed tx).', ) } @@ -62,7 +62,7 @@ export function createLegacyTxFromRLP(serialized: Uint8Array, opts: TxOptions = const values = RLP.decode(serialized) if (!Array.isArray(values)) { - throw new Error('Invalid serialized tx input. Must be array') + throw EthereumJSErrorUnsetCode('Invalid serialized tx input. Must be array') } return createLegacyTxFromBytesArray(values as TxValuesArray, opts) diff --git a/packages/tx/src/legacy/tx.ts b/packages/tx/src/legacy/tx.ts index 181a507aec2..04a617cb971 100644 --- a/packages/tx/src/legacy/tx.ts +++ b/packages/tx/src/legacy/tx.ts @@ -2,6 +2,7 @@ import { RLP } from '@ethereumjs/rlp' import { BIGINT_2, BIGINT_8, + EthereumJSErrorUnsetCode, MAX_INTEGER, bigIntToHex, bigIntToUnpaddedBytes, @@ -49,7 +50,7 @@ function validateVAndExtractChainID(common: Common, _v?: bigint): BigInt | undef // 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( + throw EthereumJSErrorUnsetCode( `Legacy txs need either v = 27/28 or v >= 37 (EIP-155 replay protection), got v = ${v}`, ) } @@ -58,7 +59,7 @@ function validateVAndExtractChainID(common: Common, _v?: bigint): BigInt | undef // 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( + throw EthereumJSErrorUnsetCode( `Incompatible EIP155-based V ${v} and chain id ${common.chainId()}. See the Common parameter of the Transaction constructor to set the chain id.`, ) } @@ -130,7 +131,7 @@ export class LegacyTx implements TransactionInterface { const chainId = validateVAndExtractChainID(this.common, this.v) if (chainId !== undefined && chainId !== this.common.chainId()) { - throw new Error( + throw EthereumJSErrorUnsetCode( `Common chain ID ${this.common.chainId} not matching the derived chain ID ${chainId}`, ) } @@ -138,7 +139,7 @@ export class LegacyTx implements TransactionInterface { this.keccakFunction = this.common.customCrypto.keccak256 ?? keccak256 if (this.gasPrice * this.gasLimit > MAX_INTEGER) { - throw new Error('gas limit * gasPrice cannot exceed MAX_INTEGER (2^256-1)') + throw EthereumJSErrorUnsetCode('gas limit * gasPrice cannot exceed MAX_INTEGER (2^256-1)') } if (this.common.gteHardfork('spuriousDragon')) { @@ -319,7 +320,7 @@ export class LegacyTx implements TransactionInterface { getMessageToVerifySignature() { if (!this.isSigned()) { const msg = Legacy.errorMsg(this, 'This transaction is not signed') - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } return this.getHashedMessageToSign() } diff --git a/packages/tx/src/transactionFactory.ts b/packages/tx/src/transactionFactory.ts index a9d2153ec7e..cbf0f2517da 100644 --- a/packages/tx/src/transactionFactory.ts +++ b/packages/tx/src/transactionFactory.ts @@ -1,4 +1,4 @@ -import { fetchFromProvider, getProvider } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, fetchFromProvider, getProvider } from '@ethereumjs/util' import { createFeeMarket1559Tx, createFeeMarket1559TxFromRLP } from './1559/constructors.js' import { createAccessList2930Tx, createAccessList2930TxFromRLP } from './2930/constructors.js' @@ -46,7 +46,9 @@ export function createTx( } else if (isEOACode7702TxData(txData)) { return createEOACode7702Tx(txData, txOptions) as Transaction[T] } else { - throw new Error(`Tx instantiation with type ${(txData as TypedTxData)?.type} not supported`) + throw EthereumJSErrorUnsetCode( + `Tx instantiation with type ${(txData as TypedTxData)?.type} not supported`, + ) } } } @@ -73,7 +75,7 @@ export function createTxFromRLP( case TransactionType.EOACodeEIP7702: return createEOACode7702TxFromRLP(data, txOptions) as Transaction[T] default: - throw new Error(`TypedTransaction with ID ${data[0]} unknown`) + throw EthereumJSErrorUnsetCode(`TypedTransaction with ID ${data[0]} unknown`) } } else { return createLegacyTxFromRLP(data, txOptions) as Transaction[T] @@ -99,7 +101,7 @@ export function createTxFromBlockBodyData( // It is a legacy transaction return createLegacyTxFromBytesArray(data, txOptions) } else { - throw new Error('Cannot decode transaction: unknown type input') + throw EthereumJSErrorUnsetCode('Cannot decode transaction: unknown type input') } } @@ -135,7 +137,7 @@ export async function createTxFromJSONRPCProvider( params: [txHash], }) if (txData === null) { - throw new Error('No data returned from provider') + throw EthereumJSErrorUnsetCode('No data returned from provider') } return createTxFromRPC(txData, txOptions) } diff --git a/packages/tx/src/util.ts b/packages/tx/src/util.ts index 5a051167a4e..074a32a676d 100644 --- a/packages/tx/src/util.ts +++ b/packages/tx/src/util.ts @@ -1,4 +1,5 @@ import { + EthereumJSErrorUnsetCode, MAX_INTEGER, MAX_UINT64, type PrefixedHexString, @@ -29,7 +30,7 @@ import type { Common } from '@ethereumjs/common' export function checkMaxInitCodeSize(common: Common, length: number) { const maxInitCodeSize = common.param('maxInitCodeSize') if (maxInitCodeSize && BigInt(length) > maxInitCodeSize) { - throw new Error( + throw EthereumJSErrorUnsetCode( `the initcode size of this transaction is too large: it is ${length} while the max is ${common.param( 'maxInitCodeSize', )}`, @@ -87,16 +88,20 @@ export class AccessLists { const address = accessListItem[0] const storageSlots = accessListItem[1] if ((accessListItem)[2] !== undefined) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'Access list item cannot have 3 elements. It can only have an address, and an array of storage slots.', ) } if (address.length !== 20) { - throw new Error('Invalid EIP-2930 transaction: address length should be 20 bytes') + throw EthereumJSErrorUnsetCode( + 'Invalid EIP-2930 transaction: address length should be 20 bytes', + ) } for (let storageSlot = 0; storageSlot < storageSlots.length; storageSlot++) { if (storageSlots[storageSlot].length !== 32) { - throw new Error('Invalid EIP-2930 transaction: storage slot length should be 32 bytes') + throw EthereumJSErrorUnsetCode( + 'Invalid EIP-2930 transaction: storage slot length should be 32 bytes', + ) } } } @@ -150,7 +155,9 @@ export class AuthorizationLists { const item: AuthorizationListItem = authorizationList[i] for (const key of jsonItems) { if (item[key as keyof typeof item] === undefined) { - throw new Error(`EIP-7702 authorization list invalid: ${key} is not defined`) + throw EthereumJSErrorUnsetCode( + `EIP-7702 authorization list invalid: ${key} is not defined`, + ) } } const chainId = hexToBytes(item.chainId) @@ -196,7 +203,7 @@ export class AuthorizationLists { public static verifyAuthorizationList(authorizationList: AuthorizationListBytes) { if (authorizationList.length === 0) { - throw new Error('Invalid EIP-7702 transaction: authorization list is empty') + throw EthereumJSErrorUnsetCode('Invalid EIP-7702 transaction: authorization list is empty') } for (let key = 0; key < authorizationList.length; key++) { const authorizationListItem = authorizationList[key] @@ -208,25 +215,27 @@ export class AuthorizationLists { const s = authorizationListItem[5] validateNoLeadingZeroes({ yParity, r, s, nonce, chainId }) if (address.length !== 20) { - throw new Error('Invalid EIP-7702 transaction: address length should be 20 bytes') + throw EthereumJSErrorUnsetCode( + 'Invalid EIP-7702 transaction: address length should be 20 bytes', + ) } if (bytesToBigInt(chainId) > MAX_INTEGER) { - throw new Error('Invalid EIP-7702 transaction: chainId exceeds 2^256 - 1') + throw EthereumJSErrorUnsetCode('Invalid EIP-7702 transaction: chainId exceeds 2^256 - 1') } if (bytesToBigInt(nonce) > MAX_UINT64) { - throw new Error('Invalid EIP-7702 transaction: nonce exceeds 2^64 - 1') + throw EthereumJSErrorUnsetCode('Invalid EIP-7702 transaction: nonce exceeds 2^64 - 1') } const yParityBigInt = bytesToBigInt(yParity) if (yParityBigInt >= BigInt(2 ** 8)) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'Invalid EIP-7702 transaction: yParity should be fit within 1 byte (0 - 255)', ) } if (bytesToBigInt(r) > MAX_INTEGER) { - throw new Error('Invalid EIP-7702 transaction: r exceeds 2^256 - 1') + throw EthereumJSErrorUnsetCode('Invalid EIP-7702 transaction: r exceeds 2^256 - 1') } if (bytesToBigInt(s) > MAX_INTEGER) { - throw new Error('Invalid EIP-7702 transaction: s exceeds 2^256 - 1') + throw EthereumJSErrorUnsetCode('Invalid EIP-7702 transaction: s exceeds 2^256 - 1') } } } @@ -260,7 +269,7 @@ export function validateNotArray(values: { [key: string]: any }) { for (const [key, value] of Object.entries(values)) { if (txDataKeys.includes(key)) { if (Array.isArray(value)) { - throw new Error(`${key} cannot be an array`) + throw EthereumJSErrorUnsetCode(`${key} cannot be an array`) } } } diff --git a/packages/util/src/account.ts b/packages/util/src/account.ts index 945beaa2a4e..0ba183942d3 100644 --- a/packages/util/src/account.ts +++ b/packages/util/src/account.ts @@ -15,6 +15,7 @@ import { utf8ToBytes, } from './bytes.js' import { BIGINT_0, KECCAK256_NULL, KECCAK256_RLP } from './constants.js' +import { EthereumJSErrorUnsetCode } from './errors.js' import { assertIsBytes, assertIsHexString, assertIsString } from './helpers.js' import { stripHexPrefix } from './internal.js' @@ -152,19 +153,19 @@ export class Account { private _validate() { if (this._nonce !== null && this._nonce < BIGINT_0) { - throw new Error('nonce must be greater than zero') + throw EthereumJSErrorUnsetCode('nonce must be greater than zero') } if (this._balance !== null && this._balance < BIGINT_0) { - throw new Error('balance must be greater than zero') + throw EthereumJSErrorUnsetCode('balance must be greater than zero') } if (this._storageRoot !== null && this._storageRoot.length !== 32) { - throw new Error('storageRoot must have a length of 32') + throw EthereumJSErrorUnsetCode('storageRoot must have a length of 32') } if (this._codeHash !== null && this._codeHash.length !== 32) { - throw new Error('codeHash must have a length of 32') + throw EthereumJSErrorUnsetCode('codeHash must have a length of 32') } if (this._codeSize !== null && this._codeSize < BIGINT_0) { - throw new Error('codeSize must be greater than zero') + throw EthereumJSErrorUnsetCode('codeSize must be greater than zero') } } @@ -317,7 +318,7 @@ export function createAccountFromRLP(serialized: Uint8Array) { const values = RLP.decode(serialized) as Uint8Array[] if (!Array.isArray(values)) { - throw new Error('Invalid serialized account input. Must be array') + throw EthereumJSErrorUnsetCode('Invalid serialized account input. Must be array') } return createAccountFromBytesArray(values) @@ -327,16 +328,16 @@ export function createPartialAccountFromRLP(serialized: Uint8Array) { const values = RLP.decode(serialized) as Uint8Array[][] if (!Array.isArray(values)) { - throw new Error('Invalid serialized account input. Must be array') + throw EthereumJSErrorUnsetCode('Invalid serialized account input. Must be array') } let nonce = null if (!Array.isArray(values[0])) { - throw new Error('Invalid partial nonce encoding. Must be array') + throw EthereumJSErrorUnsetCode('Invalid partial nonce encoding. Must be array') } else { const isNotNullIndicator = bytesToInt(values[0][0]) if (isNotNullIndicator !== 0 && isNotNullIndicator !== 1) { - throw new Error(`Invalid isNullIndicator=${isNotNullIndicator} for nonce`) + throw EthereumJSErrorUnsetCode(`Invalid isNullIndicator=${isNotNullIndicator} for nonce`) } if (isNotNullIndicator === 1) { nonce = bytesToBigInt(values[0][1]) @@ -345,11 +346,11 @@ export function createPartialAccountFromRLP(serialized: Uint8Array) { let balance = null if (!Array.isArray(values[1])) { - throw new Error('Invalid partial balance encoding. Must be array') + throw EthereumJSErrorUnsetCode('Invalid partial balance encoding. Must be array') } else { const isNotNullIndicator = bytesToInt(values[1][0]) if (isNotNullIndicator !== 0 && isNotNullIndicator !== 1) { - throw new Error(`Invalid isNullIndicator=${isNotNullIndicator} for balance`) + throw EthereumJSErrorUnsetCode(`Invalid isNullIndicator=${isNotNullIndicator} for balance`) } if (isNotNullIndicator === 1) { balance = bytesToBigInt(values[1][1]) @@ -358,11 +359,13 @@ export function createPartialAccountFromRLP(serialized: Uint8Array) { let storageRoot = null if (!Array.isArray(values[2])) { - throw new Error('Invalid partial storageRoot encoding. Must be array') + throw EthereumJSErrorUnsetCode('Invalid partial storageRoot encoding. Must be array') } else { const isNotNullIndicator = bytesToInt(values[2][0]) if (isNotNullIndicator !== 0 && isNotNullIndicator !== 1) { - throw new Error(`Invalid isNullIndicator=${isNotNullIndicator} for storageRoot`) + throw EthereumJSErrorUnsetCode( + `Invalid isNullIndicator=${isNotNullIndicator} for storageRoot`, + ) } if (isNotNullIndicator === 1) { storageRoot = values[2][1] @@ -371,11 +374,11 @@ export function createPartialAccountFromRLP(serialized: Uint8Array) { let codeHash = null if (!Array.isArray(values[3])) { - throw new Error('Invalid partial codeHash encoding. Must be array') + throw EthereumJSErrorUnsetCode('Invalid partial codeHash encoding. Must be array') } else { const isNotNullIndicator = bytesToInt(values[3][0]) if (isNotNullIndicator !== 0 && isNotNullIndicator !== 1) { - throw new Error(`Invalid isNullIndicator=${isNotNullIndicator} for codeHash`) + throw EthereumJSErrorUnsetCode(`Invalid isNullIndicator=${isNotNullIndicator} for codeHash`) } if (isNotNullIndicator === 1) { codeHash = values[3][1] @@ -384,11 +387,11 @@ export function createPartialAccountFromRLP(serialized: Uint8Array) { let codeSize = null if (!Array.isArray(values[4])) { - throw new Error('Invalid partial codeSize encoding. Must be array') + throw EthereumJSErrorUnsetCode('Invalid partial codeSize encoding. Must be array') } else { const isNotNullIndicator = bytesToInt(values[4][0]) if (isNotNullIndicator !== 0 && isNotNullIndicator !== 1) { - throw new Error(`Invalid isNullIndicator=${isNotNullIndicator} for codeSize`) + throw EthereumJSErrorUnsetCode(`Invalid isNullIndicator=${isNotNullIndicator} for codeSize`) } if (isNotNullIndicator === 1) { codeSize = bytesToInt(values[4][1]) @@ -397,11 +400,11 @@ export function createPartialAccountFromRLP(serialized: Uint8Array) { let version = null if (!Array.isArray(values[5])) { - throw new Error('Invalid partial version encoding. Must be array') + throw EthereumJSErrorUnsetCode('Invalid partial version encoding. Must be array') } else { const isNotNullIndicator = bytesToInt(values[5][0]) if (isNotNullIndicator !== 0 && isNotNullIndicator !== 1) { - throw new Error(`Invalid isNullIndicator=${isNotNullIndicator} for version`) + throw EthereumJSErrorUnsetCode(`Invalid isNullIndicator=${isNotNullIndicator} for version`) } if (isNotNullIndicator === 1) { version = bytesToInt(values[5][1]) @@ -511,10 +514,10 @@ export const generateAddress2 = function ( assertIsBytes(initCode) if (from.length !== 20) { - throw new Error('Expected from to be of length 20') + throw EthereumJSErrorUnsetCode('Expected from to be of length 20') } if (salt.length !== 32) { - throw new Error('Expected salt to be of length 32') + throw EthereumJSErrorUnsetCode('Expected salt to be of length 32') } const address = keccak256(concatBytes(hexToBytes('0xff'), from, salt, keccak256(initCode))) @@ -572,7 +575,7 @@ export const pubToAddress = function (pubKey: Uint8Array, sanitize: boolean = fa pubKey = secp256k1.ProjectivePoint.fromHex(pubKey).toRawBytes(false).slice(1) } if (pubKey.length !== 64) { - throw new Error('Expected pubKey to be of length 64') + throw EthereumJSErrorUnsetCode('Expected pubKey to be of length 64') } // Only take the lower 160bits of the hash return keccak256(pubKey).subarray(-20) diff --git a/packages/util/src/address.ts b/packages/util/src/address.ts index 83fbdef0885..f1c34545a35 100644 --- a/packages/util/src/address.ts +++ b/packages/util/src/address.ts @@ -14,6 +14,7 @@ import { setLengthLeft, } from './bytes.js' import { BIGINT_0 } from './constants.js' +import { EthereumJSErrorUnsetCode } from './errors.js' import type { PrefixedHexString } from './types.js' @@ -25,7 +26,7 @@ export class Address { constructor(bytes: Uint8Array) { if (bytes.length !== 20) { - throw new Error('Invalid address length') + throw EthereumJSErrorUnsetCode('Invalid address length') } this.bytes = bytes } @@ -84,7 +85,7 @@ export function createZeroAddress(): Address { export function createAddressFromBigInt(value: bigint): Address { const bytes = bigIntToBytes(value) if (bytes.length > 20) { - throw new Error(`Invalid address, too long: ${bytes.length}`) + throw EthereumJSErrorUnsetCode(`Invalid address, too long: ${bytes.length}`) } return new Address(setLengthLeft(bytes, 20)) } @@ -95,7 +96,7 @@ export function createAddressFromBigInt(value: bigint): Address { */ export function createAddressFromString(str: string): Address { if (!isValidAddress(str)) { - throw new Error(`Invalid address input=${str}`) + throw EthereumJSErrorUnsetCode(`Invalid address input=${str}`) } return new Address(hexToBytes(str)) } @@ -106,7 +107,7 @@ export function createAddressFromString(str: string): Address { */ export function createAddressFromPublicKey(pubKey: Uint8Array): Address { if (!(pubKey instanceof Uint8Array)) { - throw new Error('Public key should be Uint8Array') + throw EthereumJSErrorUnsetCode('Public key should be Uint8Array') } const bytes = pubToAddress(pubKey) return new Address(bytes) @@ -118,7 +119,7 @@ export function createAddressFromPublicKey(pubKey: Uint8Array): Address { */ export function createAddressFromPrivateKey(privateKey: Uint8Array): Address { if (!(privateKey instanceof Uint8Array)) { - throw new Error('Private key should be Uint8Array') + throw EthereumJSErrorUnsetCode('Private key should be Uint8Array') } const bytes = privateToAddress(privateKey) return new Address(bytes) @@ -131,7 +132,7 @@ export function createAddressFromPrivateKey(privateKey: Uint8Array): Address { */ export function createContractAddress(from: Address, nonce: bigint): Address { if (typeof nonce !== 'bigint') { - throw new Error('Expected nonce to be a bigint') + throw EthereumJSErrorUnsetCode('Expected nonce to be a bigint') } return new Address(generateAddress(from.bytes, bigIntToBytes(nonce))) } @@ -148,10 +149,10 @@ export function createContractAddress2( initCode: Uint8Array, ): Address { if (!(salt instanceof Uint8Array)) { - throw new Error('Expected salt to be a Uint8Array') + throw EthereumJSErrorUnsetCode('Expected salt to be a Uint8Array') } if (!(initCode instanceof Uint8Array)) { - throw new Error('Expected initCode to be a Uint8Array') + throw EthereumJSErrorUnsetCode('Expected initCode to be a Uint8Array') } return new Address(generateAddress2(from.bytes, salt, initCode)) } diff --git a/packages/util/src/bytes.ts b/packages/util/src/bytes.ts index 9bb589fd43a..622d54e0ffd 100644 --- a/packages/util/src/bytes.ts +++ b/packages/util/src/bytes.ts @@ -5,6 +5,7 @@ import { hexToBytes as nobleH2B, } from 'ethereum-cryptography/utils.js' +import { EthereumJSErrorUnsetCode } from './errors.js' import { assertIsArray, assertIsBytes, assertIsHexString } from './helpers.js' import { isHexString, padToEven, stripHexPrefix } from './internal.js' @@ -24,12 +25,12 @@ export const bytesToUnprefixedHex = _bytesToUnprefixedHex * @throws If the input is not a valid 0x-prefixed hex string */ export const hexToBytes = (hex: string) => { - if (!hex.startsWith('0x')) throw new Error('input string must be 0x prefixed') + if (!hex.startsWith('0x')) throw EthereumJSErrorUnsetCode('input string must be 0x prefixed') return nobleH2B(padToEven(stripHexPrefix(hex))) } export const unprefixedHexToBytes = (hex: string) => { - if (hex.startsWith('0x')) throw new Error('input string cannot be 0x prefixed') + if (hex.startsWith('0x')) throw EthereumJSErrorUnsetCode('input string cannot be 0x prefixed') return nobleH2B(padToEven(hex)) } @@ -76,7 +77,7 @@ export const bytesToBigInt = (bytes: Uint8Array, littleEndian = false): bigint = */ export const bytesToInt = (bytes: Uint8Array): number => { const res = Number(bytesToBigInt(bytes)) - if (!Number.isSafeInteger(res)) throw new Error('Number exceeds 53 bits') + if (!Number.isSafeInteger(res)) throw EthereumJSErrorUnsetCode('Number exceeds 53 bits') return res } @@ -89,7 +90,7 @@ export const bytesToInt = (bytes: Uint8Array): number => { */ export const intToHex = (i: number): PrefixedHexString => { if (!Number.isSafeInteger(i) || i < 0) { - throw new Error(`Received an invalid integer type: ${i}`) + throw EthereumJSErrorUnsetCode(`Received an invalid integer type: ${i}`) } return ('0x' + i.toString(16)) as PrefixedHexString } @@ -237,7 +238,7 @@ export const toBytes = (v: ToBytesInputTypes): Uint8Array => { if (typeof v === 'string') { if (!isHexString(v)) { - throw new Error( + throw EthereumJSErrorUnsetCode( `Cannot convert string to Uint8Array. toBytes only supports 0x-prefixed hex strings and this string was given: ${v}`, ) } @@ -250,7 +251,7 @@ export const toBytes = (v: ToBytesInputTypes): Uint8Array => { if (typeof v === 'bigint') { if (v < BIGINT_0) { - throw new Error(`Cannot convert negative bigint to Uint8Array. Given: ${v}`) + throw EthereumJSErrorUnsetCode(`Cannot convert negative bigint to Uint8Array. Given: ${v}`) } let n = v.toString(16) if (n.length % 2) n = '0' + n @@ -262,7 +263,7 @@ export const toBytes = (v: ToBytesInputTypes): Uint8Array => { return v.toBytes() } - throw new Error('invalid type') + throw EthereumJSErrorUnsetCode('invalid type') } /** @@ -332,7 +333,7 @@ export const short = (bytes: Uint8Array | string, maxLength: number = 50): strin export const validateNoLeadingZeroes = (values: { [key: string]: Uint8Array | undefined }) => { for (const [k, v] of Object.entries(values)) { if (v !== undefined && v.length > 0 && v[0] === 0) { - throw new Error(`${k} cannot have leading zeroes, received: ${bytesToHex(v)}`) + throw EthereumJSErrorUnsetCode(`${k} cannot have leading zeroes, received: ${bytesToHex(v)}`) } } } diff --git a/packages/util/src/helpers.ts b/packages/util/src/helpers.ts index 1302b91841f..135982d78e9 100644 --- a/packages/util/src/helpers.ts +++ b/packages/util/src/helpers.ts @@ -1,3 +1,4 @@ +import { EthereumJSErrorUnsetCode } from './errors.js' import { isHexString } from './internal.js' /** @@ -7,7 +8,7 @@ import { isHexString } from './internal.js' export const assertIsHexString = function (input: string): void { if (!isHexString(input)) { const msg = `This method only supports 0x-prefixed hex strings but input was: ${input}` - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } @@ -18,7 +19,7 @@ export const assertIsHexString = function (input: string): void { export const assertIsBytes = function (input: Uint8Array): void { if (!(input instanceof Uint8Array)) { const msg = `This method only supports Uint8Array but input was: ${input}` - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } @@ -29,7 +30,7 @@ export const assertIsBytes = function (input: Uint8Array): void { export const assertIsArray = function (input: number[]): void { if (!Array.isArray(input)) { const msg = `This method only supports number arrays but input was: ${input}` - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } @@ -40,6 +41,6 @@ export const assertIsArray = function (input: number[]): void { export const assertIsString = function (input: string): void { if (typeof input !== 'string') { const msg = `This method only supports strings but input was: ${input}` - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } diff --git a/packages/util/src/internal.ts b/packages/util/src/internal.ts index 4af836eaa41..3dcd1bfca76 100644 --- a/packages/util/src/internal.ts +++ b/packages/util/src/internal.ts @@ -23,6 +23,7 @@ THE SOFTWARE */ import { bytesToUnprefixedHex, utf8ToBytes } from './bytes.js' +import { EthereumJSErrorUnsetCode } from './errors.js' import type { PrefixedHexString } from './types.js' @@ -47,7 +48,9 @@ export function isHexString(value: string, length?: number): value is PrefixedHe */ export const stripHexPrefix = (str: string): string => { if (typeof str !== 'string') - throw new Error(`[stripHexPrefix] input must be type 'string', received ${typeof str}`) + throw EthereumJSErrorUnsetCode( + `[stripHexPrefix] input must be type 'string', received ${typeof str}`, + ) return isHexString(str) ? str.slice(2) : str } @@ -61,7 +64,7 @@ export function padToEven(value: string): string { let a = value if (typeof a !== 'string') { - throw new Error(`[padToEven] value must be type 'string', received ${typeof a}`) + throw EthereumJSErrorUnsetCode(`[padToEven] value must be type 'string', received ${typeof a}`) } if (a.length % 2) a = `0${a}` @@ -76,7 +79,9 @@ export function padToEven(value: string): string { */ export function getBinarySize(str: string) { if (typeof str !== 'string') { - throw new Error(`[getBinarySize] method requires input type 'string', received ${typeof str}`) + throw EthereumJSErrorUnsetCode( + `[getBinarySize] method requires input type 'string', received ${typeof str}`, + ) } return utf8ToBytes(str).byteLength @@ -96,12 +101,12 @@ export function arrayContainsArray( some?: boolean, ): boolean { if (Array.isArray(superset) !== true) { - throw new Error( + throw EthereumJSErrorUnsetCode( `[arrayContainsArray] method requires input 'superset' to be an array, got type '${typeof superset}'`, ) } if (Array.isArray(subset) !== true) { - throw new Error( + throw EthereumJSErrorUnsetCode( `[arrayContainsArray] method requires input 'subset' to be an array, got type '${typeof subset}'`, ) } @@ -175,10 +180,12 @@ export function fromAscii(stringValue: string) { */ export function getKeys(params: Record[], key: string, allowEmpty?: boolean) { if (!Array.isArray(params)) { - throw new Error(`[getKeys] method expects input 'params' to be an array, got ${typeof params}`) + throw EthereumJSErrorUnsetCode( + `[getKeys] method expects input 'params' to be an array, got ${typeof params}`, + ) } if (typeof key !== 'string') { - throw new Error( + throw EthereumJSErrorUnsetCode( `[getKeys] method expects input 'key' to be type 'string', got ${typeof params}`, ) } @@ -190,7 +197,9 @@ export function getKeys(params: Record[], key: string, allowEmpt if (allowEmpty === true && !value) { value = '' } else if (typeof value !== 'string') { - throw new Error(`invalid abi - expected type 'string', received ${typeof value}`) + throw EthereumJSErrorUnsetCode( + `invalid abi - expected type 'string', received ${typeof value}`, + ) } result.push(value) } diff --git a/packages/util/src/provider.ts b/packages/util/src/provider.ts index e2a06517c8f..72f822b280c 100644 --- a/packages/util/src/provider.ts +++ b/packages/util/src/provider.ts @@ -1,3 +1,5 @@ +import { EthereumJSErrorUnsetCode } from './errors.js' + type rpcParams = { method: string params: (string | string[] | boolean | number)[] @@ -37,7 +39,7 @@ export const fetchFromProvider = async (url: string, params: rpcParams) => { body: data, }) if (!res.ok) { - throw new Error( + throw EthereumJSErrorUnsetCode( `JSONRPCError: ${JSON.stringify( { method: params.method, @@ -67,7 +69,7 @@ export const getProvider = (provider: string | EthersProvider) => { } else if (typeof provider === 'object' && provider._getConnection !== undefined) { return provider._getConnection().url } else { - throw new Error('Must provide valid provider URL or Web3Provider') + throw EthereumJSErrorUnsetCode('Must provide valid provider URL or Web3Provider') } } diff --git a/packages/util/src/signature.ts b/packages/util/src/signature.ts index bcf8acaec27..a081b8eff85 100644 --- a/packages/util/src/signature.ts +++ b/packages/util/src/signature.ts @@ -18,6 +18,7 @@ import { SECP256K1_ORDER, SECP256K1_ORDER_DIV_2, } from './constants.js' +import { EthereumJSErrorUnsetCode } from './errors.js' import { assertIsBytes } from './helpers.js' import type { PrefixedHexString } from './types.js' @@ -64,7 +65,9 @@ export function ecsign( /* The recovery identifier is a 1 byte value specifying the parity and finiteness of the coordinates of the curve point for which r is the x-value; this value is in the range of [0, 3], however we declare the upper two possibilities, representing infinite values, invalid. */ - throw new Error(`Invalid recovery value: values 2/3 are invalid, received: ${sig.recovery}`) + throw EthereumJSErrorUnsetCode( + `Invalid recovery value: values 2/3 are invalid, received: ${sig.recovery}`, + ) } const v = @@ -103,7 +106,7 @@ export const ecrecover = function ( const signature = concatBytes(setLengthLeft(r, 32), setLengthLeft(s, 32)) const recovery = calculateSigRecovery(v, chainId) if (!isValidSigRecovery(recovery)) { - throw new Error('Invalid signature v value') + throw EthereumJSErrorUnsetCode('Invalid signature v value') } const sig = secp256k1.Signature.fromCompact(signature).addRecoveryBit(Number(recovery)) @@ -124,7 +127,7 @@ export const toRPCSig = function ( ): string { const recovery = calculateSigRecovery(v, chainId) if (!isValidSigRecovery(recovery)) { - throw new Error('Invalid signature v value') + throw EthereumJSErrorUnsetCode('Invalid signature v value') } // geth (and the RPC eth_sign method) uses the 65 byte format used by Bitcoin @@ -145,7 +148,7 @@ export const toCompactSig = function ( ): string { const recovery = calculateSigRecovery(v, chainId) if (!isValidSigRecovery(recovery)) { - throw new Error('Invalid signature v value') + throw EthereumJSErrorUnsetCode('Invalid signature v value') } const ss = Uint8Array.from([...s]) @@ -181,7 +184,7 @@ export const fromRPCSig = function (sig: PrefixedHexString): ECDSASignature { v = BigInt(bytesToInt(bytes.subarray(32, 33)) >> 7) s[0] &= 0x7f } else { - throw new Error('Invalid signature length') + throw EthereumJSErrorUnsetCode('Invalid signature length') } // support both versions of `eth_sign` responses diff --git a/packages/util/src/types.ts b/packages/util/src/types.ts index 69077a80d87..a73c2101607 100644 --- a/packages/util/src/types.ts +++ b/packages/util/src/types.ts @@ -1,4 +1,5 @@ import { bytesToBigInt, bytesToHex, toBytes } from './bytes.js' +import { EthereumJSErrorUnsetCode } from './errors.js' import { isHexString } from './internal.js' import type { Address } from './address.js' @@ -98,9 +99,9 @@ export function toType( } if (typeof input === 'string' && !isHexString(input)) { - throw new Error(`A string must be provided with a 0x-prefix, given: ${input}`) + throw EthereumJSErrorUnsetCode(`A string must be provided with a 0x-prefix, given: ${input}`) } else if (typeof input === 'number' && !Number.isSafeInteger(input)) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'The provided number is greater than MAX_SAFE_INTEGER (please use an alternative input type)', ) } @@ -115,7 +116,7 @@ export function toType( case TypeOutput.Number: { const bigInt = bytesToBigInt(output) if (bigInt > BigInt(Number.MAX_SAFE_INTEGER)) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'The provided number is greater than MAX_SAFE_INTEGER (please use an alternative output type)', ) } @@ -124,6 +125,6 @@ export function toType( case TypeOutput.PrefixedHexString: return bytesToHex(output) as TypeOutputReturnType[T] default: - throw new Error('unknown outputType') + throw EthereumJSErrorUnsetCode('unknown outputType') } } diff --git a/packages/util/src/units.ts b/packages/util/src/units.ts index 36fb61e555a..1c5ce9d25a5 100644 --- a/packages/util/src/units.ts +++ b/packages/util/src/units.ts @@ -1,4 +1,5 @@ import { BIGINT_0, BIGINT_1 } from './constants.js' +import { EthereumJSErrorUnsetCode } from './errors.js' /** Conversion constants to wei */ export const GWEI_TO_WEI = BigInt(10 ** 9) // Multiplier to convert from Gwei to Wei @@ -24,10 +25,10 @@ export function formatBigDecimal( export class Units { static validateInput(amount: number | bigint): void { if (typeof amount === 'number' && !Number.isInteger(amount)) { - throw new Error('Input must be an integer number') + throw EthereumJSErrorUnsetCode('Input must be an integer number') } if (BigInt(amount) < 0) { - throw new Error('Input must be a positive number') + throw EthereumJSErrorUnsetCode('Input must be a positive number') } } diff --git a/packages/verkle/src/node/internalNode.ts b/packages/verkle/src/node/internalNode.ts index 98486a06626..87889bd88b4 100644 --- a/packages/verkle/src/node/internalNode.ts +++ b/packages/verkle/src/node/internalNode.ts @@ -1,4 +1,4 @@ -import { type VerkleCrypto } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, type VerkleCrypto } from '@ethereumjs/util' import { BaseVerkleNode } from './baseVerkleNode.js' import { NODE_WIDTH, VerkleNodeType } from './types.js' @@ -42,12 +42,12 @@ export class InternalVerkleNode extends BaseVerkleNode static fromRawNode(rawNode: Uint8Array[], verkleCrypto: VerkleCrypto): InternalVerkleNode { const nodeType = rawNode[0][0] if (nodeType !== VerkleNodeType.Internal) { - throw new Error('Invalid node type') + throw EthereumJSErrorUnsetCode('Invalid node type') } // The length of the rawNode should be the # of children * 2 (for commitments and paths) + 2 for the node type and the commitment if (rawNode.length !== NODE_WIDTH * 2 + 2) { - throw new Error('Invalid node length') + throw EthereumJSErrorUnsetCode('Invalid node length') } const commitment = rawNode[rawNode.length - 1] diff --git a/packages/verkle/src/node/leafNode.ts b/packages/verkle/src/node/leafNode.ts index ec9b397fa65..1aeca46fcee 100644 --- a/packages/verkle/src/node/leafNode.ts +++ b/packages/verkle/src/node/leafNode.ts @@ -1,4 +1,4 @@ -import { equalsBytes, intToBytes, setLengthRight } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, equalsBytes, intToBytes, setLengthRight } from '@ethereumjs/util' import { BaseVerkleNode } from './baseVerkleNode.js' import { LeafVerkleNodeValue, NODE_WIDTH, VerkleNodeType } from './types.js' @@ -104,12 +104,12 @@ export class LeafVerkleNode extends BaseVerkleNode { static fromRawNode(rawNode: Uint8Array[], verkleCrypto: VerkleCrypto): LeafVerkleNode { const nodeType = rawNode[0][0] if (nodeType !== VerkleNodeType.Leaf) { - throw new Error('Invalid node type') + throw EthereumJSErrorUnsetCode('Invalid node type') } // The length of the rawNode should be the # of values (node width) + 5 for the node type, the stem, the commitment and the 2 commitments if (rawNode.length !== NODE_WIDTH + 5) { - throw new Error('Invalid node length') + throw EthereumJSErrorUnsetCode('Invalid node length') } const stem = rawNode[1] diff --git a/packages/verkle/src/node/util.ts b/packages/verkle/src/node/util.ts index 4213197aa7f..d940f0b4ce3 100644 --- a/packages/verkle/src/node/util.ts +++ b/packages/verkle/src/node/util.ts @@ -1,5 +1,5 @@ import { RLP } from '@ethereumjs/rlp' -import { setLengthRight } from '@ethereumjs/util' +import { EthereumJSErrorUnsetCode, setLengthRight } from '@ethereumjs/util' import { InternalVerkleNode } from './internalNode.js' import { LeafVerkleNode } from './leafNode.js' @@ -15,14 +15,14 @@ export function decodeRawVerkleNode(raw: Uint8Array[], verkleCrypto: VerkleCrypt case VerkleNodeType.Leaf: return LeafVerkleNode.fromRawNode(raw, verkleCrypto) default: - throw new Error('Invalid node type') + throw EthereumJSErrorUnsetCode('Invalid node type') } } export function decodeVerkleNode(raw: Uint8Array, verkleCrypto: VerkleCrypto) { const decoded = RLP.decode(Uint8Array.from(raw)) as Uint8Array[] if (!Array.isArray(decoded)) { - throw new Error('Invalid node') + throw EthereumJSErrorUnsetCode('Invalid node') } return decodeRawVerkleNode(decoded, verkleCrypto) } @@ -54,7 +54,7 @@ export const createDefaultLeafVerkleValues: () => number[] = () => new Array(256 */ export const createCValues = (values: (Uint8Array | LeafVerkleNodeValue)[]) => { if (values.length !== 128) - throw new Error(`got wrong number of values, expected 128, got ${values.length}`) + throw EthereumJSErrorUnsetCode(`got wrong number of values, expected 128, got ${values.length}`) const expandedValues: Uint8Array[] = new Array(256) for (let x = 0; x < 128; x++) { const retrievedValue = values[x] diff --git a/packages/verkle/src/verkleTree.ts b/packages/verkle/src/verkleTree.ts index 705194ece06..dc96612835a 100644 --- a/packages/verkle/src/verkleTree.ts +++ b/packages/verkle/src/verkleTree.ts @@ -1,4 +1,11 @@ -import { Lock, bytesToHex, equalsBytes, intToHex, matchingBytesLength } from '@ethereumjs/util' +import { + EthereumJSErrorUnsetCode, + Lock, + bytesToHex, + equalsBytes, + intToHex, + matchingBytesLength, +} from '@ethereumjs/util' import debug from 'debug' import { CheckpointDB } from './db/checkpoint.js' @@ -49,7 +56,7 @@ export class VerkleTree { this._opts = opts if (opts.db instanceof CheckpointDB) { - throw new Error('Cannot pass in an instance of CheckpointDB') + throw EthereumJSErrorUnsetCode('Cannot pass in an instance of CheckpointDB') } this._db = new CheckpointDB({ db: opts.db, cacheSize: opts.cacheSize }) @@ -93,7 +100,7 @@ export class VerkleTree { } if (value.length !== this._hashLen) { - throw new Error(`Invalid root length. Roots are ${this._hashLen} bytes`) + throw EthereumJSErrorUnsetCode(`Invalid root length. Roots are ${this._hashLen} bytes`) } this._root = value @@ -126,7 +133,8 @@ export class VerkleTree { * was found or `undefined` if no value was found at a given suffixes. */ async get(stem: Uint8Array, suffixes: number[]): Promise<(Uint8Array | undefined)[]> { - if (stem.length !== 31) throw new Error(`expected stem with length 31; got ${stem.length}`) + if (stem.length !== 31) + throw EthereumJSErrorUnsetCode(`expected stem with length 31; got ${stem.length}`) this.DEBUG && this.debug(`Stem: ${bytesToHex(stem)}; Suffix: ${suffixes}`, ['get']) const res = await this.findPath(stem) if (res.node instanceof LeafVerkleNode) { @@ -160,10 +168,13 @@ export class VerkleTree { suffixes: number[], values: (Uint8Array | LeafVerkleNodeValue.Untouched)[] = [], ): Promise { - if (stem.length !== 31) throw new Error(`expected stem with length 31, got ${stem.length}`) + if (stem.length !== 31) + throw EthereumJSErrorUnsetCode(`expected stem with length 31, got ${stem.length}`) if (values.length > 0 && values.length !== suffixes.length) { // Must have an equal number of values and suffixes - throw new Error(`expected number of values; ${values.length} to equal ${suffixes.length}`) + throw EthereumJSErrorUnsetCode( + `expected number of values; ${values.length} to equal ${suffixes.length}`, + ) } this.DEBUG && this.debug(`Stem: ${bytesToHex(stem)}`, ['put']) @@ -173,7 +184,7 @@ export class VerkleTree { // Sanity check - we should at least get the root node back if (foundPath.stack.length === 0) { - throw new Error(`Root node not found in trie`) + throw EthereumJSErrorUnsetCode(`Root node not found in trie`) } // Step 1) Create or update the leaf node @@ -182,14 +193,14 @@ export class VerkleTree { if (foundPath.node !== null) { // Sanity check to verify we have the right node type if (!isLeafVerkleNode(foundPath.node)) { - throw new Error( + throw EthereumJSErrorUnsetCode( `expected leaf node found at ${bytesToHex(stem)}. Got internal node instead`, ) } leafNode = foundPath.node // Sanity check to verify we have the right leaf node if (!equalsBytes(leafNode.stem, stem)) { - throw new Error( + throw EthereumJSErrorUnsetCode( `invalid leaf node found. Expected stem: ${bytesToHex(stem)}; got ${bytesToHex( foundPath.node.stem, )}`, @@ -377,7 +388,7 @@ export class VerkleTree { this.verkleCrypto.hashCommitment(children[0]!.commitment), ) if (rawNode === undefined) - throw new Error(`missing node in DB at ${bytesToHex(children[0]!.path)}`) + throw EthereumJSErrorUnsetCode(`missing node in DB at ${bytesToHex(children[0]!.path)}`) return { node: decodeVerkleNode(rawNode, this.verkleCrypto) as VerkleNode, lastPath: children[0]!.path, @@ -421,7 +432,7 @@ export class VerkleTree { // Get root node let rawNode = await this._db.get(this.root()) - if (rawNode === undefined) throw new Error('root node should exist') + if (rawNode === undefined) throw EthereumJSErrorUnsetCode('root node should exist') const rootNode = decodeVerkleNode(rawNode, this.verkleCrypto) as InternalVerkleNode @@ -439,7 +450,8 @@ export class VerkleTree { // Look up child node by node hash rawNode = await this._db.get(this.verkleCrypto.hashCommitment(child!.commitment)) // We should always find the node if the path is specified in child.path - if (rawNode === undefined) throw new Error(`missing node at ${bytesToHex(child!.path)}`) + if (rawNode === undefined) + throw EthereumJSErrorUnsetCode(`missing node at ${bytesToHex(child!.path)}`) const decodedNode = decodeVerkleNode(rawNode, this.verkleCrypto) // Calculate the index of the last matching byte in the key @@ -541,7 +553,7 @@ export class VerkleTree { * @param proof */ async fromProof(_proof: Proof): Promise { - throw new Error('Not implemented') + throw EthereumJSErrorUnsetCode('Not implemented') } /** @@ -549,7 +561,7 @@ export class VerkleTree { * @param key */ async createVerkleProof(_key: Uint8Array): Promise { - throw new Error('Not implemented') + throw EthereumJSErrorUnsetCode('Not implemented') } /** @@ -565,7 +577,7 @@ export class VerkleTree { _key: Uint8Array, _proof: Proof, ): Promise { - throw new Error('Not implemented') + throw EthereumJSErrorUnsetCode('Not implemented') } /** @@ -573,7 +585,7 @@ export class VerkleTree { * @return Returns a [stream](https://nodejs.org/dist/latest-v12.x/docs/api/stream.html#stream_class_stream_readable) of the contents of the `tree` */ createReadStream(): any { - throw new Error('Not implemented') + throw EthereumJSErrorUnsetCode('Not implemented') } /** @@ -633,7 +645,7 @@ export class VerkleTree { */ async commit(): Promise { if (!this.hasCheckpoints()) { - throw new Error('trying to commit when not checkpointed') + throw EthereumJSErrorUnsetCode('trying to commit when not checkpointed') } await this._lock.acquire() @@ -649,7 +661,7 @@ export class VerkleTree { */ async revert(): Promise { if (!this.hasCheckpoints()) { - throw new Error('trying to revert when not checkpointed') + throw EthereumJSErrorUnsetCode('trying to revert when not checkpointed') } await this._lock.acquire() diff --git a/packages/vm/src/bloom/index.ts b/packages/vm/src/bloom/index.ts index 9992bdd07e1..e9b0aeeb7b0 100644 --- a/packages/vm/src/bloom/index.ts +++ b/packages/vm/src/bloom/index.ts @@ -1,3 +1,4 @@ +import { EthereumJSErrorUnsetCode } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak.js' import type { Common } from '@ethereumjs/common' @@ -20,7 +21,8 @@ export class Bloom { if (!bitvector) { this.bitvector = new Uint8Array(BYTE_SIZE) } else { - if (bitvector.length !== BYTE_SIZE) throw new Error('bitvectors must be 2048 bits long') + if (bitvector.length !== BYTE_SIZE) + throw EthereumJSErrorUnsetCode('bitvectors must be 2048 bits long') this.bitvector = bitvector } } diff --git a/packages/vm/src/buildBlock.ts b/packages/vm/src/buildBlock.ts index 7d893c8e4b2..ce4308547f8 100644 --- a/packages/vm/src/buildBlock.ts +++ b/packages/vm/src/buildBlock.ts @@ -14,6 +14,7 @@ import { BIGINT_0, BIGINT_1, BIGINT_2, + EthereumJSErrorUnsetCode, GWEI_TO_WEI, KECCAK256_RLP, TypeOutput, @@ -129,10 +130,10 @@ export class BlockBuilder { */ private checkStatus() { if (this.blockStatus.status === BuildStatus.Build) { - throw new Error('Block has already been built') + throw EthereumJSErrorUnsetCode('Block has already been built') } if (this.blockStatus.status === BuildStatus.Reverted) { - throw new Error('State has already been reverted') + throw EthereumJSErrorUnsetCode('State has already been reverted') } } @@ -237,7 +238,9 @@ export class BlockBuilder { const blockGasRemaining = blockGasLimit - this.gasUsed if (tx.gasLimit > blockGasRemaining) { - throw new Error('tx has a higher gas limit than the remaining gas in the block') + throw EthereumJSErrorUnsetCode( + 'tx has a higher gas limit than the remaining gas in the block', + ) } let blobGasUsed = undefined if (tx instanceof Blob4844Tx) { @@ -251,12 +254,12 @@ export class BlockBuilder { // TODO: verify if we want this, do we want to allow the block builder to accept blob txs without the actual blobs? // (these must have at least one `blobVersionedHashes`, this is verified at tx-level) if (allowNoBlobs !== true) { - throw new Error('blobs missing for 4844 transaction') + throw EthereumJSErrorUnsetCode('blobs missing for 4844 transaction') } } if (this.blobGasUsed + BigInt(blobTx.numBlobs()) * blobGasPerBlob > blobGasLimit) { - throw new Error('block blob gas limit reached') + throw EthereumJSErrorUnsetCode('block blob gas limit reached') } blobGasUsed = this.blobGasUsed diff --git a/packages/vm/src/constructors.ts b/packages/vm/src/constructors.ts index 275fc6b4d01..70c3c687c95 100644 --- a/packages/vm/src/constructors.ts +++ b/packages/vm/src/constructors.ts @@ -1,7 +1,13 @@ import { Common, Mainnet } from '@ethereumjs/common' import { EVMMockBlockchain, createEVM, getActivePrecompiles } from '@ethereumjs/evm' import { MerkleStateManager } from '@ethereumjs/statemanager' -import { Account, Address, createAccount, unprefixedHexToBytes } from '@ethereumjs/util' +import { + Account, + Address, + EthereumJSErrorUnsetCode, + createAccount, + unprefixedHexToBytes, +} from '@ethereumjs/util' import { VM } from './vm.js' @@ -34,14 +40,14 @@ export async function createVM(opts: VMOpts = {}): Promise { if (opts.profilerOpts !== undefined) { const profilerOpts = opts.profilerOpts if (profilerOpts.reportAfterBlock === true && profilerOpts.reportAfterTx === true) { - throw new Error( + throw EthereumJSErrorUnsetCode( 'Cannot have `reportProfilerAfterBlock` and `reportProfilerAfterTx` set to `true` at the same time', ) } } if (opts.evm !== undefined && opts.evmOpts !== undefined) { - throw new Error('the evm and evmOpts options cannot be used in conjunction') + throw EthereumJSErrorUnsetCode('the evm and evmOpts options cannot be used in conjunction') } if (opts.evm === undefined) { diff --git a/packages/vm/src/requests.ts b/packages/vm/src/requests.ts index 136e6a43b00..563969e8c55 100644 --- a/packages/vm/src/requests.ts +++ b/packages/vm/src/requests.ts @@ -2,6 +2,7 @@ import { Mainnet } from '@ethereumjs/common' import { CLRequest, CLRequestType, + EthereumJSErrorUnsetCode, bigIntToAddressBytes, bigIntToBytes, bytesToHex, @@ -31,7 +32,7 @@ export const accumulateRequests = async ( const depositContractAddress = vm.common['_chainParams'].depositContractAddress ?? Mainnet.depositContractAddress if (depositContractAddress === undefined) - throw new Error('deposit contract address required with EIP 6110') + throw EthereumJSErrorUnsetCode('deposit contract address required with EIP 6110') const depositsRequest = accumulateDepositsRequest(depositContractAddress, txResults) requests.push(depositsRequest) } diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index 0d8c1f122f1..40ad48b1685 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -11,6 +11,7 @@ import { BIGINT_0, BIGINT_1, BIGINT_8, + EthereumJSErrorUnsetCode, GWEI_TO_WEI, KECCAK256_RLP, bigIntToAddressBytes, @@ -269,7 +270,7 @@ export async function runBlock(vm: VM, opts: RunBlockOpts): Promise= BigInt('0x8000000000000000')) { const msg = _errorMsg('Invalid block with gas limit greater than (2^63 - 1)', vm, block) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } else { if (vm.DEBUG) { debug(`Validate block`) @@ -412,7 +415,9 @@ async function applyBlock(vm: VM, block: Block, opts: RunBlockOpts): Promisevm.blockchain).validateHeader === 'function') { await (vm.blockchain).validateHeader(block.header) } else { - throw new Error('cannot validate header: blockchain has no `validateHeader` method') + throw EthereumJSErrorUnsetCode( + 'cannot validate header: blockchain has no `validateHeader` method', + ) } } await block.validateData() @@ -458,7 +463,7 @@ async function applyBlock(vm: VM, block: Block, opts: RunBlockOpts): Promise { if (opts.block.common.hardfork() !== vm.common.hardfork()) { // Block and VM's hardfork should match as well const msg = _errorMsg('block has a different hardfork than the vm', vm, opts.block, opts.tx) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } const gasLimit = opts.block?.header.gasLimit ?? DEFAULT_HEADER.gasLimit if (opts.skipBlockGasLimitValidation !== true && gasLimit < opts.tx.gasLimit) { const msg = _errorMsg('tx has a higher gas limit than the block', vm, opts.block, opts.tx) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } // Ensure we start with a clear warmed accounts Map @@ -135,7 +136,7 @@ export async function runTx(vm: VM, opts: RunTxOpts): Promise { opts.block, opts.tx, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } if (opts.tx.supports(Capability.EIP1559FeeMarket) && !vm.common.isActivatedEIP(1559)) { await vm.evm.journal.revert() @@ -145,7 +146,7 @@ export async function runTx(vm: VM, opts: RunTxOpts): Promise { opts.block, opts.tx, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } const castedTx = opts.tx @@ -205,7 +206,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { !(vm.stateManager instanceof StatefulVerkleStateManager) && !(vm.stateManager instanceof StatelessVerkleStateManager) ) { - throw new Error(`Verkle State Manager needed for execution of verkle blocks`) + throw EthereumJSErrorUnsetCode(`Verkle State Manager needed for execution of verkle blocks`) } stateAccesses = vm.evm.verkleAccessWitness txAccesses = new VerkleAccessWitness({ verkleCrypto: vm.stateManager.verkleCrypto }) @@ -273,7 +274,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { block, tx, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } gasLimit -= intrinsicGas if (vm.DEBUG) { @@ -295,7 +296,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { block, tx, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } if (enableProfiler) { @@ -331,7 +332,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { block, tx, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } } @@ -353,7 +354,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { block, tx, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } @@ -371,7 +372,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { if (isBlob4844Tx(tx)) { if (!vm.common.isActivatedEIP(4844)) { const msg = _errorMsg('blob transactions are only valid with EIP4844 active', vm, block, tx) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } // EIP-4844 spec // the signer must be able to afford the transaction @@ -388,7 +389,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { block, tx, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } @@ -404,7 +405,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { block, tx, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } @@ -416,7 +417,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { block, tx, ) - throw new Error(msg) + throw EthereumJSErrorUnsetCode(msg) } } From 58c90578d3722341b39c4a1fce99de8f54bef36c Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 24 Feb 2025 14:36:05 +0100 Subject: [PATCH 4/7] Squashed commit of the following: commit 85aa5fad700307ccae5ccfd868eacc429539856c Author: Jochem Brouwer Date: Mon Feb 24 05:57:39 2025 +0100 lint: add rule to disallow `new Error` throwing commit 1e975676bad78905e6f36ed413af712753048828 Merge: a8cd9e15d bd77a7450 Author: Jochem Brouwer Date: Mon Feb 24 05:44:30 2025 +0100 Merge branch 'master' into evm-error-handling commit a8cd9e15d451453cbaa2110747f24617d6a05cb5 Author: Jochem Brouwer Date: Mon Feb 24 05:44:21 2025 +0100 util: introduce new temp error with unset error code commit aa3702badef1c32ede0281b2fc68580d1d820545 Author: Jochem Brouwer Date: Mon Feb 10 16:04:41 2025 +0100 vm/client: fix build commit 16d1b38464bcbc482a3087054181ec440e3ae213 Author: Jochem Brouwer Date: Mon Feb 10 15:40:57 2025 +0100 evm/util: update to new, simpler error format commit ec01f1bc7552bb3cfedf9a8e086fff9e525a2081 Author: Jochem Brouwer Date: Mon Feb 10 14:37:24 2025 +0100 util: update base error class commit 70b2df58fdd43ab352a6e977ff283458ead76a25 Merge: 293f4ccc9 1774df6ee Author: Jochem Brouwer Date: Mon Feb 10 14:22:35 2025 +0100 Merge branch 'master' into evm-error-handling commit 293f4ccc93b830e3e35a963fadaf28a8be7481b3 Merge: b4458d766 99cfdd688 Author: Jochem Brouwer Date: Tue Jan 14 03:04:15 2025 +0100 Merge branch 'master' into evm-error-handling commit b4458d766cb2ac15a60764da780ddd4c4eb10478 Merge: 71e77dc69 100d77dfa Author: Jochem Brouwer Date: Fri Dec 13 17:50:26 2024 +0100 Merge branch 'master' into evm-error-handling commit 71e77dc695e5ad700513ab91626cdc1980f2b353 Author: Jochem Brouwer Date: Tue Oct 1 13:57:38 2024 +0200 evm: fix err handling commit d00b7bf92cf88fb39a1667b70209e87d0ac666b2 Author: Jochem Brouwer Date: Tue Oct 1 13:46:08 2024 +0200 evm/util: error overhaul --- packages/client/src/rpc/modules/eth.ts | 2 +- packages/evm/src/{exceptions.ts => errors.ts} | 27 ++- packages/evm/src/evm.ts | 60 ++++-- packages/evm/src/index.ts | 6 +- packages/evm/src/interpreter.ts | 65 ++++-- packages/evm/src/opcodes/EIP2200.ts | 4 +- packages/evm/src/opcodes/functions.ts | 196 ++++++++++++++---- packages/evm/src/opcodes/gas.ts | 134 ++++++++++-- packages/evm/src/opcodes/util.ts | 9 +- .../evm/src/precompiles/08-bn254-pairing.ts | 9 +- packages/evm/src/precompiles/09-blake2f.ts | 10 +- .../precompiles/0a-kzg-point-evaluation.ts | 37 +++- .../evm/src/precompiles/0b-bls12-g1add.ts | 16 +- .../evm/src/precompiles/0c-bls12-g1msm.ts | 30 ++- .../evm/src/precompiles/0d-bls12-g2add.ts | 16 +- .../evm/src/precompiles/0e-bls12-g2msm.ts | 23 +- .../evm/src/precompiles/0f-bls12-pairing.ts | 23 +- .../src/precompiles/10-bls12-map-fp-to-g1.ts | 16 +- .../src/precompiles/11-bls12-map-fp2-to-g2.ts | 16 +- packages/evm/src/precompiles/bls12_381/mcl.ts | 30 ++- .../evm/src/precompiles/bls12_381/noble.ts | 23 +- packages/evm/src/precompiles/bn254/noble.ts | 14 +- packages/evm/src/stack.ts | 34 ++- packages/evm/src/types.ts | 4 +- packages/util/src/index.ts | 5 + packages/vm/src/runTx.ts | 2 +- 26 files changed, 627 insertions(+), 184 deletions(-) rename packages/evm/src/{exceptions.ts => errors.ts} (71%) diff --git a/packages/client/src/rpc/modules/eth.ts b/packages/client/src/rpc/modules/eth.ts index eb7cbb27293..475642a86a7 100644 --- a/packages/client/src/rpc/modules/eth.ts +++ b/packages/client/src/rpc/modules/eth.ts @@ -521,7 +521,7 @@ export class Eth { throw { code: 3, data: bytesToHex(execResult.returnValue), - message: execResult.exceptionError.error, + message: execResult.exceptionError.type.code, } } return bytesToHex(execResult.returnValue) diff --git a/packages/evm/src/exceptions.ts b/packages/evm/src/errors.ts similarity index 71% rename from packages/evm/src/exceptions.ts rename to packages/evm/src/errors.ts index 9063f818ed5..488454d352c 100644 --- a/packages/evm/src/exceptions.ts +++ b/packages/evm/src/errors.ts @@ -1,4 +1,9 @@ -export enum ERROR { +import { EthereumJSError } from '@ethereumjs/util' + +import type { EOFError } from './eof/errors.js' + +// TODO: merge EOF errors in here +export enum EVMErrorCode { OUT_OF_GAS = 'out of gas', CODESTORE_OUT_OF_GAS = 'code store out of gas', CODESIZE_EXCEEDS_MAXIMUM = 'code size to deposit exceeds maximum code size', @@ -15,13 +20,11 @@ export enum ERROR { REFUND_EXHAUSTED = 'refund exhausted', VALUE_OVERFLOW = 'value overflow', INSUFFICIENT_BALANCE = 'insufficient balance', - INVALID_BEGINSUB = 'invalid BEGINSUB', - INVALID_RETURNSUB = 'invalid RETURNSUB', - INVALID_JUMPSUB = 'invalid JUMPSUB', INVALID_BYTECODE_RESULT = 'invalid bytecode deployed', INITCODE_SIZE_VIOLATION = 'initcode exceeds max initcode size', INVALID_INPUT_LENGTH = 'invalid input length', INVALID_EOF_FORMAT = 'invalid EOF format', + INVALID_PRECOMPILE = 'invalid precompile', // BLS errors BLS_12_381_INVALID_INPUT_LENGTH = 'invalid input length', @@ -38,12 +41,16 @@ export enum ERROR { INVALID_PROOF = 'kzg proof invalid', } -export class EvmError { - error: ERROR - errorType: string +type EVMErrorType = + | { + code: EVMErrorCode | EOFError + } + | { code: EVMErrorCode.REVERT; revertBytes: Uint8Array } - constructor(error: ERROR) { - this.error = error - this.errorType = 'EvmError' +export class EVMError extends EthereumJSError { + constructor(type: EVMErrorType, message?: string) { + super(type, message) } + + // TODO: add helper method to format the error in a human readable way } diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index 5132480c6b7..72338aaf1ee 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -9,6 +9,8 @@ import { KECCAK256_NULL, KECCAK256_RLP, MAX_INTEGER, + UsageError, + UsageErrorType, bigIntToBytes, bytesToUnprefixedHex, createZeroAddress, @@ -22,7 +24,7 @@ import { EventEmitter } from 'eventemitter3' import { FORMAT } from './eof/constants.js' import { isEOF } from './eof/util.js' -import { ERROR, EvmError } from './exceptions.js' +import { EVMError, EVMErrorCode } from './errors.js' import { Interpreter } from './interpreter.js' import { Journal } from './journal.js' import { EVMPerformanceLogger } from './logger.js' @@ -443,7 +445,9 @@ export class EVM implements EVMInterface { createdAddress: message.to, execResult: { returnValue: new Uint8Array(0), - exceptionError: new EvmError(ERROR.INITCODE_SIZE_VIOLATION), + exceptionError: new EVMError({ + code: EVMErrorCode.INITCODE_SIZE_VIOLATION, + }), executionGasUsed: message.gasLimit, }, } @@ -502,7 +506,9 @@ export class EVM implements EVMInterface { createdAddress: message.to, execResult: { returnValue: new Uint8Array(0), - exceptionError: new EvmError(ERROR.CREATE_COLLISION), + exceptionError: new EVMError({ + code: EVMErrorCode.CREATE_COLLISION, + }), executionGasUsed: message.gasLimit, }, } @@ -803,8 +809,8 @@ export class EVM implements EVMInterface { let gasUsed = message.gasLimit - interpreterRes.runState!.gasLeft if (interpreterRes.exceptionError) { if ( - interpreterRes.exceptionError.error !== ERROR.REVERT && - interpreterRes.exceptionError.error !== ERROR.INVALID_EOF_FORMAT + interpreterRes.exceptionError.type.code !== EVMErrorCode.REVERT && + interpreterRes.exceptionError.type.code !== EVMErrorCode.INVALID_EOF_FORMAT ) { gasUsed = message.gasLimit } @@ -941,7 +947,7 @@ export class EVM implements EVMInterface { const { executionGasUsed, exceptionError, returnValue } = result.execResult debug( `Received message execResult: [ gasUsed=${executionGasUsed} exceptionError=${ - exceptionError ? `'${exceptionError.error}'` : 'none' + exceptionError ? `'${exceptionError.type.code}'` : 'none' } returnValue=${short(returnValue)} gasRefund=${result.execResult.gasRefund ?? 0} ]`, ) } @@ -951,14 +957,17 @@ export class EVM implements EVMInterface { // There is one exception: if the CODESTORE_OUT_OF_GAS error is thrown // (this only happens the Frontier/Chainstart fork) // then the error is dismissed - if (err && err.error !== ERROR.CODESTORE_OUT_OF_GAS) { + if (err && err.type.code !== EVMErrorCode.CODESTORE_OUT_OF_GAS) { result.execResult.selfdestruct = new Set() result.execResult.createdAddresses = new Set() result.execResult.gasRefund = BIGINT_0 } if ( err && - !(this.common.hardfork() === Hardfork.Chainstart && err.error === ERROR.CODESTORE_OUT_OF_GAS) + !( + this.common.hardfork() === Hardfork.Chainstart && + err.type.code === EVMErrorCode.CODESTORE_OUT_OF_GAS + ) ) { result.execResult.logs = [] await this.journal.revert() @@ -1028,7 +1037,9 @@ export class EVM implements EVMInterface { gasLimit: bigint, ): Promise | ExecResult { if (typeof code !== 'function') { - throw EthereumJSErrorUnsetCode('Invalid precompile') + throw new EVMError({ + code: EVMErrorCode.INVALID_PRECOMPILE, + }) } const opts = { @@ -1088,7 +1099,9 @@ export class EVM implements EVMInterface { protected async _reduceSenderBalance(account: Account, message: Message): Promise { account.balance -= message.value if (account.balance < BIGINT_0) { - throw new EvmError(ERROR.INSUFFICIENT_BALANCE) + throw new EVMError({ + code: EVMErrorCode.INSUFFICIENT_BALANCE, + }) } const result = this.journal.putAccount(message.caller, account) if (this.DEBUG) { @@ -1100,7 +1113,9 @@ export class EVM implements EVMInterface { protected async _addToBalance(toAccount: Account, message: MessageWithTo): Promise { const newBalance = toAccount.balance + message.value if (newBalance > MAX_INTEGER) { - throw new EvmError(ERROR.VALUE_OVERFLOW) + throw new EVMError({ + code: EVMErrorCode.VALUE_OVERFLOW, + }) } toAccount.balance = newBalance // putAccount as the nonce may have changed for contract creation @@ -1148,11 +1163,14 @@ export class EVM implements EVMInterface { } } +// TODO clean me up export function OOGResult(gasLimit: bigint): ExecResult { return { returnValue: new Uint8Array(0), executionGasUsed: gasLimit, - exceptionError: new EvmError(ERROR.OUT_OF_GAS), + exceptionError: new EVMError({ + code: EVMErrorCode.OUT_OF_GAS, + }), } } // CodeDeposit OOG Result @@ -1160,7 +1178,9 @@ export function COOGResult(gasUsedCreateCode: bigint): ExecResult { return { returnValue: new Uint8Array(0), executionGasUsed: gasUsedCreateCode, - exceptionError: new EvmError(ERROR.CODESTORE_OUT_OF_GAS), + exceptionError: new EVMError({ + code: EVMErrorCode.OUT_OF_GAS, + }), } } @@ -1168,7 +1188,9 @@ export function INVALID_BYTECODE_RESULT(gasLimit: bigint): ExecResult { return { returnValue: new Uint8Array(0), executionGasUsed: gasLimit, - exceptionError: new EvmError(ERROR.INVALID_BYTECODE_RESULT), + exceptionError: new EVMError({ + code: EVMErrorCode.INVALID_BYTECODE_RESULT, + }), } } @@ -1176,7 +1198,9 @@ export function INVALID_EOF_RESULT(gasLimit: bigint): ExecResult { return { returnValue: new Uint8Array(0), executionGasUsed: gasLimit, - exceptionError: new EvmError(ERROR.INVALID_EOF_FORMAT), + exceptionError: new EVMError({ + code: EVMErrorCode.INVALID_EOF_FORMAT, + }), } } @@ -1184,11 +1208,13 @@ export function CodesizeExceedsMaximumError(gasUsed: bigint): ExecResult { return { returnValue: new Uint8Array(0), executionGasUsed: gasUsed, - exceptionError: new EvmError(ERROR.CODESIZE_EXCEEDS_MAXIMUM), + exceptionError: new EVMError({ + code: EVMErrorCode.CODESIZE_EXCEEDS_MAXIMUM, + }), } } -export function EvmErrorResult(error: EvmError, gasUsed: bigint): ExecResult { +export function EvmErrorResult(error: EVMError, gasUsed: bigint): ExecResult { return { returnValue: new Uint8Array(0), executionGasUsed: gasUsed, diff --git a/packages/evm/src/index.ts b/packages/evm/src/index.ts index 17b280c15f5..02604c2bea4 100644 --- a/packages/evm/src/index.ts +++ b/packages/evm/src/index.ts @@ -1,6 +1,6 @@ import { EOFContainer, validateEOF } from './eof/container.js' +import { EVMError, EVMErrorCode } from './errors.js' import { EVM } from './evm.js' -import { ERROR as EVMErrorMessage, EvmError } from './exceptions.js' import { Message } from './message.js' import { getOpcodesForHF } from './opcodes/index.js' import { @@ -46,8 +46,8 @@ export type { export { EOFContainer, EVM, - EvmError, - EVMErrorMessage, + EVMError, + EVMErrorCode, EVMMockBlockchain, getActivePrecompiles, getOpcodesForHF, diff --git a/packages/evm/src/interpreter.ts b/packages/evm/src/interpreter.ts index 61568e13ea5..6fb3bd749ae 100644 --- a/packages/evm/src/interpreter.ts +++ b/packages/evm/src/interpreter.ts @@ -18,7 +18,7 @@ import { FORMAT, MAGIC, VERSION } from './eof/constants.js' import { EOFContainerMode, validateEOF } from './eof/container.js' import { setupEOF } from './eof/setup.js' import { ContainerSectionType } from './eof/verify.js' -import { ERROR, EvmError } from './exceptions.js' +import { EVMError, EVMErrorCode } from './errors.js' import { type EVMPerformanceLogger, type Timer } from './logger.js' import { Memory } from './memory.js' import { Message } from './message.js' @@ -111,7 +111,7 @@ export interface RunState { export interface InterpreterResult { runState: RunState - exceptionError?: EvmError + exceptionError?: EVMError } export interface InterpreterStep { @@ -218,14 +218,18 @@ export class Interpreter { // Bytecode contains invalid EOF magic byte return { runState: this._runState, - exceptionError: new EvmError(ERROR.INVALID_BYTECODE_RESULT), + exceptionError: new EVMError({ + code: EVMErrorCode.INVALID_BYTECODE_RESULT, + }), } } if (code[2] !== VERSION) { // Bytecode contains invalid EOF version number return { runState: this._runState, - exceptionError: new EvmError(ERROR.INVALID_EOF_FORMAT), + exceptionError: new EVMError({ + code: EVMErrorCode.INVALID_EOF_FORMAT, + }), } } this._runState.code = code @@ -238,7 +242,9 @@ export class Interpreter { } catch (e) { return { runState: this._runState, - exceptionError: new EvmError(ERROR.INVALID_EOF_FORMAT), // TODO: verify if all gas should be consumed + exceptionError: new EVMError({ + code: EVMErrorCode.INVALID_EOF_FORMAT, + }), // TODO: verify if all gas should be consumed } } @@ -255,7 +261,9 @@ export class Interpreter { // Trying to deploy an invalid EOF container return { runState: this._runState, - exceptionError: new EvmError(ERROR.INVALID_EOF_FORMAT), // TODO: verify if all gas should be consumed + exceptionError: new EVMError({ + code: EVMErrorCode.INVALID_EOF_FORMAT, + }), // TODO: verify if all gas should be consumed } } } @@ -334,12 +342,12 @@ export class Interpreter { if (overheadTimer !== undefined) { this.performanceLogger.unpauseTimer(overheadTimer) } - // re-throw on non-VM errors - if (!('errorType' in e && e.errorType === 'EvmError')) { + // re-throw on non-VM-runtime errors + if (!(e instanceof EVMError)) { throw e } // STOP is not an exception - if (e.error !== ERROR.STOP) { + if (e.type.code !== EVMErrorCode.STOP) { err = e } break @@ -399,7 +407,9 @@ export class Interpreter { // Check for invalid opcode if (opInfo.isInvalid) { - throw new EvmError(ERROR.INVALID_OPCODE) + throw new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }) } // Reduce opcode's base fee @@ -559,7 +569,7 @@ export class Interpreter { } if (this._runState.gasLeft < BIGINT_0) { this._runState.gasLeft = BIGINT_0 - trap(ERROR.OUT_OF_GAS) + trap(new EVMError({ code: EVMErrorCode.OUT_OF_GAS })) } } @@ -595,7 +605,11 @@ export class Interpreter { this._runState.gasRefund -= amount if (this._runState.gasRefund < BIGINT_0) { this._runState.gasRefund = BIGINT_0 - trap(ERROR.REFUND_EXHAUSTED) + trap( + new EVMError({ + code: EVMErrorCode.REFUND_EXHAUSTED, + }), + ) } } @@ -677,7 +691,7 @@ export class Interpreter { */ finish(returnData: Uint8Array): void { this._result.returnValue = returnData - trap(ERROR.STOP) + trap(new EVMError({ code: EVMErrorCode.STOP })) } /** @@ -687,7 +701,12 @@ export class Interpreter { */ revert(returnData: Uint8Array): void { this._result.returnValue = returnData - trap(ERROR.REVERT) + trap( + new EVMError({ + code: EVMErrorCode.REVERT, + revertBytes: returnData, + }), + ) } /** @@ -1012,7 +1031,7 @@ export class Interpreter { if ( results.execResult.returnValue !== undefined && (!results.execResult.exceptionError || - results.execResult.exceptionError.error === ERROR.REVERT) + results.execResult.exceptionError.type.code === EVMErrorCode.REVERT) ) { this._runState.returnBytes = results.execResult.returnValue } @@ -1113,14 +1132,14 @@ export class Interpreter { // Set return buffer in case revert happened if ( results.execResult.exceptionError && - results.execResult.exceptionError.error === ERROR.REVERT + results.execResult.exceptionError.type.code === EVMErrorCode.REVERT ) { this._runState.returnBytes = results.execResult.returnValue } if ( !results.execResult.exceptionError || - results.execResult.exceptionError.error === ERROR.CODESTORE_OUT_OF_GAS + results.execResult.exceptionError.type.code === EVMErrorCode.CODESTORE_OUT_OF_GAS ) { for (const addressToSelfdestructHex of selfdestruct) { this._result.selfdestruct.add(addressToSelfdestructHex) @@ -1226,7 +1245,7 @@ export class Interpreter { }) } - trap(ERROR.STOP) + trap(new EVMError({ code: EVMErrorCode.STOP })) } /** @@ -1234,11 +1253,15 @@ export class Interpreter { */ log(data: Uint8Array, numberOfTopics: number, topics: Uint8Array[]): void { if (numberOfTopics < 0 || numberOfTopics > 4) { - trap(ERROR.OUT_OF_RANGE) + trap(new EVMError({ code: EVMErrorCode.OUT_OF_GAS })) } if (topics.length !== numberOfTopics) { - trap(ERROR.INTERNAL_ERROR) + trap( + new EVMError({ + code: EVMErrorCode.INTERNAL_ERROR, + }), + ) } const log: Log = [this._env.address.bytes, topics, data] @@ -1255,7 +1278,7 @@ export class Interpreter { } else { // EOF mode, call was either EXTCALL / EXTDELEGATECALL / EXTSTATICCALL if (results.execResult.exceptionError !== undefined) { - if (results.execResult.exceptionError.error === ERROR.REVERT) { + if (results.execResult.exceptionError.type.code === EVMErrorCode.REVERT) { // Revert return BIGINT_1 } else { diff --git a/packages/evm/src/opcodes/EIP2200.ts b/packages/evm/src/opcodes/EIP2200.ts index 5f08305d093..f50d1bf0f2c 100644 --- a/packages/evm/src/opcodes/EIP2200.ts +++ b/packages/evm/src/opcodes/EIP2200.ts @@ -1,6 +1,6 @@ import { equalsBytes } from '@ethereumjs/util' -import { ERROR } from '../exceptions.js' +import { EVMError, EVMErrorCode } from '../errors.js' import { adjustSstoreGasEIP2929 } from './EIP2929.js' import { trap } from './util.js' @@ -27,7 +27,7 @@ export function updateSstoreGasEIP2200( ) { // Fail if not enough gas is left if (runState.interpreter.getGasLeft() <= common.param('sstoreSentryEIP2200Gas')) { - trap(ERROR.OUT_OF_GAS) + trap(new EVMError({ code: EVMErrorCode.OUT_OF_GAS })) } // Noop diff --git a/packages/evm/src/opcodes/functions.ts b/packages/evm/src/opcodes/functions.ts index 544c081aa1b..ffeed0c0d2c 100644 --- a/packages/evm/src/opcodes/functions.ts +++ b/packages/evm/src/opcodes/functions.ts @@ -31,7 +31,7 @@ import { keccak256 } from 'ethereum-cryptography/keccak.js' import { EOFContainer, EOFContainerMode } from '../eof/container.js' import { EOFError } from '../eof/errors.js' import { EOFBYTES, EOFHASH, isEOF } from '../eof/util.js' -import { ERROR } from '../exceptions.js' +import { EVMError, EVMErrorCode } from '../errors.js' import { createAddressFromStackBigInt, @@ -65,7 +65,7 @@ export const handlers: Map = new Map([ [ 0x00, function () { - trap(ERROR.STOP) + trap(new EVMError({ code: EVMErrorCode.STOP })) }, ], // 0x01: ADD @@ -810,13 +810,23 @@ export const handlers: Map = new Map([ function (runState) { const dest = runState.stack.pop() if (dest > runState.interpreter.getCodeSize()) { - trap(ERROR.INVALID_JUMP + ' at ' + describeLocation(runState)) + trap( + new EVMError( + { code: EVMErrorCode.INVALID_JUMP }, + EVMErrorCode.INVALID_JUMP + ' at ' + describeLocation(runState), + ), + ) } const destNum = Number(dest) if (!jumpIsValid(runState, destNum)) { - trap(ERROR.INVALID_JUMP + ' at ' + describeLocation(runState)) + trap( + new EVMError( + { code: EVMErrorCode.INVALID_JUMP }, + EVMErrorCode.INVALID_JUMP + ' at ' + describeLocation(runState), + ), + ) } runState.programCounter = destNum @@ -829,13 +839,23 @@ export const handlers: Map = new Map([ const [dest, cond] = runState.stack.popN(2) if (cond !== BIGINT_0) { if (dest > runState.interpreter.getCodeSize()) { - trap(ERROR.INVALID_JUMP + ' at ' + describeLocation(runState)) + trap( + new EVMError( + { code: EVMErrorCode.INVALID_JUMP }, + EVMErrorCode.INVALID_JUMP + ' at ' + describeLocation(runState), + ), + ) } const destNum = Number(dest) if (!jumpIsValid(runState, destNum)) { - trap(ERROR.INVALID_JUMP + ' at ' + describeLocation(runState)) + trap( + new EVMError( + { code: EVMErrorCode.INVALID_JUMP }, + EVMErrorCode.INVALID_JUMP + ' at ' + describeLocation(runState), + ), + ) } runState.programCounter = destNum @@ -882,7 +902,11 @@ export const handlers: Map = new Map([ function (runState) { // TSTORE if (runState.interpreter.isStatic()) { - trap(ERROR.STATIC_STATE_CHANGE) + trap( + new EVMError({ + code: EVMErrorCode.STATIC_STATE_CHANGE, + }), + ) } const [key, val] = runState.stack.popN(2) @@ -992,7 +1016,11 @@ export const handlers: Map = new Map([ function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } const pos = runState.stack.pop() if (pos > runState.env.eof!.container.body.dataSection.length) { @@ -1017,7 +1045,11 @@ export const handlers: Map = new Map([ function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } const toLoad = Number( bytesToBigInt(runState.code.subarray(runState.programCounter, runState.programCounter + 2)), @@ -1035,7 +1067,11 @@ export const handlers: Map = new Map([ function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } runState.stack.push(BigInt(runState.env.eof!.container.body.dataSection.length)) }, @@ -1046,7 +1082,11 @@ export const handlers: Map = new Map([ function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } const [memOffset, offset, size] = runState.stack.popN(3) if (size !== BIGINT_0) { @@ -1063,7 +1103,11 @@ export const handlers: Map = new Map([ function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } else { const code = runState.env.code const rjumpDest = new DataView(code.buffer).getInt16(runState.programCounter) @@ -1077,7 +1121,11 @@ export const handlers: Map = new Map([ function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } else { const cond = runState.stack.pop() // Move PC to the PC post instruction @@ -1097,7 +1145,11 @@ export const handlers: Map = new Map([ function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } else { const code = runState.env.code const jumptableEntries = code[runState.programCounter] @@ -1124,7 +1176,11 @@ export const handlers: Map = new Map([ function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } const sectionTarget = bytesToInt( runState.code.slice(runState.programCounter, runState.programCounter + 2), @@ -1132,10 +1188,10 @@ export const handlers: Map = new Map([ const stackItems = runState.stack.length const typeSection = runState.env.eof!.container.body.typeSections[sectionTarget] if (stackItems > 1024 - typeSection.maxStackHeight + typeSection.inputs) { - trap(EOFError.StackOverflow) + trap(new EVMError({ code: EOFError.StackOverflow })) } if (runState.env.eof!.eofRunState.returnStack.length >= 1024) { - trap(EOFError.ReturnStackOverflow) + trap(new EVMError({ code: EOFError.ReturnStackOverflow })) } runState.env.eof?.eofRunState.returnStack.push(runState.programCounter + 2) @@ -1149,12 +1205,16 @@ export const handlers: Map = new Map([ function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } const newPc = runState.env.eof!.eofRunState.returnStack.pop() if (newPc === undefined) { // This should NEVER happen since it is validated that functions either terminate (the call frame) or return - trap(EOFError.RetfNoReturn) + trap(new EVMError({ code: EOFError.RetfNoReturn })) } runState.programCounter = newPc! }, @@ -1165,7 +1225,11 @@ export const handlers: Map = new Map([ function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } // NOTE: (and also TODO) this code is exactly the same as CALLF, except pushing to the return stack is now skipped // (and also the return stack overflow check) @@ -1176,7 +1240,7 @@ export const handlers: Map = new Map([ const stackItems = runState.stack.length const typeSection = runState.env.eof!.container.body.typeSections[sectionTarget] if (stackItems > 1024 - typeSection.maxStackHeight + typeSection.inputs) { - trap(EOFError.StackOverflow) + trap(new EVMError({ code: EOFError.StackOverflow })) } /*if (runState.env.eof!.eofRunState.returnStack.length >= 1024) { trap(EOFError.ReturnStackOverflow) @@ -1193,7 +1257,11 @@ export const handlers: Map = new Map([ function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } const toDup = Number( @@ -1211,7 +1279,11 @@ export const handlers: Map = new Map([ function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } const toSwap = Number( @@ -1229,7 +1301,11 @@ export const handlers: Map = new Map([ function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } const toExchange = Number( bytesToBigInt(runState.code.subarray(runState.programCounter, runState.programCounter + 1)), @@ -1246,10 +1322,18 @@ export const handlers: Map = new Map([ async function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } else { if (runState.interpreter.isStatic()) { - trap(ERROR.STATIC_STATE_CHANGE) + trap( + new EVMError({ + code: EVMErrorCode.STATIC_STATE_CHANGE, + }), + ) } // Read container index const containerIndex = runState.env.code[runState.programCounter] @@ -1285,7 +1369,11 @@ export const handlers: Map = new Map([ async function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } else { // Read container index const containerIndex = runState.env.code[runState.programCounter] @@ -1307,13 +1395,21 @@ export const handlers: Map = new Map([ const actualSectionSize = preDeployDataSectionSize + Number(auxDataSize) if (actualSectionSize < originalDataSize) { - trap(EOFError.InvalidReturnContractDataSize) + trap( + new EVMError({ + code: EOFError.InvalidReturnContractDataSize, + }), + ) } if (actualSectionSize > 0xffff) { // Data section size is now larger than the max data section size // Temp: trap OOG? - trap(ERROR.OUT_OF_GAS) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } const newSize = setLengthLeft(bigIntToBytes(BigInt(actualSectionSize)), 2) @@ -1341,7 +1437,11 @@ export const handlers: Map = new Map([ length > Number(common.param('maxInitCodeSize')) && !runState.interpreter._evm.allowUnlimitedInitCodeSize ) { - trap(ERROR.INITCODE_SIZE_VIOLATION) + trap( + new EVMError({ + code: EVMErrorCode.INITCODE_SIZE_VIOLATION, + }), + ) } const gasLimit = runState.messageGasLimit! @@ -1367,7 +1467,11 @@ export const handlers: Map = new Map([ 0xf5, async function (runState, common) { if (runState.interpreter.isStatic()) { - trap(ERROR.STATIC_STATE_CHANGE) + trap( + new EVMError({ + code: EVMErrorCode.STATIC_STATE_CHANGE, + }), + ) } const [value, offset, length, salt] = runState.stack.popN(4) @@ -1377,7 +1481,11 @@ export const handlers: Map = new Map([ length > Number(common.param('maxInitCodeSize')) && !runState.interpreter._evm.allowUnlimitedInitCodeSize ) { - trap(ERROR.INITCODE_SIZE_VIOLATION) + trap( + new EVMError({ + code: EVMErrorCode.INITCODE_SIZE_VIOLATION, + }), + ) } const gasLimit = runState.messageGasLimit! @@ -1488,7 +1596,11 @@ export const handlers: Map = new Map([ function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } const pos = runState.stack.pop() if (pos > runState.interpreter.getReturnDataSize()) { @@ -1512,7 +1624,11 @@ export const handlers: Map = new Map([ async function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } else { const [toAddr, inOffset, inLength, value] = runState.stack.popN(4) @@ -1546,7 +1662,11 @@ export const handlers: Map = new Map([ async function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } else { const value = runState.interpreter.getCallValue() const [toAddr, inOffset, inLength] = runState.stack.popN(3) @@ -1611,7 +1731,11 @@ export const handlers: Map = new Map([ async function (runState) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.INVALID_OPCODE, + }), + ) } else { const value = BIGINT_0 const [toAddr, inOffset, inLength] = runState.stack.popN(3) diff --git a/packages/evm/src/opcodes/gas.ts b/packages/evm/src/opcodes/gas.ts index 48bcd14d2e2..e5c44346f65 100644 --- a/packages/evm/src/opcodes/gas.ts +++ b/packages/evm/src/opcodes/gas.ts @@ -12,7 +12,7 @@ import { } from '@ethereumjs/util' import { EOFError } from '../eof/errors.js' -import { ERROR } from '../exceptions.js' +import { EVMError, EVMErrorCode } from '../errors.js' import { DELEGATION_7702_FLAG } from '../types.js' import { updateSstoreGasEIP1283 } from './EIP1283.js' @@ -79,7 +79,11 @@ export const dynamicGasHandlers: Map 32) { - trap(ERROR.OUT_OF_RANGE) + trap( + new EVMError({ + code: EVMErrorCode.OUT_OF_RANGE, + }), + ) } const expPricePerByte = common.param('expByteGas') gas += BigInt(byteLength) * expPricePerByte @@ -241,7 +245,11 @@ export const dynamicGasHandlers: Map { if (runState.interpreter.isStatic()) { - trap(ERROR.STATIC_STATE_CHANGE) + trap( + new EVMError({ + code: EVMErrorCode.STATIC_STATE_CHANGE, + }), + ) } const [key, val] = runState.stack.peek(2) @@ -412,7 +424,11 @@ export const dynamicGasHandlers: Map { if (runState.interpreter.isStatic()) { - trap(ERROR.STATIC_STATE_CHANGE) + trap( + new EVMError({ + code: EVMErrorCode.STATIC_STATE_CHANGE, + }), + ) } const [memOffset, memLength] = runState.stack.peek(2) @@ -420,7 +436,11 @@ export const dynamicGasHandlers: Map 4) { - trap(ERROR.OUT_OF_RANGE) + trap( + new EVMError({ + code: EVMErrorCode.OUT_OF_RANGE, + }), + ) } gas += subMemUsage(runState, memOffset, memLength, common) @@ -435,7 +455,11 @@ export const dynamicGasHandlers: Map { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.STATIC_STATE_CHANGE, + }), + ) } // Note: TX_CREATE_COST is in the base fee (this is 32000 and same as CREATE / CREATE2) @@ -511,7 +539,11 @@ export const dynamicGasHandlers: Map { if (runState.interpreter.isStatic()) { - trap(ERROR.STATIC_STATE_CHANGE) + trap( + new EVMError({ + code: EVMErrorCode.STATIC_STATE_CHANGE, + }), + ) } const [_value, offset, length] = runState.stack.peek(3) @@ -546,7 +578,11 @@ export const dynamicGasHandlers: Map runState.interpreter.getGasLeft() - gas) { - trap(ERROR.OUT_OF_GAS) + trap( + new EVMError({ + code: EVMErrorCode.OUT_OF_GAS, + }), + ) } if (gas > runState.interpreter.getGasLeft()) { - trap(ERROR.OUT_OF_GAS) + trap( + new EVMError({ + code: EVMErrorCode.OUT_OF_GAS, + }), + ) } runState.messageGasLimit = gasLimit @@ -666,7 +710,11 @@ export const dynamicGasHandlers: Map runState.interpreter.getGasLeft() - gas) { - trap(ERROR.OUT_OF_GAS) + trap( + new EVMError({ + code: EVMErrorCode.OUT_OF_GAS, + }), + ) } runState.messageGasLimit = gasLimit @@ -726,7 +774,11 @@ export const dynamicGasHandlers: Map runState.interpreter.getGasLeft() - gas) { - trap(ERROR.OUT_OF_GAS) + trap( + new EVMError({ + code: EVMErrorCode.OUT_OF_GAS, + }), + ) } runState.messageGasLimit = gasLimit @@ -738,7 +790,11 @@ export const dynamicGasHandlers: Map { if (runState.interpreter.isStatic()) { - trap(ERROR.STATIC_STATE_CHANGE) + trap( + new EVMError({ + code: EVMErrorCode.STATIC_STATE_CHANGE, + }), + ) } const [_value, offset, length, _salt] = runState.stack.peek(4) @@ -771,7 +827,11 @@ export const dynamicGasHandlers: Map { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.STATIC_STATE_CHANGE, + }), + ) } // Charge WARM_STORAGE_READ_COST (100) -> done in accessAddressEIP2929 @@ -780,7 +840,11 @@ export const dynamicGasHandlers: Map 0, charge CALL_VALUE_COST @@ -790,7 +854,11 @@ export const dynamicGasHandlers: Map 20 bytes if (toAddr > EXTCALL_TARGET_MAX) { - trap(EOFError.InvalidExtcallTarget) + trap( + new EVMError({ + code: EOFError.InvalidExtcallTarget, + }), + ) } // Charge for memory expansion @@ -848,7 +916,11 @@ export const dynamicGasHandlers: Map { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.STATIC_STATE_CHANGE, + }), + ) } // Charge WARM_STORAGE_READ_COST (100) -> done in accessAddressEIP2929 @@ -857,7 +929,11 @@ export const dynamicGasHandlers: Map 20 bytes if (toAddr > EXTCALL_TARGET_MAX) { - trap(EOFError.InvalidExtcallTarget) + trap( + new EVMError({ + code: EOFError.InvalidExtcallTarget, + }), + ) } // Charge for memory expansion @@ -952,7 +1028,11 @@ export const dynamicGasHandlers: Map { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts - trap(ERROR.INVALID_OPCODE) + trap( + new EVMError({ + code: EVMErrorCode.STATIC_STATE_CHANGE, + }), + ) } // Charge WARM_STORAGE_READ_COST (100) -> done in accessAddressEIP2929 @@ -961,7 +1041,11 @@ export const dynamicGasHandlers: Map 20 bytes if (toAddr > EXTCALL_TARGET_MAX) { - trap(EOFError.InvalidExtcallTarget) + trap( + new EVMError({ + code: EOFError.InvalidExtcallTarget, + }), + ) } // Charge for memory expansion @@ -1013,7 +1097,11 @@ export const dynamicGasHandlers: Map { if (runState.interpreter.isStatic()) { - trap(ERROR.STATIC_STATE_CHANGE) + trap( + new EVMError({ + code: EVMErrorCode.STATIC_STATE_CHANGE, + }), + ) } const selfdestructToaddressBigInt = runState.stack.peek()[0] diff --git a/packages/evm/src/opcodes/util.ts b/packages/evm/src/opcodes/util.ts index a4206f32f80..bfd278c08b9 100644 --- a/packages/evm/src/opcodes/util.ts +++ b/packages/evm/src/opcodes/util.ts @@ -15,9 +15,7 @@ import { } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak.js' -import { EvmError } from '../exceptions.js' - -import type { ERROR } from '../exceptions.js' +import type { EVMError } from '../errors.js' import type { RunState } from '../interpreter.js' import type { Common } from '@ethereumjs/common' import type { Address } from '@ethereumjs/util' @@ -87,9 +85,10 @@ export function setLengthLeftStorage(value: Uint8Array) { /** * Wraps error message as EvmError */ -export function trap(err: string) { +export function trap(err: EVMError) { + // TODO: accept EVMError constructor args instead of the error // TODO: facilitate extra data along with errors - throw new EvmError(err as ERROR) + throw err } /** diff --git a/packages/evm/src/precompiles/08-bn254-pairing.ts b/packages/evm/src/precompiles/08-bn254-pairing.ts index 2d1e67ed3a0..cd6e9498af6 100644 --- a/packages/evm/src/precompiles/08-bn254-pairing.ts +++ b/packages/evm/src/precompiles/08-bn254-pairing.ts @@ -1,7 +1,7 @@ import { bytesToHex } from '@ethereumjs/util' +import { EVMError, EVMErrorCode } from '../errors.js' import { EvmErrorResult, OOGResult } from '../evm.js' -import { ERROR, EvmError } from '../exceptions.js' import { gasLimitCheck, moduloLengthCheck } from './util.js' @@ -14,7 +14,12 @@ import type { PrecompileInput } from './types.js' export function precompile08(opts: PrecompileInput): ExecResult { const pName = getPrecompileName('08') if (!moduloLengthCheck(opts, 192, pName)) { - return EvmErrorResult(new EvmError(ERROR.INVALID_INPUT_LENGTH), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.INVALID_INPUT_LENGTH, + }), + opts.gasLimit, + ) } const inputDataSize = BigInt(Math.floor(opts.data.length / 192)) diff --git a/packages/evm/src/precompiles/09-blake2f.ts b/packages/evm/src/precompiles/09-blake2f.ts index 2cc21f6350c..e2f99a2ce64 100644 --- a/packages/evm/src/precompiles/09-blake2f.ts +++ b/packages/evm/src/precompiles/09-blake2f.ts @@ -1,7 +1,7 @@ import { bytesToHex } from '@ethereumjs/util' +import { EVMError, EVMErrorCode } from '../errors.js' import { OOGResult } from '../evm.js' -import { ERROR, EvmError } from '../exceptions.js' import { gasLimitCheck } from './util.js' @@ -182,7 +182,9 @@ export function precompile09(opts: PrecompileInput): ExecResult { return { returnValue: new Uint8Array(0), executionGasUsed: opts.gasLimit, - exceptionError: new EvmError(ERROR.OUT_OF_RANGE), + exceptionError: new EVMError({ + code: EVMErrorCode.OUT_OF_RANGE, + }), } } const lastByte = data.subarray(212, 213)[0] @@ -193,7 +195,9 @@ export function precompile09(opts: PrecompileInput): ExecResult { return { returnValue: new Uint8Array(0), executionGasUsed: opts.gasLimit, - exceptionError: new EvmError(ERROR.OUT_OF_RANGE), + exceptionError: new EVMError({ + code: EVMErrorCode.OUT_OF_RANGE, + }), } } diff --git a/packages/evm/src/precompiles/0a-kzg-point-evaluation.ts b/packages/evm/src/precompiles/0a-kzg-point-evaluation.ts index ff35a1033ae..6dda32ed349 100644 --- a/packages/evm/src/precompiles/0a-kzg-point-evaluation.ts +++ b/packages/evm/src/precompiles/0a-kzg-point-evaluation.ts @@ -7,8 +7,8 @@ import { setLengthLeft, } from '@ethereumjs/util' +import { EVMError, EVMErrorCode } from '../errors.js' import { EvmErrorResult, OOGResult } from '../evm.js' -import { ERROR, EvmError } from '../exceptions.js' import { gasLimitCheck } from './util.js' @@ -34,7 +34,12 @@ export async function precompile0a(opts: PrecompileInput): Promise { } if (opts.data.length !== 192) { - return EvmErrorResult(new EvmError(ERROR.INVALID_INPUT_LENGTH), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.INVALID_INPUT_LENGTH, + }), + opts.gasLimit, + ) } const version = Number(opts.common.param('blobCommitmentVersionKzg')) @@ -49,7 +54,12 @@ export async function precompile0a(opts: PrecompileInput): Promise { if (opts._debug !== undefined) { opts._debug(`${pName} failed: INVALID_COMMITMENT`) } - return EvmErrorResult(new EvmError(ERROR.INVALID_COMMITMENT), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.INVALID_COMMITMENT, + }), + opts.gasLimit, + ) } if (opts._debug !== undefined) { @@ -62,19 +72,34 @@ export async function precompile0a(opts: PrecompileInput): Promise { try { const res = opts.common.customCrypto?.kzg?.verifyProof(commitment, z, y, kzgProof) if (res === false) { - return EvmErrorResult(new EvmError(ERROR.INVALID_PROOF), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.INVALID_PROOF, + }), + opts.gasLimit, + ) } } catch (err: any) { if (err.message.includes('C_KZG_BADARGS') === true) { if (opts._debug !== undefined) { opts._debug(`${pName} failed: INVALID_INPUTS`) } - return EvmErrorResult(new EvmError(ERROR.INVALID_INPUTS), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.INVALID_INPUTS, + }), + opts.gasLimit, + ) } if (opts._debug !== undefined) { opts._debug(`${pName} failed: Unknown error - ${err.message}`) } - return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.INTERNAL_ERROR, + }), + opts.gasLimit, + ) } // Return value - FIELD_ELEMENTS_PER_BLOB and BLS_MODULUS as padded 32 byte big endian values diff --git a/packages/evm/src/precompiles/0b-bls12-g1add.ts b/packages/evm/src/precompiles/0b-bls12-g1add.ts index cc3b6c5e5a1..3d6750a2e14 100644 --- a/packages/evm/src/precompiles/0b-bls12-g1add.ts +++ b/packages/evm/src/precompiles/0b-bls12-g1add.ts @@ -1,7 +1,7 @@ import { bytesToHex } from '@ethereumjs/util' +import { EVMError, EVMErrorCode } from '../errors.js' import { EvmErrorResult, OOGResult } from '../evm.js' -import { ERROR, EvmError } from '../exceptions.js' import { leading16ZeroBytesCheck } from './bls12_381/index.js' import { equalityLengthCheck, gasLimitCheck } from './util.js' @@ -22,7 +22,12 @@ export async function precompile0b(opts: PrecompileInput): Promise { } if (!equalityLengthCheck(opts, 256, pName)) { - return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INVALID_INPUT_LENGTH), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.BLS_12_381_INVALID_INPUT_LENGTH, + }), + opts.gasLimit, + ) } // check if some parts of input are zero bytes. @@ -33,7 +38,12 @@ export async function precompile0b(opts: PrecompileInput): Promise { [192, 208], ] if (!leading16ZeroBytesCheck(opts, zeroByteRanges, pName)) { - return EvmErrorResult(new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.BLS_12_381_POINT_NOT_ON_CURVE, + }), + opts.gasLimit, + ) } let returnValue diff --git a/packages/evm/src/precompiles/0c-bls12-g1msm.ts b/packages/evm/src/precompiles/0c-bls12-g1msm.ts index 4d1e2daafc4..5f268b04808 100644 --- a/packages/evm/src/precompiles/0c-bls12-g1msm.ts +++ b/packages/evm/src/precompiles/0c-bls12-g1msm.ts @@ -1,7 +1,7 @@ import { bytesToHex } from '@ethereumjs/util' +import { EVMError, EVMErrorCode } from '../errors.js' import { EvmErrorResult, OOGResult } from '../evm.js' -import { ERROR, EvmError } from '../exceptions.js' import { BLS_GAS_DISCOUNT_PAIRS_G1, @@ -25,7 +25,12 @@ export async function precompile0c(opts: PrecompileInput): Promise { if (opts._debug !== undefined) { opts._debug(`${pName} failed: Empty input`) } - return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INPUT_EMPTY), opts.gasLimit) // follow Geth's implementation + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.BLS_12_381_INPUT_EMPTY, + }), + opts.gasLimit, + ) // follow Geth's implementation } // TODO: Double-check respectively confirm that this order is really correct that the gas check @@ -43,10 +48,20 @@ export async function precompile0c(opts: PrecompileInput): Promise { if (opts._debug !== undefined) { opts._debug(`${pName} failed: Invalid input length length=${inputData.length}`) } - return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INVALID_INPUT_LENGTH), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.BLS_12_381_POINT_NOT_ON_CURVE, + }), + opts.gasLimit, + ) } if (!moduloLengthCheck(opts, 160, pName)) { - return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INVALID_INPUT_LENGTH), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.BLS_12_381_POINT_NOT_ON_CURVE, + }), + opts.gasLimit, + ) } // prepare pairing list and check for mandatory zero bytes @@ -59,7 +74,12 @@ export async function precompile0c(opts: PrecompileInput): Promise { // zero bytes check const pairStart = 160 * k if (!leading16ZeroBytesCheck(opts, zeroByteRanges, pName, pairStart)) { - return EvmErrorResult(new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.BLS_12_381_POINT_NOT_ON_CURVE, + }), + opts.gasLimit, + ) } } diff --git a/packages/evm/src/precompiles/0d-bls12-g2add.ts b/packages/evm/src/precompiles/0d-bls12-g2add.ts index 5ab25b8da0c..a93e1c7bcd9 100644 --- a/packages/evm/src/precompiles/0d-bls12-g2add.ts +++ b/packages/evm/src/precompiles/0d-bls12-g2add.ts @@ -1,7 +1,7 @@ import { bytesToHex } from '@ethereumjs/util' +import { EVMError, EVMErrorCode } from '../errors.js' import { EvmErrorResult, OOGResult } from '../evm.js' -import { ERROR, EvmError } from '../exceptions.js' import { leading16ZeroBytesCheck } from './bls12_381/index.js' import { equalityLengthCheck, gasLimitCheck } from './util.js' @@ -22,7 +22,12 @@ export async function precompile0d(opts: PrecompileInput): Promise { } if (!equalityLengthCheck(opts, 512, pName)) { - return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INVALID_INPUT_LENGTH), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.BLS_12_381_INVALID_INPUT_LENGTH, + }), + opts.gasLimit, + ) } // check if some parts of input are zero bytes. @@ -37,7 +42,12 @@ export async function precompile0d(opts: PrecompileInput): Promise { [448, 464], ] if (!leading16ZeroBytesCheck(opts, zeroByteRanges, pName)) { - return EvmErrorResult(new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.BLS_12_381_POINT_NOT_ON_CURVE, + }), + opts.gasLimit, + ) } // TODO: verify that point is on G2 diff --git a/packages/evm/src/precompiles/0e-bls12-g2msm.ts b/packages/evm/src/precompiles/0e-bls12-g2msm.ts index 9390aa629ee..9aed6396855 100644 --- a/packages/evm/src/precompiles/0e-bls12-g2msm.ts +++ b/packages/evm/src/precompiles/0e-bls12-g2msm.ts @@ -1,7 +1,7 @@ import { bytesToHex } from '@ethereumjs/util' +import { EVMError, EVMErrorCode } from '../errors.js' import { EvmErrorResult, OOGResult } from '../evm.js' -import { ERROR, EvmError } from '../exceptions.js' import { BLS_GAS_DISCOUNT_PAIRS_G2, @@ -23,7 +23,12 @@ export async function precompile0e(opts: PrecompileInput): Promise { if (opts._debug !== undefined) { opts._debug(`${pName} failed: Empty input`) } - return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INPUT_EMPTY), opts.gasLimit) // follow Geth's implementation + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.BLS_12_381_INPUT_EMPTY, + }), + opts.gasLimit, + ) // follow Geth's implementation } const numPairs = Math.floor(opts.data.length / 288) @@ -35,7 +40,12 @@ export async function precompile0e(opts: PrecompileInput): Promise { } if (!moduloLengthCheck(opts, 288, pName)) { - return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INVALID_INPUT_LENGTH), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.BLS_12_381_INVALID_INPUT_LENGTH, + }), + opts.gasLimit, + ) } // prepare pairing list and check for mandatory zero bytes @@ -50,7 +60,12 @@ export async function precompile0e(opts: PrecompileInput): Promise { // zero bytes check const pairStart = 288 * k if (!leading16ZeroBytesCheck(opts, zeroByteRanges, pName, pairStart)) { - return EvmErrorResult(new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.BLS_12_381_POINT_NOT_ON_CURVE, + }), + opts.gasLimit, + ) } } diff --git a/packages/evm/src/precompiles/0f-bls12-pairing.ts b/packages/evm/src/precompiles/0f-bls12-pairing.ts index 6b454c0be13..015a3fc2aaa 100644 --- a/packages/evm/src/precompiles/0f-bls12-pairing.ts +++ b/packages/evm/src/precompiles/0f-bls12-pairing.ts @@ -1,7 +1,7 @@ import { bytesToHex } from '@ethereumjs/util' +import { EVMError, EVMErrorCode } from '../errors.js' import { EvmErrorResult, OOGResult } from '../evm.js' -import { ERROR, EvmError } from '../exceptions.js' import { leading16ZeroBytesCheck } from './bls12_381/index.js' import { gasLimitCheck, moduloLengthCheck } from './util.js' @@ -22,7 +22,12 @@ export async function precompile0f(opts: PrecompileInput): Promise { if (opts._debug !== undefined) { opts._debug(`${pName} failed: Empty input`) } - return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INPUT_EMPTY), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.BLS_12_381_INPUT_EMPTY, + }), + opts.gasLimit, + ) } const gasUsedPerPair = opts.common.param('bls12381PairingPerPairGas') ?? BigInt(0) @@ -31,7 +36,12 @@ export async function precompile0f(opts: PrecompileInput): Promise { // gas check. I will keep it there to not side-change the existing implementation, but we should // check (respectively Jochem can maybe have a word) if this is something intended or not if (!moduloLengthCheck(opts, 384, pName)) { - return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INVALID_INPUT_LENGTH), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.BLS_12_381_INVALID_INPUT_LENGTH, + }), + opts.gasLimit, + ) } const gasUsed = baseGas + gasUsedPerPair * BigInt(Math.floor(opts.data.length / 384)) @@ -52,7 +62,12 @@ export async function precompile0f(opts: PrecompileInput): Promise { // zero bytes check const pairStart = 384 * k if (!leading16ZeroBytesCheck(opts, zeroByteRanges, pName, pairStart)) { - return EvmErrorResult(new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.BLS_12_381_POINT_NOT_ON_CURVE, + }), + opts.gasLimit, + ) } } diff --git a/packages/evm/src/precompiles/10-bls12-map-fp-to-g1.ts b/packages/evm/src/precompiles/10-bls12-map-fp-to-g1.ts index 9a1bd2368b2..20004d2408f 100644 --- a/packages/evm/src/precompiles/10-bls12-map-fp-to-g1.ts +++ b/packages/evm/src/precompiles/10-bls12-map-fp-to-g1.ts @@ -1,7 +1,7 @@ import { bytesToHex } from '@ethereumjs/util' +import { EVMError, EVMErrorCode } from '../errors.js' import { EvmErrorResult, OOGResult } from '../evm.js' -import { ERROR, EvmError } from '../exceptions.js' import { leading16ZeroBytesCheck } from './bls12_381/index.js' import { equalityLengthCheck, gasLimitCheck } from './util.js' @@ -22,13 +22,23 @@ export async function precompile10(opts: PrecompileInput): Promise { } if (!equalityLengthCheck(opts, 64, pName)) { - return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INVALID_INPUT_LENGTH), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.BLS_12_381_INVALID_INPUT_LENGTH, + }), + opts.gasLimit, + ) } // check if some parts of input are zero bytes. const zeroByteRanges = [[0, 16]] if (!leading16ZeroBytesCheck(opts, zeroByteRanges, pName)) { - return EvmErrorResult(new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.BLS_12_381_POINT_NOT_ON_CURVE, + }), + opts.gasLimit, + ) } let returnValue diff --git a/packages/evm/src/precompiles/11-bls12-map-fp2-to-g2.ts b/packages/evm/src/precompiles/11-bls12-map-fp2-to-g2.ts index b2fb36eb21a..93bed970274 100644 --- a/packages/evm/src/precompiles/11-bls12-map-fp2-to-g2.ts +++ b/packages/evm/src/precompiles/11-bls12-map-fp2-to-g2.ts @@ -1,7 +1,7 @@ import { bytesToHex } from '@ethereumjs/util' +import { EVMError, EVMErrorCode } from '../errors.js' import { EvmErrorResult, OOGResult } from '../evm.js' -import { ERROR, EvmError } from '../exceptions.js' import { leading16ZeroBytesCheck } from './bls12_381/index.js' import { equalityLengthCheck, gasLimitCheck } from './util.js' @@ -22,7 +22,12 @@ export async function precompile11(opts: PrecompileInput): Promise { } if (!equalityLengthCheck(opts, 128, pName)) { - return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INVALID_INPUT_LENGTH), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.INVALID_INPUT_LENGTH, + }), + opts.gasLimit, + ) } // check if some parts of input are zero bytes. @@ -31,7 +36,12 @@ export async function precompile11(opts: PrecompileInput): Promise { [64, 80], ] if (!leading16ZeroBytesCheck(opts, zeroByteRanges, pName)) { - return EvmErrorResult(new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE), opts.gasLimit) + return EvmErrorResult( + new EVMError({ + code: EVMErrorCode.BLS_12_381_POINT_NOT_ON_CURVE, + }), + opts.gasLimit, + ) } let returnValue diff --git a/packages/evm/src/precompiles/bls12_381/mcl.ts b/packages/evm/src/precompiles/bls12_381/mcl.ts index 5fe748f9e33..65632d2c625 100644 --- a/packages/evm/src/precompiles/bls12_381/mcl.ts +++ b/packages/evm/src/precompiles/bls12_381/mcl.ts @@ -7,7 +7,7 @@ import { unprefixedHexToBytes, } from '@ethereumjs/util' -import { ERROR, EvmError } from '../../exceptions.js' +import { EVMError, EVMErrorCode } from '../../errors.js' import { BLS_FIELD_MODULUS, @@ -53,12 +53,16 @@ function BLS12_381_ToG1Point(input: Uint8Array, mcl: any, verifyOrder = true): a mcl.verifyOrderG1(verifyOrder) if (verifyOrder && G1.isValidOrder() === false) { - throw new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE) + throw new EVMError({ + code: EVMErrorCode.BLS_12_381_POINT_NOT_ON_CURVE, + }) } // Check if these coordinates are actually on the curve. if (G1.isValid() === false) { - throw new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE) + throw new EVMError({ + code: EVMErrorCode.BLS_12_381_POINT_NOT_ON_CURVE, + }) } return G1 @@ -87,10 +91,14 @@ function BLS12_381_FromG1Point(input: any): Uint8Array { function BLS12_381_ToFp2Point(fpXCoordinate: Uint8Array, fpYCoordinate: Uint8Array, mcl: any): any { // check if the coordinates are in the field if (bytesToBigInt(fpXCoordinate) >= BLS_FIELD_MODULUS) { - throw new EvmError(ERROR.BLS_12_381_FP_NOT_IN_FIELD) + throw new EVMError({ + code: EVMErrorCode.BLS_12_381_FP_NOT_IN_FIELD, + }) } if (bytesToBigInt(fpYCoordinate) >= BLS_FIELD_MODULUS) { - throw new EvmError(ERROR.BLS_12_381_FP_NOT_IN_FIELD) + throw new EVMError({ + code: EVMErrorCode.BLS_12_381_FP_NOT_IN_FIELD, + }) } const fp_x = new mcl.Fp() @@ -146,11 +154,15 @@ function BLS12_381_ToG2Point(input: Uint8Array, mcl: any, verifyOrder = true): a mcl.verifyOrderG2(verifyOrder) if (verifyOrder && p.isValidOrder() === false) { - throw new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE) + throw new EVMError({ + code: EVMErrorCode.BLS_12_381_POINT_NOT_ON_CURVE, + }) } if (p.isValid() === false) { - throw new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE) + throw new EVMError({ + code: EVMErrorCode.BLS_12_381_POINT_NOT_ON_CURVE, + }) } return p @@ -190,7 +202,9 @@ function BLS12_381_ToFrPoint(input: Uint8Array, mcl: any): any { function BLS12_381_ToFpPoint(fpCoordinate: Uint8Array, mcl: any): any { // check if point is in field if (bytesToBigInt(fpCoordinate) >= BLS_FIELD_MODULUS) { - throw new EvmError(ERROR.BLS_12_381_FP_NOT_IN_FIELD) + throw new EVMError({ + code: EVMErrorCode.BLS_12_381_FP_NOT_IN_FIELD, + }) } const fp = new mcl.Fp() diff --git a/packages/evm/src/precompiles/bls12_381/noble.ts b/packages/evm/src/precompiles/bls12_381/noble.ts index 5868dee71c4..7893d188080 100644 --- a/packages/evm/src/precompiles/bls12_381/noble.ts +++ b/packages/evm/src/precompiles/bls12_381/noble.ts @@ -8,7 +8,7 @@ import { } from '@ethereumjs/util' import { bls12_381 } from '@noble/curves/bls12-381' -import { ERROR, EvmError } from '../../exceptions.js' +import { EVMError, EVMErrorCode } from '../../errors.js' import { BLS_FIELD_MODULUS, @@ -28,12 +28,17 @@ const G1_ZERO = bls12_381.G1.ProjectivePoint.ZERO const G2_ZERO = bls12_381.G2.ProjectivePoint.ZERO function BLS12_381_ToFp2Point(fpXCoordinate: Uint8Array, fpYCoordinate: Uint8Array) { + // check if the coordinates are in the field // check if the coordinates are in the field if (bytesToBigInt(fpXCoordinate) >= BLS_FIELD_MODULUS) { - throw new EvmError(ERROR.BLS_12_381_FP_NOT_IN_FIELD) + throw new EVMError({ + code: EVMErrorCode.BLS_12_381_FP_NOT_IN_FIELD, + }) } if (bytesToBigInt(fpYCoordinate) >= BLS_FIELD_MODULUS) { - throw new EvmError(ERROR.BLS_12_381_FP_NOT_IN_FIELD) + throw new EVMError({ + code: EVMErrorCode.BLS_12_381_FP_NOT_IN_FIELD, + }) } const fpBytes = concatBytes(fpXCoordinate.subarray(16), fpYCoordinate.subarray(16)) @@ -65,7 +70,9 @@ function BLS12_381_ToG1Point(input: Uint8Array, verifyOrder = true) { G1.assertValidity() } catch (e) { if (verifyOrder || (e as Error).message !== 'bad point: not in prime-order subgroup') - throw new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE) + throw new EVMError({ + code: EVMErrorCode.BLS_12_381_POINT_NOT_ON_CURVE, + }) } return G1 @@ -108,7 +115,9 @@ function BLS12_381_ToG2Point(input: Uint8Array, verifyOrder = true) { pG2.assertValidity() } catch (e) { if (verifyOrder || (e as Error).message !== 'bad point: not in prime-order subgroup') - throw new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE) + throw new EVMError({ + code: EVMErrorCode.BLS_12_381_POINT_NOT_ON_CURVE, + }) } return pG2 @@ -159,7 +168,9 @@ function BLS12_381_ToFrPoint(input: Uint8Array): bigint { function BLS12_381_ToFpPoint(fpCoordinate: Uint8Array) { // check if point is in field if (bytesToBigInt(fpCoordinate) >= BLS_FIELD_MODULUS) { - throw new EvmError(ERROR.BLS_12_381_FP_NOT_IN_FIELD) + throw new EVMError({ + code: EVMErrorCode.BLS_12_381_FP_NOT_IN_FIELD, + }) } const FP = bls12_381.fields.Fp.fromBytes(fpCoordinate.slice(16)) return FP diff --git a/packages/evm/src/precompiles/bn254/noble.ts b/packages/evm/src/precompiles/bn254/noble.ts index 9f441ea8999..c3b72a3576f 100644 --- a/packages/evm/src/precompiles/bn254/noble.ts +++ b/packages/evm/src/precompiles/bn254/noble.ts @@ -9,7 +9,7 @@ import { } from '@ethereumjs/util' import { bn254 } from '@noble/curves/bn254' -import { ERROR, EvmError } from '../../exceptions.js' +import { EVMError, EVMErrorCode } from '../../errors.js' import type { EVMBN254Interface } from '../../types.js' import type { AffinePoint } from '@noble/curves/abstract/weierstrass' @@ -64,10 +64,14 @@ function toFrPoint(input: Uint8Array): bigint { function toFp2Point(fpXCoordinate: Uint8Array, fpYCoordinate: Uint8Array) { if (bytesToBigInt(fpXCoordinate) >= bn254.fields.Fp2.ORDER) { - throw new EvmError(ERROR.BN254_FP_NOT_IN_FIELD) + throw new EVMError({ + code: EVMErrorCode.BN254_FP_NOT_IN_FIELD, + }) } if (bytesToBigInt(fpYCoordinate) >= bn254.fields.Fp2.ORDER) { - throw new EvmError(ERROR.BN254_FP_NOT_IN_FIELD) + throw new EVMError({ + code: EVMErrorCode.BN254_FP_NOT_IN_FIELD, + }) } const fpBytes = concatBytes(fpXCoordinate, fpYCoordinate) @@ -96,7 +100,9 @@ function toG2Point(input: Uint8Array) { for (const p of [p_x_1, p_x_2, p_y_1, p_y_2]) { const pB = bytesToBigInt(p) if (bn254.fields.Fp.create(pB) !== pB) { - throw new EvmError(ERROR.BN254_FP_NOT_IN_FIELD) + throw new EVMError({ + code: EVMErrorCode.BN254_FP_NOT_IN_FIELD, + }) } } diff --git a/packages/evm/src/stack.ts b/packages/evm/src/stack.ts index d8ff351cb24..75520a67cf4 100644 --- a/packages/evm/src/stack.ts +++ b/packages/evm/src/stack.ts @@ -1,4 +1,4 @@ -import { ERROR, EvmError } from './exceptions.js' +import { EVMError, EVMErrorCode } from './errors.js' /** * Implementation of the stack used in evm. @@ -23,7 +23,9 @@ export class Stack { push(value: bigint) { if (this._len >= this._maxHeight) { - throw new EvmError(ERROR.STACK_OVERFLOW) + throw new EVMError({ + code: EVMErrorCode.STACK_OVERFLOW, + }) } // Read current length, set `_store` to value, and then increase the length @@ -32,7 +34,9 @@ export class Stack { pop(): bigint { if (this._len < 1) { - throw new EvmError(ERROR.STACK_UNDERFLOW) + throw new EVMError({ + code: EVMErrorCode.STACK_UNDERFLOW, + }) } // Length is checked above, so pop shouldn't return undefined @@ -49,7 +53,9 @@ export class Stack { */ popN(num: number = 1): bigint[] { if (this._len < num) { - throw new EvmError(ERROR.STACK_UNDERFLOW) + throw new EVMError({ + code: EVMErrorCode.STACK_UNDERFLOW, + }) } if (num === 0) { @@ -79,7 +85,9 @@ export class Stack { for (let peek = 0; peek < num; peek++) { const index = --start if (index < 0) { - throw new EvmError(ERROR.STACK_UNDERFLOW) + throw new EVMError({ + code: EVMErrorCode.STACK_UNDERFLOW, + }) } peekArray[peek] = this._store[index] } @@ -92,7 +100,9 @@ export class Stack { */ swap(position: number) { if (this._len <= position) { - throw new EvmError(ERROR.STACK_UNDERFLOW) + throw new EVMError({ + code: EVMErrorCode.STACK_UNDERFLOW, + }) } const head = this._len - 1 @@ -115,12 +125,16 @@ export class Stack { dup(position: number) { const len = this._len if (len < position) { - throw new EvmError(ERROR.STACK_UNDERFLOW) + throw new EVMError({ + code: EVMErrorCode.STACK_UNDERFLOW, + }) } // Note: this code is borrowed from `push()` (avoids a call) if (len >= this._maxHeight) { - throw new EvmError(ERROR.STACK_OVERFLOW) + throw new EVMError({ + code: EVMErrorCode.STACK_OVERFLOW, + }) } const i = len - position @@ -139,7 +153,9 @@ export class Stack { // Stack underflow is not possible in EOF if (exchangeIndex1 < 0 || exchangeIndex2 < 0) { - throw new EvmError(ERROR.STACK_UNDERFLOW) + throw new EVMError({ + code: EVMErrorCode.STACK_UNDERFLOW, + }) } const cache = this._store[exchangeIndex2] diff --git a/packages/evm/src/types.ts b/packages/evm/src/types.ts index 8d5e7abf494..1ec8b8c12cb 100644 --- a/packages/evm/src/types.ts +++ b/packages/evm/src/types.ts @@ -1,5 +1,5 @@ import type { EOFContainer } from './eof/container.js' -import type { EvmError } from './exceptions.js' +import type { EVMError } from './errors.js' import type { InterpreterStep, RunState } from './interpreter.js' import type { Message } from './message.js' import type { AsyncDynamicGasHandler, SyncDynamicGasHandler } from './opcodes/gas.js' @@ -388,7 +388,7 @@ export interface ExecResult { /** * Description of the exception, if any occurred */ - exceptionError?: EvmError + exceptionError?: EVMError /** * Amount of gas left */ diff --git a/packages/util/src/index.ts b/packages/util/src/index.ts index 234667b00ca..9444878875b 100644 --- a/packages/util/src/index.ts +++ b/packages/util/src/index.ts @@ -33,6 +33,11 @@ export * from './db.js' */ export * from './withdrawal.js' +/** + * EthereumJS Extended Errors + */ +export * from './errors.js' + /** * ECDSA signature */ diff --git a/packages/vm/src/runTx.ts b/packages/vm/src/runTx.ts index d300d73a8ef..4e54d8406a0 100644 --- a/packages/vm/src/runTx.ts +++ b/packages/vm/src/runTx.ts @@ -609,7 +609,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { debug('-'.repeat(100)) debug( `Received tx execResult: [ executionGasUsed=${executionGasUsed} exceptionError=${ - exceptionError !== undefined ? `'${exceptionError.error}'` : 'none' + exceptionError !== undefined ? `'${exceptionError.type.code}'` : 'none' } returnValue=${short(returnValue)} gasRefund=${results.gasRefund ?? 0} ]`, ) } From 9a1b4bf9bc25e9a6f649b1d86cd879ea6e793da6 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 24 Feb 2025 16:47:03 +0100 Subject: [PATCH 5/7] evm: fix build client: add TODO --- packages/client/src/rpc/modules/eth.ts | 2 +- packages/evm/src/evm.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/client/src/rpc/modules/eth.ts b/packages/client/src/rpc/modules/eth.ts index 475642a86a7..6c6364167f0 100644 --- a/packages/client/src/rpc/modules/eth.ts +++ b/packages/client/src/rpc/modules/eth.ts @@ -521,7 +521,7 @@ export class Eth { throw { code: 3, data: bytesToHex(execResult.returnValue), - message: execResult.exceptionError.type.code, + message: execResult.exceptionError.type.code, // TODO EVMError now puts the error "message" into the error type, is this correct? } } return bytesToHex(execResult.returnValue) diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index 72338aaf1ee..e9c4c08fabd 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -9,8 +9,6 @@ import { KECCAK256_NULL, KECCAK256_RLP, MAX_INTEGER, - UsageError, - UsageErrorType, bigIntToBytes, bytesToUnprefixedHex, createZeroAddress, From ef0e70cea44bbc7cc3ba919891db506637d0c942 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 24 Feb 2025 17:10:45 +0100 Subject: [PATCH 6/7] evm: fix test --- packages/evm/test/eips/eip-3860.spec.ts | 2 +- .../evm/test/precompiles/01-ecrecover.spec.ts | 2 +- .../evm/test/precompiles/03-ripemd160.spec.ts | 2 +- .../evm/test/precompiles/09-blake2f.spec.ts | 6 ++++- .../precompiles/0a-pointevaluation.spec.ts | 2 +- .../evm/test/precompiles/eip-2537-bls.spec.ts | 2 +- packages/evm/test/runCall.spec.ts | 25 +++++++++++-------- packages/evm/test/runCode.spec.ts | 11 +++++--- packages/evm/test/stack.spec.ts | 2 +- packages/evm/test/verkle.spec.ts | 4 +-- 10 files changed, 35 insertions(+), 23 deletions(-) diff --git a/packages/evm/test/eips/eip-3860.spec.ts b/packages/evm/test/eips/eip-3860.spec.ts index 09a38485c1d..98e8617a791 100644 --- a/packages/evm/test/eips/eip-3860.spec.ts +++ b/packages/evm/test/eips/eip-3860.spec.ts @@ -43,7 +43,7 @@ describe('EIP 3860 tests', () => { } const result = await evm.runCall(runCallArgs) assert.ok( - (result.execResult.exceptionError?.error as string) === 'initcode exceeds max initcode size', + result.execResult.exceptionError?.type.code === 'initcode exceeds max initcode size', 'initcode exceeds max size', ) }) diff --git a/packages/evm/test/precompiles/01-ecrecover.spec.ts b/packages/evm/test/precompiles/01-ecrecover.spec.ts index ed6b8884d3e..0c9e8182dc4 100644 --- a/packages/evm/test/precompiles/01-ecrecover.spec.ts +++ b/packages/evm/test/precompiles/01-ecrecover.spec.ts @@ -43,6 +43,6 @@ describe('Precompiles: ECRECOVER', () => { common, _EVM: evm, }) - assert.equal(result.exceptionError!.error, 'out of gas', 'should error when not enough gas') + assert.equal(result.exceptionError!.type.code, 'out of gas', 'should error when not enough gas') }) }) diff --git a/packages/evm/test/precompiles/03-ripemd160.spec.ts b/packages/evm/test/precompiles/03-ripemd160.spec.ts index e7c9b20aee6..7619c0922da 100644 --- a/packages/evm/test/precompiles/03-ripemd160.spec.ts +++ b/packages/evm/test/precompiles/03-ripemd160.spec.ts @@ -38,6 +38,6 @@ describe('Precompiles: RIPEMD160', () => { common, _EVM: evm, }) - assert.equal(result.exceptionError!.error, 'out of gas', 'should error when not enough gas') + assert.equal(result.exceptionError!.type.code, 'out of gas', 'should error when not enough gas') }) }) diff --git a/packages/evm/test/precompiles/09-blake2f.spec.ts b/packages/evm/test/precompiles/09-blake2f.spec.ts index 5dd3235c8a4..d50d49590d5 100644 --- a/packages/evm/test/precompiles/09-blake2f.spec.ts +++ b/packages/evm/test/precompiles/09-blake2f.spec.ts @@ -120,7 +120,11 @@ describe('Precompiles: BLAKE2F', () => { common, _EVM: evm, }) - assert.equal(result.exceptionError!.error, t.expectedError, 'should generate expected error') + assert.equal( + result.exceptionError!.type.code, + t.expectedError, + 'should generate expected error', + ) }) } diff --git a/packages/evm/test/precompiles/0a-pointevaluation.spec.ts b/packages/evm/test/precompiles/0a-pointevaluation.spec.ts index 83202709ba0..0b5b1797c50 100644 --- a/packages/evm/test/precompiles/0a-pointevaluation.spec.ts +++ b/packages/evm/test/precompiles/0a-pointevaluation.spec.ts @@ -78,6 +78,6 @@ describe('Precompiles: point evaluation', () => { common, } res = await pointEvaluation(optsWithInvalidCommitment) - assert.ok(res.exceptionError?.error.match('invalid input length'), 'invalid input length') + assert.ok(res.exceptionError?.type.code.match('invalid input length'), 'invalid input length') }) }) diff --git a/packages/evm/test/precompiles/eip-2537-bls.spec.ts b/packages/evm/test/precompiles/eip-2537-bls.spec.ts index ac37ebb8517..7df1c1d10f4 100644 --- a/packages/evm/test/precompiles/eip-2537-bls.spec.ts +++ b/packages/evm/test/precompiles/eip-2537-bls.spec.ts @@ -83,7 +83,7 @@ for (const bls of [undefined, mclbls]) { ) assert.equal(result.executionGasUsed, BigInt(data.Gas)) } catch (e: any) { - assert.fail(e.message) + assert.fail(e.type.code) } } }) diff --git a/packages/evm/test/runCall.spec.ts b/packages/evm/test/runCall.spec.ts index 8c3ddc5ad2c..2d4002e0d67 100644 --- a/packages/evm/test/runCall.spec.ts +++ b/packages/evm/test/runCall.spec.ts @@ -18,8 +18,7 @@ import { assert, describe, it } from 'vitest' import { eip4844Data } from '../../client/test/testdata/geth-genesis/eip4844.js' import { defaultBlock } from '../src/evm.js' -import { ERROR } from '../src/exceptions.js' -import { createEVM } from '../src/index.js' +import { EVMErrorCode, createEVM } from '../src/index.js' import type { EVMRunCallOpts } from '../src/types.js' @@ -138,7 +137,7 @@ describe('RunCall tests', () => { assert.ok( byzantiumResult.execResult.exceptionError && - byzantiumResult.execResult.exceptionError.error === 'invalid opcode', + byzantiumResult.execResult.exceptionError.type.code === 'invalid opcode', 'byzantium cannot accept constantinople opcodes (SHL)', ) assert.ok( @@ -272,7 +271,10 @@ describe('RunCall tests', () => { assert.equal(runCallArgs.gasLimit, result.execResult.executionGasUsed, 'gas used correct') assert.equal(result.execResult.gasRefund, BigInt(0), 'gas refund correct') - assert.ok(result.execResult.exceptionError!.error === ERROR.OUT_OF_GAS, 'call went out of gas') + assert.ok( + result.execResult.exceptionError!.type.code === EVMErrorCode.OUT_OF_GAS, + 'call went out of gas', + ) }) it('ensure selfdestruct pays for creating new accounts', async () => { @@ -512,7 +514,7 @@ describe('RunCall tests', () => { const res2 = await evm.runCall({ ...runCallArgs, skipBalance: false }) assert.ok( - res2.execResult.exceptionError?.error.match('insufficient balance'), + res2.execResult.exceptionError?.type.code.match('insufficient balance'), 'runCall reverts when insufficient sender balance and skipBalance is false', ) }) @@ -536,8 +538,8 @@ describe('RunCall tests', () => { const result = await evm.runCall(runCallArgs) assert.equal( - result.execResult.exceptionError?.error, - ERROR.CODESIZE_EXCEEDS_MAXIMUM, + result.execResult.exceptionError?.type.code, + EVMErrorCode.CODESIZE_EXCEEDS_MAXIMUM, 'reported error is correct', ) }) @@ -648,7 +650,7 @@ describe('RunCall tests', () => { } const res = await evm.runCall(runCallArgs) - assert.ok(res.execResult.exceptionError?.error === ERROR.CODESIZE_EXCEEDS_MAXIMUM) + assert.ok(res.execResult.exceptionError?.type.code === EVMErrorCode.CODESIZE_EXCEEDS_MAXIMUM) // Create a contract which goes OOG when creating const runCallArgs2 = { @@ -657,7 +659,7 @@ describe('RunCall tests', () => { } const res2 = await evm.runCall(runCallArgs2) - assert.ok(res2.execResult.exceptionError?.error === ERROR.OUT_OF_GAS) + assert.ok(res2.execResult.exceptionError?.type.code === EVMErrorCode.OUT_OF_GAS) }) it('ensure code deposit errors are logged correctly (Frontier)', async () => { @@ -671,7 +673,8 @@ describe('RunCall tests', () => { } const res = await evm.runCall(runCallArgs) - assert.ok(res.execResult.exceptionError?.error === ERROR.CODESTORE_OUT_OF_GAS) + // TODO: This now fails? + assert.ok(res.execResult.exceptionError?.type.code === EVMErrorCode.CODESTORE_OUT_OF_GAS) // Create a contract which goes OOG when creating const runCallArgs2 = { @@ -680,7 +683,7 @@ describe('RunCall tests', () => { } const res2 = await evm.runCall(runCallArgs2) - assert.ok(res2.execResult.exceptionError?.error === ERROR.OUT_OF_GAS) + assert.ok(res2.execResult.exceptionError?.type.code === EVMErrorCode.OUT_OF_GAS) }) it('ensure call and callcode handle gas stipend correctly', async () => { diff --git a/packages/evm/test/runCode.spec.ts b/packages/evm/test/runCode.spec.ts index 821626a339a..027a0adff4f 100644 --- a/packages/evm/test/runCode.spec.ts +++ b/packages/evm/test/runCode.spec.ts @@ -1,7 +1,7 @@ import { Account, createAddressFromString, hexToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' -import { createEVM } from '../src/index.js' +import { EVMError, createEVM } from '../src/index.js' const PUSH1 = '60' const STOP = '00' @@ -45,6 +45,11 @@ describe('VM.runCode: initial program counter', () => { } if (testData.error !== undefined) { + // TODO: for all tests in EVM: this is a perfect place + // to also roll-out the instanceof check for errors + // This currently throws not EVMError but instead the generic EthereumJSError + // with the error code: `ETHEREUMJS_UNSET_ERROR_CODE` + // Here we can thus verify that the error type is generic. err = err?.message ?? 'no error thrown' assert.equal(err, testData.error, 'error message should match') err = false @@ -71,8 +76,8 @@ describe('VM.runCode: interpreter', () => { } catch (e: any) { assert.fail('should not throw error') } - assert.equal(result!.exceptionError!.errorType, 'EvmError') - assert.ok(result!.exceptionError!.error.includes('invalid opcode')) + assert.ok(result!.exceptionError! instanceof EVMError) + assert.ok(result!.exceptionError!.type.code.includes('invalid opcode')) }) it('should throw on non-EvmError', async () => { diff --git a/packages/evm/test/stack.spec.ts b/packages/evm/test/stack.spec.ts index b759c02e24a..ac5d98208bb 100644 --- a/packages/evm/test/stack.spec.ts +++ b/packages/evm/test/stack.spec.ts @@ -143,7 +143,7 @@ describe('Stack', () => { const executionReturnValue = res.execResult.returnValue assert.deepEqual(executionReturnValue, expectedReturnValue) } catch (e: any) { - assert.fail(e.message) + assert.fail(e.type.code) } }) diff --git a/packages/evm/test/verkle.spec.ts b/packages/evm/test/verkle.spec.ts index 7d40ad008b7..f8440f693e0 100644 --- a/packages/evm/test/verkle.spec.ts +++ b/packages/evm/test/verkle.spec.ts @@ -76,7 +76,7 @@ describe('verkle tests', () => { ([_, chunk]) => chunk.write !== undefined, ) assert.ok(writtenChunks.length === 0) - assert.equal(res.execResult.exceptionError?.error, 'out of gas') + assert.equal(res.execResult.exceptionError?.type.code, 'out of gas') }) it('access witness should contain a write access', async () => { @@ -109,7 +109,7 @@ describe('verkle tests', () => { ([_, chunk]) => chunk.write !== undefined, ) assert.ok(writtenChunks.length === 1) - assert.equal(res.execResult.exceptionError?.error, undefined) + assert.equal(res.execResult.exceptionError?.type.code, undefined) }) }) describe('generate an execution witness', () => { From 013c7eca1c314a0fc016d71dd7ba122f8130b155 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 24 Feb 2025 17:23:03 +0100 Subject: [PATCH 7/7] vm: fix tests --- packages/vm/test/api/EIPs/eip-3855.spec.ts | 6 +++--- packages/vm/test/api/EIPs/eip-3860.spec.ts | 2 +- packages/vm/test/api/istanbul/eip-1344.spec.ts | 6 +++--- packages/vm/test/api/istanbul/eip-1884.spec.ts | 6 +++--- packages/vm/test/api/runTx.spec.ts | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/vm/test/api/EIPs/eip-3855.spec.ts b/packages/vm/test/api/EIPs/eip-3855.spec.ts index 1b789036244..11fa9c3d334 100644 --- a/packages/vm/test/api/EIPs/eip-3855.spec.ts +++ b/packages/vm/test/api/EIPs/eip-3855.spec.ts @@ -1,5 +1,5 @@ import { Common, Hardfork, Mainnet } from '@ethereumjs/common' -import { EVMErrorMessage } from '@ethereumjs/evm' +import { EVMErrorCode } from '@ethereumjs/evm' import { hexToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' @@ -65,7 +65,7 @@ describe('EIP 3855 tests', () => { gasLimit: BigInt(10000), }) - assert.equal(result.exceptionError?.error, EVMErrorMessage.STACK_OVERFLOW) + assert.equal(result.exceptionError?.type.code, EVMErrorCode.STACK_OVERFLOW) }) it('push0 is not available if EIP3855 is not activated', async () => { @@ -76,6 +76,6 @@ describe('EIP 3855 tests', () => { gasLimit: BigInt(10000), }) - assert.equal(result.exceptionError!.error, EVMErrorMessage.INVALID_OPCODE) + assert.equal(result.exceptionError!.type.code, EVMErrorCode.INVALID_OPCODE) }) }) diff --git a/packages/vm/test/api/EIPs/eip-3860.spec.ts b/packages/vm/test/api/EIPs/eip-3860.spec.ts index 92adf30e4a0..c13d4b0e22c 100644 --- a/packages/vm/test/api/EIPs/eip-3860.spec.ts +++ b/packages/vm/test/api/EIPs/eip-3860.spec.ts @@ -40,7 +40,7 @@ describe('EIP 3860 tests', () => { ).sign(pkey) const result = await runTx(vm, { tx }) assert.ok( - (result.execResult.exceptionError?.error as string) === 'initcode exceeds max initcode size', + result.execResult.exceptionError?.type.code === 'initcode exceeds max initcode size', 'initcode exceeds max size', ) }) diff --git a/packages/vm/test/api/istanbul/eip-1344.spec.ts b/packages/vm/test/api/istanbul/eip-1344.spec.ts index 0d24b903ac9..336a0d6408a 100644 --- a/packages/vm/test/api/istanbul/eip-1344.spec.ts +++ b/packages/vm/test/api/istanbul/eip-1344.spec.ts @@ -1,5 +1,5 @@ import { Common, Hardfork, Mainnet } from '@ethereumjs/common' -import { EVMErrorMessage } from '@ethereumjs/evm' +import { EVMErrorCode } from '@ethereumjs/evm' import { bytesToBigInt, hexToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' @@ -7,7 +7,7 @@ import { createVM } from '../../../src/index.js' const testCases = [ { chain: Mainnet, hardfork: Hardfork.Istanbul, chainId: BigInt(1) }, - { chain: Mainnet, hardfork: Hardfork.Constantinople, err: EVMErrorMessage.INVALID_OPCODE }, + { chain: Mainnet, hardfork: Hardfork.Constantinople, err: EVMErrorCode.INVALID_OPCODE }, ] // CHAINID PUSH8 0x00 MSTORE8 PUSH8 0x01 PUSH8 0x00 RETURN @@ -27,7 +27,7 @@ describe('Istanbul: EIP-1344', () => { try { const res = await vm.evm.runCode!(runCodeArgs) if (testCase.err !== undefined) { - assert.equal(res.exceptionError?.error, testCase.err) + assert.equal(res.exceptionError?.type.code, testCase.err) } else { assert.ok(res.exceptionError === undefined) assert.equal(testCase.chainId, bytesToBigInt(res.returnValue)) diff --git a/packages/vm/test/api/istanbul/eip-1884.spec.ts b/packages/vm/test/api/istanbul/eip-1884.spec.ts index 9ca0582116c..e06276ed954 100644 --- a/packages/vm/test/api/istanbul/eip-1884.spec.ts +++ b/packages/vm/test/api/istanbul/eip-1884.spec.ts @@ -1,5 +1,5 @@ import { Common, Hardfork, Mainnet } from '@ethereumjs/common' -import { EVMErrorMessage } from '@ethereumjs/evm' +import { EVMErrorCode } from '@ethereumjs/evm' import { Address, bytesToBigInt, hexToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' @@ -8,7 +8,7 @@ import { createAccountWithDefaults } from '../utils.js' const testCases = [ { chain: Mainnet, hardfork: Hardfork.Istanbul, selfbalance: '0xf1' }, - { chain: Mainnet, hardfork: Hardfork.Constantinople, err: EVMErrorMessage.INVALID_OPCODE }, + { chain: Mainnet, hardfork: Hardfork.Constantinople, err: EVMErrorCode.INVALID_OPCODE }, ] // SELFBALANCE PUSH8 0x00 MSTORE8 PUSH8 0x01 PUSH8 0x00 RETURN @@ -35,7 +35,7 @@ describe('Istanbul: EIP-1884', () => { try { const res = await vm.evm.runCode!(runCodeArgs) if (testCase.err !== undefined) { - assert.equal(res.exceptionError?.error, testCase.err) + assert.equal(res.exceptionError?.type.code, testCase.err) } else { assert.ok(res.exceptionError === undefined) assert.ok(BigInt(testCase.selfbalance!) === bytesToBigInt(res.returnValue)) diff --git a/packages/vm/test/api/runTx.spec.ts b/packages/vm/test/api/runTx.spec.ts index ce5021db209..894818cc7fb 100644 --- a/packages/vm/test/api/runTx.spec.ts +++ b/packages/vm/test/api/runTx.spec.ts @@ -492,7 +492,7 @@ describe('runTx() -> runtime errors', () => { const res = await runTx(vm, { tx }) assert.equal( - res.execResult!.exceptionError!.error, + res.execResult!.exceptionError!.type.code, 'value overflow', `result should have 'value overflow' error set (${txType.name})`, ) @@ -520,7 +520,7 @@ describe('runTx() -> runtime errors', () => { const res = await runTx(vm, { tx }) assert.equal( - res.execResult!.exceptionError!.error, + res.execResult!.exceptionError!.type.code, 'value overflow', `result should have 'value overflow' error set (${txType.name})`, )