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'], diff --git a/packages/client/src/rpc/modules/eth.ts b/packages/client/src/rpc/modules/eth.ts index 5eb5070897e..509ee29bed0 100644 --- a/packages/client/src/rpc/modules/eth.ts +++ b/packages/client/src/rpc/modules/eth.ts @@ -520,7 +520,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 a07ea4dade4..aec0f60f957 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -8,6 +8,8 @@ import { KECCAK256_NULL, KECCAK256_RLP, MAX_INTEGER, + UsageError, + UsageErrorType, bigIntToBytes, bytesToUnprefixedHex, createZeroAddress, @@ -21,7 +23,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' @@ -189,12 +191,20 @@ 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 new UsageError( + { + code: UsageErrorType.UNSUPPORTED_FEATURE, + }, + `EIP-${eip} is not supported by the EVM`, + ) } } if (!EVM.supportedHardforks.includes(this.common.hardfork() as Hardfork)) { - throw new Error( + throw new UsageError( + { + code: UsageErrorType.UNSUPPORTED_FEATURE, + }, `Hardfork ${this.common.hardfork()} not set as supported in supportedHardforks`, ) } @@ -442,7 +452,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, }, } @@ -501,7 +513,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, }, } @@ -802,8 +816,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 } @@ -940,7 +954,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} ]`, ) } @@ -950,14 +964,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() @@ -1027,7 +1044,9 @@ export class EVM implements EVMInterface { gasLimit: bigint, ): Promise | ExecResult { if (typeof code !== 'function') { - throw new Error('Invalid precompile') + throw new EVMError({ + code: EVMErrorCode.INVALID_PRECOMPILE, + }) } const opts = { @@ -1087,7 +1106,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) { @@ -1099,7 +1120,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 @@ -1147,11 +1170,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 @@ -1159,7 +1185,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, + }), } } @@ -1167,7 +1195,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, + }), } } @@ -1175,7 +1205,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, + }), } } @@ -1183,11 +1215,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 eda6ae09d39..3555fd844f0 100644 --- a/packages/evm/src/interpreter.ts +++ b/packages/evm/src/interpreter.ts @@ -17,7 +17,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' @@ -110,7 +110,7 @@ export interface RunState { export interface InterpreterResult { runState: RunState - exceptionError?: EvmError + exceptionError?: EVMError } export interface InterpreterStep { @@ -217,14 +217,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 @@ -237,7 +241,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 } } @@ -254,7 +260,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 } } } @@ -333,12 +341,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 @@ -398,7 +406,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 @@ -558,7 +568,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 })) } } @@ -594,7 +604,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, + }), + ) } } @@ -676,7 +690,7 @@ export class Interpreter { */ finish(returnData: Uint8Array): void { this._result.returnValue = returnData - trap(ERROR.STOP) + trap(new EVMError({ code: EVMErrorCode.STOP })) } /** @@ -686,7 +700,12 @@ export class Interpreter { */ revert(returnData: Uint8Array): void { this._result.returnValue = returnData - trap(ERROR.REVERT) + trap( + new EVMError({ + code: EVMErrorCode.REVERT, + revertBytes: returnData, + }), + ) } /** @@ -1011,7 +1030,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 } @@ -1112,14 +1131,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) @@ -1225,7 +1244,7 @@ export class Interpreter { }) } - trap(ERROR.STOP) + trap(new EVMError({ code: EVMErrorCode.STOP })) } /** @@ -1233,11 +1252,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] @@ -1254,7 +1277,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 990fe37b2e3..89d290c647e 100644 --- a/packages/evm/src/precompiles/0a-kzg-point-evaluation.ts +++ b/packages/evm/src/precompiles/0a-kzg-point-evaluation.ts @@ -6,8 +6,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' @@ -33,7 +33,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')) @@ -48,7 +53,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) { @@ -61,19 +71,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/errors.ts b/packages/util/src/errors.ts new file mode 100644 index 00000000000..c3f211cdf5c --- /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 + +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..15d8ea2de97 100644 --- a/packages/util/src/index.ts +++ b/packages/util/src/index.ts @@ -28,6 +28,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 fa4d6565a7c..f4179c86342 100644 --- a/packages/vm/src/runTx.ts +++ b/packages/vm/src/runTx.ts @@ -608,7 +608,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} ]`, ) }