def _check_events(erc_event, contract, ret): name = erc_event.name parameters = erc_event.parameters indexes = erc_event.indexes sig = f'{name}({",".join(parameters)})' event = contract.get_event_from_signature(sig) if not event: txt = f"[ ] {sig} is missing" logger.info(txt) missing_event = output.Output(txt, additional_fields={"event": sig}) missing_event.add(contract) ret["missing_event"].append(missing_event.data) return txt = f"[✓] {sig} is present" logger.info(txt) for i, index in enumerate(indexes): if index: if event.elems[i].indexed: txt = f"\t[✓] parameter {i} is indexed" logger.info(txt) else: txt = f"\t[ ] parameter {i} should be indexed" logger.info(txt) missing_event_index = output.Output(txt, additional_fields={"missing_index": i}) missing_event_index.add_event(event) ret["missing_event_index"].append(missing_event_index.data)
def output(self, _filename): info = '' for contract in self.contracts: stack_name = [] stack_definition = [] info += "\n\nContact Name: " + contract.name info += " Constructor Call Sequence: " cst = contract.constructors_declared if cst: stack_name.append(contract.name) stack_definition.append(self._get_soruce_code(cst)) for inherited_contract in contract.inheritance: cst = inherited_contract.constructors_declared if cst: stack_name.append(inherited_contract.name) stack_definition.append(self._get_soruce_code(cst)) if len(stack_name) > 0: info += " " + ' '.join(stack_name[len(stack_name) - 1]) count = len(stack_name) - 2 while count >= 0: info += "-->" + ' '.join(stack_name[count]) count = count - 1 info += "\n Constructor Definitions:" count = len(stack_definition) - 1 while count >= 0: info += "\n Contract name:" + str(stack_name[count]) info += "\n" + str(stack_definition[count]) count = count - 1 self.info(info) res = output.Output(info) return res
def generate_output(self, info, additional_fields=None): if additional_fields is None: additional_fields = {} printer_output = output.Output(info, additional_fields) printer_output.data["printer"] = self.ARGUMENT return printer_output
def output(self, _filename): info = '' for contract in self.slither.contracts_derived: stack_name = [] stack_definition = [] cst = contract.constructors_declared if cst: stack_name.append(contract.name) stack_definition.append(self._get_soruce_code(cst)) for inherited_contract in contract.inheritance: cst = inherited_contract.constructors_declared if cst: stack_name.append(inherited_contract.name) stack_definition.append(self._get_soruce_code(cst)) if len(stack_name) > 0: info += '\n########' + "#" * len(contract.name) + "########\n" info += "####### " + contract.name + " #######\n" info += '########' + "#" * len(contract.name) + "########\n\n" info += "## Constructor Call Sequence" + '\n' for name in stack_name[::-1]: info += "\t- " + name + '\n' info += "\n## Constructor Definitions" + '\n' count = len(stack_definition) - 1 while count >= 0: info += "\n### " + stack_name[count] + '\n' info += "\n" + str(stack_definition[count]) + '\n' count = count - 1 self.info(info) res = output.Output(info) return res
def generate_output(self, info, additional_fields=None): if additional_fields is None: additional_fields = {} d = output.Output(info, additional_fields) d.data['printer'] = self.ARGUMENT return d
def generate_output( self, info: Union[str, List[Union[str, SupportedOutput]]], additional_fields: Optional[Dict] = None, ) -> output.Output: if additional_fields is None: additional_fields = {} printer_output = output.Output(info, additional_fields) printer_output.data["printer"] = self.ARGUMENT return printer_output
def _generate_output_unresolved(kspec, message, color, generate_json): info = "" for contract, function in kspec: info += f"{message} {contract}.{function}\n" if info: logger.info(color(info)) if generate_json: json_kspec_present = output.Output(info, additional_fields={"signatures": kspec}) return json_kspec_present.data return None
def _generate_output(kspec, message, color, generate_json): info = "" for function in kspec: info += f"{message} {function.contract.name}.{function.full_name}\n" if info: logger.info(color(info)) if generate_json: json_kspec_present = output.Output(info) for function in kspec: json_kspec_present.add(function) return json_kspec_present.data return None
def approval_race_condition(contract, ret): increaseAllowance = contract.get_function_from_signature( 'increaseAllowance(address,uint256)') if not increaseAllowance: increaseAllowance = contract.get_function_from_signature( 'safeIncreaseAllowance(address,uint256)') if increaseAllowance: txt = f'\t[✓] {contract.name} has {increaseAllowance.full_name}' logger.info(txt) else: txt = f'\t[ ] {contract.name} is not protected for the ERC20 approval race condition' logger.info(txt) lack_of_erc20_race_condition_protection = output.Output(txt) lack_of_erc20_race_condition_protection.add(contract) ret["lack_of_erc20_race_condition_protection"].append( lack_of_erc20_race_condition_protection.data)
def check_variable_initialization(contract): results = {'variables-initialized': []} logger.info( green( '\n## Run variable initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks)' )) error_found = False for s in contract.state_variables: if s.initialized and not s.is_constant: info = f'{s.canonical_name} has an initial value ({s.source_mapping_str})' logger.info(red(info)) res = output.Output(info) res.add(s) results['variables-initialized'].append(res.data) error_found = True if not error_found: logger.info(green('No error found')) return results
def events_safeBatchTransferFrom(contract, ret): function = contract.get_function_from_signature( "safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)" ) events = [ { "name": "TransferSingle", "parameters": ["address", "address", "address", "uint256", "uint256"], }, { "name": "TransferBatch", "parameters": ["address", "address", "address", "uint256[]", "uint256[]"], }, ] event_counter_name = 0 event_counter_parameters = 0 if function: for event in events: for ir in function.all_slithir_operations(): if isinstance(ir, EventCall) and ir.name == event["name"]: event_counter_name += 1 if event["parameters"] == [str(a.type) for a in ir.arguments]: event_counter_parameters += 1 if event_counter_parameters == 1 and event_counter_name == 1: txt = "[✓] safeBatchTransferFrom emit TransferSingle or TransferBatch" logger.info(txt) else: txt = "[ ] safeBatchTransferFrom must emit TransferSingle or TransferBatch" logger.info(txt) erroneous_erc1155_safeBatchTransferFrom_event = output.Output(txt) erroneous_erc1155_safeBatchTransferFrom_event.add(contract) ret["erroneous_erc1155_safeBatchTransferFrom_event"].append( erroneous_erc1155_safeBatchTransferFrom_event.data )
def output(self, _filename): """ _filename is not used Args: _filename(string) """ txt = "\n" txt += self._compilation_type() results = { 'contracts': { "elements": [] }, 'number_lines': 0, 'number_lines_in_dependencies': 0, 'number_lines_assembly': 0, 'standard_libraries': [], 'ercs': [], } lines_number = self._lines_number() if lines_number: total_lines, total_dep_lines = lines_number txt += f'Number of lines: {total_lines} (+ {total_dep_lines} in dependencies)\n' results['number_lines'] = total_lines results['number_lines__dependencies'] = total_dep_lines total_asm_lines = self._get_number_of_assembly_lines() txt += f"Number of assembly lines: {total_asm_lines}\n" results['number_lines_assembly'] = total_asm_lines number_contracts, number_contracts_deps = self._number_contracts() txt += f'Number of contracts: {number_contracts} (+ {number_contracts_deps} in dependencies) \n\n' txt += self.get_detectors_result() libs = self._standard_libraries() if libs: txt += f'\nUse: {", ".join(libs)}\n' results['standard_libraries'] = [str(l) for l in libs] ercs = self._ercs() if ercs: txt += f'ERCs: {", ".join(ercs)}\n' results['ercs'] = [str(e) for e in ercs] for contract in self.slither.contracts_derived: txt += "\nContract {}\n".format(contract.name) txt += self.is_complex_code(contract) txt += '\tNumber of functions: {}\n'.format( self._number_functions(contract)) ercs = contract.ercs() if ercs: txt += '\tERCs: ' + ','.join(ercs) + '\n' is_erc20 = contract.is_erc20() if is_erc20: txt += '\tERC20 info:\n' txt += self.get_summary_erc20(contract) self.info(txt) results_contract = output.Output('') for contract in self.slither.contracts_derived: optimization, info, low, medium, high = self._get_detectors_result( ) contract_d = { 'contract_name': contract.name, 'is_complex_code': self._is_complex_code(contract), 'optimization_issues': optimization, 'informational_issues': info, 'low_issues': low, 'medium_issues': medium, 'high_issues': high, 'is_erc20': contract.is_erc20(), 'number_functions': self._number_functions(contract) } if contract_d['is_erc20']: pause, mint_limited, race_condition_mitigated = self._get_summary_erc20( contract) contract_d['erc20_pause'] = pause if mint_limited is not None: contract_d['erc20_can_mint'] = True contract_d['erc20_mint_limited'] = mint_limited else: contract_d['erc20_can_mint'] = False contract_d[ 'erc20_race_condition_mitigated'] = race_condition_mitigated results_contract.add_contract(contract, additional_fields=contract_d) results['contracts']['elements'] = results_contract.elements json = self.generate_output(txt, additional_fields=results) return json
def output(self, _filename): """ _filename is not used Args: _filename(string) """ txt = "" all_contracts = [] for c in self.contracts: if c.is_top_level: continue is_upgradeable_proxy = c.is_upgradeable_proxy is_upgradeable = c.is_upgradeable additional_txt_info = '' if is_upgradeable_proxy: additional_txt_info += ' (Upgradeable Proxy)' if is_upgradeable: additional_txt_info += ' (Upgradeable)' if c in self.slither.contracts_derived: additional_txt_info += ' (Most derived contract)' txt += blue(f"\n+ Contract {c.name}{additional_txt_info}\n") additional_fields = output.Output( '', additional_fields={ 'is_upgradeable_proxy': is_upgradeable_proxy, 'is_upgradeable': is_upgradeable, 'is_most_derived': c in self.slither.contracts_derived }) # Order the function with # contract_declarer -> list_functions public = [(f.contract_declarer.name, f) for f in c.functions if (not f.is_shadowed and not f.is_constructor_variables) ] collect = collections.defaultdict(list) for a, b in public: collect[a].append(b) public = list(collect.items()) for contract, functions in public: txt += blue(" - From {}\n".format(contract)) functions = sorted(functions, key=lambda f: f.full_name) for function in functions: if function.visibility in ['external', 'public']: txt += green(" - {} ({})\n".format( function.full_name, function.visibility)) if function.visibility in ['internal', 'private']: txt += magenta(" - {} ({})\n".format( function.full_name, function.visibility)) if function.visibility not in [ 'external', 'public', 'internal', 'private' ]: txt += " - {} ({})\n".format( function.full_name, function.visibility) additional_fields.add( function, additional_fields={"visibility": function.visibility}) all_contracts.append((c, additional_fields.data)) self.info(txt) res = self.generate_output(txt) for contract, additional_fields in all_contracts: res.add(contract, additional_fields=additional_fields) return res
def _check_signature(erc_function, contract, ret): name = erc_function.name parameters = erc_function.parameters return_type = erc_function.return_type view = erc_function.view required = erc_function.required events = erc_function.events sig = f'{name}({",".join(parameters)})' function = contract.get_function_from_signature(sig) if not function: # The check on state variable is needed until we have a better API to handle state variable getters state_variable_as_function = contract.get_state_variable_from_name(name) if not state_variable_as_function or not state_variable_as_function.visibility in [ "public", "external", ]: txt = f'[ ] {sig} is missing {"" if required else "(optional)"}' logger.info(txt) missing_func = output.Output( txt, additional_fields={"function": sig, "required": required} ) missing_func.add(contract) ret["missing_function"].append(missing_func.data) return types = [str(x) for x in export_nested_types_from_variable(state_variable_as_function)] if types != parameters: txt = f'[ ] {sig} is missing {"" if required else "(optional)"}' logger.info(txt) missing_func = output.Output( txt, additional_fields={"function": sig, "required": required} ) missing_func.add(contract) ret["missing_function"].append(missing_func.data) return function_return_type = [export_return_type_from_variable(state_variable_as_function)] function = state_variable_as_function function_view = True else: function_return_type = function.return_type # pylint: disable=no-member function_view = function.view # pylint: disable=no-member txt = f"[✓] {sig} is present" logger.info(txt) if function_return_type: function_return_type = ",".join([str(x) for x in function_return_type]) if function_return_type == return_type: txt = f"\t[✓] {sig} -> ({function_return_type}) (correct return type)" logger.info(txt) else: txt = f"\t[ ] {sig} -> ({function_return_type}) should return {return_type}" logger.info(txt) incorrect_return = output.Output( txt, additional_fields={ "expected_return_type": return_type, "actual_return_type": function_return_type, }, ) incorrect_return.add(function) ret["incorrect_return_type"].append(incorrect_return.data) elif not return_type: txt = f"\t[✓] {sig} -> () (correct return type)" logger.info(txt) else: txt = f"\t[ ] {sig} -> () should return {return_type}" logger.info(txt) incorrect_return = output.Output( txt, additional_fields={ "expected_return_type": return_type, "actual_return_type": function_return_type, }, ) incorrect_return.add(function) ret["incorrect_return_type"].append(incorrect_return.data) if view: if function_view: txt = f"\t[✓] {sig} is view" logger.info(txt) else: txt = f"\t[ ] {sig} should be view" logger.info(txt) should_be_view = output.Output(txt) should_be_view.add(function) ret["should_be_view"].append(should_be_view.data) if events: # pylint: disable=too-many-nested-blocks for event in events: event_sig = f'{event.name}({",".join(event.parameters)})' if not function: txt = f"\t[ ] Must emit be view {event_sig}" logger.info(txt) missing_event_emmited = output.Output( txt, additional_fields={"missing_event": event_sig} ) missing_event_emmited.add(function) ret["missing_event_emmited"].append(missing_event_emmited.data) else: event_found = False for ir in function.all_slithir_operations(): if isinstance(ir, EventCall): if ir.name == event.name: if event.parameters == [str(a.type) for a in ir.arguments]: event_found = True break if event_found: txt = f"\t[✓] {event_sig} is emitted" logger.info(txt) else: txt = f"\t[ ] Must emit be view {event_sig}" logger.info(txt) missing_event_emmited = output.Output( txt, additional_fields={"missing_event": event_sig} ) missing_event_emmited.add(function) ret["missing_event_emmited"].append(missing_event_emmited.data)
def output(self, _filename): # pylint: disable=too-many-locals """ _filename is not used Args: _filename(string) """ txt = "" all_contracts = [] for c in self.contracts: if c.is_top_level: continue is_upgradeable_proxy = c.is_upgradeable_proxy is_upgradeable = c.is_upgradeable additional_txt_info = "" if is_upgradeable_proxy: additional_txt_info += " (Upgradeable Proxy)" if is_upgradeable: additional_txt_info += " (Upgradeable)" if c in self.slither.contracts_derived: additional_txt_info += " (Most derived contract)" txt += blue(f"\n+ Contract {c.name}{additional_txt_info}\n") additional_fields = output.Output( "", additional_fields={ "is_upgradeable_proxy": is_upgradeable_proxy, "is_upgradeable": is_upgradeable, "is_most_derived": c in self.slither.contracts_derived, }, ) # Order the function with # contract_declarer -> list_functions public = [ (f.contract_declarer.name, f) for f in c.functions if (not f.is_shadowed and not f.is_constructor_variables) ] collect = collections.defaultdict(list) for a, b in public: collect[a].append(b) public = list(collect.items()) for contract, functions in public: txt += blue(f" - From {contract}\n") functions = sorted(functions, key=lambda f: f.full_name) for function in functions: if function.visibility in ["external", "public"]: txt += green(f" - {function.full_name} ({function.visibility})\n") if function.visibility in ["internal", "private"]: txt += magenta(f" - {function.full_name} ({function.visibility})\n") if function.visibility not in [ "external", "public", "internal", "private", ]: txt += f" - {function.full_name} ({function.visibility})\n" additional_fields.add( function, additional_fields={"visibility": function.visibility} ) all_contracts.append((c, additional_fields.data)) self.info(txt) res = self.generate_output(txt) for contract, additional_fields in all_contracts: res.add(contract, additional_fields=additional_fields) return res
def output(self, _filename): """ _filename is not used Args: _filename(string) """ txt = "\n" txt += self._compilation_type() results = { 'contracts': { "elements": [] }, 'number_lines': 0, 'number_lines_in_dependencies': 0, 'number_lines_assembly': 0, 'standard_libraries': [], 'ercs': [], 'number_findings': dict(), 'detectors': [] } lines_number = self._lines_number() if lines_number: total_lines, total_dep_lines, total_tests_lines = lines_number txt += f'Number of lines: {total_lines} (+ {total_dep_lines} in dependencies, + {total_tests_lines} in tests)\n' results['number_lines'] = total_lines results['number_lines__dependencies'] = total_dep_lines total_asm_lines = self._get_number_of_assembly_lines() txt += f"Number of assembly lines: {total_asm_lines}\n" results['number_lines_assembly'] = total_asm_lines number_contracts, number_contracts_deps, number_contracts_tests = self._number_contracts( ) txt += f'Number of contracts: {number_contracts} (+ {number_contracts_deps} in dependencies, + {number_contracts_tests} tests) \n\n' txt_detectors, detectors_results, optimization, info, low, medium, high = self.get_detectors_result( ) txt += txt_detectors results['number_findings'] = { 'optimization_issues': optimization, 'informational_issues': info, 'low_issues': low, 'medium_issues': medium, 'high_issues': high } results['detectors'] = detectors_results libs = self._standard_libraries() if libs: txt += f'\nUse: {", ".join(libs)}\n' results['standard_libraries'] = [str(l) for l in libs] ercs = self._ercs() if ercs: txt += f'ERCs: {", ".join(ercs)}\n' results['ercs'] = [str(e) for e in ercs] table = MyPrettyTable([ "Name", "# functions", "ERCS", "ERC20 info", "Complex code", "Features" ]) for contract in self.slither.contracts_derived: if contract.is_from_dependency() or contract.is_test: continue is_complex = self.is_complex_code(contract) number_functions = self._number_functions(contract) ercs = ','.join(contract.ercs()) is_erc20 = contract.is_erc20() erc20_info = '' if is_erc20: erc20_info += self.get_summary_erc20(contract) features = "\n".join([ name for name, to_print in self._get_features(contract).items() if to_print ]) table.add_row([ contract.name, number_functions, ercs, erc20_info, is_complex, features ]) self.info(txt + '\n' + str(table)) results_contract = output.Output('') for contract in self.slither.contracts_derived: if contract.is_test or contract.is_from_dependency(): continue contract_d = { 'contract_name': contract.name, 'is_complex_code': self._is_complex_code(contract), 'is_erc20': contract.is_erc20(), 'number_functions': self._number_functions(contract), 'features': [ name for name, to_print in self._get_features(contract).items() if to_print ] } if contract_d['is_erc20']: pause, mint_limited, race_condition_mitigated = self._get_summary_erc20( contract) contract_d['erc20_pause'] = pause if mint_limited is not None: contract_d['erc20_can_mint'] = True contract_d['erc20_mint_limited'] = mint_limited else: contract_d['erc20_can_mint'] = False contract_d[ 'erc20_race_condition_mitigated'] = race_condition_mitigated results_contract.add_contract(contract, additional_fields=contract_d) results['contracts']['elements'] = results_contract.elements json = self.generate_output(txt, additional_fields=results) return json
def output(self, _filename): # pylint: disable=too-many-locals,too-many-statements """ _filename is not used Args: _filename(string) """ txt = "\n" txt += self._compilation_type() results = { "contracts": {"elements": []}, "number_lines": 0, "number_lines_in_dependencies": 0, "number_lines_assembly": 0, "standard_libraries": [], "ercs": [], "number_findings": dict(), "detectors": [], } lines_number = self._lines_number() if lines_number: total_lines, total_dep_lines, total_tests_lines = lines_number txt += f"Number of lines: {total_lines} (+ {total_dep_lines} in dependencies, + {total_tests_lines} in tests)\n" results["number_lines"] = total_lines results["number_lines__dependencies"] = total_dep_lines total_asm_lines = self._get_number_of_assembly_lines() txt += f"Number of assembly lines: {total_asm_lines}\n" results["number_lines_assembly"] = total_asm_lines ( number_contracts, number_contracts_deps, number_contracts_tests, ) = self._number_contracts() txt += f"Number of contracts: {number_contracts} (+ {number_contracts_deps} in dependencies, + {number_contracts_tests} tests) \n\n" ( txt_detectors, detectors_results, optimization, info, low, medium, high, ) = self.get_detectors_result() txt += txt_detectors results["number_findings"] = { "optimization_issues": optimization, "informational_issues": info, "low_issues": low, "medium_issues": medium, "high_issues": high, } results["detectors"] = detectors_results libs = self._standard_libraries() if libs: txt += f'\nUse: {", ".join(libs)}\n' results["standard_libraries"] = [str(l) for l in libs] ercs = self._ercs() if ercs: txt += f'ERCs: {", ".join(ercs)}\n' results["ercs"] = [str(e) for e in ercs] table = MyPrettyTable( ["Name", "# functions", "ERCS", "ERC20 info", "Complex code", "Features"] ) for contract in self.slither.contracts_derived: if contract.is_from_dependency() or contract.is_test: continue is_complex = self.is_complex_code(contract) number_functions = self._number_functions(contract) ercs = ",".join(contract.ercs()) is_erc20 = contract.is_erc20() erc20_info = "" if is_erc20: erc20_info += self.get_summary_erc20(contract) features = "\n".join( [name for name, to_print in self._get_features(contract).items() if to_print] ) table.add_row( [contract.name, number_functions, ercs, erc20_info, is_complex, features,] ) self.info(txt + "\n" + str(table)) results_contract = output.Output("") for contract in self.slither.contracts_derived: if contract.is_test or contract.is_from_dependency(): continue contract_d = { "contract_name": contract.name, "is_complex_code": self._is_complex_code(contract), "is_erc20": contract.is_erc20(), "number_functions": self._number_functions(contract), "features": [ name for name, to_print in self._get_features(contract).items() if to_print ], } if contract_d["is_erc20"]: pause, mint_limited, race_condition_mitigated = self._get_summary_erc20(contract) contract_d["erc20_pause"] = pause if mint_limited is not None: contract_d["erc20_can_mint"] = True contract_d["erc20_mint_limited"] = mint_limited else: contract_d["erc20_can_mint"] = False contract_d["erc20_race_condition_mitigated"] = race_condition_mitigated results_contract.add_contract(contract, additional_fields=contract_d) results["contracts"]["elements"] = results_contract.elements json = self.generate_output(txt, additional_fields=results) return json