UNPKG

vaporyjs-vm

Version:
192 lines (169 loc) 4.99 kB
const async = require('async') const vapUtil = require('vaporyjs-util') const BN = vapUtil.BN const fees = require('vapory-common') const constants = require('./constants.js') const ERROR = constants.ERROR /** * runs a CALL operation * @method runCall * @param opts * @param opts.block {Block} * @param opts.caller {Buffer} * @param opts.code {Buffer} this is for CALLCODE where the code to load is different than the code from the to account. * @param opts.data {Buffer} * @param opts.gasLimit {Buffer | BN.js } * @param opts.gasPrice {Buffer} * @param opts.origin {Buffer} [] * @param opts.to {Buffer} * @param opts.value {Buffer} */ module.exports = function (opts, cb) { var self = this var stateManager = self.stateManager var vmResults = {} var toAccount var toAddress = opts.to var createdAddress var txValue = opts.value || Buffer.from(0) var caller = opts.caller var account = stateManager.cache.get(caller) var block = opts.block var code = opts.code var txData = opts.data var gasLimit = opts.gasLimit || new BN(0xffffff) gasLimit = new BN(opts.gasLimit) // make sure is a BN var gasPrice = opts.gasPrice var gasUsed = new BN(0) var origin = opts.origin var isCompiled = opts.compiled var depth = opts.depth var suicides = opts.suicides var enableHomestead = this.opts.enableHomestead === undefined ? block.isHomestead() : this.opts.enableHomestead txValue = new BN(txValue) stateManager.checkpoint() // run and parse subTxValue() async.series([ loadToAccount, loadCode, runCode, saveCode ], parseCallResult) function loadToAccount (done) { // get receiver's account // toAccount = stateManager.cache.get(toAddress) if (!toAddress) { // generate a new contract if no `to` code = txData txData = undefined var newNonce = new BN(account.nonce).subn(1) createdAddress = toAddress = vapUtil.generateAddress(caller, newNonce.toArray()) stateManager.getAccount(createdAddress, function (err, account) { toAccount = account done(err) }) } else { // else load the `to` account toAccount = stateManager.cache.get(toAddress) done() } } function subTxValue () { account.balance = new BN(account.balance).sub(txValue) stateManager.cache.put(caller, account) } function addTxValue () { // add the amount sent to the `to` account toAccount.balance = new BN(toAccount.balance).add(txValue) stateManager.cache.put(toAddress, toAccount) } function loadCode (cb) { addTxValue() // loads the contract's code if the account is a contract if (code || !(toAccount.isContract() || vapUtil.isPrecompiled(toAddress))) { cb() return } if (vapUtil.isPrecompiled(toAddress)) { isCompiled = true code = self._precompiled[toAddress.toString('hex')] cb() return } stateManager.getContractCode(toAddress, function (err, c, comp) { if (err) return cb(err) isCompiled = comp code = c cb() }) } function runCode (cb) { if (!code) { vmResults.exception = 1 stateManager.commit(cb) return } var runCodeOpts = { code: code, data: txData, gasLimit: gasLimit, gasPrice: gasPrice, address: toAddress, origin: origin, caller: caller, value: Buffer.from(txValue.toArray()), block: block, depth: depth, suicides: suicides, populateCache: false } // run Code through vm var codeRunner = isCompiled ? self.runJIT : self.runCode codeRunner.call(self, runCodeOpts, parseRunResult) function parseRunResult (err, results) { toAccount = self.stateManager.cache.get(toAddress) vmResults = results if (createdAddress) { // fee for size of the return value var returnFee = results.return.length * fees.createDataGas.v var totalGas = results.gasUsed.addn(returnFee) // if not enough gas if (totalGas.cmp(gasLimit) <= 0) { results.gasUsed = totalGas } else { results.return = Buffer.from([]) if (enableHomestead) { results.exception = 0 err = results.exceptionError = ERROR.OUT_OF_GAS results.gasUsed = gasLimit } } } gasUsed = results.gasUsed if (err) { results.logs = [] stateManager.revert(cb) } else { stateManager.commit(cb) } } } function saveCode (cb) { // store code for a new contract if (createdAddress && vmResults.return.toString() !== '') { stateManager.putContractCode(createdAddress, vmResults.return, cb) } else { cb() } } function parseCallResult (err) { if (err) return cb(err) var results = { gasUsed: gasUsed, createdAddress: createdAddress, vm: vmResults } cb(null, results) } }