diff --git a/lib/opFns.js b/lib/opFns.js index 774ff786bb..71c5a8d3ac 100644 --- a/lib/opFns.js +++ b/lib/opFns.js @@ -7,7 +7,17 @@ const exceptions = require('./exceptions.js') const logTable = require('./logTable.js') const ERROR = exceptions.ERROR const VmError = exceptions.VmError -const MAX_INT = 9007199254740991 + +// Find Ceil(`this` / `num`) +BN.prototype.divCeil = function divCeil (num) { + var dm = this.divmod(num) + + // Fast case - exact division + if (dm.mod.isZero()) return dm.div + + // Round up + return dm.div.negative !== 0 ? dm.div.isubn(1) : dm.div.iaddn(1) +} // the opcode functions module.exports = { @@ -197,11 +207,11 @@ module.exports = { }, // 0x20 range - crypto SHA3: function (offset, length, runState) { - offset = utils.bufferToInt(offset) - length = utils.bufferToInt(length) + offset = new BN(offset) + length = new BN(length) var data = memLoad(runState, offset, length) // copy fee - subGas(runState, new BN(fees.sha3WordGas.v).imuln(Math.ceil(length / 32))) + subGas(runState, new BN(fees.sha3WordGas.v).imul(length.divCeil(new BN(32)))) return utils.sha3(data) }, // 0x30 range - closure state @@ -253,25 +263,25 @@ module.exports = { } }, CALLDATACOPY: function (memOffset, dataOffset, dataLength, runState) { - memOffset = utils.bufferToInt(memOffset) - dataLength = utils.bufferToInt(dataLength) - dataOffset = utils.bufferToInt(dataOffset) + memOffset = new BN(memOffset) + dataLength = new BN(dataLength) + dataOffset = new BN(dataOffset) memStore(runState, memOffset, runState.callData, dataOffset, dataLength) // sub the COPY fee - subGas(runState, new BN(fees.copyGas.v).imuln(Math.ceil(dataLength / 32))) + subGas(runState, new BN(fees.copyGas.v).imul(dataLength.divCeil(new BN(32)))) }, CODESIZE: function (runState) { return utils.intToBuffer(runState.code.length) }, CODECOPY: function (memOffset, codeOffset, length, runState) { - memOffset = utils.bufferToInt(memOffset) - codeOffset = utils.bufferToInt(codeOffset) - length = utils.bufferToInt(length) + memOffset = new BN(memOffset) + codeOffset = new BN(codeOffset) + length = new BN(length) memStore(runState, memOffset, runState.code, codeOffset, length) // sub the COPY fee - subGas(runState, new BN(fees.copyGas.v).imuln(Math.ceil(length / 32))) + subGas(runState, new BN(fees.copyGas.v).imul(length.divCeil(new BN(32)))) }, EXTCODESIZE: function (address, runState, cb) { var stateManager = runState.stateManager @@ -284,14 +294,14 @@ module.exports = { EXTCODECOPY: function (address, memOffset, codeOffset, length, runState, cb) { var stateManager = runState.stateManager address = utils.setLengthLeft(address, 20) - memOffset = utils.bufferToInt(memOffset) - codeOffset = utils.bufferToInt(codeOffset) - length = utils.bufferToInt(length) + memOffset = new BN(memOffset) + codeOffset = new BN(codeOffset) + length = new BN(length) // FIXME: for some reason this must come before subGas subMemUsage(runState, memOffset, length) // copy fee - subGas(runState, new BN(fees.copyGas.v).imuln(Math.ceil(length / 32))) + subGas(runState, new BN(fees.copyGas.v).imul(length.divCeil(new BN(32)))) stateManager.getContractCode(address, function (err, code) { if (err) return cb(err) @@ -303,17 +313,17 @@ module.exports = { return utils.intToBuffer(runState.lastReturned.length) }, RETURNDATACOPY: function (memOffset, returnDataOffset, length, runState) { - memOffset = utils.bufferToInt(memOffset) - returnDataOffset = utils.bufferToInt(returnDataOffset) - length = utils.bufferToInt(length) + memOffset = new BN(memOffset) + returnDataOffset = new BN(returnDataOffset) + length = new BN(length) - if (returnDataOffset + length > runState.lastReturned.length) { + if ((returnDataOffset.add(length)).gt(new BN(runState.lastReturned.length))) { trap(ERROR.OUT_OF_GAS) } memStore(runState, memOffset, utils.toBuffer(runState.lastReturned), returnDataOffset, length, false) // sub the COPY fee - subGas(runState, new BN(fees.copyGas.v).imuln(Math.ceil(length / 32))) + subGas(runState, new BN(fees.copyGas.v).mul(length.divCeil(new BN(32)))) }, GASPRICE: function (runState) { return utils.setLengthLeft(runState.gasPrice, 32) @@ -321,7 +331,7 @@ module.exports = { // '0x40' range - block operations BLOCKHASH: function (number, runState, cb) { var stateManager = runState.stateManager - var diff = new BN(runState.block.header.number).sub(new BN(number)) + var diff = new BN(runState.block.header.number).isub(new BN(number)) // block lookups must be within the past 256 blocks if (diff.gtn(256) || diff.lten(0)) { @@ -352,20 +362,19 @@ module.exports = { // 0x50 range - 'storage' and execution POP: function () {}, MLOAD: function (pos, runState) { - pos = utils.bufferToInt(pos) - var loaded = utils.unpad(memLoad(runState, pos, 32)) - return loaded + pos = new BN(pos) + return utils.unpad(memLoad(runState, pos, new BN(32))) }, MSTORE: function (offset, word, runState) { - offset = utils.bufferToInt(offset) + offset = new BN(offset) word = utils.setLengthLeft(word, 32) - memStore(runState, offset, word, 0, 32) + memStore(runState, offset, word, new BN(0), new BN(32)) }, MSTORE8: function (offset, byte, runState) { - offset = utils.bufferToInt(offset) + offset = new BN(offset) // grab the last byte byte = byte.slice(byte.length - 1) - memStore(runState, offset, byte, 0, 1) + memStore(runState, offset, byte, new BN(0), new BN(1)) }, SLOAD: function (key, runState, cb) { var stateManager = runState.stateManager @@ -448,7 +457,7 @@ module.exports = { return utils.intToBuffer(runState.programCounter - 1) }, MSIZE: function (runState) { - return utils.intToBuffer(runState.memoryWordCount * 32) + return runState.memoryWordCount.muln(32).toArrayLike(Buffer, 'be', 32) }, GAS: function (runState) { return runState.gasLeft.toArrayLike(Buffer, 'be', 32) @@ -494,11 +503,11 @@ module.exports = { return utils.setLengthLeft(a, 32) }) - memOffset = utils.bufferToInt(memOffset) - memLength = utils.bufferToInt(memLength) + memOffset = new BN(memOffset) + memLength = new BN(memLength) const numOfTopics = runState.opCode - 0xa0 const mem = memLoad(runState, memOffset, memLength) - subGas(runState, new BN(fees.logTopicGas.v).imuln(numOfTopics).iadd(new BN(fees.logDataGas.v).imuln(memLength))) + subGas(runState, new BN(fees.logTopicGas.v).imuln(numOfTopics).iadd(memLength.muln(fees.logDataGas.v))) // add address var log = [runState.address] @@ -516,8 +525,8 @@ module.exports = { } value = new BN(value) - offset = utils.bufferToInt(offset) - length = utils.bufferToInt(length) + offset = new BN(offset) + length = new BN(length) var data = memLoad(runState, offset, length) @@ -529,8 +538,8 @@ module.exports = { var localOpts = { inOffset: offset, inLength: length, - outOffset: 0, - outLength: 0 + outOffset: new BN(0), + outLength: new BN(0) } checkCallMemCost(runState, options, localOpts) @@ -542,10 +551,10 @@ module.exports = { gasLimit = new BN(gasLimit) toAddress = utils.setLengthLeft(toAddress, 20) value = new BN(value) - inOffset = utils.bufferToInt(inOffset) - inLength = utils.bufferToInt(inLength) - outOffset = utils.bufferToInt(outOffset) - outLength = utils.bufferToInt(outLength) + inOffset = new BN(inOffset) + inLength = new BN(inLength) + outOffset = new BN(outOffset) + outLength = new BN(outLength) if (runState.static && !value.isZero()) { trap(ERROR.STATIC_STATE_CHANGE) @@ -617,10 +626,10 @@ module.exports = { gas = new BN(gas) toAddress = utils.setLengthLeft(toAddress, 20) value = new BN(value) - inOffset = utils.bufferToInt(inOffset) - inLength = utils.bufferToInt(inLength) - outOffset = utils.bufferToInt(outOffset) - outLength = utils.bufferToInt(outLength) + inOffset = new BN(inOffset) + inLength = new BN(inLength) + outOffset = new BN(outOffset) + outLength = new BN(outLength) var data = memLoad(runState, inOffset, inLength) @@ -673,10 +682,10 @@ module.exports = { var value = runState.callValue gas = new BN(gas) toAddress = utils.setLengthLeft(toAddress, 20) - inOffset = utils.bufferToInt(inOffset) - inLength = utils.bufferToInt(inLength) - outOffset = utils.bufferToInt(outOffset) - outLength = utils.bufferToInt(outLength) + inOffset = new BN(inOffset) + inLength = new BN(inLength) + outOffset = new BN(outOffset) + outLength = new BN(outLength) var data = memLoad(runState, inOffset, inLength) @@ -722,10 +731,10 @@ module.exports = { gasLimit = new BN(gasLimit) toAddress = utils.setLengthLeft(toAddress, 20) var value = new BN(0) - inOffset = utils.bufferToInt(inOffset) - inLength = utils.bufferToInt(inLength) - outOffset = utils.bufferToInt(outOffset) - outLength = utils.bufferToInt(outLength) + inOffset = new BN(inOffset) + inLength = new BN(inLength) + outOffset = new BN(outOffset) + outLength = new BN(outLength) var data = memLoad(runState, inOffset, inLength) @@ -769,13 +778,13 @@ module.exports = { }) }, RETURN: function (offset, length, runState) { - offset = utils.bufferToInt(offset) - length = utils.bufferToInt(length) + offset = new BN(offset) + length = new BN(length) runState.returnValue = memLoad(runState, offset, length) }, REVERT: function (offset, length, runState) { - offset = utils.bufferToInt(offset) - length = utils.bufferToInt(length) + offset = new BN(offset) + length = new BN(length) runState.stopped = true runState.returnValue = memLoad(runState, offset, length) @@ -860,27 +869,18 @@ function trap (err) { /** * Subtracts the amount needed for memory usage from `runState.gasLeft` * @method subMemUsage - * @param {Number} offset - * @param {Number} length + * @param {BN} offset + * @param {BN} length * @return {String} */ function subMemUsage (runState, offset, length) { // YP (225): access with zero length will not extend the memory - if (!length) return - - // hacky: if the dataOffset is larger than the largest safeInt then just - // load 0's because if tx.data did have that amount of data then the fee - // would be high than the maxGasLimit in the block - if (offset > MAX_INT || length > MAX_INT) { - runState.gasLeft = new BN(0) - trap(ERROR.OUT_OF_GAS) - } - - const newMemoryWordCount = Math.ceil((offset + length) / 32) + if (length.isZero()) return - if (newMemoryWordCount <= runState.memoryWordCount) return + const newMemoryWordCount = offset.add(length).divCeil(new BN(32)) + if (newMemoryWordCount.lte(runState.memoryWordCount)) return - const words = new BN(newMemoryWordCount) + const words = newMemoryWordCount const fee = new BN(fees.memoryGas.v) const quadCoeff = new BN(fees.quadCoeffDiv.v) // words * 3 + words ^2 / 512 @@ -899,8 +899,8 @@ function subMemUsage (runState, offset, length) { * a string is instead returned. The function also subtracts the amount of * gas need for memory expansion. * @method memLoad - * @param {Number} offset where to start reading from - * @param {Number} length how far to read + * @param {BN} offset where to start reading from + * @param {BN} length how far to read * @return {Buffer|String} */ function memLoad (runState, offset, length) { @@ -908,14 +908,18 @@ function memLoad (runState, offset, length) { subMemUsage(runState, offset, length) // shortcut - if (length === 0) { + if (length.isZero()) { return new Buffer('') } + // NOTE: in theory this could overflow, but unlikely due to OOG above + offset = offset.toNumber() + length = length.toNumber() + var loaded = runState.memory.slice(offset, offset + length) // fill the remaining lenth with zeros for (var i = loaded.length; i < length; i++) { - loaded.push(0) + loaded[i] = 0 } return Buffer.from(loaded) } @@ -924,8 +928,11 @@ function memLoad (runState, offset, length) { * Stores bytes to memory. If an error occurs a string is instead returned. * The function also subtracts the amount of gas need for memory expansion. * @method memStore - * @param {Number} offset where to start reading from - * @param {Number} length how far to read + * @param {BN} offset where to start reading from + * @param {Buffer} val + * @param {BN} valOffset + * @param {BN} length how far to read + * @param {Boolean} skipSubMem * @return {Buffer|String} */ function memStore (runState, offset, val, valOffset, length, skipSubMem) { @@ -934,15 +941,43 @@ function memStore (runState, offset, val, valOffset, length, skipSubMem) { } // shortcut - if (length === 0) { + if (length.isZero()) { return } - var valLength = Math.min(val.length, length) + // NOTE: in theory this could overflow, but unlikely due to OOG above + offset = offset.toNumber() + length = length.toNumber() + + var safeLen = 0 + if (valOffset.addn(length).gtn(val.length)) { + if (valOffset.gte(new BN(val.length))) { + safeLen = 0 + } else { + valOffset = valOffset.toNumber() + safeLen = val.length - valOffset + } + } else { + valOffset = valOffset.toNumber() + safeLen = val.length + } + + let i = 0 + if (safeLen > 0) { + safeLen = safeLen > length ? length : safeLen + for (; i < safeLen; i++) { + runState.memory[offset + i] = val[valOffset + i] + } + } - // read max possible from the value - for (var i = 0; i < valLength; i++) { - runState.memory[offset + i] = val[valOffset + i] + /* + pad the remaining length with zeros IF AND ONLY IF a value was stored + (even if value offset > value length, strange spec...) + */ + if (val.length > 0 && i < length) { + for (; i < length; i++) { + runState.memory[offset + i] = 0 + } } } @@ -958,7 +993,7 @@ function checkCallMemCost (runState, callOptions, localOpts) { subMemUsage(runState, localOpts.outOffset, localOpts.outLength) if (!callOptions.gasLimit) { - callOptions.gasLimit = runState.gasLeft + callOptions.gasLimit = new BN(runState.gasLeft) } } @@ -1018,7 +1053,7 @@ function makeCall (runState, callOptions, localOpts, cb) { // save results to memory if (results.vm.return && (!results.vm.exceptionError || results.vm.exceptionError.error === ERROR.REVERT)) { - memStore(runState, localOpts.outOffset, results.vm.return, 0, localOpts.outLength, false) + memStore(runState, localOpts.outOffset, results.vm.return, new BN(0), localOpts.outLength, false) if (results.vm.exceptionError && results.vm.exceptionError.error === ERROR.REVERT && runState.opName === 'CREATE') { runState.lastReturned = results.vm.return diff --git a/lib/runCall.js b/lib/runCall.js index 0b4caa30d2..107ce942fd 100644 --- a/lib/runCall.js +++ b/lib/runCall.js @@ -192,7 +192,7 @@ module.exports = function (opts, cb) { function saveCode (cb) { // store code for a new contract - if (createdAddress && !vmResults.runState.vmError && vmResults.return.toString() !== '') { + if (createdAddress && !vmResults.runState.vmError && vmResults.return && vmResults.return.toString() !== '') { stateManager.putContractCode(createdAddress, vmResults.return, cb) } else { cb() diff --git a/lib/runCode.js b/lib/runCode.js index 1df0bb84a7..d3e1d2d999 100644 --- a/lib/runCode.js +++ b/lib/runCode.js @@ -57,7 +57,7 @@ module.exports = function (opts, cb) { gasLimit: new BN(opts.gasLimit), gasPrice: opts.gasPrice, memory: [], - memoryWordCount: 0, + memoryWordCount: new BN(0), stack: [], lastReturned: [], logs: [],