Skip to content

Commit

Permalink
Add a Message interface (#47)
Browse files Browse the repository at this point in the history
* Add a Message interface

* Fix linter errors
  • Loading branch information
sz-piotr authored Feb 1, 2020
1 parent daea731 commit 3673b4f
Show file tree
Hide file tree
Showing 17 changed files with 364 additions and 409 deletions.
61 changes: 27 additions & 34 deletions packages/evm/src/evm/ExecutionContext.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,54 @@
import { Stack } from './Stack'
import { Opcode } from './opcodes'
import { OutOfGas } from './errors'
import { Memory, GasAwareMemory } from './Memory'
import { ReadonlyState, State } from './State'
import { Address } from './Address'
import { State } from './State'
import { Byte } from './Byte'

export interface ExecutionParameters {
address: Address,
gasLimit: number,
state: ReadonlyState,
}
import { Message } from './Message'
import { Opcode } from './opcodes'
import { parseBytecode } from './parseBytecode'

export class ExecutionContext {
code: Opcode[]
stack = new Stack()
memory = new GasAwareMemory(new Memory(), this.useGas.bind(this))
memory: GasAwareMemory
state: State
returnValue?: Byte[]
reverted = false
programCounter = 0

address: Address
gasLimit: number
state: State
private gasUsed = 0
private refund = 0

constructor (
public code: Opcode[],
params: ExecutionParameters,
) {
this.address = params.address
this.gasLimit = params.gasLimit
this.state = params.state.clone()
private _gasUsed = 0
private _gasRefund = 0

constructor (public message: Message) {
this.state = message.state.clone()
this.code = parseBytecode(message.code)
this.memory = new GasAwareMemory(
new Memory(),
this.useGas.bind(this),
)
}

getGasUsed () {
return this.gasUsed
get gasUsed () {
return this._gasUsed
}

useGas (gas: number) {
this.gasUsed += gas
if (this.gasUsed > this.gasLimit) {
this.gasUsed = this.gasLimit
this._gasUsed += gas
if (this._gasUsed > this.message.gasLimit) {
this._gasUsed = this.message.gasLimit
throw new OutOfGas()
}
}

useRemainingGas () {
this.gasUsed = this.gasLimit
this._gasUsed = this.message.gasLimit
}

addRefund (gas: number) {
this.refund += gas
get gasRefund () {
return this._gasRefund
}

applyRefund () {
const refund = Math.min(Math.floor(this.gasUsed / 2), this.refund)
this.gasUsed -= refund
refund (gas: number) {
this._gasRefund += gas
}
}
2 changes: 1 addition & 1 deletion packages/evm/src/evm/Memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface IMemory {

export class GasAwareMemory implements IMemory {
constructor (
public memory: IMemory,
public memory: Memory,
private useGas: (gas: number) => void,
) {}

Expand Down
18 changes: 18 additions & 0 deletions packages/evm/src/evm/Message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Address } from './Address'
import { Bytes32 } from './Bytes32'
import { Byte } from './Byte'
import { State } from './State'

export interface Message {
account: Address,
code: Byte[],
data: Byte[],
origin: Address,
sender: Address,
gasLimit: number,
gasPrice: Bytes32,
value: Bytes32,
enableStateModifications: boolean,
callDepth: number,
state: State,
}
8 changes: 0 additions & 8 deletions packages/evm/src/evm/State.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,6 @@ import { Bytes32 } from './Bytes32'
import { Address } from './Address'
import { Byte } from './Byte'

export interface ReadonlyState {
getBalance (address: Address): Bytes32,
getNonce (address: Address): number,
getStorage (address: Address, location: Bytes32): Bytes32,
getCode (address: Address): readonly Byte[],
clone (): State,
}

export class State {
private balances: Record<string, Bytes32 | undefined> = {}
private nonces: Record<string, number | undefined> = {}
Expand Down
5 changes: 3 additions & 2 deletions packages/evm/src/evm/errors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Bytes32 } from './Bytes32'
import { Byte } from './Byte'

export class VMError extends Error {
constructor (message: string) {
Expand All @@ -25,8 +26,8 @@ export class InvalidBytecode extends VMError {
}

export class InvalidOpcode extends VMError {
constructor (opcode: string) {
super('Invalid opcode 0x' + opcode)
constructor (opcode: Byte) {
super('Invalid opcode 0x' + opcode.toString(16).padStart(2, '0'))
}
}

Expand Down
48 changes: 19 additions & 29 deletions packages/evm/src/evm/executeCode.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,50 @@
import { Opcode } from './opcodes'
import { ExecutionContext, ExecutionParameters } from './ExecutionContext'
import { Byte } from './Byte'
import { ExecutionContext } from './ExecutionContext'
import { Memory } from './Memory'
import { Message } from './Message'
import { opSTOP } from './opcodes/control'
import { Stack } from './Stack'
import { State } from './State'
import { VMError } from './errors'
import { IMemory } from './Memory'
import { opSTOP } from './opcodes/control'
import { ReadonlyState } from './State'
import { Byte } from './Byte'

export interface ExecutionResult {
stack: Stack,
memory: IMemory,
state: ReadonlyState,
memory: Memory,
state: State,
gasUsed: number,
gasRefund: number,
programCounter: number,
reverted: boolean,
returnValue?: Byte[],
error?: VMError,
}

export function executeCode (code: Opcode[], params: ExecutionParameters): ExecutionResult {
const ctx = new ExecutionContext(code, params)

export function executeCode (message: Message): ExecutionResult {
const ctx = new ExecutionContext(message)
while (ctx.returnValue === undefined) {
const opCode = code[ctx.programCounter] || opSTOP
const opcode = ctx.code[ctx.programCounter] || opSTOP
ctx.programCounter++
try {
opCode(ctx)
opcode(ctx)
} catch (e) {
if (e instanceof VMError) {
ctx.useRemainingGas()
return toResult(ctx, params.state, e)
return toResult(ctx, e)
} else {
throw e
throw e // this should never happen
}
}
}

if (ctx.reverted) {
// TODO: should there be a refund here?
return toResult(ctx, params.state)
}
ctx.applyRefund()
return toResult(ctx)
}

function toResult (
ctx: ExecutionContext,
state?: ReadonlyState,
error?: VMError,
): ExecutionResult {
function toResult (ctx: ExecutionContext, error?: VMError): ExecutionResult {
return {
stack: ctx.stack,
// This prevents us from retaining a reference to ctx
memory: ctx.memory.memory,
state: state ?? ctx.state,
gasUsed: ctx.getGasUsed(),
state: (ctx.reverted || error) ? ctx.message.state : ctx.state,
gasUsed: ctx.gasUsed,
gasRefund: ctx.gasRefund,
reverted: ctx.reverted,
programCounter: ctx.programCounter,
returnValue: ctx.returnValue,
Expand Down
146 changes: 73 additions & 73 deletions packages/evm/src/evm/opcodes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,87 +38,87 @@ import { invalidOpcode } from './invalid'
import { makeOpDUP, makeOpSWAP, opPOP } from './stack'
import { opMSIZE, opMLOAD, opMSTORE, opMSTORE8 } from './memory'
import { opSSTORE, opSLOAD } from './storage'
import { Byte } from '../Byte'

export { opUnreachable } from './invalid'
export { makeOpPUSH } from './stack'
export { Opcode } from './Opcode'
export { GasCost, GasRefund } from './gasCosts'

export function getOpcode (hex: string) {
export function getOpcode (hex: Byte) {
return OP_CODES[hex] ?? invalidOpcode(hex)
}

/* eslint-disable quote-props */
const OP_CODES: Record<string, Opcode | undefined> = {
'00': opSTOP,
'01': opADD,
'02': opMUL,
'03': opSUB,
'04': opDIV,
'05': opSDIV,
'06': opMOD,
'07': opSMOD,
'08': opADDMOD,
'09': opMULMOD,
'0a': opEXP,
'0b': opSIGNEXTEND,
'10': opLT,
'11': opGT,
'12': opSLT,
'13': opSGT,
'14': opEQ,
'15': opISZERO,
'16': opAND,
'17': opOR,
'18': opXOR,
'19': opNOT,
'1a': opBYTE,
'1b': opSHL,
'1c': opSHR,
'1d': opSAR,
'50': opPOP,
'51': opMLOAD,
'52': opMSTORE,
'53': opMSTORE8,
'54': opSLOAD,
'55': opSSTORE,
'56': opJUMP,
'57': opJUMPI,
'59': opMSIZE,
'5b': opJUMPDEST,
const OP_CODES: Record<number, Opcode | undefined> = {
0x00: opSTOP,
0x01: opADD,
0x02: opMUL,
0x03: opSUB,
0x04: opDIV,
0x05: opSDIV,
0x06: opMOD,
0x07: opSMOD,
0x08: opADDMOD,
0x09: opMULMOD,
0x0a: opEXP,
0x0b: opSIGNEXTEND,
0x10: opLT,
0x11: opGT,
0x12: opSLT,
0x13: opSGT,
0x14: opEQ,
0x15: opISZERO,
0x16: opAND,
0x17: opOR,
0x18: opXOR,
0x19: opNOT,
0x1a: opBYTE,
0x1b: opSHL,
0x1c: opSHR,
0x1d: opSAR,
0x50: opPOP,
0x51: opMLOAD,
0x52: opMSTORE,
0x53: opMSTORE8,
0x54: opSLOAD,
0x55: opSSTORE,
0x56: opJUMP,
0x57: opJUMPI,
0x59: opMSIZE,
0x5b: opJUMPDEST,
// 60 - 7f PUSH - handled differently
'80': makeOpDUP(1),
'81': makeOpDUP(2),
'82': makeOpDUP(3),
'83': makeOpDUP(4),
'84': makeOpDUP(5),
'85': makeOpDUP(6),
'86': makeOpDUP(7),
'87': makeOpDUP(8),
'88': makeOpDUP(9),
'89': makeOpDUP(10),
'8a': makeOpDUP(11),
'8b': makeOpDUP(12),
'8c': makeOpDUP(13),
'8d': makeOpDUP(14),
'8e': makeOpDUP(15),
'8f': makeOpDUP(16),
'90': makeOpSWAP(1),
'91': makeOpSWAP(2),
'92': makeOpSWAP(3),
'93': makeOpSWAP(4),
'94': makeOpSWAP(5),
'95': makeOpSWAP(6),
'96': makeOpSWAP(7),
'97': makeOpSWAP(8),
'98': makeOpSWAP(9),
'99': makeOpSWAP(10),
'9a': makeOpSWAP(11),
'9b': makeOpSWAP(12),
'9c': makeOpSWAP(13),
'9d': makeOpSWAP(14),
'9e': makeOpSWAP(15),
'9f': makeOpSWAP(16),
'f3': opRETURN,
'fd': opREVERT,
0x80: makeOpDUP(1),
0x81: makeOpDUP(2),
0x82: makeOpDUP(3),
0x83: makeOpDUP(4),
0x84: makeOpDUP(5),
0x85: makeOpDUP(6),
0x86: makeOpDUP(7),
0x87: makeOpDUP(8),
0x88: makeOpDUP(9),
0x89: makeOpDUP(10),
0x8a: makeOpDUP(11),
0x8b: makeOpDUP(12),
0x8c: makeOpDUP(13),
0x8d: makeOpDUP(14),
0x8e: makeOpDUP(15),
0x8f: makeOpDUP(16),
0x90: makeOpSWAP(1),
0x91: makeOpSWAP(2),
0x92: makeOpSWAP(3),
0x93: makeOpSWAP(4),
0x94: makeOpSWAP(5),
0x95: makeOpSWAP(6),
0x96: makeOpSWAP(7),
0x97: makeOpSWAP(8),
0x98: makeOpSWAP(9),
0x99: makeOpSWAP(10),
0x9a: makeOpSWAP(11),
0x9b: makeOpSWAP(12),
0x9c: makeOpSWAP(13),
0x9d: makeOpSWAP(14),
0x9e: makeOpSWAP(15),
0x9f: makeOpSWAP(16),
0xf3: opRETURN,
0xfd: opREVERT,
}
3 changes: 2 additions & 1 deletion packages/evm/src/evm/opcodes/invalid.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ExecutionContext } from '../ExecutionContext'
import { InvalidOpcode, UnreachableInstruction } from '../errors'
import { Byte } from '../Byte'

export function invalidOpcode (opcode: string) {
export function invalidOpcode (opcode: Byte) {
return function (ctx: ExecutionContext) {
throw new InvalidOpcode(opcode)
}
Expand Down
Loading

0 comments on commit 3673b4f

Please sign in to comment.