def compare_function_ids(implem, proxy): results = { 'function-id-collision':[], 'shadowing':[], } logger.info(green('\n## Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks)')) signatures_implem = get_signatures(implem) signatures_proxy = get_signatures(proxy) signatures_ids_implem = {get_function_id(s): s for s in signatures_implem} signatures_ids_proxy = {get_function_id(s): s for s in signatures_proxy} error_found = False for (k, _) in signatures_ids_implem.items(): if k in signatures_ids_proxy: error_found = True if signatures_ids_implem[k] != signatures_ids_proxy[k]: implem_function = _get_function_or_variable(implem, signatures_ids_implem[k]) proxy_function = _get_function_or_variable(proxy, signatures_ids_proxy[k]) info = f'Function id collision found: {implem_function.canonical_name} ({implem_function.source_mapping_str}) {proxy_function.canonical_name} ({proxy_function.source_mapping_str})' logger.info(red(info)) res = Output(info) res.add(implem_function) res.add(proxy_function) results['function-id-collision'].append(res.data) else: implem_function = _get_function_or_variable(implem, signatures_ids_implem[k]) proxy_function = _get_function_or_variable(proxy, signatures_ids_proxy[k]) info = f'Shadowing between {implem_function.canonical_name} ({implem_function.source_mapping_str}) and {proxy_function.canonical_name} ({proxy_function.source_mapping_str})' logger.info(red(info)) res = Output(info) res.add(implem_function) res.add(proxy_function) results['shadowing'].append(res.data) if not error_found: logger.info(green('No error found')) return results
def check_initialization(contract): results = { 'Initializable-present': False, 'Initializable-inherited': False, 'Initializable.initializer()-present': False, 'missing-initializer-modifier': [], 'initialize_target': {}, 'missing-calls': [], 'multiple-calls': [] } error_found = False logger.info( green( '\n## Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks)' )) # Check if the Initializable contract is present initializable = contract.slither.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 results results['Initializable-present'] = True # Check if the Initializable contract is inherited if initializable not in contract.inheritance: logger.info( yellow('The logic contract does not call the initializer.')) return results results['Initializable-inherited'] = True # Check if the Initializable contract is inherited initializer = contract.get_modifier_from_canonical_name( 'Initializable.initializer()') if initializer is None: logger.info(yellow('Initializable.initializer() does not exist')) return results results['Initializable.initializer()-present'] = True # Check if a init function lacks the initializer modifier initializer_modifier_missing = False all_init_functions = _get_initialize_functions(contract) for f in all_init_functions: if not initializer in f.modifiers: initializer_modifier_missing = True info = f'{f.canonical_name} does not call the initializer modifier' logger.info(red(info)) res = Output(info) res.add(f) results['missing-initializer-modifier'].append(res.data) if not initializer_modifier_missing: logger.info( green('All the init functions have the initializer modifier')) # Check if we can determine the initialize function that will be called # TODO: handle MultipleInitTarget try: most_derived_init = _get_most_derived_init(contract) except MultipleInitTarget: logger.info(red('Too many init targets')) return results if most_derived_init is None: init_info = f'{contract.name} has no initialize function\n' logger.info(green(init_info)) results['initialize_target'] = {} return results # results['initialize_target'] is set at the end, as we want to print it last # Check if an initialize function is not called from the most_derived_init function missing_call = False 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: info = f'Missing call to {f.canonical_name} in {most_derived_init.canonical_name}' logger.info(red(info)) res = Output(info) res.add(f, {"is_most_derived_init_function": False}) res.add(most_derived_init, {"is_most_derived_init_function": True}) results['missing-calls'].append(res.data) missing_call = True if not missing_call: logger.info(green('No missing call to an init function found')) # Check if an init function is called multiple times double_calls = list( set([ f for f in all_init_functions_called if all_init_functions_called.count(f) > 1 ])) double_calls_found = False for f in double_calls: info = f'{f.canonical_name} is called multiple times in {most_derived_init.full_name}' logger.info(red(info)) res = Output(info) res.add(f) results['multiple-calls'].append(res.data) double_calls_found = True if not double_calls_found: logger.info(green('No double call to init functions found')) # Print the initialize_target info init_info = f'{contract.name} needs to be initialized by {most_derived_init.full_name}\n' logger.info( green( 'Check the deployement script to ensure that these functions are called:\n' + init_info)) res = Output(init_info) res.add(most_derived_init) results['initialize_target'] = res.data if not error_found: logger.info(green('No error found')) return results
def constant_conformance_check(contract_v1, contract_v2): results = { "became_constants": [], "were_constants": [], "not_found_in_v2": [], } logger.info( green( '\n## Run variable constants conformance check... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks)' )) error_found = False state_variables_v1 = contract_v1.state_variables state_variables_v2 = contract_v2.state_variables v2_additional_variables = len(state_variables_v2) - len(state_variables_v1) if v2_additional_variables < 0: v2_additional_variables = 0 # We keep two index, because we need to have them out of sync if v2 # has additional non constant variables idx_v1 = 0 idx_v2 = 0 while idx_v1 < len(state_variables_v1): state_v1 = contract_v1.state_variables[idx_v1] if len(state_variables_v2) <= idx_v2: break state_v2 = contract_v2.state_variables[idx_v2] if state_v2: if state_v1.is_constant: if not state_v2.is_constant: # If v2 has additional non constant variables, we need to skip them if (state_v1.name != state_v2.name or state_v1.type != state_v2.type) and v2_additional_variables > 0: v2_additional_variables -= 1 idx_v2 += 1 continue info = f'{state_v1.canonical_name} ({state_v1.source_mapping_str}) was constant and {state_v2.canonical_name} is not ({state_v2.source_mapping_str})' logger.info(red(info)) res = Output(info) res.add(state_v1) res.add(state_v2) results['were_constants'].append(res.data) error_found = True elif state_v2.is_constant: info = f'{state_v1.canonical_name} ({state_v1.source_mapping_str}) was not constant but {state_v2.canonical_name} is ({state_v2.source_mapping_str})' logger.info(red(info)) res = Output(info) res.add(state_v1) res.add(state_v2) results['became_constants'].append(res.data) error_found = True else: info = f'{state_v1.canonical_name} not found in {contract_v2.name}, not check was done' logger.info(yellow(info)) res = Output(info) res.add(state_v1) res.add(contract_v2) results['not_found_in_v2'].append(res.data) error_found = True idx_v1 += 1 idx_v2 += 1 if not error_found: logger.info(green('No error found')) return results
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