import re, sys, logging from slither.core.expressions.identifier import Identifier from slither.core.cfg.node import Node from slither.slithir.operations import NewContract from slither.slithir.operations import Member from slither.utils.colors import red, yellow, set_colorization_enabled logging.basicConfig(level=logging.INFO) logger = logging.getLogger('Slither.Format') set_colorization_enabled(True) class FormatNamingConvention: @staticmethod def format(slither, patches, elements): for element in elements: if (element['additional_fields']['target'] == "parameter"): FormatNamingConvention.create_patch(slither, patches, element['additional_fields']['target'], \ element['name'], element['type_specific_fields']['parent']['name'], \ element['type_specific_fields']['parent']['type_specific_fields'] ['parent']['name'], element['source_mapping']['filename_absolute'], \ element['source_mapping']['filename_relative'], \ element['source_mapping']['start'],(element['source_mapping']['start'] + element['source_mapping']['length'])) elif (element['additional_fields']['target'] == "modifier" or element['additional_fields']['target'] == "function" or element['additional_fields']['target'] == "event" or element['additional_fields']['target'] == "variable" or element['additional_fields']['target'] == "variable_constant" or element['additional_fields']['target'] == "enum" or element['additional_fields']['target'] == "structure"): FormatNamingConvention.create_patch(slither, patches, element['additional_fields']['target'], \ element['name'], element['name'], \ element['type_specific_fields']['parent']['name'], \ element['source_mapping']['filename_absolute'], \ element['source_mapping']['filename_relative'], \ element['source_mapping']['start'],(element['source_mapping']['start'] + element['source_mapping']['length'])) else: FormatNamingConvention.create_patch(slither, patches, element['additional_fields']['target'], \ element['name'], element['name'], element['name'], \ element['source_mapping']['filename_absolute'], \ element['source_mapping']['filename_relative'], \ element['source_mapping']['start'],(element['source_mapping']['start'] + element['source_mapping']['length'])) @staticmethod def create_patch(slither, patches, _target, name, function_name, contract_name, in_file, in_file_relative, modify_loc_start, modify_loc_end): if _target == "contract": FormatNamingConvention.create_patch_contract_definition(slither, patches, name, in_file, in_file_relative, modify_loc_start, modify_loc_end) FormatNamingConvention.create_patch_contract_uses(slither, patches, name, in_file, in_file_relative) elif _target == "structure": FormatNamingConvention.create_patch_struct_definition(slither, patches, name, contract_name, in_file, in_file_relative, modify_loc_start, modify_loc_end) FormatNamingConvention.create_patch_struct_uses(slither, patches, name, contract_name, in_file, in_file_relative) elif _target == "event": FormatNamingConvention.create_patch_event_definition(slither, patches, name, contract_name, in_file, in_file_relative, modify_loc_start, modify_loc_end) FormatNamingConvention.create_patch_event_calls(slither, patches, name, contract_name, in_file, in_file_relative) elif _target == "function": if name != contract_name: FormatNamingConvention.create_patch_function_definition(slither, patches, name, contract_name, in_file, in_file_relative, modify_loc_start, modify_loc_end) FormatNamingConvention.create_patch_function_calls(slither, patches, name, contract_name, in_file, in_file_relative) elif _target == "parameter": FormatNamingConvention.create_patch_parameter_declaration(slither, patches, name, function_name, contract_name, in_file, in_file_relative, modify_loc_start, modify_loc_end) FormatNamingConvention.create_patch_parameter_uses(slither, patches, name, function_name, contract_name, in_file, in_file_relative) elif _target == "variable_constant" or _target == "variable": FormatNamingConvention.create_patch_state_variable_declaration(slither, patches, _target, name, contract_name, in_file, in_file_relative, modify_loc_start, modify_loc_end) FormatNamingConvention.create_patch_state_variable_uses(slither, patches, _target, name, contract_name, in_file, in_file_relative) elif _target == "enum": FormatNamingConvention.create_patch_enum_definition(slither, patches, name, contract_name, in_file, in_file_relative, modify_loc_start, modify_loc_end) FormatNamingConvention.create_patch_enum_uses(slither, patches, name, contract_name, in_file, in_file_relative) elif _target == "modifier": FormatNamingConvention.create_patch_modifier_definition(slither, patches, name, contract_name, in_file, in_file_relative, modify_loc_start, modify_loc_end) FormatNamingConvention.create_patch_modifier_uses(slither, patches, name, contract_name, in_file, in_file_relative) else: logger.error(red("Unknown naming convention! " + _target)) sys.exit(-1) @staticmethod def create_patch_contract_definition(slither, patches, name, in_file, in_file_relative, modify_loc_start, modify_loc_end): in_file_str = slither.source_code[in_file].encode('utf-8') old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] # Locate the name following keywords `contract` | `interface` | `library` m = re.match(r'(.*)'+"(contract|interface|library)"+r'(.*)'+name, old_str_of_interest.decode('utf-8')) old_str_of_interest = in_file_str[modify_loc_start:modify_loc_start+m.span()[1]] # Capitalize the name (new_str_of_interest, num_repl) = re.subn(r'(.*)'+r'(contract|interface|library)'+r'(.*)'+name, r'\1'+r'\2'+r'\3'+name.capitalize(), old_str_of_interest.decode('utf-8'), 1) if num_repl != 0: patch = { "file" : in_file, "detector" : "naming-convention (contract definition)", "start":modify_loc_start, "end":modify_loc_start+m.span()[1], "old_string":old_str_of_interest.decode('utf-8'), "new_string":new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) else: logger.error(red("Could not find contract?!")) sys.exit(-1) @staticmethod def create_patch_contract_uses(slither, patches, name, in_file, in_file_relative): for contract in slither.contracts: # Ignore contract definition if contract.name != name: in_file_str = slither.source_code[in_file].encode('utf-8') # Check state variables of contract type # To-do: Deep-check aggregate types (struct and mapping) svs = contract.variables for sv in svs: if (str(sv.type) == name): old_str_of_interest = in_file_str[sv.source_mapping['start']:(sv.source_mapping['start'] + sv.source_mapping['length'])] (new_str_of_interest, num_repl) = re.subn(name, name.capitalize(), old_str_of_interest.decode('utf-8'), 1) patch = { "file" : in_file, "detector" : "naming-convention (contract state variable)", "start" : sv.source_mapping['start'], "end" : sv.source_mapping['start'] + sv.source_mapping['length'], "old_string" : old_str_of_interest.decode('utf-8'), "new_string" : new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) # Check function+modifier locals+parameters+returns # To-do: Deep-check aggregate types (struct and mapping) fms = contract.functions + contract.modifiers for fm in fms: for v in fm.variables: if (str(v.type) == name): old_str_of_interest = in_file_str[v.source_mapping['start']:(v.source_mapping['start'] + v.source_mapping['length'])] # Get only the contract variable name even if it is initialised old_str_of_interest = old_str_of_interest.decode('utf-8').split('=')[0] (new_str_of_interest, num_repl) = re.subn(name, name.capitalize(),old_str_of_interest, 1) patch = { "file" : in_file, "detector" : "naming-convention (contract function variable)", "start" : v.source_mapping['start'], "end" : v.source_mapping['start'] + len(old_str_of_interest), "old_string" : old_str_of_interest, "new_string" : new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) # Check "new" expressions for creation of contract objects for function in contract.functions: for node in function.nodes: for ir in node.irs: if isinstance(ir, NewContract) and ir.contract_name == name: old_str_of_interest = in_file_str[node.source_mapping['start']:node.source_mapping['start'] + node.source_mapping['length']] # Search for the name after the `new` keyword m = re.search("new"+r'(.*)'+name, old_str_of_interest.decode('utf-8')) # Skip rare cases where re search fails. To-do: Investigate if not m: continue old_str_of_interest = old_str_of_interest.decode('utf-8')[m.span()[0]:] (new_str_of_interest, num_repl) = re.subn("new"+r'(.*)'+name, "new"+r'\1'+name.capitalize(), old_str_of_interest, 1) if num_repl != 0: patch = { "file" : in_file, "detector" : "naming-convention (contract new object)", # start after the `new` keyword where the name begins "start" : node.source_mapping['start'] + m.span()[0], "end" : node.source_mapping['start'] + m.span()[1], "old_string" : old_str_of_interest, "new_string" : new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) else: logger.error(red("Could not find new object?!")) sys.exit(-1) @staticmethod def create_patch_modifier_definition(slither, patches, name, contract_name, in_file, in_file_relative, modify_loc_start, modify_loc_end): target_contract = slither.get_contract_from_name(contract_name) if not target_contract: logger.error(red("Contract not found?!")) sys.exit(-1) for modifier in target_contract.modifiers: if modifier.name == name: in_file_str = slither.source_code[in_file].encode('utf-8') old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] # Search for the modifier name after the `modifier` keyword m = re.match(r'(.*)'+"modifier"+r'(.*)'+name, old_str_of_interest.decode('utf-8')) old_str_of_interest = in_file_str[modify_loc_start:modify_loc_start+m.span()[1]] # Change the first letter of the modifier name to lowercase (new_str_of_interest, num_repl) = re.subn(r'(.*)'+"modifier"+r'(.*)'+name, r'\1'+"modifier"+r'\2' + name[0].lower()+name[1:], old_str_of_interest.decode('utf-8'), 1) if num_repl != 0: patch = { "file" : in_file, "detector" : "naming-convention (modifier definition)", "start" : modify_loc_start, "end" : modify_loc_start+m.span()[1], "old_string" : old_str_of_interest.decode('utf-8'), "new_string" : new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) else: logger.error(red("Could not find modifier?!")) sys.exit(-1) @staticmethod def create_patch_modifier_uses(slither, patches, name, contract_name, in_file, in_file_relative): target_contract = slither.get_contract_from_name(contract_name) if not target_contract: logger.error(red("Contract not found?!")) sys.exit(-1) for contract in [target_contract] + target_contract.derived_contracts: for function in contract.functions: for m in function.modifiers: if (m.name == name): in_file_str = slither.source_code[in_file].encode('utf-8') # Get the text from function parameters until the return statement or function body beginning # This text will include parameter declarations, any Solidity keywords and modifier call # Parameter names cannot collide with modifier name per Solidity rules old_str_of_interest = in_file_str[int(function.parameters_src.source_mapping['start']): int(function.returns_src.source_mapping['start'])] # Change the first letter of the modifier name (if present) to lowercase (new_str_of_interest, num_repl) = re.subn(name, name[0].lower()+name[1:], old_str_of_interest.decode('utf-8'),1) if num_repl != 0: patch = { "file" : in_file, "detector" : "naming-convention (modifier uses)", "start" : int(function.parameters_src.source_mapping['start']), "end" : int(function.returns_src.source_mapping['start']), "old_string" : old_str_of_interest.decode('utf-8'), "new_string" : new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) else: logger.error(red("Could not find modifier name?!")) sys.exit(-1) @staticmethod def create_patch_function_definition(slither, patches, name, contract_name, in_file, in_file_relative, modify_loc_start, modify_loc_end): target_contract = slither.get_contract_from_name(contract_name) if not target_contract: logger.error(red("Contract not found?!")) sys.exit(-1) for function in target_contract.functions: if function.name == name: in_file_str = slither.source_code[in_file].encode('utf-8') old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] # Search for the function name after the `function` keyword m = re.match(r'(.*)'+"function"+r'\s*'+name, old_str_of_interest.decode('utf-8')) old_str_of_interest = in_file_str[modify_loc_start:modify_loc_start+m.span()[1]] # Change the first letter of the function name to lowercase (new_str_of_interest, num_repl) = re.subn(r'(.*)'+"function"+r'(.*)'+name, r'\1'+"function"+r'\2'+ name[0].lower()+name[1:], old_str_of_interest.decode('utf-8'), 1) if num_repl != 0: patch = { "file" : in_file, "detector" : "naming-convention (function definition)", "start" : modify_loc_start, "end" : modify_loc_start+m.span()[1], "old_string" : old_str_of_interest.decode('utf-8'), "new_string" : new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) else: logger.error(red("Could not find function?!")) sys.exit(-1) @staticmethod def create_patch_function_calls(slither, patches, name, contract_name, in_file, in_file_relative): for contract in slither.contracts: for function in contract.functions: for node in function.nodes: # Function call from another contract for high_level_call in node.high_level_calls: if (high_level_call[0].name == contract_name and high_level_call[1].name == name): for external_call in node.external_calls_as_expressions: # Check the called function name called_function = str(external_call.called).split('.')[-1] if called_function == high_level_call[1].name: in_file_str = slither.source_code[in_file].encode('utf-8') old_str_of_interest = in_file_str[int(external_call.source_mapping['start']): int(external_call.source_mapping['start']) + int(external_call.source_mapping['length'])] # Get the called function name. To-do: Check if we need to avoid parameters called_function_name = old_str_of_interest.decode('utf-8').split('.')[-1] # Convert first letter of name to lowercase fixed_function_name = called_function_name[0].lower() + called_function_name[1:] # Reconstruct the entire call new_string = '.'.join(old_str_of_interest.decode('utf-8').split('.')[:-1]) + '.' + \ fixed_function_name patch = { "file" : in_file, "detector" : "naming-convention (function calls)", "start" : external_call.source_mapping['start'], "end" : int(external_call.source_mapping['start']) + int(external_call.source_mapping['length']), "old_string" : old_str_of_interest.decode('utf-8'), "new_string" : new_string } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) # Function call from within same contract for internal_call in node.internal_calls_as_expressions: if (str(internal_call.called) == name): in_file_str = slither.source_code[in_file].encode('utf-8') old_str_of_interest = in_file_str[int(internal_call.source_mapping['start']): int(internal_call.source_mapping['start']) + int(internal_call.source_mapping['length'])] # Get the called function name and avoid parameters old_str_of_interest = old_str_of_interest.decode('utf-8').split('(')[0] patch = { "file" : in_file, "detector" : "naming-convention (function calls)", "start" : internal_call.source_mapping['start'], # Avoid parameters "end" : int(internal_call.source_mapping['start']) + int(internal_call.source_mapping['length']) - len('('.join(in_file_str[int(internal_call.source_mapping['start']): int(internal_call.source_mapping['start']) + int(internal_call.source_mapping['length'])] \ .decode('utf-8').split('(')[1:])) - 1, "old_string" : old_str_of_interest, "new_string" : old_str_of_interest[0].lower()+old_str_of_interest[1:] } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) @staticmethod def create_patch_event_definition(slither, patches, name, contract_name, in_file, in_file_relative, modify_loc_start, modify_loc_end): target_contract = slither.get_contract_from_name(contract_name) if not target_contract: logger.error(red("Contract not found?!")) sys.exit(-1) for event in target_contract.events: if event.name == name: # Get only event name without parameters event_name = name.split('(')[0] in_file_str = slither.source_code[in_file].encode('utf-8') old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] # Capitalize event name (new_str_of_interest, num_repl) = re.subn(r'(.*)'+"event"+r'(.*)'+event_name, r'\1'+"event"+r'\2' + event_name.capitalize(), old_str_of_interest.decode('utf-8'), 1) if num_repl != 0: patch = { "file" : in_file, "detector" : "naming-convention (event definition)", "start" : modify_loc_start, "end" : modify_loc_end, "old_string" : old_str_of_interest.decode('utf-8'), "new_string" : new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) else: logger.error(red("Could not find event?!")) sys.exit(-1) @staticmethod def create_patch_event_calls(slither, patches, name, contract_name, in_file, in_file_relative): # Get only event name without parameters event_name = name.split('(')[0] target_contract = slither.get_contract_from_name(contract_name) if not target_contract: logger.error(red("Contract not found?!")) sys.exit(-1) for contract in [target_contract] + target_contract.derived_contracts: for function in contract.functions: for node in function.nodes: for call in node.internal_calls_as_expressions: if (str(call.called) == event_name): in_file_str = slither.source_code[in_file].encode('utf-8') old_str_of_interest = in_file_str[int(call.source_mapping['start']): int(call.source_mapping['start']) + int(call.source_mapping['length'])] patch = { "file" : in_file, "detector" : "naming-convention (event calls)", "start" : call.source_mapping['start'], "end" : int(call.source_mapping['start']) + int(call.source_mapping['length']), "old_string" : old_str_of_interest.decode('utf-8'), # Capitalize event name "new_string" : old_str_of_interest.decode('utf-8').capitalize() } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) @staticmethod def create_patch_parameter_declaration(slither, patches, name, function_name, contract_name, in_file, in_file_relative, modify_loc_start, modify_loc_end): target_contract = slither.get_contract_from_name(contract_name) if not target_contract: logger.error(red("Contract not found?!")) sys.exit(-1) for function in target_contract.functions: if function.name == function_name: in_file_str = slither.source_code[in_file].encode('utf-8') old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] # To-do: Change format logic below - how do we convert a name to mixedCase? if(name[0] == '_'): # If parameter name begins with underscore, capitalize the letter after underscore (new_str_of_interest, num_repl) = re.subn(r'(.*)'+name+r'(.*)', r'\1'+name[0]+name[1].upper() + name[2:]+r'\2', old_str_of_interest.decode('utf-8'), 1) else: # Add underscore and capitalize the first letter (new_str_of_interest, num_repl) = re.subn(r'(.*)'+name+r'(.*)', r'\1'+'_'+name.capitalize() + r'\2', old_str_of_interest.decode('utf-8'), 1) if num_repl != 0: patch = { "file" : in_file, "detector" : "naming-convention (parameter declaration)", "start" : modify_loc_start, "end" : modify_loc_end, "old_string" : old_str_of_interest.decode('utf-8'), "new_string" : new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) else: logger.error(red("Could not find parameter declaration?!")) sys.exit(-1) @staticmethod def create_patch_parameter_uses(slither, patches, name, function_name, contract_name, in_file, in_file_relative): for contract in slither.contracts: if (contract.name == contract_name): for function in contract.functions: if (function.name == function_name): in_file_str = slither.source_code[in_file].encode('utf-8') for node in function.nodes: vars = node._expression_vars_written + node._expression_vars_read for v in vars: if isinstance(v, Identifier) and str(v) == name and [str(lv) for lv in (node._local_vars_read + node._local_vars_written) if str(lv) == name]: # Skip rare cases where source_mapping is absent. To-do: Investigate if not v.source_mapping: continue modify_loc_start = int(v.source_mapping['start']) modify_loc_end = int(v.source_mapping['start']) + int(v.source_mapping['length']) old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] # To-do: Change format logic below - how do we convert a name to mixedCase? if(name[0] == '_'): # If parameter name begins with underscore, capitalize the letter after underscore (new_str_of_interest, num_repl) = re.subn(r'(.*)'+name+r'(.*)', r'\1'+name[0]+name[1].upper()+name[2:] + r'\2', old_str_of_interest.decode('utf-8'), 1) else: # Add underscore and capitalize the first letter (new_str_of_interest, num_repl) = re.subn(r'(.*)'+name+r'(.*)', r'\1'+'_' + name.capitalize()+r'\2', old_str_of_interest.decode('utf-8'), 1) if num_repl != 0: patch = { "file" : in_file, "detector" : "naming-convention (parameter uses)", "start" : modify_loc_start, "end" : modify_loc_end, "old_string" : old_str_of_interest.decode('utf-8'), "new_string" : new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) else: logger.error(red("Could not find parameter use?!")) sys.exit(-1) # Process function parameters passed to modifiers for modifier in function._expression_modifiers: for arg in modifier.arguments: if str(arg) == name: old_str_of_interest = in_file_str[modifier.source_mapping['start']: modifier.source_mapping['start'] + modifier.source_mapping['length']] # Get text beyond modifier name which contains parameters old_str_of_interest_beyond_modifier_name = old_str_of_interest.decode('utf-8')\ .split('(')[1] # To-do: Change format logic below - how do we convert a name to mixedCase? if(name[0] == '_'): # If parameter name begins with underscore, capitalize the letter after underscore (new_str_of_interest, num_repl) = re.subn(r'(.*)'+name+r'(.*)', r'\1'+name[0]+ name[1].upper()+name[2:]+r'\2', old_str_of_interest_beyond_modifier_name, 1) else: # Add underscore and capitalize the first letter (new_str_of_interest, num_repl) = re.subn(r'(.*)'+name+r'(.*)', r'\1'+'_'+ name.capitalize()+r'\2', old_str_of_interest_beyond_modifier_name, 1) if num_repl != 0: patch = { "file" : in_file, "detector" : "naming-convention (parameter uses)", # Start beyond modifier name which contains parameters "start" : modifier.source_mapping['start'] + len(old_str_of_interest.decode('utf-8').split('(')[0]) + 1, "end" : modifier.source_mapping['start'] + modifier.source_mapping['length'], "old_string" : old_str_of_interest_beyond_modifier_name, "new_string" : new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) else: logger.error(red("Could not find parameter use in modifier?!")) sys.exit(-1) @staticmethod def create_patch_state_variable_declaration(slither, patches, _target, name, contract_name, in_file, in_file_relative, modify_loc_start, modify_loc_end): for contract in slither.contracts: if (contract.name == contract_name): for var in contract.state_variables: if (var.name == name): in_file_str = slither.source_code[in_file].encode('utf-8') old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] # Search for the state variable name and avoid the type m = re.search(name, old_str_of_interest.decode('utf-8')) # Skip rare cases where re search fails. To-do: Investigate if not m: continue if (_target == "variable_constant"): # Convert constant state variables to upper case new_string = old_str_of_interest.decode('utf-8')[m.span()[0]:m.span()[1]].upper() else: new_string = old_str_of_interest.decode('utf-8')[m.span()[0]:m.span()[1]] new_string = new_string[0].lower()+new_string[1:] patch = { "file" : in_file, "detector" : "naming-convention (state variable declaration)", # Target only the state variable name and avoid the type "start" : modify_loc_start+m.span()[0], "end" : modify_loc_start+m.span()[1], "old_string" : old_str_of_interest.decode('utf-8')[m.span()[0]:m.span()[1]], "new_string" : new_string } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) @staticmethod def create_patch_state_variable_uses(slither, patches, _target, name, contract_name, in_file, in_file_relative): # To-do: Check cross-contract state variable uses for contract in slither.contracts: if (contract.name == contract_name): target_contract = contract for contract in slither.contracts: if (contract == target_contract or (contract in target_contract.derived_contracts)): fms = contract.functions + contract.modifiers for fm in fms: for node in fm.nodes: vars = node._expression_vars_written + node._expression_vars_read for v in vars: if isinstance(v, Identifier) and str(v) == name and [str(sv) for sv in (node._state_vars_read + node._state_vars_written) if str(sv) == name]: modify_loc_start = int(v.source_mapping['start']) modify_loc_end = int(v.source_mapping['start']) + int(v.source_mapping['length']) in_file_str = slither.source_code[in_file].encode('utf-8') old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] if (_target == "variable_constant"): # Convert constant state variables to upper case new_str_of_interest = old_str_of_interest.decode('utf-8').upper() else: new_str_of_interest = old_str_of_interest.decode('utf-8') new_str_of_interest = new_str_of_interest[0].lower()+new_str_of_interest[1:] patch = { "file" : in_file, "detector" : "naming-convention (state variable uses)", "start" : modify_loc_start, "end" : modify_loc_end, "old_string" : old_str_of_interest.decode('utf-8'), "new_string" : new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) @staticmethod def create_patch_enum_definition(slither, patches, name, contract_name, in_file, in_file_relative, modify_loc_start, modify_loc_end): for contract in slither.contracts: if (contract.name == contract_name): for enum in contract.enums: if (enum.name == name): in_file_str = slither.source_code[in_file].encode('utf-8') old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] # Search for the enum name after the `enum` keyword # Capitalize enum name (new_str_of_interest, num_repl) = re.subn(r'(.*)'+"enum"+r'(.*)'+name, r'\1'+"enum"+r'\2'+ name.capitalize(), old_str_of_interest.decode('utf-8'), 1) if num_repl != 0: patch = { "file" : in_file, "detector" : "naming-convention (enum definition)", "start" : modify_loc_start, "end" : modify_loc_end, "old_string" : old_str_of_interest.decode('utf-8'), "new_string" : new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) else: logger.error(red("Could not find enum?!")) sys.exit(-1) @staticmethod def create_patch_enum_uses(slither, patches, name, contract_name, in_file, in_file_relative): for contract in slither.contracts: if (contract.name == contract_name): target_contract = contract for contract in slither.contracts: if (contract == target_contract or (contract in target_contract.derived_contracts)): in_file_str = slither.source_code[in_file].encode('utf-8') # Check state variable declarations of enum type # To-do: Deep-check aggregate types (struct and mapping) svs = contract.variables for sv in svs: if (str(sv.type) == contract_name + "." + name): old_str_of_interest = in_file_str[sv.source_mapping['start']:(sv.source_mapping['start']+ sv.source_mapping['length'])] (new_str_of_interest, num_repl) = re.subn(name, name.capitalize(), old_str_of_interest.decode('utf-8'), 1) patch = { "file" : in_file, "detector" : "naming-convention (enum use)", "start" : sv.source_mapping['start'], "end" : sv.source_mapping['start'] + sv.source_mapping['length'], "old_string" : old_str_of_interest.decode('utf-8'), "new_string" : new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) # Check function+modifier locals+parameters+returns # To-do: Deep-check aggregate types (struct and mapping) fms = contract.functions + contract.modifiers for fm in fms: # Enum declarations for v in fm.variables: if (str(v.type) == contract_name + "." + name): old_str_of_interest = in_file_str[v.source_mapping['start']:(v.source_mapping['start']+ v.source_mapping['length'])] (new_str_of_interest, num_repl) = re.subn(name, name.capitalize(), old_str_of_interest.decode('utf-8'), 1) patch = { "file" : in_file, "detector" : "naming-convention (enum use)", "start" : v.source_mapping['start'], "end" : v.source_mapping['start'] + v.source_mapping['length'], "old_string" : old_str_of_interest.decode('utf-8'), "new_string" : new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) # Capture enum uses such as "num = numbers.ONE;" for function in contract.functions: for node in function.nodes: for ir in node.irs: if isinstance(ir, Member): if str(ir.variable_left) == name: # Skip past the assignment old_str_of_interest = in_file_str[node.source_mapping['start']: (node.source_mapping['start']+ node.source_mapping['length'])].decode('utf-8')\ .split('=')[1] m = re.search(r'(.*)'+name, old_str_of_interest) # Skip rare cases where re search fails. To-do: Investigate if not m: continue old_str_of_interest = old_str_of_interest[m.span()[0]:] (new_str_of_interest, num_repl) = re.subn(r'(.*)'+name, r'\1'+name.capitalize(), old_str_of_interest, 1) if num_repl != 0: patch = { "file" : in_file, "detector" : "naming-convention (enum use)", # Start past the assignment "start" : node.source_mapping['start'] + len(in_file_str[node.source_mapping['start']: (node.source_mapping['start']+ node.source_mapping['length'])].decode('utf-8').split('=')[0]) + 1 + m.span()[0], # End accounts for the assignment from the start "end" : node.source_mapping['start'] + len(in_file_str[node.source_mapping['start']:(node.source_mapping['start']+ node.source_mapping['length'])].\ decode('utf-8').split('=')[0]) + 1 + m.span()[0] + len(old_str_of_interest), "old_string" : old_str_of_interest, "new_string" : new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) else: logger.error(red("Could not find new object?!")) sys.exit(-1) # To-do: Check any other place/way where enum type is used @staticmethod def create_patch_struct_definition(slither, patches, name, contract_name, in_file, in_file_relative, modify_loc_start, modify_loc_end): for contract in slither.contracts: if (contract.name == contract_name): for struct in contract.structures: if (struct.name == name): in_file_str = slither.source_code[in_file].encode('utf-8') old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] # Capitalize the struct name beyond the keyword `struct` (new_str_of_interest, num_repl) = re.subn(r'(.*)'+"struct"+r'(.*)'+name, r'\1'+"struct"+r'\2'+ name.capitalize(), old_str_of_interest.decode('utf-8'), 1) if num_repl != 0: patch = { "file" : in_file, "detector" : "naming-convention (struct definition)", "start" : modify_loc_start, "end" : modify_loc_end, "old_string" : old_str_of_interest.decode('utf-8'), "new_string" : new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) else: logger.error(red("Could not find struct?!")) sys.exit(-1) @staticmethod def create_patch_struct_uses(slither, patches, name, contract_name, in_file, in_file_relative): for contract in slither.contracts: if (contract.name == contract_name): target_contract = contract for contract in slither.contracts: if (contract == target_contract or (contract in target_contract.derived_contracts)): in_file_str = slither.source_code[in_file].encode('utf-8') # Check state variables of struct type # To-do: Deep-check aggregate types (struct and mapping) svs = contract.variables for sv in svs: if (str(sv.type) == contract_name + "." + name): old_str_of_interest = in_file_str[sv.source_mapping['start']:(sv.source_mapping['start']+ sv.source_mapping['length'])] (new_str_of_interest, num_repl) = re.subn(name, name.capitalize(), old_str_of_interest.decode('utf-8'), 1) patch = { "file" : in_file, "detector" : "naming-convention (struct use)", "start" : sv.source_mapping['start'], "end" : sv.source_mapping['start'] + sv.source_mapping['length'], "old_string" : old_str_of_interest.decode('utf-8'), "new_string" : new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) # Check function+modifier locals+parameters+returns # To-do: Deep-check aggregate types (struct and mapping) fms = contract.functions + contract.modifiers for fm in fms: for v in fm.variables: if (str(v.type) == contract_name + "." + name): old_str_of_interest = in_file_str[v.source_mapping['start']:(v.source_mapping['start']+ v.source_mapping['length'])] (new_str_of_interest, num_repl) = re.subn(name, name.capitalize(), old_str_of_interest.decode('utf-8'), 1) patch = { "file" : in_file, "detector" : "naming-convention (struct use)", "start" : v.source_mapping['start'], "end" : v.source_mapping['start'] + v.source_mapping['length'], "old_string" : old_str_of_interest.decode('utf-8'), "new_string" : new_str_of_interest } if not patch in patches[in_file_relative]: patches[in_file_relative].append(patch) # To-do: Check any other place/way where struct type is used (e.g. typecast)