diff --git a/docs/index.md b/docs/index.md index 78ab793aa3..2115a26e03 100644 --- a/docs/index.md +++ b/docs/index.md @@ -141,6 +141,7 @@ Runs EVM code - `opts.gasLimit` **[Buffer][42]** the gas limit for the code - `opts.origin` **[Buffer][42]** the address where the call originated from. The address should be a `Buffer` of 20bits. Defaults to `0` - `opts.value` **[Buffer][42]** the value in ether that is being sent to `opt.address`. Defaults to `0` + - `opts.pc` **[Number][32]** the initial program counter. Defaults to `0` - `cb` **[runCode~callback][44]** callback ## Event: beforeBlock diff --git a/lib/runCode.js b/lib/runCode.js index d1cde98b20..ac715f2f0c 100644 --- a/lib/runCode.js +++ b/lib/runCode.js @@ -38,6 +38,7 @@ const VmError = exceptions.VmError * @param {Buffer} opts.gasLimit the gas limit for the code * @param {Buffer} opts.origin the address where the call originated from. The address should be a `Buffer` of 20bits. Defaults to `0` * @param {Buffer} opts.value the value in ether that is being sent to `opt.address`. Defaults to `0` + * @param {Number} opts.pc the initial program counter. Defaults to `0` * @param {runCode~callback} cb callback */ @@ -69,7 +70,7 @@ module.exports = function (opts, cb) { returnValue: false, stopped: false, vmError: false, - programCounter: 0, + programCounter: opts.pc | 0, opCode: undefined, opName: undefined, gasLeft: new BN(opts.gasLimit), @@ -108,6 +109,12 @@ module.exports = function (opts, cb) { // iterate through the given ops until something breaks or we hit STOP function runVm (err) { + // Check that the programCounter is in range. Does not overwrite the previous err, if any. + const pc = runState.programCounter + if (!err && pc !== 0 && (pc < 0 || pc >= runState.code.length)) { + err = new VmError(ERROR.INVALID_OPCODE) + } + if (err) { return parseVmResults(err) } diff --git a/tests/api/runCode.js b/tests/api/runCode.js new file mode 100644 index 0000000000..0283a3f992 --- /dev/null +++ b/tests/api/runCode.js @@ -0,0 +1,58 @@ +const tape = require('tape') +const async = require('async') +const VM = require('../../lib/index') + +const STOP = '00' +const JUMP = '56' +const JUMPDEST = '5b' +const PUSH1 = '60' + +const testCases = [ + { code: [STOP, JUMPDEST, PUSH1, '05', JUMP, JUMPDEST], pc: 1, resultPC: 6 }, + { code: [STOP, JUMPDEST, PUSH1, '05', JUMP, JUMPDEST], pc: -1, error: 'invalid opcode' }, + { code: [STOP], pc: 3, error: 'invalid opcode' }, + { code: [STOP], resultPC: 1 } +] + +tape('VM.runcode: initial program counter', function (t) { + const vm = new VM() + + testCases.forEach(function (testData, i) { + t.test('should start the execution at the specified pc or 0 #' + i, function (st) { + const runCodeArgs = { + code: Buffer.from(testData.code.join(''), 'hex'), + pc: testData.pc, + gasLimit: 0xffff + } + let result + + async.series([ + function (done) { + vm.runCode(runCodeArgs, function (err, res) { + if (res) { + result = res + } + + done(err) + }) + }, + function (done) { + if (testData.resultPC !== undefined) { + t.equals(result.runState.programCounter, testData.resultPC, 'runstate.programCounter') + } + + done() + } + ], function (err) { + if (testData.error) { + err = err ? err.error : 'no error thrown' + t.equals(err, testData.error, 'error message should match') + err = false + } + + t.assert(!err) + st.end() + }) + }) + }) +})