diff --git a/lib/opFns.js b/lib/opFns.js index b9c8e1b1b7f..888fa1ed6f7 100644 --- a/lib/opFns.js +++ b/lib/opFns.js @@ -514,6 +514,37 @@ module.exports = { checkOutOfGas(runState, options) makeCall(runState, options, localOpts, done) }, + CREATE2: function (value, offset, length, salt, runState, done) { + if (!runState._common.gteHardfork('constantinople')) { + trap(ERROR.INVALID_OPCODE) + } + + if (runState.static) { + trap(ERROR.STATIC_STATE_CHANGE) + } + + var data = memLoad(runState, offset, length) + + // set up config + var options = { + value: value, + data: data, + salt: salt.toBuffer('be', 32) + } + + var localOpts = { + inOffset: offset, + inLength: length, + outOffset: new BN(0), + outLength: new BN(0) + } + + // Deduct gas costs for hashing + subGas(runState, new BN(runState._common.param('gasPrices', 'sha3Word')).imul(length.divCeil(new BN(32)))) + checkCallMemCost(runState, options, localOpts) + checkOutOfGas(runState, options) + makeCall(runState, options, localOpts, done) + }, CALL: function (gasLimit, toAddress, value, inOffset, inLength, outOffset, outLength, runState, done) { var stateManager = runState.stateManager toAddress = addressToBuffer(toAddress) @@ -980,7 +1011,7 @@ function makeCall (runState, callOptions, localOpts, cb) { if (results.vm.return && (!results.vm.exceptionError || results.vm.exceptionError.error === ERROR.REVERT)) { 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') { + if (results.vm.exceptionError && results.vm.exceptionError.error === ERROR.REVERT && isCreateOpCode(runState.opName)) { runState.lastReturned = results.vm.return } @@ -1018,6 +1049,10 @@ function makeCall (runState, callOptions, localOpts, cb) { } } +function isCreateOpCode (opName) { + return opName === 'CREATE' || opName === 'CREATE2' +} + function getContractStorage (runState, address, key, cb) { if (runState._common.gteHardfork('constantinople')) { async.parallel({ diff --git a/lib/opcodes.js b/lib/opcodes.js index fe9c57cf9eb..bc23ce977ef 100644 --- a/lib/opcodes.js +++ b/lib/opcodes.js @@ -152,6 +152,7 @@ const codes = { 0xf2: ['CALLCODE', 700, 7, 1, true, true], 0xf3: ['RETURN', 0, 2, 0, false], 0xf4: ['DELEGATECALL', 700, 6, 1, true, true], + 0xf5: ['CREATE2', 32000, 4, 1, true, true], 0xfa: ['STATICCALL', 700, 6, 1, true, true], 0xfd: ['REVERT', 0, 2, 0, false], diff --git a/lib/runCall.js b/lib/runCall.js index ed8e4ca67bb..dd5a871b5f0 100644 --- a/lib/runCall.js +++ b/lib/runCall.js @@ -47,6 +47,7 @@ module.exports = function (opts, cb) { var selfdestruct = opts.selfdestruct || opts.suicides var delegatecall = opts.delegatecall || false var isStatic = opts.static || false + var salt = opts.salt || null txValue = new BN(txValue) @@ -77,41 +78,14 @@ module.exports = function (opts, cb) { code = txData txData = undefined var newNonce = new BN(account.nonce).subn(1) - createdAddress = toAddress = ethUtil.generateAddress(caller, newNonce.toArray()) - stateManager.clearContractStorage(createdAddress, function (err) { - if (err) { - done(err) - } - - async.series([ - newContractEvent, - getAccount - ], done) - function newContractEvent (callback) { - /** - * The `newContract` event when a contract is created - * - * @event Event: newContract - * @type {Object} - * @property {Buffer} address the created address for the new contract (type `Buffer | Uint8Array`) - * @property {Buffer} code the deployment bytecode for reference (type `Buffer | Uint8Array`) - */ - self.emit('newContract', { - address: createdAddress, - code: code - }, callback) - } + if (salt) { + createdAddress = toAddress = ethUtil.generateAddress2(caller, salt, code) + } else { + createdAddress = toAddress = ethUtil.generateAddress(caller, newNonce.toArray()) + } - function getAccount (callback) { - stateManager.getAccount(createdAddress, function (err, account) { - toAccount = account - const NONCE_OFFSET = 1 - toAccount.nonce = new BN(toAccount.nonce).addn(NONCE_OFFSET).toArrayLike(Buffer) - callback(err) - }) - } - }) + checkAccountState(createdAddress, setupNewContract, done) } else { // else load the `to` account stateManager.getAccount(toAddress, function (err, account) { @@ -121,6 +95,53 @@ module.exports = function (opts, cb) { } } + function checkAccountState (address, next, done) { + stateManager.getAccount(address, function (err, account) { + if (err) { + done(err) + return + } + + if ((account.nonce && new BN(account.nonce) > 0) || account.codeHash.compare(ethUtil.KECCAK256_NULL) !== 0) { + toAccount = account + code = new Buffer('fe', 'hex') // Invalid init code + done() + return + } + + next(address, done) + }) + } + + function setupNewContract (address, done) { + stateManager.clearContractStorage(address, function (err) { + if (err) { + done(err) + return + } + + async.series([ + newContractEvent, + getAccount + ], done) + + function newContractEvent (callback) { + self.emit('newContract', { + address: address, + code: code + }, callback) + } + + function getAccount (callback) { + stateManager.getAccount(address, function (err, account) { + toAccount = account + toAccount.nonce = new BN(toAccount.nonce).addn(1).toArrayLike(Buffer) + callback(err) + }) + } + }) + } + function subTxValue (cb) { if (delegatecall) { cb() @@ -199,7 +220,6 @@ module.exports = function (opts, cb) { var totalGas = results.gasUsed if (!results.runState.vmError) { var returnFee = new BN(results.return.length * self._common.param('gasPrices', 'createData')) - totalGas = totalGas.add(returnFee) } // if not enough gas