sets up the environment and runs a `call`
function runCall (cb) {
    var options = {
      caller: tx.from,
      gasLimit: gasLimit,
      gasPrice: tx.gasPrice,
      to: tx.to,
      value: tx.value,
      data: tx.data,
      block: block,
      storageReader: storageReader
    }

    if (tx.to.toString('hex') === '') {
      delete options.to
    }

    // run call
    self.runCall(options, parseResults)

    function parseResults (err, _results) {
      if (err) return cb(err)
      results = _results

      // generate the bloom for the tx
      results.bloom = txLogsBloom(results.vm.logs)

      // caculate the total gas used
      results.gasUsed = results.gasUsed.add(basefee)

      // process any gas refund
      results.gasRefund = results.vm.gasRefund
      if (results.gasRefund) {
        if (results.gasRefund.lt(results.gasUsed.divn(2))) {
          results.gasUsed.isub(results.gasRefund)
        } else {
          results.gasUsed.isub(results.gasUsed.divn(2))
        }
      }

      results.amountSpent = results.gasUsed.mul(new BN(tx.gasPrice))

      async.series([
        loadFromAccount,
        updateFromAccount,
        loadMinerAccount,
        updateMinerAccount,
        cleanupAccounts
      ], cb)

      var fromAccount
      function loadFromAccount (next) {
        self.stateManager.getAccount(tx.from, function (err, account) {
          fromAccount = account
          next(err)
        })
      }

      function updateFromAccount (next) {
        // refund the leftover gas amount
        var finalFromBalance = new BN(tx.gasLimit).sub(results.gasUsed)
          .mul(new BN(tx.gasPrice))
          .add(new BN(fromAccount.balance))
        fromAccount.balance = finalFromBalance

        self.stateManager.putAccount(utils.toBuffer(tx.from), fromAccount, next)
      }

      var minerAccount
      function loadMinerAccount (next) {
        self.stateManager.getAccount(block.header.coinbase, function (err, account) {
          minerAccount = account
          next(err)
        })
      }

      function updateMinerAccount (next) {
        // add the amount spent on gas to the miner's account
        minerAccount.balance = new BN(minerAccount.balance)
          .add(results.amountSpent)

        // save the miner's account
        if (!(new BN(minerAccount.balance).isZero())) {
          self.stateManager.putAccount(block.header.coinbase, minerAccount, next)
        } else {
          next()
        }
      }

      function cleanupAccounts (next) {
        if (!results.vm.selfdestruct) {
          results.vm.selfdestruct = {}
        }

        var keys = Object.keys(results.vm.selfdestruct)

        async.series([
          deleteSelfDestructs,
          cleanTouched
        ], next)

        function deleteSelfDestructs (done) {
          async.each(keys, function (s, cb) {
            self.stateManager.putAccount(Buffer.from(s, 'hex'), new Account(), cb)
          }, done)
        }

        function cleanTouched (done) {
          self.stateManager.cleanupTouchedAccounts(done)
        }
      }
    }
  }