Beispiel #1
0
    def test_logging(self):
        set_verbosity(5)
        self.assertEqual(get_verbosity('manticore.native.cpu.abstractcpu'), logging.DEBUG)
        self.assertEqual(get_verbosity('manticore.ethereum.abi'), logging.DEBUG)

        set_verbosity(1)
        self.assertEqual(get_verbosity('manticore.native.cpu.abstractcpu'), logging.WARNING)
        self.assertEqual(get_verbosity('manticore.ethereum.abi'), logging.INFO)
Beispiel #2
0
    def test_logging(self):
        set_verbosity(1)
        self.assertEqual(get_verbosity("manticore.native.cpu.abstractcpu"),
                         logging.WARNING)
        self.assertEqual(get_verbosity("manticore.ethereum.abi"), logging.INFO)

        set_verbosity(0)
        self.assertEqual(get_verbosity("manticore.native.cpu.abstractcpu"),
                         DEFAULT_LOG_LEVEL)
        self.assertEqual(get_verbosity("manticore.ethereum.abi"),
                         DEFAULT_LOG_LEVEL)
Beispiel #3
0
def do_run_test(state, apis, test, workspace, hook_test=False):
    """Run an individual test case."""
    state.cpu.PC = test.ea

    mc = DeepManticore(state)
    mc.context['apis'] = apis

    # Tell the system that we're using symbolic execution.
    mc.write_uint32_t(apis["UsingSymExec"], 8589934591)

    mc.begin_test(test)

    del mc

    # NOTE(alan): cannot init State with new native.Manticore in 0.3.0 as it
    # will try to delete the non-existent stored state from new workspace
    m = manticore.native.Manticore(state,
                                   sys.argv[1:],
                                   workspace_url=workspace)
    log.set_verbosity(1)

    m.add_hook(apis['IsSymbolicUInt'], hook(hook_IsSymbolicUInt))
    m.add_hook(apis['ConcretizeData'], hook(hook_ConcretizeData))
    m.add_hook(apis['ConcretizeCStr'], hook(hook_ConcretizeCStr))
    m.add_hook(apis['MinUInt'], hook(hook_MinUInt))
    m.add_hook(apis['MaxUInt'], hook(hook_MaxUInt))
    m.add_hook(apis['Assume'], hook(hook_Assume))
    m.add_hook(apis['Pass'], hook(hook_Pass))
    m.add_hook(apis['Crash'], hook(hook_Crash))
    m.add_hook(apis['Fail'], hook(hook_Fail))
    m.add_hook(apis['SoftFail'], hook(hook_SoftFail))
    m.add_hook(apis['Abandon'], hook(hook_Abandon))
    m.add_hook(apis['Log'], hook(hook_Log))
    m.add_hook(apis['StreamInt'], hook(hook_StreamInt))
    m.add_hook(apis['StreamFloat'], hook(hook_StreamFloat))
    m.add_hook(apis['StreamString'], hook(hook_StreamString))
    m.add_hook(apis['ClearStream'], hook(hook_ClearStream))
    m.add_hook(apis['LogStream'], hook(hook_LogStream))

    if hook_test:
        m.add_hook(test.ea, hook(hook_TakeOver))

    m.subscribe('will_terminate_state', done_test)

    # attempts to kill after consts.timeout
    with m.kill_timeout(consts.timeout):
        m.run()

    # if Manticore is stuck, forcefully kill all workers
    m.kill()
Beispiel #4
0
    def test_profiling_data(self):
        p = Profiler()
        set_verbosity(0)
        self.m.register_plugin(p)
        self.m.run()
        self.m.finalize()
        profile_path = os.path.join(self.m.workspace, "profiling.bin")
        self.assertTrue(os.path.exists(profile_path))
        self.assertTrue(os.path.getsize(profile_path) > 0)

        profile_path_2 = os.path.join(self.m.workspace, "profiling_2.bin")
        with open(profile_path_2, "wb") as f:
            p.save_profiling_data(f)

        self.assertTrue(os.path.exists(profile_path_2))
        self.assertTrue(os.path.getsize(profile_path_2) > 0)

        self.assertTrue(filecmp.cmp(profile_path, profile_path_2))
Beispiel #5
0
def main():
  args = DeepManticore.parse_args()

  consts.procs = args.num_workers
  consts.timeout = args.timeout
  consts.mprocessing = consts.mprocessing.single

  try:
    m = manticore.native.Manticore(args.binary)
  except Exception as e:
    L.critical("Cannot create Manticore instance on binary %s: %s", args.binary, e)
    return 1

  log.set_verbosity(args.verbosity)

  if args.take_over:
    return main_takeover(m, args, 'DeepState_TakeOver')
  elif args.klee:
    return main_takeover(m, args, 'main')
  else:
    return main_unit_test(m, args)
Beispiel #6
0
import os
import shutil
from eth_general import make_mock_evm_state

from manticore.core.smtlib import operators
from manticore.ethereum import ManticoreEVM, DetectIntegerOverflow, DetectUnusedRetVal, DetectSelfdestruct, \
    LoopDepthLimiter, DetectDelegatecall, \
    DetectExternalCallAndLeak, DetectEnvInstruction, DetectRaceCondition

THIS_DIR = os.path.dirname(os.path.abspath(__file__))

# FIXME(mark): Remove these two lines when logging works for ManticoreEVM
from manticore.utils.log import init_logging, set_verbosity

init_logging()
set_verbosity(0)


class EthDetectorTest(unittest.TestCase):
    """
    Subclasses must assign this class variable to the class for the detector
    """
    DETECTOR_CLASS = None

    def setUp(self):
        self.mevm = ManticoreEVM()
        self.mevm.verbosity(0)
        self.worksp = self.mevm.workspace

    def tearDown(self):
        self.mevm = None
Beispiel #7
0
def main():
    from crytic_compile import is_supported, cryticparser

    parser = argparse.ArgumentParser(
        description="Solidity property verifier",
        prog="manticore_verifier",
        # formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )

    # Add crytic compile arguments
    # See https://github.com/crytic/crytic-compile/wiki/Configuration
    cryticparser.init(parser)

    parser.add_argument(
        "source_code",
        type=str,
        nargs="*",
        default=[],
        help="Contract source code",
    )
    parser.add_argument("-v",
                        action="count",
                        default=0,
                        help="Specify verbosity level from -v to -vvvv")

    parser.add_argument(
        "--workspace",
        type=str,
        default=None,
        help=("A folder name for temporaries and results."
              "(default mcore_?????)"),
    )

    current_version = pkg_resources.get_distribution("manticore").version
    parser.add_argument(
        "--version",
        action="version",
        version=f"Manticore {current_version}",
        help="Show program version information",
    )
    parser.add_argument(
        "--propconfig",
        type=str,
        help="Solidity property accounts config file (.yml)",
    )
    eth_flags = parser.add_argument_group("Ethereum flags")

    eth_flags.add_argument(
        "--quick-mode",
        action="store_true",
        help=
        "Configure Manticore for quick exploration. Disable gas, generate testcase only for alive states, "
        "do not explore constant functions. Disable all detectors.",
    )
    eth_flags.add_argument(
        "--contract_name",
        type=str,
        help="The target contract name defined in the source code")
    eth_flags.add_argument(
        "--maxfail",
        type=int,
        help="stop after maxfail properties are failing. All if None")
    eth_flags.add_argument(
        "--maxcov",
        type=int,
        default=100,
        help=" Stop after maxcov %% coverage is obtained in the main contract",
    )
    eth_flags.add_argument("--maxt",
                           type=int,
                           default=3,
                           help="Max transaction count to explore")
    eth_flags.add_argument(
        "--deployer",
        type=str,
        help="(optional) address of account used to deploy the contract")
    eth_flags.add_argument(
        "--senders",
        type=str,
        help=
        "(optional) a comma separated list of sender addresses. The properties are going to be tested sending transactions from these addresses.",
    )
    eth_flags.add_argument(
        "--psender",
        type=str,
        help="(optional) address from where the property is tested")
    eth_flags.add_argument(
        "--propre",
        default=r"crytic_.*",
        type=str,
        help="A regular expression for selecting properties",
    )
    eth_flags.add_argument("--timeout",
                           default=240,
                           type=int,
                           help="Exploration timeout in seconds")
    eth_flags.add_argument("--outputspace_url",
                           type=str,
                           help="where to put the extended result")

    config_flags = parser.add_argument_group("Constants")
    config.add_config_vars_to_argparse(config_flags)

    parsed = parser.parse_args(sys.argv[1:])
    config.process_config_values(parser, parsed)

    if not parsed.source_code:
        print(parser.format_usage() +
              "error: You need to provide a contract source code.")
        sys.exit(1)
    args = parsed
    set_verbosity(args.v)
    logger = logging.getLogger("manticore.main")

    # read yaml config file
    deployer = None
    senders = None
    psenders = None
    if args.propconfig:
        """
        deployer: "0x41414141414141414141"  #who deploys the contract
        sender: ["0x51515151515151515151", "0x52525252525252525252"] #who calls the transactions (potentially can be multiple users)
        psender: "0x616161616161616161" #who calls the property
        """
        import yaml

        with open(args.propconfig) as f:
            c = yaml.safe_load(f)
            deployer = c.get("deployer")
            if deployer is not None:
                deployer = int(deployer, 0)

            senders = c.get("sender")
            if senders is not None:
                senders = [int(sender, 0) for sender in senders]

            psender = c.get("psender")
            if psender is not None:
                psender = int(psender, 0)

    # override with commandline args
    deployer = None
    if args.deployer is not None:
        deployer = int(args.deployer, 0)

    senders = None
    if args.senders is not None:
        senders = [int(sender, 0) for sender in args.senders.split(",")]

    psender = None
    if args.psender is not None:
        psender = int(args.psender, 0)

    source_code = args.source_code[0]
    contract_name = args.contract_name
    maxfail = args.maxfail
    maxt = args.maxt
    maxcov = args.maxcov
    return manticore_verifier(source_code,
                              contract_name,
                              maxfail=maxfail,
                              maxt=maxt,
                              maxcov=100,
                              senders=senders,
                              deployer=deployer,
                              psender=psender,
                              timeout=args.timeout,
                              propre=args.propre,
                              compile_args=vars(parsed))
Beispiel #8
0
 def setUp(self):
     self.mevm = ManticoreEVM()
     self.mevm.register_plugin(KeepOnlyIfStorageChanges())
     log.set_verbosity(0)
     self.worksp = self.mevm.workspace
Beispiel #9
0
from manticore.wasm import ManticoreWASM
from manticore.wasm.types import I32
from manticore.core.plugin import Plugin
from manticore.utils.log import set_verbosity


print(
    """

============ Example 1 ============
"""
)

m = ManticoreWASM("collatz.wasm")
set_verbosity(2)


def arg_gen(state):
    # Generate a symbolic argument to pass to the collatz function.
    # Possible values: 4, 6, 8
    arg = state.new_symbolic_value(32, "collatz_arg")
    state.constrain(arg > 3)
    state.constrain(arg < 9)
    state.constrain(arg % 2 == 0)
    return [arg]


# Tell Manticore to run the collatz function with the given argument generator.
# We use an argument generator function instead of a list of arguments because Manticore
# might have multiple states waiting to begin execution, and we can conveniently map a
Beispiel #10
0
def main(argv=None):
    parser = argparse.ArgumentParser(
        description='An Ethereum JSON RPC multiplexer and Manticore wrapper')
    parser.add_argument('--debug',
                        action='store_true',
                        default=False,
                        help='Enable debugging from within the web server')
    parser.add_argument(
        '--run-publicly',
        action='store_true',
        default=False,
        help='Allow the web server to accept external connections')
    parser.add_argument(
        '-p',
        '--port',
        type=int,
        default=GETH_DEFAULT_RPC_PORT,
        help='Port on which to run the JSON RPC webserver (default=%d)' %
        GETH_DEFAULT_RPC_PORT)
    parser.add_argument(
        '-a',
        '--accounts',
        type=int,
        default=None,
        help='Number of accounts to create in the client (default=10)')
    parser.add_argument(
        '-b',
        '--balance',
        type=float,
        default=100.0,
        help=
        'Default balance (in Ether) to seed to each account (default=100.0)')
    parser.add_argument('-c',
                        '--gas-price',
                        type=int,
                        default=None,
                        help='Default gas price (default=20000000000)')
    parser.add_argument(
        '-i',
        '--network-id',
        type=int,
        default=None,
        help=
        'Specify a network ID (default is the network ID of the master client)'
    )
    parser.add_argument('-m',
                        '--manticore',
                        action='store_true',
                        default=False,
                        help='Run all transactions through manticore')
    parser.add_argument(
        '-r',
        '--manticore-script',
        type=argparse.FileType('rb'),
        default=None,
        help=
        'Instead of running automated detectors and analyses, run this Manticore script'
    )
    parser.add_argument('--manticore-max-depth',
                        type=int,
                        default=None,
                        help='Maximum state depth for Manticore to explore')
    parser.add_argument(
        '-e',
        '--echidna',
        action='store_true',
        default=False,
        help='Fuzz the clients using transactions generated by Echidna')
    parser.add_argument(
        '--fuzz-limit',
        type=int,
        default=None,
        help=
        'The maximum number of transactions for Echidna to generate (default=unlimited)'
    )
    parser.add_argument(
        '--fuzz-contract',
        type=str,
        default=None,
        help=
        'Path to a Solidity contract to have Echidna use for fuzzing (default is to use a builtin '
        'generic Echidna fuzzing contract)')
    parser.add_argument(
        '-t',
        '--truffle',
        action='store_true',
        default=False,
        help='Run the truffle migrations in the current directory and exit')
    parser.add_argument('--truffle-cmd',
                        type=str,
                        default='truffle',
                        help='Command to run truffle (default=truffle)')
    parser.add_argument('--truffle-args',
                        type=str,
                        default='migrate',
                        help='Arguments to pass to truffle (default=migrate)')
    parser.add_argument(
        '-g',
        '--ganache',
        action='store_true',
        default=False,
        help=
        'Run Ganache as a master JSON RPC client (cannot be used in conjunction with --master)'
    )
    parser.add_argument('--ganache-cmd',
                        type=str,
                        default=None,
                        help='Specify a command that runs Ganache '
                        '(default="/usr/bin/env ganache-cli")')
    parser.add_argument('--ganache-args',
                        type=str,
                        default=None,
                        help='Additional arguments to pass to Ganache')
    parser.add_argument(
        '--ganache-port',
        type=int,
        default=None,
        help=
        'Port on which to run Ganache (defaults to the closest available port to the port '
        'specified with --port plus one)')
    parser.add_argument('-go',
                        '--geth',
                        action='store_true',
                        default=False,
                        help='Run Geth as a JSON RPC client')
    parser.add_argument(
        '--geth-port',
        type=int,
        default=None,
        help=
        'Port on which to run Geth (defaults to the closest available port to the port specified '
        'with --port plus one)')
    parser.add_argument('-pa',
                        '--parity',
                        action='store_true',
                        default=False,
                        help='Run Parity as a JSON RPC client')
    parser.add_argument(
        '--parity-port',
        type=int,
        default=None,
        help=
        'Port on which to run Parity (defaults to the closest available port to the port '
        'specified with --port plus one)')
    parser.add_argument(
        '-j',
        '--genesis',
        type=str,
        default=None,
        help=
        'Path to a genesis.json file to use for initializing clients. Any genesis-related options '
        'like --network-id will override the values in this file. If --accounts is greater than '
        'zero, that many new accounts will be appended to the accounts in the genesis file.'
    )
    parser.add_argument(
        '--save-genesis',
        type=str,
        default=None,
        help=
        "Save a genesis.json file to reproduce the state of this run. Note that this genesis file "
        "will include all known private keys for the genesis accounts, so use this with caution."
    )
    parser.add_argument(
        '--constantinople-block',
        type=int,
        default=None,
        help=
        'The block in which to enable Constantinople EIPs (default=do not enable Constantinople)'
    )
    parser.add_argument(
        '--constantinople',
        action='store_true',
        default=False,
        help=
        'Enables Constantinople EIPs; equivalent to `--constantinople-block 0`'
    )
    parser.add_argument(
        '--no-differential-testing',
        action='store_false',
        dest='run_differential',
        default=True,
        help='Do not run differential testing, which is run by default')
    parser.add_argument(
        '-l',
        '--log-level',
        type=str.upper,
        choices={'CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG'},
        default='INFO',
        help='Set Etheno\'s log level (default=INFO)')
    parser.add_argument('--log-file',
                        type=str,
                        default=None,
                        help='Path to save all log output to a single file')
    parser.add_argument(
        '--log-dir',
        type=str,
        default=None,
        help=
        'Path to a directory in which to save all log output, divided by logging source'
    )
    parser.add_argument(
        '-d',
        '--dump-jsonrpc',
        type=str,
        default=None,
        help=
        'Path to a JSON file in which to dump all raw JSON RPC calls; if `--log-dir` is provided, '
        'the raw JSON RPC calls will additionally be dumped to `rpc.json` in the log directory.'
    )
    parser.add_argument(
        '-x',
        '--export-summary',
        type=str,
        default=None,
        help='Path to a JSON file in which to export an event summary')
    parser.add_argument('-v',
                        '--version',
                        action='store_true',
                        default=False,
                        help='Print version information and exit')
    parser.add_argument(
        'client',
        type=str,
        nargs='*',
        help=
        'JSON RPC client URLs to multiplex; if no client is specified for --master, the first '
        'client in this list will default to the master (format="http://foo.com:8545/")'
    )
    parser.add_argument('-s',
                        '--master',
                        type=str,
                        default=None,
                        help='A JSON RPC client to use as the master '
                        '(format="http://foo.com:8545/")')
    parser.add_argument(
        '--raw',
        type=str,
        nargs='*',
        action='append',
        help=
        'JSON RPC client URLs to multiplex that do not have any local accounts; Etheno will '
        'automatically use auto-generated accounts with known private keys, pre-sign all '
        'transactions, and only use eth_sendRawTransaction')

    if argv is None:
        argv = sys.argv

    args = parser.parse_args(argv[1:])

    if args.version:
        print(VERSION_NAME)
        sys.exit(0)

    if args.constantinople and args.constantinople_block is None:
        args.constantinople_block = 0

    ETHENO.log_level = args.log_level

    if args.log_file:
        ETHENO.logger.save_to_file(args.log_file)

    if args.log_dir:
        if os.path.exists(args.log_dir):
            if not ynprompt(
                    "Logging path `%s` already exists! Would you like to overwrite it? [yN] "
                    % args.log_dir):
                sys.exit(1)
            elif os.path.isfile(args.log_dir):
                os.remove(args.log_dir)
            else:
                # don't delete the directory, just its contents
                # we can't use shutil.rmtree here, because that deletes the directory and also it doesn't work on
                # symlinks
                if not ynprompt(
                        "We are about to delete the contents of `%s`. Are you sure? [yN] "
                        % args.log_dir):
                    sys.exit(1)
                abspath = os.path.abspath(args.log_dir)
                if abspath == '' or abspath == '/' or abspath.endswith(
                        '://') or abspath.endswith(':\\\\'):
                    print(
                        "Wait a sec, you want me to delete `%s`?!\nThat looks too dangerous.\nIf I were to do that, "
                        "you'd file an angry GitHub issue complaining that I deleted your hard drive.\nYou're on "
                        "your own deleting this directory!" % abspath)
                    sys.exit(1)
                clear_directory(args.log_dir)

        ETHENO.logger.save_to_directory(args.log_dir)
        if not args.log_file:
            # Also create a unified log in the log dir:
            ETHENO.logger.save_to_file(
                os.path.join(args.log_dir, 'Complete.log'))

        ETHENO.add_plugin(
            JSONRPCExportPlugin(os.path.join(args.log_dir, 'rpc.json')))

    if args.dump_jsonrpc is not None:
        ETHENO.add_plugin(JSONRPCExportPlugin(args.dump_jsonrpc))

    if args.export_summary is not None:
        ETHENO.add_plugin(EventSummaryExportPlugin(args.export_summary))

    # First, see if we need to install Echidna:
    if args.echidna:
        if not echidna_exists():
            if not ynprompt(
                    'Echidna does not appear to be installed.\nWould you like to have Etheno attempt to '
                    'install it now? [yN] '):
                sys.exit(1)
            install_echidna()
            if not echidna_exists():
                ETHENO.logger.error(
                    'Etheno failed to install Echidna. Please install it manually '
                    'https://github.com/trailofbits/echidna')
                sys.exit(1)

    if args.genesis is None:
        # Set defaults since no genesis was supplied
        if args.accounts is None:
            args.accounts = 10
        if args.gas_price is None:
            args.gas_price = 20000000000

    accounts = []

    if args.genesis:
        with open(args.genesis, 'rb') as f:
            genesis = json.load(f)
            if 'config' not in genesis:
                genesis['config'] = {}
            if 'alloc' not in genesis:
                genesis['alloc'] = {}
            if args.network_id is None:
                args.network_id = genesis['config'].get('chainId', None)
            if args.constantinople_block is None:
                args.constantinople_block = genesis['config'].get(
                    'constantinopleBlock', None)
                args.constantinople = args.constantinople_block is not None
            for addr, bal in genesis['alloc'].items():
                pkey = None
                if 'privateKey' in bal:
                    pkey = bal['privateKey']
                accounts.append(
                    Account(address=int(addr, 16),
                            balance=decode_value(bal['balance']),
                            private_key=decode_value(pkey)))
    else:
        # We will generate it further below once we've resolved all of the parameters
        genesis = None

    accounts += make_accounts(args.accounts,
                              default_balance=int(args.balance *
                                                  1000000000000000000))

    if genesis is not None:
        # add the new accounts to the genesis
        for account in accounts[len(genesis['alloc']):]:
            genesis['alloc'][format_hex_address(account.address)] = {
                'balance':
                "%d" % account.balance,
                'privateKey':
                format_hex_address(account.private_key),
                'comment':
                '`privateKey` and `comment` are ignored.  In a real chain, the private key should _not_ be '
                'stored!'
            }

    if args.raw is None:
        args.raw = []
    else:
        args.raw = [r[0] for r in args.raw]

    if args.ganache and args.master:
        parser.print_help()
        sys.stderr.write(
            '\nError: You cannot specify both --ganache and --master at the same time!\n'
        )
        sys.exit(1)
    elif args.ganache:
        if args.ganache_port is None:
            args.ganache_port = find_open_port(args.port + 1)

        if args.network_id is None:
            args.network_id = 0x657468656E6F  # 'etheno' in hex

        ganache_accounts = [
            "--account=%s,0x%x" % (acct.private_key, acct.balance)
            for acct in accounts
        ]

        ganache_args = ganache_accounts + [
            '-g', str(args.gas_price), '-i',
            str(args.network_id)
        ]

        if args.ganache_args is not None:
            ganache_args += shlex.split(args.ganache_args)

        ganache_instance = ganache.Ganache(cmd=args.ganache_cmd,
                                           args=ganache_args,
                                           port=args.ganache_port)

        ETHENO.master_client = ganache.GanacheClient(ganache_instance)

        ganache_instance.start()
    elif args.master:
        ETHENO.master_client = AddressSynchronizingClient(
            RpcProxyClient(args.master))
    elif args.client and not args.geth and not args.parity:
        ETHENO.master_client = AddressSynchronizingClient(
            RpcProxyClient(args.client[0]))
        args.client = args.client[1:]
    elif args.raw and not args.geth and not args.parity:
        ETHENO.master_client = RawTransactionClient(
            RpcProxyClient(args.raw[0]), accounts)
        args.raw = args.raw[1:]

    if args.network_id is None:
        if ETHENO.master_client:
            args.network_id = int(
                ETHENO.master_client.post({
                    'id': 1,
                    'jsonrpc': '2.0',
                    'method': 'net_version'
                })['result'], 16)
        else:
            args.network_id = 0x657468656E6F  # 'etheno' in hex

    if genesis is None:
        genesis = make_genesis(network_id=args.network_id,
                               accounts=accounts,
                               constantinople_block=args.constantinople_block)
    else:
        # Update the genesis with any overridden values
        genesis['config']['chainId'] = args.network_id

    if args.save_genesis:
        with open(args.save_genesis, 'wb') as f:
            f.write(json.dumps(genesis).encode('utf-8'))
            ETHENO.logger.info("Saved genesis to %s" % args.save_genesis)

    if args.geth:
        if args.geth_port is None:
            args.geth_port = find_open_port(args.port + 1)

        geth_instance = geth.GethClient(genesis=genesis, port=args.geth_port)
        geth_instance.etheno = ETHENO
        for account in accounts:
            # TODO: Make some sort of progress bar here
            geth_instance.logger.info(
                "Unlocking Geth account %s" %
                format_hex_address(account.address, True))
            geth_instance.import_account(account.private_key)
        geth_instance.start(unlock_accounts=True)
        if ETHENO.master_client is None:
            ETHENO.master_client = geth_instance
        else:
            ETHENO.add_client(AddressSynchronizingClient(geth_instance))

    if args.parity:
        if args.parity_port is None:
            if args.geth_port is not None:
                args.parity_port = find_open_port(args.geth_port + 1)
            else:
                args.parity_port = find_open_port(args.port + 1)

        parity_instance = parity.ParityClient(genesis=genesis,
                                              port=args.parity_port)
        parity_instance.etheno = ETHENO
        for account in accounts:
            # TODO: Make some sort of progress bar here
            parity_instance.import_account(account.private_key)
        parity_instance.start(unlock_accounts=True)
        if ETHENO.master_client is None:
            ETHENO.master_client = parity_instance
        else:
            ETHENO.add_client(AddressSynchronizingClient(parity_instance))

    for client in args.client:
        ETHENO.add_client(AddressSynchronizingClient(RpcProxyClient(client)))

    for client in args.raw:
        ETHENO.add_client(
            RawTransactionClient(RpcProxyClient(client), accounts))

    manticore_client = None
    if args.manticore:
        if not MANTICORE_INSTALLED:
            ETHENO.logger.error(
                'Manticore is not installed! Running Etheno with Manticore requires Manticore version '
                '0.2.2 or newer. Reinstall Etheno with Manticore support by running '
                '`pip3 install --user \'etheno[manticore]\'`, or install Manticore separately with '
                '`pip3 install --user \'manticore\'`')
            sys.exit(1)
        new_enough = manticoreutils.manticore_is_new_enough()
        if new_enough is None:
            ETHENO.logger.warning(
                f"Unknown Manticore version {manticoreutils.manticore_version()}; it may not be new "
                "enough to have Etheno support!")
        elif not new_enough:
            ETHENO.logger.error(
                f"The version of Manticore installed is {manticoreutils.manticore_version()}, but the "
                f"minimum required version with Etheno support is 0.2.2. We will try to proceed, but "
                f"things might not work correctly! Please upgrade Manticore.")
        manticore_client = ManticoreClient()
        ETHENO.add_client(manticore_client)
        if args.manticore_max_depth is not None:
            manticore_client.manticore.register_detector(
                manticoreutils.StopAtDepth(args.manticore_max_depth))
        if manticoreutils.manticore_is_new_enough(0, 2, 4):
            # the verbosity static method was deprecated
            from manticore.utils.log import set_verbosity
            set_verbosity(getattr(logger, args.log_level))
        else:
            manticore_client.manticore.verbosity(
                getattr(logger, args.log_level))

    if args.truffle:
        truffle_controller = truffle.Truffle(truffle_cmd=args.truffle_cmd,
                                             parent_logger=ETHENO.logger)

        def truffle_thread():
            if ETHENO.master_client:
                ETHENO.master_client.wait_until_running()
            ETHENO.logger.info("Etheno Started! Running Truffle...")
            ret = truffle_controller.run(args.truffle_args)
            if ret != 0:
                ETHENO.logger.error("Truffle exited with code %s" % ret)
                ETHENO.shutdown()
                # TODO: Propagate the error code elsewhere so Etheno doesn't exit with code 0

            for plugin in ETHENO.plugins:
                plugin.finalize()

            if manticore_client is not None:
                if args.manticore_script is not None:
                    f = args.manticore_script
                    code = compile(f.read(), f.name, 'exec')
                    exec(
                        code, {
                            'manticore':
                            manticore_client.manticore,
                            'manticoreutils':
                            manticoreutils,
                            'logger':
                            logger.EthenoLogger(os.path.basename(
                                args.manticore_script.name),
                                                parent=manticore_client.logger)
                        })
                else:
                    manticoreutils.register_all_detectors(
                        manticore_client.manticore)
                    manticore_client.multi_tx_analysis()
                    manticore_client.manticore.finalize()
                manticore_client.logger.info(
                    "Results are in %s" % manticore_client.manticore.workspace)
                ETHENO.shutdown()
            elif not ETHENO.clients and not ETHENO.plugins:
                ETHENO.logger.info("No clients or plugins running; exiting...")
                ETHENO.shutdown()

        thread = Thread(target=truffle_thread)
        thread.start()

    if args.run_differential and (ETHENO.master_client is not None) and \
            next(filter(lambda c: not isinstance(c, ManticoreClient), ETHENO.clients), False):
        # There are at least two non-Manticore clients running
        ETHENO.logger.info(
            "Initializing differential tests to compare clients %s" %
            ', '.join(map(str, [ETHENO.master_client] + ETHENO.clients)))
        ETHENO.add_plugin(DifferentialTester())

    if args.echidna:
        contract_source = None
        if args.fuzz_contract is not None:
            with open(args.fuzz_contract, 'rb') as c:
                contract_source = c.read()
        ETHENO.add_plugin(
            EchidnaPlugin(transaction_limit=args.fuzz_limit,
                          contract_source=contract_source))

    had_plugins = len(ETHENO.plugins) > 0

    if ETHENO.master_client is None and not ETHENO.clients and not ETHENO.plugins:
        if not had_plugins:
            ETHENO.logger.info("No clients or plugins provided; exiting...")
        # else: this can also happen if there were plugins but they uninstalled themselves after running
        return

    etheno = EthenoView()
    app.add_url_rule('/', view_func=etheno.as_view('etheno'))

    ETHENO.run(debug=args.debug,
               run_publicly=args.run_publicly,
               port=args.port)
    if args.truffle:
        truffle_controller.terminate()

    if args.log_file is not None:
        print("Log file saved to: %s" % os.path.realpath(args.log_file))
    if args.log_dir is not None:
        print("Logs %ssaved to: %s" %
              (['', 'also '
                ][args.log_file is not None], os.path.realpath(args.log_dir)))