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 check_initialization(s): initializable = s.get_contract_from_name('Initializable') if initializable is None: logger.info( yellow( 'Initializable contract not found, the contract does not follow a standard initalization schema.' )) return initializer = initializable.get_modifier_from_signature('initializer()') init_info = '' double_calls_found = False missing_call = False initializer_modifier_missing = False for contract in s.contracts: if initializable in contract.inheritance: all_init_functions = _get_initialize_functions(contract) for f in all_init_functions: if not initializer in f.modifiers: initializer_modifier_missing = True logger.info( red(f'{f.contract.name}.{f.name} does not call initializer' )) most_derived_init = _get_most_derived_init(contract) if most_derived_init is None: init_info += f'{contract.name} has no initialize function\n' continue else: init_info += f'{contract.name} needs to be initialized by {most_derived_init.full_name}\n' all_init_functions_called = _get_all_internal_calls( most_derived_init) + [most_derived_init] missing_calls = [ f for f in all_init_functions if not f in all_init_functions_called ] for f in missing_calls: logger.info( red(f'Missing call to {f.contract.name}.{f.name} in {contract.name}' )) missing_call = True double_calls = list( set([ f for f in all_init_functions_called if all_init_functions_called.count(f) > 1 ])) for f in double_calls: logger.info( red(f'{f.contract.name + "." + f.full_name} is called multiple time in {contract.name}' )) double_calls_found = True if not initializer_modifier_missing: logger.info( green('All the init functions have the initiliazer modifier')) if not double_calls_found: logger.info(green('No double call to init functions found')) if not missing_call: logger.info(green('No missing call to an init function found')) logger.info( green( 'Check the deployement script to ensure that these functions are called:\n' + init_info))
def generate_erc20(contract: Contract, type_property: str, addresses: Addresses): """ Generate the ERC20 tests Files generated: - interfaces.sol: generic crytic interface - Properties[CONTRACTNAME].sol: erc20 properties - Test[CONTRACTNAME].sol: Target, its constructor needs to be manually updated - If truffle - migrations/x_Test[CONTRACTNAME].js - test/crytic/InitializationTest[CONTRACTNAME].js: unit tests to check that the contract is correctly configured - test/crytic/Test[CONTRACTNAME].js: ERC20 checks - echidna_config.yaml: configuration file :param addresses: :param contract: :param type_property: One of ERC20_PROPERTIES.keys() :return: """ if contract.slither.crytic_compile.type not in [PlatformType.TRUFFLE, PlatformType.SOLC]: logging.error(f"{contract.slither.crytic_compile.type} not yet supported by slither-prop") return # Check if the contract is an ERC20 contract and if the functions have the correct visibility errors = _check_compatibility(contract) if errors: logger.error(red(errors)) return properties = ERC20_PROPERTIES.get(type_property, None) if properties is None: logger.error( f"{type_property} unknown. Types available {[x for x in ERC20_PROPERTIES.keys()]}" ) return properties = properties.properties # Generate the output directory output_dir = _platform_to_output_dir(contract.slither.crytic_compile.platform) output_dir.mkdir(exist_ok=True) # Get the properties solidity_properties, unit_tests = _get_properties(contract, properties) # Generate the contract containing the properties generate_solidity_interface(output_dir, addresses) property_file = generate_solidity_properties( contract, type_property, solidity_properties, output_dir ) # Generate the Test contract initialization_recommendation = _initialization_recommendation(type_property) contract_filename, contract_name = generate_test_contract( contract, type_property, output_dir, property_file, initialization_recommendation ) # Generate Echidna config file echidna_config_filename = generate_echidna_config( Path(contract.slither.crytic_compile.target).parent, addresses ) unit_test_info = "" # If truffle, generate unit tests if contract.slither.crytic_compile.type == PlatformType.TRUFFLE: unit_test_info = generate_truffle_test(contract, type_property, unit_tests, addresses) logger.info("################################################") logger.info(green(f"Update the constructor in {Path(output_dir, contract_filename)}")) if unit_test_info: logger.info(green(unit_test_info)) logger.info(green("To run Echidna:")) txt = f"\t echidna-test {contract.slither.crytic_compile.target} " txt += f"--contract {contract_name} --config {echidna_config_filename}" logger.info(green(txt))
def output(self, _filename): """ _filename is not used Args: _filename(string) """ txt = "" if not self.slither.crytic_compile: txt = "The EVM printer requires to compile with crytic-compile" self.info(red(txt)) res = self.generate_output(txt) return res evm_info = _extract_evm_info(self.slither) for contract in self.slither.contracts_derived: txt += blue("Contract {}\n".format(contract.name)) contract_file = self.slither.source_code[ contract.source_mapping["filename_absolute"] ].encode("utf-8") contract_file_lines = open( contract.source_mapping["filename_absolute"], "r" ).readlines() contract_pcs = {} contract_cfg = {} for function in contract.functions: txt += blue(f"\tFunction {function.canonical_name}\n") # CFG and source mapping depend on function being constructor or not if function.is_constructor: contract_cfg = evm_info["cfg_init", contract.name] contract_pcs = evm_info["mapping_init", contract.name] else: contract_cfg = evm_info["cfg", contract.name] contract_pcs = evm_info["mapping", contract.name] for node in function.nodes: txt += green("\t\tNode: " + str(node) + "\n") node_source_line = ( contract_file[0 : node.source_mapping["start"]].count("\n".encode("utf-8")) + 1 ) txt += green( "\t\tSource line {}: {}\n".format( node_source_line, contract_file_lines[node_source_line - 1].rstrip(), ) ) txt += magenta("\t\tEVM Instructions:\n") node_pcs = contract_pcs.get(node_source_line, []) for pc in node_pcs: txt += magenta( "\t\t\t0x{:x}: {}\n".format( int(pc), contract_cfg.get_instruction_at(pc) ) ) for modifier in contract.modifiers: txt += blue(f"\tModifier {modifier.canonical_name}\n") for node in modifier.nodes: txt += green("\t\tNode: " + str(node) + "\n") node_source_line = ( contract_file[0 : node.source_mapping["start"]].count("\n".encode("utf-8")) + 1 ) txt += green( "\t\tSource line {}: {}\n".format( node_source_line, contract_file_lines[node_source_line - 1].rstrip(), ) ) txt += magenta("\t\tEVM Instructions:\n") node_pcs = contract_pcs.get(node_source_line, []) for pc in node_pcs: txt += magenta( "\t\t\t0x{:x}: {}\n".format( int(pc), contract_cfg.get_instruction_at(pc) ) ) self.info(txt) res = self.generate_output(txt) return res
def output(self, _filename): """ _filename is not used Args: _filename(string) """ txt = "" all_contracts = [] for c in self.contracts: 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 output(self, filename): """ Output the inheritance relation _filename is not used Args: _filename(string) """ info = 'Inheritance\n' if not self.contracts: return info += blue('Child_Contract -> ') + green('Immediate_Base_Contracts') info += green(' [Not_Immediate_Base_Contracts]') result = {'child_to_base': {}} for child in self.contracts: if child.is_top_level: continue info += blue(f'\n+ {child.name}\n') result['child_to_base'][child.name] = { 'immediate': [], 'not_immediate': [] } if child.inheritance: immediate = child.immediate_inheritance not_immediate = [ i for i in child.inheritance if i not in immediate ] info += ' -> ' + green(", ".join(map(str, immediate))) + '\n' result['child_to_base'][child.name]['immediate'] = list( map(str, immediate)) if not_immediate: info += ", [" + green(", ".join(map( str, not_immediate))) + "]\n" result['child_to_base'][ child.name]['not_immediate'] = list( map(str, not_immediate)) info += green('\n\nBase_Contract -> ') + blue( 'Immediate_Child_Contracts') + '\n' info += blue(' [Not_Immediate_Child_Contracts]') + '\n' result['base_to_child'] = {} for base in self.contracts: if base.is_top_level: continue info += green(f'\n+ {base.name}') + '\n' children = list(self._get_child_contracts(base)) result['base_to_child'][base.name] = { 'immediate': [], 'not_immediate': [] } if children: immediate = [ child for child in children if base in child.immediate_inheritance ] not_immediate = [ child for child in children if not child in immediate ] info += ' -> ' + blue(", ".join(map(str, immediate))) + '\n' result['base_to_child'][base.name]['immediate'] = list( map(str, immediate)) if not_immediate: info += ', [' + blue(", ".join(map( str, not_immediate))) + ']' + '\n' result['base_to_child'][base.name]['not_immediate'] = list( map(str, immediate)) self.info(info) res = self.generate_output(info, additional_fields=result) return res
def compare_variables_order(contract1, contract2, missing_variable_check=True): results = { 'missing_variables': [], 'different-variables': [], 'extra-variables': [] } logger.info( green( f'\n## Run variables ordering checks between {contract1.name} and {contract2.name}... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks)' )) order1 = [ variable for variable in contract1.state_variables if not variable.is_constant ] order2 = [ variable for variable in contract2.state_variables if not variable.is_constant ] error_found = False idx = 0 for idx in range(0, len(order1)): variable1 = order1[idx] if len(order2) <= idx: if missing_variable_check: info = f'Variable only in {contract1.name}: {variable1.name} ({variable1.source_mapping_str})' logger.info(yellow(info)) res = Output(info) res.add(variable1) results['missing_variables'].append(res.data) error_found = True continue variable2 = order2[idx] if (variable1.name != variable2.name) or (variable1.type != variable2.type): info = f'Different variables between {contract1.name} and {contract2.name}:\n' info += f'\t Variable {idx} in {contract1.name}: {variable1.name} {variable1.type} ({variable1.source_mapping_str})\n' info += f'\t Variable {idx} in {contract2.name}: {variable2.name} {variable2.type} ({variable2.source_mapping_str})\n' logger.info(red(info)) res = Output(info, additional_fields={'index': idx}) res.add(variable1) res.add(variable2) results['different-variables'].append(res.data) error_found = True idx = idx + 1 while idx < len(order2): variable2 = order2[idx] info = f'Extra variables in {contract2.name}: {variable2.name} ({variable2.source_mapping_str})\n' logger.info(yellow(info)) res = Output(info, additional_fields={'index': idx}) res.add(variable2) results['extra-variables'].append(res.data) idx = idx + 1 if not error_found: logger.info(green('No error found')) return results