sets up and calls runCall
function makeCall (runState, callOptions, localOpts, cb) {
  var selfdestruct = Object.assign({}, runState.selfdestruct)
  callOptions.caller = callOptions.caller || runState.address
  callOptions.origin = runState.origin
  callOptions.gasPrice = runState.gasPrice
  callOptions.block = runState.block
  callOptions.static = callOptions.static || false
  callOptions.selfdestruct = selfdestruct
  callOptions.storageReader = runState.storageReader

  // increment the runState.depth
  callOptions.depth = runState.depth + 1

  // empty the return data buffer
  runState.lastReturned = Buffer.alloc(0)

  // check if account has enough ether
  // Note: in the case of delegatecall, the value is persisted and doesn't need to be deducted again
  if (runState.depth >= runState._common.param('vm', 'stackLimit') || (callOptions.delegatecall !== true && new BN(runState.contract.balance).lt(callOptions.value))) {
    runState.stack.push(new BN(0))
    cb(null)
  } else {
    // if creating a new contract then increament the nonce
    if (!callOptions.to) {
      runState.contract.nonce = new BN(runState.contract.nonce).addn(1)
    }

    runState.stateManager.putAccount(runState.address, runState.contract, function (err) {
      if (err) return cb(err)
      runState._vm.runCall(callOptions, parseCallResults)
    })
  }

  function parseCallResults (err, results) {
    if (err) return cb(err)

    // concat the runState.logs
    if (results.vm.logs) {
      runState.logs = runState.logs.concat(results.vm.logs)
    }

    // add gasRefund
    if (results.vm.gasRefund) {
      runState.gasRefund = runState.gasRefund.add(results.vm.gasRefund)
    }

    // this should always be safe
    runState.gasLeft.isub(results.gasUsed)

    // save results to memory
    if (results.vm.return && (!results.vm.exceptionError || results.vm.exceptionError.error === ERROR.REVERT)) {
      if (results.vm.return.length > 0) {
        const memOffset = localOpts.outOffset.toNumber()
        let dataLength = localOpts.outLength.toNumber()
        if (results.vm.return.length < dataLength) {
          dataLength = results.vm.return.length
        }
        const data = getDataSlice(results.vm.return, new BN(0), new BN(dataLength))
        runState.memory.extend(memOffset, dataLength)
        runState.memory.write(memOffset, dataLength, data)
      }

      if (results.vm.exceptionError && results.vm.exceptionError.error === ERROR.REVERT && isCreateOpCode(runState.opName)) {
        runState.lastReturned = results.vm.return
      }

      switch (runState.opName) {
        case 'CALL':
        case 'CALLCODE':
        case 'DELEGATECALL':
        case 'STATICCALL':
          runState.lastReturned = results.vm.return
          break
      }
    }

    if (!results.vm.exceptionError) {
      Object.assign(runState.selfdestruct, selfdestruct)
      // update stateRoot on current contract
      runState.stateManager.getAccount(runState.address, function (err, account) {
        if (err) return cb(err)

        runState.contract = account
        // push the created address to the stack
        if (results.createdAddress) {
          runState.stack.push(new BN(results.createdAddress))
          cb(null)
        } else {
          runState.stack.push(new BN(results.vm.exception))
          cb(null)
        }
      })
    } else {
      // creation failed so don't increment the nonce
      if (results.vm.createdAddress) {
        runState.contract.nonce = new BN(runState.contract.nonce).subn(1)
      }

      runState.stack.push(new BN(results.vm.exception))
      cb(null)
    }
  }
}