diff --git a/lib/evm/eei.ts b/lib/evm/eei.ts index cb5df7b5f4..4ba8153392 100644 --- a/lib/evm/eei.ts +++ b/lib/evm/eei.ts @@ -296,6 +296,7 @@ export default class EEI { */ finish (returnData: Buffer): void { this._result.returnValue = returnData + trap(ERROR.STOP) } /** diff --git a/lib/evm/interpreter.ts b/lib/evm/interpreter.ts index 461286c8c0..df128e71d5 100644 --- a/lib/evm/interpreter.ts +++ b/lib/evm/interpreter.ts @@ -9,13 +9,27 @@ import { OOGResult } from './precompiles/types' import TxContext from './txContext' import Message from './message' import EEI from './eei' -import { default as Loop, LoopResult } from './loop' +import { default as Loop, LoopResult, RunState, IsException, RunOpts } from './loop' const Block = require('ethereumjs-block') export interface InterpreterResult { gasUsed: BN createdAddress?: Buffer - vm: LoopResult + vm: ExecResult +} + +export interface ExecResult { + runState?: RunState + exception: IsException + exceptionError?: VmError | ERROR + gas?: BN + gasUsed: BN + return: Buffer + // From RunResult + logs?: Buffer[] + returnValue?: Buffer + gasRefund?: BN + selfdestruct?: {[k: string]: Buffer} } export default class Interpreter { @@ -89,7 +103,13 @@ export default class Interpreter { } } - const result = await this.runLoop(message) + let result + if (message.isCompiled) { + result = this.runPrecompile(message.code as PrecompileFunc, message.data, message.gasLimit) + } else { + result = await this.runLoop(message) + } + return { gasUsed: result.gasUsed, vm: result @@ -170,40 +190,53 @@ export default class Interpreter { } } - async runLoop (message: Message, loopOpts: {[k: string]: any} = {}): Promise { - const opts = Object.assign({}, { - storageReader: this._storageReader, - block: this._block, - txContext: this._tx, - message - }, loopOpts) + async runLoop (message: Message, loopOpts: RunOpts = {}): Promise { + const env = { + blockchain: this._vm.blockchain, // Only used in BLOCKHASH + address: message.to || zeros(32), + caller: message.caller || zeros(32), + callData: message.data || Buffer.from([0]), + callValue: message.value || new BN(0), + code: message.code as Buffer, + isStatic: message.isStatic || false, + depth: message.depth || 0, + gasPrice: this._tx.gasPrice, + origin: this._tx.origin || message.caller || zeros(32), + block: this._block || new Block(), + contract: await this._state.getAccount(message.to || zeros(32)) + } + const eei = new EEI(env, this._state, this, this._vm._common, message.gasLimit.clone()) + if (message.selfdestruct) { + eei._result.selfdestruct = message.selfdestruct + } - // Run code - let results - if (message.isCompiled) { - results = this.runPrecompile(message.code as PrecompileFunc, message.data, message.gasLimit) - } else { - const gasLeft = message.gasLimit.clone() - const env = { - blockchain: this._vm.blockchain, // Only used in BLOCKHASH - address: opts.message.to || zeros(32), - caller: opts.message.caller || zeros(32), - callData: opts.message.data || Buffer.from([0]), - callValue: opts.message.value || new BN(0), - code: opts.message.code as Buffer, - isStatic: opts.message.isStatic || false, - depth: opts.message.depth || 0, - gasPrice: opts.txContext.gasPrice, - origin: opts.txContext.origin || opts.message.caller || zeros(32), - block: opts.block || new Block(), - contract: await this._state.getAccount(opts.message.to || zeros(32)) + const loop = new Loop(this._vm, eei) + const opts = Object.assign({ storageReader: this._storageReader }, loopOpts) + const loopRes = await loop.run(message.code as Buffer, opts) + + let result = eei._result + let gasUsed = message.gasLimit.sub(eei._gasLeft) + if (loopRes.exceptionError) { + if ((loopRes.exceptionError as VmError).error !== ERROR.REVERT) { + gasUsed = message.gasLimit } - const eei = new EEI(env, this._state, this, this._vm._common, gasLeft) - const loop = new Loop(this._vm, eei) - results = await loop.run(opts) + + // remove any logs on error + result = Object.assign({}, result, { + logs: [], + gasRefund: null, + selfdestruct: null + }) } - return results + return Object.assign({}, result, { + runState: Object.assign({}, loopRes.runState, result, eei._env), + exception: loopRes.exception, + exceptionError: loopRes.exceptionError, + gas: eei._gasLeft, + gasUsed, + 'return': result.returnValue ? result.returnValue : Buffer.alloc(0) + }) } /** diff --git a/lib/evm/loop.ts b/lib/evm/loop.ts index d82986afed..6e86ac7644 100644 --- a/lib/evm/loop.ts +++ b/lib/evm/loop.ts @@ -1,5 +1,4 @@ import BN = require('bn.js') -import { zeros } from 'ethereumjs-util' import Common from 'ethereumjs-common' import { StateManager, StorageReader } from '../state' import PStateManager from '../state/promisified' @@ -7,23 +6,17 @@ import { ERROR, VmError } from '../exceptions' import Memory from './memory' import Stack from './stack' import EEI from './eei' -import Message from './message' -import TxContext from './txContext' import { lookupOpInfo, OpInfo } from './opcodes' -const opFns = require('./opFns.js') +import { handlers as opHandlers, OpHandler } from './opFns.js' -type IsException = 0 | 1 +export type IsException = 0 | 1 export interface RunOpts { - storageReader: StorageReader - block: any - message: Message - txContext: TxContext pc?: number + storageReader?: StorageReader } export interface RunState { - stopped: boolean programCounter: number opCode: number memory: Memory @@ -42,14 +35,6 @@ export interface LoopResult { runState?: RunState exception: IsException exceptionError?: VmError | ERROR - gas?: BN - gasUsed: BN - return: Buffer - // From RunResult - logs?: Buffer[] - returnValue?: Buffer - gasRefund?: BN - selfdestruct?: {[k: string]: Buffer} } export default class Loop { @@ -63,7 +48,6 @@ export default class Loop { this._state = new PStateManager(vm.stateManager) this._eei = eei this._runState = { - stopped: false, programCounter: 0, opCode: 0xfe, // INVALID opcode memory: new Memory(), @@ -80,15 +64,11 @@ export default class Loop { } } - async run (opts: RunOpts): Promise { - if (opts.message.selfdestruct) { - this._eei._result.selfdestruct = opts.message.selfdestruct - } - + async run (code: Buffer, opts: RunOpts = {}): Promise { Object.assign(this._runState, { - code: opts.message.code, + code: code, programCounter: opts.pc || this._runState.programCounter, - validJumps: this._getValidJumpDests(opts.message.code as Buffer), + validJumps: this._getValidJumpDests(code), storageReader: opts.storageReader || this._runState.storageReader }) @@ -99,87 +79,57 @@ export default class Loop { } let err - // iterate through the given ops until something breaks or we hit STOP - while (this.canContinueExecution()) { + // Iterate through the given ops until something breaks or we hit STOP + while (this._runState.programCounter < this._runState.code.length) { const opCode = this._runState.code[this._runState.programCounter] this._runState.opCode = opCode - await this.runStepHook() + await this._runStepHook() try { await this.runStep() } catch (e) { - if (e.error === ERROR.STOP) { - this._runState.stopped = true - } else { + // STOP is not an exception + if (e.error !== ERROR.STOP) { err = e - break } + // TODO: Throw on non-VmError exceptions + break } } - let result = this._eei._result - let gasUsed = opts.message.gasLimit.sub(this._eei.getGasLeft()) - if (err) { - if (err.error !== ERROR.REVERT) { - gasUsed = opts.message.gasLimit - } - - // remove any logs on error - result = Object.assign({}, result, { - logs: [], - gasRefund: null, - selfdestruct: null - }) - } - - return Object.assign({}, result, { - runState: Object.assign({}, this._runState, result, this._eei._env), + return { + runState: this._runState, exception: err ? 0 as IsException : 1 as IsException, - exceptionError: err, - gas: this._eei.getGasLeft(), - gasUsed, - 'return': result.returnValue ? result.returnValue : Buffer.alloc(0) - }) - } - - canContinueExecution (): boolean { - const notAtEnd = this._runState.programCounter < this._runState.code.length - return !this._runState.stopped && notAtEnd && !this._eei._result.returnValue + exceptionError: err + } } async runStep (): Promise { const opInfo = lookupOpInfo(this._runState.opCode) - // check for invalid opcode + // Check for invalid opcode if (opInfo.name === 'INVALID') { throw new VmError(ERROR.INVALID_OPCODE) } - // calculate gas + // Reduce opcode's base fee this._eei.useGas(new BN(opInfo.fee)) - - // advance program counter + // Advance program counter this._runState.programCounter++ - await this.handleOp(opInfo) - } - - async handleOp (opInfo: OpInfo): Promise { + // Execute opcode handler const opFn = this.getOpHandler(opInfo) - let args = [this._runState] - - // run the opcode if (opInfo.isAsync) { - await opFn.apply(null, args) + await opFn.apply(null, [this._runState]) } else { - opFn.apply(null, args) + opFn.apply(null, [this._runState]) } } - getOpHandler (opInfo: OpInfo) { - return opFns[opInfo.name] + getOpHandler (opInfo: OpInfo): OpHandler { + return opHandlers[opInfo.name] } - async runStepHook (): Promise { + async _runStepHook (): Promise { const eventObj = { pc: this._runState.programCounter, gasLeft: this._eei.getGasLeft(), diff --git a/lib/evm/opFns.ts b/lib/evm/opFns.ts index faca3d16e9..388d78eef1 100644 --- a/lib/evm/opFns.ts +++ b/lib/evm/opFns.ts @@ -34,7 +34,7 @@ export type OpHandler = SyncOpHandler | AsyncOpHandler // the opcode functions export const handlers: {[k: string]: OpHandler} = { STOP: function (runState: RunState) { - runState.stopped = true + trap(ERROR.STOP) }, ADD: function (runState: RunState) { const [a, b] = runState.stack.popN(2) @@ -745,7 +745,6 @@ export const handlers: {[k: string]: OpHandler} = { }, REVERT: function (runState: RunState) { const [offset, length] = runState.stack.popN(2) - runState.stopped = true subMemUsage(runState, offset, length) let returnData = Buffer.alloc(0) if (!length.isZero()) {