Skip to content

Commit

Permalink
Cleanup loop and interpreter
Browse files Browse the repository at this point in the history
  • Loading branch information
s1na committed May 8, 2019
1 parent 3469087 commit 31e9838
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 111 deletions.
1 change: 1 addition & 0 deletions lib/evm/eei.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ export default class EEI {
*/
finish (returnData: Buffer): void {
this._result.returnValue = returnData
trap(ERROR.STOP)
}

/**
Expand Down
99 changes: 66 additions & 33 deletions lib/evm/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -170,40 +190,53 @@ export default class Interpreter {
}
}

async runLoop (message: Message, loopOpts: {[k: string]: any} = {}): Promise<any> {
const opts = Object.assign({}, {
storageReader: this._storageReader,
block: this._block,
txContext: this._tx,
message
}, loopOpts)
async runLoop (message: Message, loopOpts: RunOpts = {}): Promise<ExecResult> {
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)
})
}

/**
Expand Down
102 changes: 26 additions & 76 deletions lib/evm/loop.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,22 @@
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'
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
Expand All @@ -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 {
Expand All @@ -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(),
Expand All @@ -80,15 +64,11 @@ export default class Loop {
}
}

async run (opts: RunOpts): Promise<LoopResult> {
if (opts.message.selfdestruct) {
this._eei._result.selfdestruct = opts.message.selfdestruct
}

async run (code: Buffer, opts: RunOpts = {}): Promise<LoopResult> {
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
})

Expand All @@ -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<void> {
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<void> {
// 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<void> {
async _runStepHook (): Promise<void> {
const eventObj = {
pc: this._runState.programCounter,
gasLeft: this._eei.getGasLeft(),
Expand Down
3 changes: 1 addition & 2 deletions lib/evm/opFns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()) {
Expand Down

0 comments on commit 31e9838

Please sign in to comment.