diff --git a/.circleci/config.yml b/.circleci/config.yml index ea170a6f..fdce4c05 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -54,9 +54,9 @@ jobs: working_directory: /home/mythril no_output_timeout: 10m environment: - LC_ALL: en_US.ASCII LANG: en_US.ASCII MYTHRIL_DIR: '/home/mythril' + INFURA_ID: $INFURA_ID - store_test_results: path: /home/mythril/.tox/output diff --git a/docs/source/security-analysis.rst b/docs/source/security-analysis.rst index 26166d97..3e5e0eda 100644 --- a/docs/source/security-analysis.rst +++ b/docs/source/security-analysis.rst @@ -57,15 +57,15 @@ Analyzing On-Chain Contracts When analyzing contracts on the blockchain, Mythril will by default attempt to query INFURA. You can use the built-in INFURA support or manually configure the RPC settings with the :code:`--rpc` argument. -+--------------------------------+-------------------------------------------------+ -| :code:`--rpc ganache` | Connect to local Ganache | -+--------------------------------+-------------------------------------------------+ -| :code:`--rpc infura-[netname]` | Connect to mainnet, rinkeby, kovan, or ropsten. | -+--------------------------------+-------------------------------------------------+ -| :code:`--rpc host:port` | Connect to custom rpc | -+--------------------------------+-------------------------------------------------+ -| :code:`--rpctls ` | RPC connection over TLS (default: False) | -+--------------------------------+-------------------------------------------------+ ++-------------------------------------------------+-------------------------------------------------+ +| :code:`--rpc ganache` | Connect to local Ganache | ++-------------------------------------------------+-------------------------------------------------+ +| :code:`--rpc infura-[netname] --infura-id ` | Connect to mainnet, rinkeby, kovan, or ropsten. | ++-------------------------------------------------+-------------------------------------------------+ +| :code:`--rpc host:port` | Connect to custom rpc | ++-------------------------------------------------+-------------------------------------------------+ +| :code:`--rpctls ` | RPC connection over TLS (default: False) | ++-------------------------------------------------+-------------------------------------------------+ To specify a contract address, use :code:`-a
` @@ -73,13 +73,14 @@ Analyze mainnet contract via INFURA: .. code-block:: bash - myth analyze -a 0x5c436ff914c458983414019195e0f4ecbef9e6dd + myth analyze -a 0x5c436ff914c458983414019195e0f4ecbef9e6dd --infura-id +You can also use the environment variable `INFURA_ID` instead of the cmd line argument or set it in ~/.mythril/config.ini. Adding the :code:`-l` flag will cause mythril to automatically retrieve dependencies, such as dynamically linked library contracts: .. code-block:: bash - myth -v4 analyze -l -a 0xEbFD99838cb0c132016B9E117563CB41f2B02264 + myth -v4 analyze -l -a 0xEbFD99838cb0c132016B9E117563CB41f2B02264 --infura-id ****************** Speed vs. Coverage @@ -88,3 +89,4 @@ Speed vs. Coverage The execution timeout can be specified with the :code:`--execution-timeout ` argument. When the timeout is reached, mythril will stop analysis and print out all currently found issues. The maximum recursion depth for the symbolic execution engine can be controlled with the :code:`--max-depth` argument. The default value is 22. Lowering this value will decrease the number of explored states and analysis time, while increasing this number will increase the number of explored states and increase analysis time. For some contracts, it helps to fine tune this number to get the best analysis results. +- diff --git a/mythril/ethereum/interface/rpc/client.py b/mythril/ethereum/interface/rpc/client.py index 8f1269c2..b7ace33a 100644 --- a/mythril/ethereum/interface/rpc/client.py +++ b/mythril/ethereum/interface/rpc/client.py @@ -52,11 +52,20 @@ class EthJsonRpc(BaseClient): :return: """ params = params or [] - data = {"jsonrpc": "2.0", "method": method, "params": params, "id": _id} + data = { + "jsonrpc": "2.0", + "method": method, + "params": params, + "id": _id, + } scheme = "http" if self.tls: scheme += "s" - url = "{}://{}:{}".format(scheme, self.host, self.port) + if self.host: + url = "{}://{}:{}".format(scheme, self.host, self.port) + else: + url = "{}".format(scheme) + headers = {"Content-Type": JSON_MEDIA_TYPE} log.debug("rpc send: %s" % json.dumps(data)) try: diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index d5cc5ed5..35c4dee4 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -374,6 +374,10 @@ def create_analyzer_parser(analyzer_parser: ArgumentParser): action="store_true", help="analyze a truffle project (run from project dir)", ) + commands.add_argument( + "--infura-id", help="set infura id for onchain analysis", + ) + options = analyzer_parser.add_argument_group("options") options.add_argument( "-m", @@ -529,6 +533,8 @@ def set_config(args: Namespace): :return: modified config """ config = MythrilConfig() + if args.__dict__.get("infura_id", None): + config.set_api_infura_id(args.infura_id) if ( args.command in ANALYZE_LIST and (args.dynld or not args.no_onchain_storage_access) diff --git a/mythril/mythril/mythril_config.py b/mythril/mythril/mythril_config.py index 6377c205..174e47e5 100644 --- a/mythril/mythril/mythril_config.py +++ b/mythril/mythril/mythril_config.py @@ -23,6 +23,7 @@ class MythrilConfig: """ def __init__(self): + self.infura_id = os.getenv("INFURA_ID") # type: str self.mythril_dir = self._init_mythril_dir() self.config_path = os.path.join(self.mythril_dir, "config.ini") self.leveldb_dir = None @@ -30,6 +31,9 @@ class MythrilConfig: self.eth = None # type: Optional[EthJsonRpc] self.eth_db = None # type: Optional[EthLevelDB] + def set_api_infura_id(self, id): + self.infura_id = id + @staticmethod def _init_mythril_dir() -> str: """ @@ -83,12 +87,17 @@ class MythrilConfig: if not config.has_option("defaults", "dynamic_loading"): self._add_dynamic_loading_option(config) + if not config.has_option("defaults", "infura_id"): + config.set("defaults", "infura_id", "") + with codecs.open(self.config_path, "w", "utf-8") as fp: config.write(fp) leveldb_dir = config.get( "defaults", "leveldb_dir", fallback=leveldb_default_path ) + if not self.infura_id: + self.infura_id = config.get("defaults", "infura_id", fallback="") self.leveldb_dir = os.path.expanduser(leveldb_dir) @staticmethod @@ -167,7 +176,9 @@ class MythrilConfig: def set_api_rpc_infura(self) -> None: """Set the RPC mode to INFURA on Mainnet.""" log.info("Using INFURA Main Net for RPC queries") - self.eth = EthJsonRpc("mainnet.infura.io", 443, True) + self.eth = EthJsonRpc( + "mainnet.infura.io/v3/{}".format(self.infura_id), None, True + ) def set_api_rpc(self, rpc: str = None, rpctls: bool = False) -> None: """ @@ -178,8 +189,20 @@ class MythrilConfig: rpcconfig = ("localhost", 7545, False) else: m = re.match(r"infura-(.*)", rpc) + if m and m.group(1) in ["mainnet", "rinkeby", "kovan", "ropsten"]: - rpcconfig = (m.group(1) + ".infura.io", 443, True) + if self.infura_id in (None, ""): + raise CriticalError( + "Infura key not provided. Use --infura-id " + "or set it in the environment variable INFURA_ID " + "or in the ~/.mythril/config.ini file'" + ) + + rpcconfig = ( + "{}.infura.io/v3/{}".format(m.group(1), self.infura_id), + None, + True, + ) else: try: host, port = rpc.split(":") @@ -191,7 +214,7 @@ class MythrilConfig: if rpcconfig: log.info("Using RPC settings: %s" % str(rpcconfig)) - self.eth = EthJsonRpc(rpcconfig[0], int(rpcconfig[1]), rpcconfig[2]) + self.eth = EthJsonRpc(rpcconfig[0], rpcconfig[1], rpcconfig[2]) else: raise CriticalError("Invalid RPC settings, check help for details.") diff --git a/tests/mythril/mythril_config_test.py b/tests/mythril/mythril_config_test.py index 2dc404f6..ee310d5a 100644 --- a/tests/mythril/mythril_config_test.py +++ b/tests/mythril/mythril_config_test.py @@ -1,4 +1,5 @@ import pytest +import os from configparser import ConfigParser from pathlib import Path @@ -13,16 +14,15 @@ def test_config_path_dynloading(): Path(__file__).parent.parent / "testdata/mythril_config_inputs/config.ini" ) config.set_api_from_config_path() - assert config.eth.host == "mainnet.infura.io" - assert config.eth.port == 443 + assert "mainnet.infura.io/v3/" in config.eth.host rpc_types_tests = [ - ("infura", "mainnet.infura.io", 443, True), - ("ganache", "localhost", 7545, True), - ("infura-rinkeby", "rinkeby.infura.io", 443, True), - ("infura-ropsten", "ropsten.infura.io", 443, True), - ("infura-kovan", "kovan.infura.io", 443, True), + ("infura", "mainnet.infura.io/v3/", None, True), + ("ganache", "localhost", None, True), + ("infura-rinkeby", "rinkeby.infura.io/v3/", None, True), + ("infura-ropsten", "ropsten.infura.io/v3/", None, True), + ("infura-kovan", "kovan.infura.io/v3/", None, True), ("localhost", "localhost", 8545, True), ("localhost:9022", "localhost", 9022, True), ("pinfura", None, None, False), @@ -35,8 +35,7 @@ def test_set_rpc(rpc_type, host, port, success): config = MythrilConfig() if success: config._set_rpc(rpc_type) - assert config.eth.host == host - assert config.eth.port == port + assert host in config.eth.host else: with pytest.raises(CriticalError): config._set_rpc(rpc_type) diff --git a/tox.ini b/tox.ini index 804b6791..278abc4e 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ envlist = py36 deps = pytest pytest-mock -passenv = MYTHRIL_DIR = {homedir} +passenv = MYTHRIL_DIR INFURA_ID whitelist_externals = mkdir commands = mkdir -p {toxinidir}/tests/testdata/outputs_current/ @@ -24,7 +24,7 @@ deps = pytest pytest-mock pytest-cov -passenv = MYTHRIL_DIR = {homedir} +passenv = MYTHRIL_DIR INFURA_ID whitelist_externals = mkdir commands = mypy --follow-imports=silent --warn-unused-ignores --ignore-missing-imports --no-strict-optional mythril