class Mythril(object): """Mythril main interface class. 1. create mythril object 2. set rpc or leveldb interface if needed 3. load contracts (from solidity, bytecode, address) 4. fire_lasers .. code-block:: python mythril = Mythril() mythril.set_api_rpc_infura() # (optional) other API adapters mythril.set_api_rpc(args) mythril.set_api_rpc_localhost() mythril.set_api_leveldb(path) # (optional) other func mythril.analyze_truffle_project(args) mythril.search_db(args) # load contract mythril.load_from_bytecode(bytecode) mythril.load_from_address(address) mythril.load_from_solidity(solidity_file) # analyze print(mythril.fire_lasers(args).as_text()) # (optional) graph for contract in mythril.contracts: # prints html or save it to file print(mythril.graph_html(args)) # (optional) other funcs mythril.dump_statespaces(args) mythril.disassemble(contract) mythril.get_state_variable_from_storage(args) """ def __init__( self, solv=None, solc_args=None, dynld=False, enable_online_lookup=False, onchain_storage_access=True, ): self.solv = solv self.solc_args = solc_args self.dynld = dynld self.onchain_storage_access = onchain_storage_access self.enable_online_lookup = enable_online_lookup self.mythril_dir = self._init_mythril_dir() # tries mythril_dir/signatures.db by default (provide path= arg to make this configurable) self.sigs = signatures.SignatureDB( enable_online_lookup=self.enable_online_lookup) self.solc_binary = self._init_solc_binary(solv) self.config_path = os.path.join(self.mythril_dir, "config.ini") self.leveldb_dir = self._init_config() self.eth = None # ethereum API client self.eth_db = None # ethereum LevelDB client self.contracts = [] # loaded contracts @staticmethod def _init_mythril_dir(): try: mythril_dir = os.environ["MYTHRIL_DIR"] except KeyError: mythril_dir = os.path.join(os.path.expanduser("~"), ".mythril") if not os.path.exists(mythril_dir): # Initialize data directory log.info("Creating mythril data directory") os.mkdir(mythril_dir) db_path = str(Path(mythril_dir) / "signatures.db") if not os.path.exists(db_path): # if the default mythril dir doesn't contain a signature DB # initialize it with the default one from the project root asset_dir = Path(__file__).parent / "support" / "assets" copyfile(str(asset_dir / "signatures.db"), db_path) return mythril_dir def _init_config(self): """If no config file exists, create it and add default options. Default LevelDB path is specified based on OS dynamic loading is set to infura by default in the file Returns: leveldb directory """ system = platform.system().lower() leveldb_fallback_dir = os.path.expanduser("~") if system.startswith("darwin"): leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "Library", "Ethereum") elif system.startswith("windows"): leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "AppData", "Roaming", "Ethereum") else: leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, ".ethereum") leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "geth", "chaindata") if not os.path.exists(self.config_path): log.info("No config file found. Creating default: " + self.config_path) open(self.config_path, "a").close() config = ConfigParser(allow_no_value=True) config.optionxform = str config.read(self.config_path, "utf-8") if "defaults" not in config.sections(): self._add_default_options(config) if not config.has_option("defaults", "leveldb_dir"): self._add_leveldb_option(config, leveldb_fallback_dir) if not config.has_option("defaults", "dynamic_loading"): self._add_dynamic_loading_option(config) with codecs.open(self.config_path, "w", "utf-8") as fp: config.write(fp) leveldb_dir = config.get("defaults", "leveldb_dir", fallback=leveldb_fallback_dir) return os.path.expanduser(leveldb_dir) @staticmethod def _add_default_options(config): config.add_section("defaults") @staticmethod def _add_leveldb_option(config, leveldb_fallback_dir): config.set("defaults", "#Default chaindata locations:") config.set("defaults", "#– Mac: ~/Library/Ethereum/geth/chaindata") config.set("defaults", "#– Linux: ~/.ethereum/geth/chaindata") config.set( "defaults", "#– Windows: %USERPROFILE%\\AppData\\Roaming\\Ethereum\\geth\\chaindata", ) config.set("defaults", "leveldb_dir", leveldb_fallback_dir) @staticmethod def _add_dynamic_loading_option(config): config.set("defaults", "#– To connect to Infura use dynamic_loading: infura") config.set( "defaults", "#– To connect to Rpc use " "dynamic_loading: HOST:PORT / ganache / infura-[network_name]", ) config.set( "defaults", "#– To connect to local host use dynamic_loading: localhost") config.set("defaults", "dynamic_loading", "infura") def analyze_truffle_project(self, *args, **kwargs): """ :param args: :param kwargs: :return: """ return analyze_truffle_project( self.sigs, *args, **kwargs) # just passthru by passing signatures for now @staticmethod def _init_solc_binary(version): """Figure out solc binary and version. Only proper versions are supported. No nightlies, commits etc (such as available in remix). """ if not version: return os.environ.get("SOLC") or "solc" # tried converting input to semver, seemed not necessary so just slicing for now main_version = solc.main.get_solc_version_string() main_version_number = re.match(r"\d+.\d+.\d+", main_version) if main_version is None: raise CriticalError( "Could not extract solc version from string {}".format( main_version)) if version == main_version_number: log.info("Given version matches installed version") solc_binary = os.environ.get("SOLC") or "solc" else: solc_binary = util.solc_exists(version) if solc_binary: log.info("Given version is already installed") else: try: solc.install_solc("v" + version) solc_binary = util.solc_exists(version) if not solc_binary: raise SolcError() except SolcError: raise CriticalError( "There was an error when trying to install the specified solc version" ) log.info("Setting the compiler to %s", solc_binary) return solc_binary def set_api_leveldb(self, leveldb): """ :param leveldb: :return: """ self.eth_db = EthLevelDB(leveldb) self.eth = self.eth_db return self.eth def set_api_rpc_infura(self): """Set the RPC mode to INFURA on mainnet.""" self.eth = EthJsonRpc("mainnet.infura.io", 443, True) log.info("Using INFURA for RPC queries") def set_api_rpc(self, rpc=None, rpctls=False): """ :param rpc: :param rpctls: """ if rpc == "ganache": rpcconfig = ("localhost", 8545, 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) else: try: host, port = rpc.split(":") rpcconfig = (host, int(port), rpctls) except ValueError: raise CriticalError( "Invalid RPC argument, use 'ganache', 'infura-[network]' or 'HOST:PORT'" ) if rpcconfig: self.eth = EthJsonRpc(rpcconfig[0], int(rpcconfig[1]), rpcconfig[2]) log.info("Using RPC settings: %s" % str(rpcconfig)) else: raise CriticalError( "Invalid RPC settings, check help for details.") def set_api_rpc_localhost(self): """Set the RPC mode to a local instance.""" self.eth = EthJsonRpc("localhost", 8545) log.info("Using default RPC settings: http://localhost:8545") def set_api_from_config_path(self): """Set the RPC mode based on a given config file.""" config = ConfigParser(allow_no_value=False) config.optionxform = str config.read(self.config_path, "utf-8") if config.has_option("defaults", "dynamic_loading"): dynamic_loading = config.get("defaults", "dynamic_loading") else: dynamic_loading = "infura" if dynamic_loading == "infura": self.set_api_rpc_infura() elif dynamic_loading == "localhost": self.set_api_rpc_localhost() else: self.set_api_rpc(dynamic_loading) def search_db(self, search): """ :param search: """ def search_callback(_, address, balance): """ :param _: :param address: :param balance: """ print("Address: " + address + ", balance: " + str(balance)) try: self.eth_db.search(search, search_callback) except SyntaxError: raise CriticalError("Syntax error in search expression.") def contract_hash_to_address(self, hash): """ :param hash: """ if not re.match(r"0x[a-fA-F0-9]{64}", hash): raise CriticalError( "Invalid address hash. Expected format is '0x...'.") print(self.eth_db.contract_hash_to_address(hash)) def load_from_bytecode(self, code, bin_runtime=False, address=None): """ :param code: :param bin_runtime: :param address: :return: """ if address is None: address = util.get_indexed_address(0) if bin_runtime: self.contracts.append( EVMContract( code=code, name="MAIN", enable_online_lookup=self.enable_online_lookup, )) else: self.contracts.append( EVMContract( creation_code=code, name="MAIN", enable_online_lookup=self.enable_online_lookup, )) return address, self.contracts[ -1] # return address and contract object def load_from_address(self, address): """ :param address: :return: """ if not re.match(r"0x[a-fA-F0-9]{40}", address): raise CriticalError( "Invalid contract address. Expected format is '0x...'.") try: code = self.eth.eth_getCode(address) except FileNotFoundError as e: raise CriticalError("IPC error: " + str(e)) except ConnectionError: raise CriticalError( "Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly." ) except Exception as e: raise CriticalError("IPC / RPC error: " + str(e)) else: if code == "0x" or code == "0x0": raise CriticalError( "Received an empty response from eth_getCode. Check the contract address and verify that you are on the correct chain." ) else: self.contracts.append( EVMContract( code, name=address, enable_online_lookup=self.enable_online_lookup, )) return address, self.contracts[ -1] # return address and contract object def load_from_solidity(self, solidity_files): """ :param solidity_files: :return: """ address = util.get_indexed_address(0) contracts = [] for file in solidity_files: if ":" in file: file, contract_name = file.split(":") else: contract_name = None file = os.path.expanduser(file) try: # import signatures from solidity source self.sigs.import_solidity_file(file, solc_binary=self.solc_binary, solc_args=self.solc_args) if contract_name is not None: contract = SolidityContract( input_file=file, name=contract_name, solc_args=self.solc_args, solc_binary=self.solc_binary, ) self.contracts.append(contract) contracts.append(contract) else: for contract in get_contracts_from_file( input_file=file, solc_args=self.solc_args, solc_binary=self.solc_binary, ): self.contracts.append(contract) contracts.append(contract) except FileNotFoundError: raise CriticalError("Input file not found: " + file) except CompilerError as e: raise CriticalError(e) except NoContractFoundError: log.error("The file " + file + " does not contain a compilable contract.") return address, contracts def dump_statespace( self, strategy, contract, address=None, max_depth=None, execution_timeout=None, create_timeout=None, enable_iprof=False, ): """ :param strategy: :param contract: :param address: :param max_depth: :param execution_timeout: :param create_timeout: :return: """ sym = SymExecWrapper( contract, address, strategy, dynloader=DynLoader( self.eth, storage_loading=self.onchain_storage_access, contract_loading=self.dynld, ), max_depth=max_depth, execution_timeout=execution_timeout, create_timeout=create_timeout, enable_iprof=enable_iprof, ) return get_serializable_statespace(sym) def graph_html( self, strategy, contract, address, max_depth=None, enable_physics=False, phrackify=False, execution_timeout=None, create_timeout=None, enable_iprof=False, ): """ :param strategy: :param contract: :param address: :param max_depth: :param enable_physics: :param phrackify: :param execution_timeout: :param create_timeout: :return: """ sym = SymExecWrapper( contract, address, strategy, dynloader=DynLoader( self.eth, storage_loading=self.onchain_storage_access, contract_loading=self.dynld, ), max_depth=max_depth, execution_timeout=execution_timeout, create_timeout=create_timeout, enable_iprof=enable_iprof, ) return generate_graph(sym, physics=enable_physics, phrackify=phrackify) def fire_lasers( self, strategy, contracts=None, address=None, modules=None, verbose_report=False, max_depth=None, execution_timeout=None, create_timeout=None, transaction_count=None, enable_iprof=False, ): """ :param strategy: :param contracts: :param address: :param modules: :param verbose_report: :param max_depth: :param execution_timeout: :param create_timeout: :param transaction_count: :return: """ all_issues = [] for contract in contracts or self.contracts: try: sym = SymExecWrapper( contract, address, strategy, dynloader=DynLoader( self.eth, storage_loading=self.onchain_storage_access, contract_loading=self.dynld, ), max_depth=max_depth, execution_timeout=execution_timeout, create_timeout=create_timeout, transaction_count=transaction_count, modules=modules, compulsory_statespace=False, enable_iprof=enable_iprof, ) issues = fire_lasers(sym, modules) except KeyboardInterrupt: log.critical("Keyboard Interrupt") issues = retrieve_callback_issues(modules) except Exception: log.critical( "Exception occurred, aborting analysis. Please report this issue to the Mythril GitHub page.\n" + traceback.format_exc()) issues = retrieve_callback_issues(modules) for issue in issues: issue.add_code_info(contract) all_issues += issues source_data = Source() source_data.get_source_from_contracts_list(self.contracts) # Finally, output the results report = Report(verbose_report, source_data) for issue in all_issues: report.append_issue(issue) return report def get_state_variable_from_storage(self, address, params=None): """ :param address: :param params: :return: """ if params is None: params = [] (position, length, mappings) = (0, 1, []) try: if params[0] == "mapping": if len(params) < 3: raise CriticalError("Invalid number of parameters.") position = int(params[1]) position_formatted = utils.zpad( utils.int_to_big_endian(position), 32) for i in range(2, len(params)): key = bytes(params[i], "utf8") key_formatted = utils.rzpad(key, 32) mappings.append( int.from_bytes( utils.sha3(key_formatted + position_formatted), byteorder="big", )) length = len(mappings) if length == 1: position = mappings[0] else: if len(params) >= 4: raise CriticalError("Invalid number of parameters.") if len(params) >= 1: position = int(params[0]) if len(params) >= 2: length = int(params[1]) if len(params) == 3 and params[2] == "array": position_formatted = utils.zpad( utils.int_to_big_endian(position), 32) position = int.from_bytes(utils.sha3(position_formatted), byteorder="big") except ValueError: raise CriticalError( "Invalid storage index. Please provide a numeric value.") outtxt = [] try: if length == 1: outtxt.append("{}: {}".format( position, self.eth.eth_getStorageAt(address, position))) else: if len(mappings) > 0: for i in range(0, len(mappings)): position = mappings[i] outtxt.append("{}: {}".format( hex(position), self.eth.eth_getStorageAt(address, position), )) else: for i in range(position, position + length): outtxt.append("{}: {}".format( hex(i), self.eth.eth_getStorageAt(address, i))) except FileNotFoundError as e: raise CriticalError("IPC error: " + str(e)) except ConnectionError: raise CriticalError( "Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly." ) return "\n".join(outtxt) @staticmethod def disassemble(contract): """ :param contract: :return: """ return contract.get_easm() @staticmethod def hash_for_function_signature(sig): """ :param sig: :return: """ return "0x%s" % utils.sha3(sig)[:4].hex()
class Mythril(object): """ Mythril main interface class. 1. create mythril object 2. set rpc or leveldb interface if needed 3. load contracts (from solidity, bytecode, address) 4. fire_lasers Example: mythril = Mythril() mythril.set_api_rpc_infura() # (optional) other API adapters mythril.set_api_rpc(args) mythril.set_api_rpc_localhost() mythril.set_api_leveldb(path) # (optional) other func mythril.analyze_truffle_project(args) mythril.search_db(args) # load contract mythril.load_from_bytecode(bytecode) mythril.load_from_address(address) mythril.load_from_solidity(solidity_file) # analyze print(mythril.fire_lasers(args).as_text()) # (optional) graph for contract in mythril.contracts: print(mythril.graph_html(args)) # prints html or save it to file # (optional) other funcs mythril.dump_statespaces(args) mythril.disassemble(contract) mythril.get_state_variable_from_storage(args) """ def __init__(self, solv=None, solc_args=None, dynld=False): self.solv = solv self.solc_args = solc_args self.dynld = dynld self.mythril_dir = self._init_mythril_dir() self.sigs = signatures.SignatureDb() try: self.sigs.open( ) # tries mythril_dir/signatures.json by default (provide path= arg to make this configurable) except FileNotFoundError as fnfe: logging.info( "No signature database found. Creating database if sigs are loaded in: " + self.sigs.signatures_file + "\n" + "Consider replacing it with the pre-initialized database at https://raw.githubusercontent.com/ConsenSys/mythril/master/signatures.json" ) except json.JSONDecodeError as jde: raise CriticalError("Invalid JSON in signatures file " + self.sigs.signatures_file + "\n" + str(jde)) self.solc_binary = self._init_solc_binary(solv) self.config_path = os.path.join(self.mythril_dir, 'config.ini') self.leveldb_dir = self._init_config() self.eth = None # ethereum API client self.eth_db = None # ethereum LevelDB client self.contracts = [] # loaded contracts @staticmethod def _init_mythril_dir(): try: mythril_dir = os.environ['MYTHRIL_DIR'] except KeyError: mythril_dir = os.path.join(os.path.expanduser('~'), ".mythril") # Initialize data directory and signature database if not os.path.exists(mythril_dir): logging.info("Creating mythril data directory") os.mkdir(mythril_dir) return mythril_dir def _init_config(self): """ If no config file exists, create it and add default options. Default LevelDB path is specified based on OS dynamic loading is set to infura by default in the file Returns: leveldb directory """ system = platform.system().lower() leveldb_fallback_dir = os.path.expanduser('~') if system.startswith("darwin"): leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "Library", "Ethereum") elif system.startswith("windows"): leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "AppData", "Roaming", "Ethereum") else: leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, ".ethereum") leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "geth", "chaindata") if not os.path.exists(self.config_path): logging.info("No config file found. Creating default: " + self.config_path) open(self.config_path, 'a').close() config = ConfigParser(allow_no_value=True) config.optionxform = str config.read(self.config_path, 'utf-8') if 'defaults' not in config.sections(): self._add_default_options(config) if not config.has_option('defaults', 'leveldb_dir'): self._add_leveldb_option(config, leveldb_fallback_dir) if not config.has_option('defaults', 'dynamic_loading'): self._add_dynamic_loading_option(config) with codecs.open(self.config_path, 'w', 'utf-8') as fp: config.write(fp) leveldb_dir = config.get('defaults', 'leveldb_dir', fallback=leveldb_fallback_dir) return os.path.expanduser(leveldb_dir) @staticmethod def _add_default_options(config): config.add_section('defaults') @staticmethod def _add_leveldb_option(config, leveldb_fallback_dir): config.set('defaults', "#Default chaindata locations:") config.set('defaults', "#– Mac: ~/Library/Ethereum/geth/chaindata") config.set('defaults', "#– Linux: ~/.ethereum/geth/chaindata") config.set( 'defaults', "#– Windows: %USERPROFILE%\\AppData\\Roaming\\Ethereum\\geth\\chaindata" ) config.set('defaults', 'leveldb_dir', leveldb_fallback_dir) @staticmethod def _add_dynamic_loading_option(config): config.set('defaults', '#– To connect to Infura use dynamic_loading: infura') config.set( 'defaults', '#– To connect to Rpc use ' 'dynamic_loading: HOST:PORT / ganache / infura-[network_name]') config.set( 'defaults', '#– To connect to local host use dynamic_loading: localhost') config.set('defaults', 'dynamic_loading', 'infura') def analyze_truffle_project(self, *args, **kwargs): return analyze_truffle_project( self.sigs, *args, **kwargs) # just passthru by passing signatures for now @staticmethod def _init_solc_binary(version): # Figure out solc binary and version # Only proper versions are supported. No nightlies, commits etc (such as available in remix) if version: # tried converting input to semver, seemed not necessary so just slicing for now if version == str(solc.main.get_solc_version())[:6]: logging.info('Given version matches installed version') try: solc_binary = os.environ['SOLC'] except KeyError: solc_binary = 'solc' else: if util.solc_exists(version): logging.info('Given version is already installed') else: try: solc.install_solc('v' + version) except SolcError: raise CriticalError( "There was an error when trying to install the specified solc version" ) solc_binary = os.path.join(os.environ['HOME'], ".py-solc/solc-v" + version, "bin/solc") logging.info("Setting the compiler to " + str(solc_binary)) else: try: solc_binary = os.environ['SOLC'] except KeyError: solc_binary = 'solc' return solc_binary def set_api_leveldb(self, leveldb): self.eth_db = EthLevelDB(leveldb) self.eth = self.eth_db return self.eth def set_api_rpc_infura(self): self.eth = EthJsonRpc('mainnet.infura.io', 443, True) logging.info("Using INFURA for RPC queries") def set_api_rpc(self, rpc=None, rpctls=False): if rpc == 'ganache': rpcconfig = ('localhost', 8545, 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) else: try: host, port = rpc.split(":") rpcconfig = (host, int(port), rpctls) except ValueError: raise CriticalError( "Invalid RPC argument, use 'ganache', 'infura-[network]' or 'HOST:PORT'" ) if rpcconfig: self.eth = EthJsonRpc(rpcconfig[0], int(rpcconfig[1]), rpcconfig[2]) logging.info("Using RPC settings: %s" % str(rpcconfig)) else: raise CriticalError( "Invalid RPC settings, check help for details.") def set_api_rpc_localhost(self): self.eth = EthJsonRpc('localhost', 8545) logging.info("Using default RPC settings: http://localhost:8545") def set_api_from_config_path(self): config = ConfigParser(allow_no_value=False) config.optionxform = str config.read(self.config_path, 'utf-8') if config.has_option('defaults', 'dynamic_loading'): dynamic_loading = config.get('defaults', 'dynamic_loading') else: dynamic_loading = 'infura' if dynamic_loading == 'infura': self.set_api_rpc_infura() elif dynamic_loading == 'localhost': self.set_api_rpc_localhost() else: self.set_api_rpc(dynamic_loading) def search_db(self, search): def search_callback(contract, address, balance): print("Address: " + address + ", balance: " + str(balance)) try: self.eth_db.search(search, search_callback) except SyntaxError: raise CriticalError("Syntax error in search expression.") def contract_hash_to_address(self, hash): if not re.match(r'0x[a-fA-F0-9]{64}', hash): raise CriticalError( "Invalid address hash. Expected format is '0x...'.") print(self.eth_db.contract_hash_to_address(hash)) def load_from_bytecode(self, code): address = util.get_indexed_address(0) self.contracts.append(ETHContract(code, name="MAIN")) return address, self.contracts[ -1] # return address and contract object def load_from_address(self, address): if not re.match(r'0x[a-fA-F0-9]{40}', address): raise CriticalError( "Invalid contract address. Expected format is '0x...'.") try: code = self.eth.eth_getCode(address) except FileNotFoundError as e: raise CriticalError("IPC error: " + str(e)) except ConnectionError as e: raise CriticalError( "Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly." ) except Exception as e: raise CriticalError("IPC / RPC error: " + str(e)) else: if code == "0x" or code == "0x0": raise CriticalError( "Received an empty response from eth_getCode. Check the contract address and verify that you are on the correct chain." ) else: self.contracts.append(ETHContract(code, name=address)) return address, self.contracts[ -1] # return address and contract object def load_from_solidity(self, solidity_files): """ UPDATES self.sigs! :param solidity_files: :return: """ address = util.get_indexed_address(0) contracts = [] for file in solidity_files: if ":" in file: file, contract_name = file.split(":") else: contract_name = None file = os.path.expanduser(file) try: # import signatures from solidity source self.sigs.import_from_solidity_source( file, solc_binary=self.solc_binary, solc_args=self.solc_args) # Save updated function signatures self.sigs.write( ) # dump signatures to disk (previously opened file or default location) if contract_name is not None: contract = SolidityContract(file, contract_name, solc_args=self.solc_args) self.contracts.append(contract) contracts.append(contract) else: for contract in get_contracts_from_file( file, solc_args=self.solc_args): self.contracts.append(contract) contracts.append(contract) except FileNotFoundError: raise CriticalError("Input file not found: " + file) except CompilerError as e: raise CriticalError(e) except NoContractFoundError: logging.info("The file " + file + " does not contain a compilable contract.") return address, contracts def dump_statespace(self, strategy, contract, address=None, max_depth=None, execution_timeout=None, create_timeout=None): sym = SymExecWrapper( contract, address, strategy, dynloader=DynLoader(self.eth) if self.dynld else None, max_depth=max_depth, execution_timeout=execution_timeout, create_timeout=create_timeout) return get_serializable_statespace(sym) def graph_html(self, strategy, contract, address, max_depth=None, enable_physics=False, phrackify=False, execution_timeout=None, create_timeout=None): sym = SymExecWrapper( contract, address, strategy, dynloader=DynLoader(self.eth) if self.dynld else None, max_depth=max_depth, execution_timeout=execution_timeout, create_timeout=create_timeout) return generate_graph(sym, physics=enable_physics, phrackify=phrackify) def fire_lasers(self, strategy, contracts=None, address=None, modules=None, verbose_report=False, max_depth=None, execution_timeout=None, create_timeout=None, max_transaction_count=None): all_issues = [] for contract in (contracts or self.contracts): sym = SymExecWrapper( contract, address, strategy, dynloader=DynLoader(self.eth) if self.dynld else None, max_depth=max_depth, execution_timeout=execution_timeout, create_timeout=create_timeout, max_transaction_count=max_transaction_count) issues = fire_lasers(sym, modules) if type(contract) == SolidityContract: for issue in issues: issue.add_code_info(contract) all_issues += issues # Finally, output the results report = Report(verbose_report) for issue in all_issues: report.append_issue(issue) return report def get_state_variable_from_storage(self, address, params=None): if params is None: params = [] (position, length, mappings) = (0, 1, []) try: if params[0] == "mapping": if len(params) < 3: raise CriticalError("Invalid number of parameters.") position = int(params[1]) position_formatted = utils.zpad( utils.int_to_big_endian(position), 32) for i in range(2, len(params)): key = bytes(params[i], 'utf8') key_formatted = utils.rzpad(key, 32) mappings.append( int.from_bytes(utils.sha3(key_formatted + position_formatted), byteorder='big')) length = len(mappings) if length == 1: position = mappings[0] else: if len(params) >= 4: raise CriticalError("Invalid number of parameters.") if len(params) >= 1: position = int(params[0]) if len(params) >= 2: length = int(params[1]) if len(params) == 3 and params[2] == "array": position_formatted = utils.zpad( utils.int_to_big_endian(position), 32) position = int.from_bytes(utils.sha3(position_formatted), byteorder='big') except ValueError: raise CriticalError( "Invalid storage index. Please provide a numeric value.") outtxt = [] try: if length == 1: outtxt.append("{}: {}".format( position, self.eth.eth_getStorageAt(address, position))) else: if len(mappings) > 0: for i in range(0, len(mappings)): position = mappings[i] outtxt.append("{}: {}".format( hex(position), self.eth.eth_getStorageAt(address, position))) else: for i in range(position, position + length): outtxt.append("{}: {}".format( hex(i), self.eth.eth_getStorageAt(address, i))) except FileNotFoundError as e: raise CriticalError("IPC error: " + str(e)) except ConnectionError as e: raise CriticalError( "Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly." ) return '\n'.join(outtxt) @staticmethod def disassemble(contract): return contract.get_easm() @staticmethod def hash_for_function_signature(sig): return "0x%s" % utils.sha3(sig)[:4].hex()