Skip to content

Commit

Permalink
evm/util: error overhaul
Browse files Browse the repository at this point in the history
  • Loading branch information
jochem-brouwer committed Oct 1, 2024
1 parent f1fda27 commit d00b7bf
Show file tree
Hide file tree
Showing 27 changed files with 812 additions and 178 deletions.
49 changes: 39 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 RuntimeErrorMessage {
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,38 @@ export enum ERROR {
INVALID_PROOF = 'kzg proof invalid',
}

export class EvmError {
error: ERROR
errorType: string
export enum EvmErrorCode {
UNSUPPORTED_FEATURE = 'EVM_ERROR_UNSUPPORTED_FEATURE',
RUNTIME_ERROR = 'EVM_ERROR_RUNTIME_ERROR',
}

type EvmRuntimeErrorType = {
code: EvmErrorCode.RUNTIME_ERROR
reason: RuntimeErrorMessage | EOFError
} & (
| { reason: RuntimeErrorMessage.REVERT; revertBytes: Uint8Array }
| { reason: Exclude<RuntimeErrorMessage, RuntimeErrorMessage.REVERT> | EOFError }
)

export type EvmErrorType = { code: EvmErrorCode.UNSUPPORTED_FEATURE } | EvmRuntimeErrorType

constructor(error: ERROR) {
this.error = error
this.errorType = 'EvmError'
export class EvmError extends EthereumJSError<EvmErrorType> {
constructor(type: EvmErrorType, message?: string) {
super(type, message)
}
}

export function getRuntimeError(error: EvmError): RuntimeErrorMessage | EOFError | undefined {
if (error.type.code === EvmErrorCode.RUNTIME_ERROR) {
return error.type.reason
}
}

/*
throw new EvmError({
code: EvmErrorCode.RUNTIME_ERROR,
reason: RuntimeErrorMessage.REVERT
})
*/
76 changes: 58 additions & 18 deletions packages/evm/src/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import debugDefault from 'debug'

import { FORMAT } from './eof/constants.js'
import { isEOF } from './eof/util.js'
import { ERROR, EvmError } from './exceptions.js'
import { EvmError, EvmErrorCode, RuntimeErrorMessage, getRuntimeError } from './errors.js'
import { Interpreter } from './interpreter.js'
import { Journal } from './journal.js'
import { EVMPerformanceLogger } from './logger.js'
Expand Down Expand Up @@ -184,12 +184,18 @@ 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 EvmError(
{ code: EvmErrorCode.UNSUPPORTED_FEATURE },
`EIP-${eip} is not supported by the EVM`,
)
}
}

if (!EVM.supportedHardforks.includes(this.common.hardfork() as Hardfork)) {
throw new Error(
throw new EvmError(
{
code: EvmErrorCode.UNSUPPORTED_FEATURE,
},
`Hardfork ${this.common.hardfork()} not set as supported in supportedHardforks`,
)
}
Expand Down Expand Up @@ -418,7 +424,10 @@ 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.RUNTIME_ERROR,
reason: RuntimeErrorMessage.INITCODE_SIZE_VIOLATION,
}),
executionGasUsed: message.gasLimit,
},
}
Expand Down Expand Up @@ -475,7 +484,10 @@ export class EVM implements EVMInterface {
createdAddress: message.to,
execResult: {
returnValue: new Uint8Array(0),
exceptionError: new EvmError(ERROR.CREATE_COLLISION),
exceptionError: new EvmError({
code: EvmErrorCode.RUNTIME_ERROR,
reason: RuntimeErrorMessage.CREATE_COLLISION,
}),
executionGasUsed: message.gasLimit,
},
}
Expand Down Expand Up @@ -770,8 +782,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
getRuntimeError(interpreterRes.exceptionError) !== RuntimeErrorMessage.REVERT &&
getRuntimeError(interpreterRes.exceptionError) !== RuntimeErrorMessage.INVALID_EOF_FORMAT
) {
gasUsed = message.gasLimit
}
Expand Down Expand Up @@ -907,7 +919,7 @@ export class EVM implements EVMInterface {
const { executionGasUsed, exceptionError, returnValue } = result.execResult
debug(
`Received message execResult: [ gasUsed=${executionGasUsed} exceptionError=${
exceptionError ? `'${exceptionError.error}'` : 'none'
exceptionError ? `'${getRuntimeError(exceptionError)}'` : 'none'
} returnValue=${short(returnValue)} gasRefund=${result.execResult.gasRefund ?? 0} ]`,
)
}
Expand All @@ -917,14 +929,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 && getRuntimeError(err) !== RuntimeErrorMessage.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 &&
getRuntimeError(err) === RuntimeErrorMessage.CODESTORE_OUT_OF_GAS
)
) {
result.execResult.logs = []
await this.journal.revert()
Expand Down Expand Up @@ -993,7 +1008,10 @@ export class EVM implements EVMInterface {
gasLimit: bigint,
): Promise<ExecResult> | ExecResult {
if (typeof code !== 'function') {
throw new Error('Invalid precompile')
throw new EvmError({
code: EvmErrorCode.RUNTIME_ERROR,
reason: RuntimeErrorMessage.INVALID_PRECOMPILE,
})
}

const opts = {
Expand Down Expand Up @@ -1053,7 +1071,10 @@ 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.RUNTIME_ERROR,
reason: RuntimeErrorMessage.INSUFFICIENT_BALANCE,
})
}
const result = this.journal.putAccount(message.caller, account)
if (this.DEBUG) {
Expand All @@ -1065,7 +1086,10 @@ 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.RUNTIME_ERROR,
reason: RuntimeErrorMessage.VALUE_OVERFLOW,
})
}
toAccount.balance = newBalance
// putAccount as the nonce may have changed for contract creation
Expand Down Expand Up @@ -1114,43 +1138,59 @@ 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.RUNTIME_ERROR,
reason: RuntimeErrorMessage.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.RUNTIME_ERROR,
reason: RuntimeErrorMessage.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.RUNTIME_ERROR,
reason: RuntimeErrorMessage.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.RUNTIME_ERROR,
reason: RuntimeErrorMessage.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.RUNTIME_ERROR,
reason: RuntimeErrorMessage.CODESIZE_EXCEEDS_MAXIMUM,
}),
}
}

Expand Down
11 changes: 9 additions & 2 deletions packages/evm/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { EOFContainer, validateEOF } from './eof/container.js'
import {
RuntimeErrorMessage as EVMRuntimeErrorMessage,
EvmError,
EvmErrorCode,
EvmErrorType,
} 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 @@ -47,8 +52,10 @@ export {
EOFContainer,
EVM,
EvmError,
EVMErrorMessage,
EvmErrorCode,
EvmErrorType,
EVMMockBlockchain,
EVMRuntimeErrorMessage,
getActivePrecompiles,
getOpcodesForHF,
MCLBLS,
Expand Down
Loading

0 comments on commit d00b7bf

Please sign in to comment.