diff --git a/.gitignore b/.gitignore index 778a16a21c..87a6a8c1ad 100644 --- a/.gitignore +++ b/.gitignore @@ -62,4 +62,6 @@ build/Release # Dependency directory # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git node_modules +package-lock.json + diff --git a/lib/index.js b/lib/index.js index ebc0829053..59a36a2053 100644 --- a/lib/index.js +++ b/lib/index.js @@ -25,6 +25,7 @@ VM.deps = { * @param {Blockchain} [opts.blockchain] A blockchain object for storing/retrieving blocks * @param {Boolean} [opts.activatePrecompiles] Create entries in the state tree for the precompiled contracts * @param {Boolean} [opts.enableHomestead] Force enable Homestead irrelevant to block number + * @param {Boolean} [opts.enableHomesteadReprice] Force enable Homestead Reprice (EIP150) irrevelant to block number */ function VM (opts = {}) { this.stateManager = new StateManager({ diff --git a/lib/opFns.js b/lib/opFns.js index db450c270d..8e54a1e021 100644 --- a/lib/opFns.js +++ b/lib/opFns.js @@ -514,6 +514,7 @@ module.exports = { } checkCallMemCost(runState, options, localOpts) + checkOutOfGas(runState, options) makeCall(runState, options, localOpts, done) }, CALL: function (gasLimit, toAddress, value, inOffset, inLength, outOffset, outLength, runState, done) { @@ -539,11 +540,8 @@ module.exports = { outLength: outLength } - // add stipend if (!value.isZero()) { - runState.gasLeft.iadd(new BN(fees.callStipend.v)) subGas(runState, new BN(fees.callValueTransferGas.v)) - options.gasLimit.iadd(new BN(fees.callStipend.v)) } stateManager.exists(toAddress, function (err, exists) { @@ -563,11 +561,17 @@ module.exports = { try { checkCallMemCost(runState, options, localOpts) + checkOutOfGas(runState, options) } catch (e) { done(e.error) return } + if (!value.isZero()) { + runState.gasLeft.iadd(new BN(fees.callStipend.v)) + options.gasLimit.iadd(new BN(fees.callStipend.v)) + } + makeCall(runState, options, localOpts, done) }) }, @@ -594,30 +598,30 @@ module.exports = { outLength: outLength } - // add stipend if (!value.isZero()) { - runState.gasLeft.isub(new BN(fees.callValueTransferGas.v)).iadd(new BN(fees.callStipend.v)) - options.gasLimit.iadd(new BN(fees.callStipend.v)) + subGas(runState, new BN(fees.callValueTransferGas.v)) } checkCallMemCost(runState, options, localOpts) + checkOutOfGas(runState, options) - // load the code - stateManager.getAccount(toAddress, function (err, account) { - if (err) return done(err) - if (utils.isPrecompiled(toAddress)) { - options.compiled = true - options.code = runState._precompiled[toAddress.toString('hex')] + if (!value.isZero()) { + runState.gasLeft.iadd(new BN(fees.callStipend.v)) + options.gasLimit.iadd(new BN(fees.callStipend.v)) + } + + if (utils.isPrecompiled(toAddress)) { + options.compiled = true + options.code = runState._precompiled[toAddress.toString('hex')] + makeCall(runState, options, localOpts, done) + } else { + stateManager.getContractCode(toAddress, function (err, code, compiled) { + if (err) return done(err) + options.code = code + options.compiled = compiled makeCall(runState, options, localOpts, done) - } else { - stateManager.getContractCode(toAddress, function (err, code, compiled) { - if (err) return done(err) - options.code = code - options.compiled = compiled - makeCall(runState, options, localOpts, done) - }) - } - }) + }) + } }, DELEGATECALL: function (gas, toAddress, inOffset, inLength, outOffset, outLength, runState, done) { var stateManager = runState.stateManager @@ -645,6 +649,7 @@ module.exports = { } checkCallMemCost(runState, options, localOpts) + checkOutOfGas(runState, options) // load the code stateManager.getAccount(toAddress, function (err, account) { @@ -675,20 +680,29 @@ module.exports = { var contractAddress = runState.address suicideToAddress = utils.setLengthLeft(suicideToAddress, 20) - // only add to refund if this is the first suicide for the address - if (!runState.suicides[contractAddress.toString('hex')]) { - runState.gasRefund = runState.gasRefund.add(new BN(fees.suicideRefundGas.v)) - } - - runState.suicides[contractAddress.toString('hex')] = suicideToAddress - runState.stopped = true - stateManager.getAccount(suicideToAddress, function (err, toAccount) { // update balances if (err) { cb(err) return } + + if (!toAccount.exists) { + try { + subGas(runState, new BN(fees.callNewAccountGas.v)) + } catch (e) { + cb(e.error) + return + } + } + + // only add to refund if this is the first suicide for the address + if (!runState.suicides[contractAddress.toString('hex')]) { + runState.gasRefund = runState.gasRefund.add(new BN(fees.suicideRefundGas.v)) + } + runState.suicides[contractAddress.toString('hex')] = suicideToAddress + runState.stopped = true + var newBalance = new Buffer(new BN(contract.balance).add(new BN(toAccount.balance)).toArray()) async.series([ stateManager.putAccountBalance.bind(stateManager, suicideToAddress, newBalance), @@ -813,9 +827,12 @@ function checkCallMemCost (runState, callOptions, localOpts) { if (!callOptions.gasLimit) { callOptions.gasLimit = runState.gasLeft } +} - if (runState.gasLeft.cmp(callOptions.gasLimit) === -1) { - trap(ERROR.OUT_OF_GAS) +function checkOutOfGas (runState, callOptions) { + const gasAllowed = runState.gasLeft.sub(runState.gasLeft.div(new BN(64))) + if (callOptions.gasLimit.gt(gasAllowed)) { + callOptions.gasLimit = gasAllowed } } diff --git a/lib/opcodes.js b/lib/opcodes.js index 4c9d982abb..6bb081929f 100644 --- a/lib/opcodes.js +++ b/lib/opcodes.js @@ -32,7 +32,7 @@ const codes = { // 0x30 range - closure state 0x30: ['ADDRESS', 2, 0, 1, true], - 0x31: ['BALANCE', 20, 1, 1, true], + 0x31: ['BALANCE', 400, 1, 1, true], 0x32: ['ORIGIN', 2, 0, 1, true], 0x33: ['CALLER', 2, 0, 1, true], 0x34: ['CALLVALUE', 2, 0, 1, true], @@ -42,8 +42,8 @@ const codes = { 0x38: ['CODESIZE', 2, 0, 1, false], 0x39: ['CODECOPY', 3, 3, 0, false], 0x3a: ['GASPRICE', 2, 0, 1, false], - 0x3b: ['EXTCODESIZE', 20, 1, 1, true], - 0x3c: ['EXTCODECOPY', 20, 4, 0, true], + 0x3b: ['EXTCODESIZE', 700, 1, 1, true], + 0x3c: ['EXTCODECOPY', 700, 4, 0, true], // '0x40' range - block operations 0x40: ['BLOCKHASH', 20, 1, 1, true], @@ -58,7 +58,7 @@ const codes = { 0x51: ['MLOAD', 3, 1, 1, false], 0x52: ['MSTORE', 3, 2, 0, false], 0x53: ['MSTORE8', 3, 2, 0, false], - 0x54: ['SLOAD', 50, 1, 1, true], + 0x54: ['SLOAD', 200, 1, 1, true], 0x55: ['SSTORE', 0, 2, 0, true], 0x56: ['JUMP', 8, 1, 0, false], 0x57: ['JUMPI', 10, 2, 0, false], @@ -143,13 +143,13 @@ const codes = { // '0xf0' range - closures 0xf0: ['CREATE', 32000, 3, 1, true], - 0xf1: ['CALL', 40, 7, 1, true], - 0xf2: ['CALLCODE', 40, 7, 1, true], + 0xf1: ['CALL', 700, 7, 1, true], + 0xf2: ['CALLCODE', 700, 7, 1, true], 0xf3: ['RETURN', 0, 2, 0, false], - 0xf4: ['DELEGATECALL', 40, 6, 1, true], + 0xf4: ['DELEGATECALL', 700, 6, 1, true], // '0x70', range - other - 0xff: ['SUICIDE', 0, 1, 0, false] + 0xff: ['SUICIDE', 5000, 1, 0, false] } module.exports = function (op, full) { diff --git a/lib/runCall.js b/lib/runCall.js index 677cfb5c90..d9892a7d4e 100644 --- a/lib/runCall.js +++ b/lib/runCall.js @@ -154,7 +154,7 @@ module.exports = function (opts, cb) { var returnFee = results.return.length * fees.createDataGas.v var totalGas = results.gasUsed.addn(returnFee) // if not enough gas - if (totalGas.cmp(gasLimit) <= 0) { + if (totalGas.cmp(gasLimit) <= 0 && results.return.length <= 24576) { results.gasUsed = totalGas } else { results.return = new Buffer([]) diff --git a/lib/runCode.js b/lib/runCode.js index 3c856af098..2d9e46abcd 100644 --- a/lib/runCode.js +++ b/lib/runCode.js @@ -71,7 +71,7 @@ module.exports = function (opts, cb) { callData: opts.data || new Buffer([0]), code: opts.code, populateCache: opts.populateCache === undefined ? true : opts.populateCache, - enableHomestead: this.opts.enableHomestead === undefined ? block.isHomestead() : this.opts.enableHomestead // this == vm + enableHomestead: this.opts.enableHomestead === undefined ? block.isHomestead() : this.opts.enableHomestead } // temporary - to be factored out @@ -105,6 +105,7 @@ module.exports = function (opts, cb) { function vmIsActive () { var notAtEnd = runState.programCounter < runState.code.length + return !runState.stopped && notAtEnd && !runState.vmError && !runState.returnValue } diff --git a/lib/runTx.js b/lib/runTx.js index 659e54e992..4a64ddb4c6 100644 --- a/lib/runTx.js +++ b/lib/runTx.js @@ -5,7 +5,7 @@ const Bloom = require('./bloom.js') const Block = require('ethereumjs-block') /** - * Process a transaction. Run the vm. Transfers eth. checks balaces + * Process a transaction. Run the vm. Transfers eth. Checks balances. * @method processTx * @param opts * @param opts.tx {Transaction} - a transaction @@ -27,10 +27,6 @@ module.exports = function (opts, cb) { block = new Block() } - if (this.opts.enableHomestead) { - tx._homestead = true - } - if (new BN(block.header.gasLimit).cmp(new BN(tx.gasLimit)) === -1) { cb(new Error('tx has a higher gas limit than the block')) return @@ -70,7 +66,7 @@ module.exports = function (opts, cb) { } /** - * populates the cache with the two and from of the tx + * populates the cache with the 'to' and 'from' of the tx */ function populateCache (cb) { var accounts = new Set() @@ -85,10 +81,9 @@ module.exports = function (opts, cb) { self.stateManager.warmCache(accounts, cb) } - // sets up the envorment and runs a `call` + // sets up the environment and runs a `call` function runCall (cb) { - // check to the sender's account to make sure it has enought wei and the - // correct nonce + // check to the sender's account to make sure it has enough wei and the correct nonce var fromAccount = self.stateManager.cache.get(tx.from) var message @@ -141,10 +136,10 @@ module.exports = function (opts, cb) { results.bloom = txLogsBloom(results.vm.logs) fromAccount = self.stateManager.cache.get(tx.from) - // caculate the totall gas used + // caculate the total gas used results.gasUsed = results.gasUsed.add(basefee) - // refund the accoun.stateManagert + // process any gas refund var gasRefund = results.vm.gasRefund if (gasRefund) { if (gasRefund.cmp(results.gasUsed.divn(2)) === -1) { @@ -155,7 +150,7 @@ module.exports = function (opts, cb) { } results.amountSpent = results.gasUsed.mul(new BN(tx.gasPrice)) - // refund the left over gas amount + // refund the leftover gas amount fromAccount.balance = new BN(tx.gasLimit).sub(results.gasUsed) .mul(new BN(tx.gasPrice)) .add(new BN(fromAccount.balance)) diff --git a/tests/GeneralStateTestsRunner.js b/tests/GeneralStateTestsRunner.js index e3f1b27a1b..659767a1a2 100644 --- a/tests/GeneralStateTestsRunner.js +++ b/tests/GeneralStateTestsRunner.js @@ -36,12 +36,10 @@ function runTestCase (testData, t, cb) { function (done) { var tx = testUtil.makeTx(testData.transaction) block = testUtil.makeBlockFromEnv(testData.env) - if (!block.isHomestead() && !testData.homestead) { - tx._homestead = false - } else { - block.isHomestead = function () { - return true - } + tx._homestead = true + tx.enableHomestead = true + block.isHomestead = function () { + return true } if (tx.validate()) { diff --git a/tests/hooked.js b/tests/hooked.js index 075da50086..8cbcaa0ad4 100644 --- a/tests/hooked.js +++ b/tests/hooked.js @@ -32,7 +32,8 @@ tape('hooked-vm', function (test) { } var vm = createHookedVm({ - enableHomestead: true + enableHomestead: true, + enableHomsteadReprice: true }, hooksForBlockchainState(blockchainState)) // vm.on('step', function(stepData){ diff --git a/tests/tester.js b/tests/tester.js index 4e699d4cca..a1e6088993 100644 --- a/tests/tester.js +++ b/tests/tester.js @@ -2,7 +2,7 @@ const argv = require('minimist')(process.argv.slice(2)) const async = require('async') const tape = require('tape') const testing = require('ethereumjs-testing') -const FORK_CONFIG = argv.fork || 'Homestead' +const FORK_CONFIG = argv.fork || 'EIP150' const skip = [ 'CreateHashCollision', // impossible hash collision on generating address 'SuicidesMixingCoinbase', // sucides to the coinbase, since we run a blockLevel we create coinbase account.