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

Implement EIP-2315: subroutines #754

Merged
merged 10 commits into from
Aug 14, 2020
15 changes: 14 additions & 1 deletion packages/common/src/hardforks/berlin.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,20 @@
"status": "Draft"
},
"gasConfig": {},
"gasPrices": {},
"gasPrices": {
"beginsub": {
"v": 2,
"d": "Base fee of the BEGINSUB opcode"
},
"returnsub": {
"v": 5,
"d": "Base fee of the RETURNSUB opcode"
},
"jumpsub": {
"v": 10,
"d": "Base fee of the JUMPSUB opcode"
}
},
"vm": {},
"pow": {}
}
27 changes: 23 additions & 4 deletions packages/vm/lib/evm/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ export interface RunState {
memoryWordCount: BN
highestMemCost: BN
stack: Stack
returnStack: Stack
code: Buffer
validJumps: number[]
validJumpSubs: number[]
_common: Common
stateManager: StateManager
eei: EEI
Expand All @@ -36,6 +38,7 @@ export interface InterpreterStep {
gasLeft: BN
stateManager: StateManager
stack: BN[]
returnStack: BN[]
pc: number
depth: number
address: Buffer
Expand All @@ -50,6 +53,11 @@ export interface InterpreterStep {
codeAddress: Buffer
}

interface JumpDests {
jumps: number[]
jumpSubs: number[]
}

/**
* Parses and executes EVM bytecode.
*/
Expand All @@ -70,8 +78,10 @@ export default class Interpreter {
memoryWordCount: new BN(0),
highestMemCost: new BN(0),
stack: new Stack(),
returnStack: new Stack(1023), // 1023 return stack height limit per EIP 2315 spec
code: Buffer.alloc(0),
validJumps: [],
validJumpSubs: [],
// TODO: Replace with EEI methods
_common: this._vm._common,
stateManager: this._state,
Expand All @@ -82,7 +92,10 @@ export default class Interpreter {
async run(code: Buffer, opts: InterpreterOpts = {}): Promise<InterpreterResult> {
this._runState.code = code
this._runState.programCounter = opts.pc || this._runState.programCounter
this._runState.validJumps = this._getValidJumpDests(code)

const valid = this._getValidJumpDests(code)
this._runState.validJumps = valid.jumps
this._runState.validJumpSubs = valid.jumpSubs

// Check that the programCounter is in range
const pc = this._runState.programCounter
Expand Down Expand Up @@ -167,6 +180,7 @@ export default class Interpreter {
isAsync: opcode.isAsync,
},
stack: this._runState.stack._store,
returnStack: this._runState.returnStack._store,
depth: this._eei._env.depth,
address: this._eei._env.address,
account: this._eei._env.contract,
Expand Down Expand Up @@ -194,9 +208,10 @@ export default class Interpreter {
return this._vm._emit('step', eventObj)
}

// Returns all valid jump destinations.
_getValidJumpDests(code: Buffer): number[] {
// Returns all valid jump and jumpsub destinations.
_getValidJumpDests(code: Buffer): JumpDests {
const jumps = []
const jumpSubs = []

for (let i = 0; i < code.length; i++) {
const curOpCode = this.lookupOpInfo(code[i]).name
Expand All @@ -209,8 +224,12 @@ export default class Interpreter {
if (curOpCode === 'JUMPDEST') {
jumps.push(i)
}

if (curOpCode === 'BEGINSUB') {
jumpSubs.push(i)
}
}

return jumps
return { jumps, jumpSubs }
}
}
32 changes: 32 additions & 0 deletions packages/vm/lib/evm/opFns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,33 @@ export const handlers: { [k: string]: OpHandler } = {
runState.stack.push(new BN(runState.eei.getGasLeft()))
},
JUMPDEST: function (runState: RunState) {},
BEGINSUB: function (runState: RunState) {
trap(ERROR.INVALID_BEGINSUB + ' at ' + describeLocation(runState))
cgewecke marked this conversation as resolved.
Show resolved Hide resolved
},
JUMPSUB: function (runState: RunState) {
const dest = runState.stack.pop()

if (dest.gt(runState.eei.getCodeSize())) {
trap(ERROR.INVALID_JUMPSUB + ' at ' + describeLocation(runState))
}

const destNum = dest.toNumber()

if (!jumpSubIsValid(runState, destNum)) {
trap(ERROR.INVALID_JUMPSUB + ' at ' + describeLocation(runState))
}

runState.returnStack.push(new BN(runState.programCounter))
runState.programCounter = destNum + 1
},
RETURNSUB: function (runState: RunState) {
if (runState.returnStack.length < 1) {
trap(ERROR.INVALID_RETURNSUB)
}

const dest = runState.returnStack.pop()
runState.programCounter = dest.toNumber()
},
PUSH: function (runState: RunState) {
const numToPush = runState.opCode - 0x5f
const loaded = new BN(
Expand Down Expand Up @@ -927,6 +954,11 @@ function maxCallGas(gasLimit: BN, gasLeft: BN, runState: RunState): BN {
}
}

// checks if a jumpsub is valid given a destination
function jumpSubIsValid(runState: RunState, dest: number): boolean {
return runState.validJumpSubs.indexOf(dest) !== -1
}

async function getContractStorage(runState: RunState, address: Buffer, key: Buffer) {
const current = await runState.stateManager.getContractStorage(address, key)
if (
Expand Down
8 changes: 8 additions & 0 deletions packages/vm/lib/evm/opcodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,14 @@ const hardforkOpcodes = [
0x47: { name: 'SELFBALANCE', isAsync: false }, // EIP 1884
},
},
{
hardforkName: 'berlin',
opcodes: {
0x5c: { name: 'BEGINSUB', isAsync: false }, // EIP 2315
0x5d: { name: 'RETURNSUB', isAsync: false }, // EIP 2315
0x5e: { name: 'JUMPSUB', isAsync: false }, // EIP 2315
},
},
]

/**
Expand Down
6 changes: 4 additions & 2 deletions packages/vm/lib/evm/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ const { ERROR, VmError } = require('../exceptions')
*/
export default class Stack {
_store: BN[]
_maxHeight: number

constructor() {
constructor(maxHeight?: number) {
this._store = []
this._maxHeight = maxHeight || 1024
}

get length() {
Expand All @@ -25,7 +27,7 @@ export default class Stack {
throw new VmError(ERROR.OUT_OF_RANGE)
}

if (this._store.length > 1023) {
if (this._store.length >= this._maxHeight) {
throw new VmError(ERROR.STACK_OVERFLOW)
}

Expand Down
3 changes: 3 additions & 0 deletions packages/vm/lib/exceptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export enum ERROR {
STOP = 'stop',
REFUND_EXHAUSTED = 'refund exhausted',
VALUE_OVERFLOW = 'value overflow',
INVALID_BEGINSUB = 'invalid BEGINSUB',
INVALID_RETURNSUB = 'invalid RETURNSUB',
INVALID_JUMPSUB = 'invalid JUMPSUB',
}

export class VmError {
Expand Down
1 change: 1 addition & 0 deletions packages/vm/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export default class VM extends AsyncEventEmitter {
'petersburg',
'istanbul',
'muirGlacier',
'berlin',
]

this._common = new Common(chain, hardfork, supportedHardforks)
Expand Down
2 changes: 1 addition & 1 deletion packages/vm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"@types/node": "^11.13.4",
"@types/tape": "^4.13.0",
"browserify": "^16.5.1",
"ethereumjs-testing": "git+https://github.com/ethereumjs/ethereumjs-testing.git#v1.3.1",
"ethereumjs-testing": "git+https://github.com/ethereumjs/ethereumjs-testing.git#v1.3.3",
"karma": "^4.1.0",
"karma-browserify": "^6.0.0",
"karma-chrome-launcher": "^2.2.0",
Expand Down
Loading