def load_from_bytecode( self, code: str, bin_runtime: bool = False, address: Optional[str] = None) -> Tuple[str, EVMContract]: """ Returns the address and the contract class for the given bytecode :param code: Bytecode :param bin_runtime: Whether the code is runtime code or creation code :param address: address of contract :return: tuple(address, Contract class) """ 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 test_generate_graph(self): for input_file in TESTDATA_INPUTS.iterdir(): output_expected = TESTDATA_OUTPUTS_EXPECTED / ( input_file.name + ".graph.html" ) output_current = TESTDATA_OUTPUTS_CURRENT / ( input_file.name + ".graph.html" ) contract = EVMContract(input_file.read_text()) sym = SymExecWrapper( contract, address=(util.get_indexed_address(0)), strategy="dfs", transaction_count=1, execution_timeout=5, ) html = generate_graph(sym) output_current.write_text(html) lines_expected = re.findall( r"'label': '.*'", str(output_current.read_text()) ) lines_found = re.findall(r"'label': '.*'", str(output_current.read_text())) if not (lines_expected == lines_found): self.found_changed_files(input_file, output_expected, output_current) self.assert_and_show_changed_files()
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 test_generate_graph(self): for input_file in TESTDATA_INPUTS.iterdir(): output_expected = TESTDATA_OUTPUTS_EXPECTED / (input_file.name + ".graph.html") output_current = TESTDATA_OUTPUTS_CURRENT / (input_file.name + ".graph.html") contract = EVMContract(input_file.read_text()) disassembler = MythrilDisassembler() disassembler.contracts.append(contract) analyzer = MythrilAnalyzer( disassembler=disassembler, strategy="dfs", execution_timeout=5, max_depth=30, address=(util.get_indexed_address(0)), ) html = analyzer.graph_html(transaction_count=1) output_current.write_text(html) lines_expected = re.findall(r"'label': '.*'", str(output_current.read_text())) lines_found = re.findall(r"'label': '.*'", str(output_current.read_text())) if not (lines_expected == lines_found): self.found_changed_files(input_file, output_expected, output_current) self.assert_and_show_changed_files()
def test_sym_exec(): contract = SolidityContract( str(tests.TESTDATA_INPUTS_CONTRACTS / "calls.sol"), solc_binary=Mythril._init_solc_binary("0.5.0"), ) sym = SymExecWrapper(contract, address=(util.get_indexed_address(0)), strategy="dfs") issues = fire_lasers(sym) assert len(issues) != 0
def test_sym_exec(): contract = SolidityContract( str(tests.TESTDATA_INPUTS_CONTRACTS / "calls.sol")) sym = SymExecWrapper( contract, address=(util.get_indexed_address(0)), strategy="dfs", execution_timeout=25, ) issues = fire_lasers(sym) assert len(issues) != 0
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 _generate_report(input_file): contract = EVMContract(input_file.read_text(), enable_online_lookup=False) sym = SymExecWrapper( contract, address=(util.get_indexed_address(0)), strategy="dfs", execution_timeout=30, transaction_count=1, ) issues = fire_lasers(sym) report = Report() for issue in issues: issue.filename = "test-filename.sol" report.append_issue(issue) return report, input_file
def load_from_solidity( self, solidity_files: List[str]) -> Tuple[str, List[SolidityContract]]: """ :param solidity_files: List of solidity_files :return: tuple of address, contract class list """ 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_settings_json=self.solc_settings_json, ) if contract_name is not None: contract = SolidityContract( input_file=file, name=contract_name, solc_settings_json=self.solc_settings_json, solc_binary=self.solc_binary, ) self.contracts.append(contract) contracts.append(contract) else: for contract in get_contracts_from_file( input_file=file, solc_settings_json=self.solc_settings_json, 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: error_msg = str(e) # Check if error is related to solidity version mismatch if ("Error: Source file requires different compiler version" in error_msg): # Grab relevant line "pragma solidity <solv>...", excluding any comments solv_pragma_line = error_msg.split("\n")[-3].split("//")[0] # Grab solidity version from relevant line solv_match = re.findall(r"[0-9]+\.[0-9]+\.[0-9]+", solv_pragma_line) error_suggestion = ("<version_number>" if len(solv_match) != 1 else solv_match[0]) error_msg = ( error_msg + '\nSolidityVersionMismatch: Try adding the option "--solv ' + error_suggestion + '"\n') raise CriticalError(error_msg) except NoContractFoundError: log.error("The file " + file + " does not contain a compilable contract.") return address, contracts
def analyze_truffle_project(sigs, args): """ :param sigs: :param args: """ project_root = os.getcwd() build_dir = os.path.join(project_root, "build", "contracts") files = os.listdir(build_dir) for filename in files: if re.match(r".*\.json$", filename) and filename != "Migrations.json": with open(os.path.join(build_dir, filename)) as cf: contractdata = json.load(cf) try: name = contractdata["contractName"] bytecode = contractdata["deployedBytecode"] filename = PurePath(contractdata["sourcePath"]).name except KeyError: print( "Unable to parse contract data. Please use Truffle 4 to compile your project." ) sys.exit() if len(bytecode) < 4: continue get_sigs_from_truffle(sigs, contractdata) ethcontract = EVMContract(bytecode, name=name) address = util.get_indexed_address(0) sym = SymExecWrapper( ethcontract, address, args.strategy, max_depth=args.max_depth, create_timeout=args.create_timeout, execution_timeout=args.execution_timeout, transaction_count=args.transaction_count, modules=args.modules or [], compulsory_statespace=False, ) issues = fire_lasers(sym) if not len(issues): if args.outform == "text" or args.outform == "markdown": print("# Analysis result for " + name + "\n\nNo issues found.") else: result = { "contract": name, "result": { "success": True, "error": None, "issues": [] }, } print(json.dumps(result)) else: report = Report() # augment with source code deployed_disassembly = ethcontract.disassembly constructor_disassembly = ethcontract.creation_disassembly source = contractdata["source"] deployed_source_map = contractdata["deployedSourceMap"].split( ";") source_map = contractdata["sourceMap"].split(";") deployed_mappings = get_mappings(source, deployed_source_map) constructor_mappings = get_mappings(source, source_map) for issue in issues: if issue.function == "constructor": mappings = constructor_mappings disassembly = constructor_disassembly else: mappings = deployed_mappings disassembly = deployed_disassembly index = get_instruction_index(disassembly.instruction_list, issue.address) if index: try: offset = mappings[index].offset length = mappings[index].length issue.filename = filename issue.code = source.encode("utf-8")[offset:offset + length].decode( "utf-8") issue.lineno = mappings[index].lineno except IndexError: log.debug("No code mapping at index %d", index) report.append_issue(issue) if args.outform == "json": result = { "contract": name, "result": { "success": True, "error": None, "issues": list(map(lambda x: x.as_dict, issues)), }, } print(json.dumps(result)) else: if args.outform == "text": print("# Analysis result for " + name + ":\n\n" + report.as_text()) elif args.outform == "markdown": print(report.as_markdown())