diff --git a/slither/__main__.py b/slither/__main__.py index 9ef12a008..0b3c7c3bc 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -180,7 +180,7 @@ def get_detectors_and_printers(): from slither.printers.summary.human_summary import PrinterHumanSummary from slither.printers.functions.cfg import CFG from slither.printers.summary.function_ids import FunctionIds - + from slither.printers.summary.variables_order import VariablesOrder printers = [FunctionSummary, ContractSummary, PrinterInheritance, @@ -191,7 +191,8 @@ def get_detectors_and_printers(): PrinterSlithIRSSA, PrinterHumanSummary, CFG, - FunctionIds] + FunctionIds, + VariablesOrder] # Handle plugins! for entry_point in iter_entry_points(group='slither_analyzer.plugin', name=None): diff --git a/slither/core/solidity_types/mapping_type.py b/slither/core/solidity_types/mapping_type.py index 1c016c8c6..cf7e0794a 100644 --- a/slither/core/solidity_types/mapping_type.py +++ b/slither/core/solidity_types/mapping_type.py @@ -18,7 +18,7 @@ class MappingType(Type): return self._to def __str__(self): - return 'mapping({} => {}'.format(str(self._from), str(self._to)) + return 'mapping({} => {})'.format(str(self._from), str(self._to)) def __eq__(self, other): if not isinstance(other, MappingType): diff --git a/slither/printers/summary/function_ids.py b/slither/printers/summary/function_ids.py index 034afd4a5..cba119c11 100644 --- a/slither/printers/summary/function_ids.py +++ b/slither/printers/summary/function_ids.py @@ -21,7 +21,7 @@ class FunctionIds(AbstractPrinter): txt = '' for contract in self.slither.contracts_derived: - txt = '\n{}:\n'.format(contract.name) + txt += '\n{}:\n'.format(contract.name) table = PrettyTable(['Name', 'ID']) for function in contract.functions: if function.visibility in ['public', 'external']: diff --git a/slither/printers/summary/variables_order.py b/slither/printers/summary/variables_order.py new file mode 100644 index 000000000..a32acd53d --- /dev/null +++ b/slither/printers/summary/variables_order.py @@ -0,0 +1,29 @@ +""" + Module printing summary of the contract +""" + +from prettytable import PrettyTable +from slither.printers.abstract_printer import AbstractPrinter + +class VariablesOrder(AbstractPrinter): + + ARGUMENT = 'variables-order' + HELP = 'Print the storage order of the state variables' + + def output(self, _filename): + """ + _filename is not used + Args: + _filename(string) + """ + + txt = '' + for contract in self.slither.contracts_derived: + txt += '\n{}:\n'.format(contract.name) + table = PrettyTable(['Name', 'Type']) + for variable in contract.state_variables: + if not variable.is_constant: + table.add_row([variable.name, str(variable.type)]) + txt += str(table) + '\n' + + self.info(txt) diff --git a/utils/upgradability/compare_variables_order.py b/utils/upgradability/compare_variables_order.py new file mode 100644 index 000000000..64f4238c2 --- /dev/null +++ b/utils/upgradability/compare_variables_order.py @@ -0,0 +1,56 @@ +''' + This utility looks for functions collisions between a proxy and the implementation + More for information: https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357 +''' + +import sys +from slither import Slither +from slither.utils.function import get_function_id +from slither.utils.colors import red, green + +if __name__ == "__main__": + + if len(sys.argv) != 5: + print('Usage: python3 compare_variables_order.py v1.sol Contract1 v2.sol Contract2') + + v1 = Slither(sys.argv[1]) + v2 = Slither(sys.argv[3]) + + contract_v1 = v1.get_contract_from_name(sys.argv[2]) + if contract_v1 is None: + print(red('Contract {} not found'.format(sys.argv[2]))) + exit(-1) + + contract_v2 = v2.get_contract_from_name(sys.argv[4]) + if contract_v2 is None: + print(red('Contract {} not found'.format(sys.argv[4]))) + exit(-1) + + + order_v1 = [(variable.name, variable.type) for variable in contract_v1.state_variables if not variable.is_constant] + order_v2 = [(variable.name, variable.type) for variable in contract_v2.state_variables if not variable.is_constant] + + + found = False + for idx in range(0, len(order_v1)): + (v1_name, v1_type) = order_v1[idx] + if len(order_v2) < idx: + print(red('Missing variable in the new version: {} {}'.format(v1_name, v1_type))) + continue + (v2_name, v2_type) = order_v2[idx] + + if (v1_name != v2_name) or (v1_type != v2_type): + found = True + print(red('Different variable: {} {} -> {} {}'.format(v1_name, + v1_type, + v2_name, + v2_type))) + + if len(order_v2) > len(order_v1): + new_variables = order_v2[len(order_v1):] + for (name, t) in new_variables: + print(green('New variable: {} {}'.format(name, t))) + + if not found: + print(green('No error found')) +