Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EVM: error handling #3714

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
8 changes: 7 additions & 1 deletion config/eslint.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/rpc/modules/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
27 changes: 17 additions & 10 deletions packages/evm/src/exceptions.ts → packages/evm/src/errors.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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',
Expand All @@ -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<EVMErrorType> {
constructor(type: EVMErrorType, message?: string) {
super(type, message)
}

// TODO: add helper method to format the error in a human readable way
}
72 changes: 53 additions & 19 deletions packages/evm/src/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
KECCAK256_NULL,
KECCAK256_RLP,
MAX_INTEGER,
UsageError,
UsageErrorType,
bigIntToBytes,
bytesToUnprefixedHex,
createZeroAddress,
Expand All @@ -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'
Expand Down Expand Up @@ -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`,
)
}
Expand Down Expand Up @@ -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,
},
}
Expand Down Expand Up @@ -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,
},
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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} ]`,
)
}
Expand All @@ -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()
Expand Down Expand Up @@ -1027,7 +1044,9 @@ export class EVM implements EVMInterface {
gasLimit: bigint,
): Promise<ExecResult> | ExecResult {
if (typeof code !== 'function') {
throw new Error('Invalid precompile')
throw new EVMError({
code: EVMErrorCode.INVALID_PRECOMPILE,
})
}

const opts = {
Expand Down Expand Up @@ -1087,7 +1106,9 @@ export class EVM implements EVMInterface {
protected async _reduceSenderBalance(account: Account, message: Message): Promise<void> {
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) {
Expand All @@ -1099,7 +1120,9 @@ export class EVM implements EVMInterface {
protected async _addToBalance(toAccount: Account, message: MessageWithTo): Promise<void> {
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
Expand Down Expand Up @@ -1147,47 +1170,58 @@ 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
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,
}),
}
}

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,
}),
}
}

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,
}),
}
}

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,
Expand Down
6 changes: 3 additions & 3 deletions packages/evm/src/index.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -46,8 +46,8 @@ export type {
export {
EOFContainer,
EVM,
EvmError,
EVMErrorMessage,
EVMError,
EVMErrorCode,
EVMMockBlockchain,
getActivePrecompiles,
getOpcodesForHF,
Expand Down
Loading
Loading