From 67520736e4673750a43ed3bc2bf68a98269d0cf5 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Sat, 4 Aug 2018 06:38:32 +0000 Subject: [PATCH 1/3] add support for CREATE2 trap if not constantinople add check if nonce or code at address Fix lint errors Refactoring Fix lint Use invalid init code on colission Fix lint --- lib/opFns.js | 31 ++++++++++++++++++ lib/opcodes.js | 1 + lib/runCall.js | 88 +++++++++++++++++++++++++++++++------------------- 3 files changed, 87 insertions(+), 33 deletions(-) diff --git a/lib/opFns.js b/lib/opFns.js index b9c8e1b1b7..761455777c 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 hashingq + 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) diff --git a/lib/opcodes.js b/lib/opcodes.js index fe9c57cf9e..bc23ce977e 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 ed8e4ca67b..0276613ee2 100644 --- a/lib/runCall.js +++ b/lib/runCall.js @@ -5,6 +5,7 @@ const BN = ethUtil.BN const exceptions = require('./exceptions.js') const ERROR = exceptions.ERROR +const EMPTY_CODE_HASH = ethUtil.keccak256() /** * runs a CALL operation @@ -47,6 +48,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 +79,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 +96,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(EMPTY_CODE_HASH) !== 0) { + toAccount = account + code = new Buffer('0xfe', '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() From a8abcfa25dd01d8c153d707d1cad9993964c45ea Mon Sep 17 00:00:00 2001 From: Richard Meissner Date: Sun, 4 Nov 2018 00:04:44 +0100 Subject: [PATCH 2/3] Fix creation of invalid code --- lib/runCall.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/runCall.js b/lib/runCall.js index 0276613ee2..76f26a4566 100644 --- a/lib/runCall.js +++ b/lib/runCall.js @@ -105,7 +105,7 @@ module.exports = function (opts, cb) { if ((account.nonce && new BN(account.nonce) > 0) || account.codeHash.compare(EMPTY_CODE_HASH) !== 0) { toAccount = account - code = new Buffer('0xfe', 'hex') // Invalid init code + code = new Buffer('fe', 'hex') // Invalid init code done() return } From 6efe3d1c9a4aae519c98780c759e3e29a42aae36 Mon Sep 17 00:00:00 2001 From: Richard Meissner Date: Sun, 4 Nov 2018 00:30:52 +0100 Subject: [PATCH 3/3] Handle revert in create2 with message --- lib/opFns.js | 8 ++++++-- lib/runCall.js | 4 +--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/opFns.js b/lib/opFns.js index 761455777c..888fa1ed6f 100644 --- a/lib/opFns.js +++ b/lib/opFns.js @@ -539,7 +539,7 @@ module.exports = { outLength: new BN(0) } - // Deduct gas costs for hashingq + // 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) @@ -1011,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 } @@ -1049,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/runCall.js b/lib/runCall.js index 76f26a4566..dd5a871b5f 100644 --- a/lib/runCall.js +++ b/lib/runCall.js @@ -5,7 +5,6 @@ const BN = ethUtil.BN const exceptions = require('./exceptions.js') const ERROR = exceptions.ERROR -const EMPTY_CODE_HASH = ethUtil.keccak256() /** * runs a CALL operation @@ -103,7 +102,7 @@ module.exports = function (opts, cb) { return } - if ((account.nonce && new BN(account.nonce) > 0) || account.codeHash.compare(EMPTY_CODE_HASH) !== 0) { + 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() @@ -221,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