diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 71f22ddfc..47ec663ad 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -113,9 +113,11 @@ class Function(ChildContract, ChildInheritance, SourceMapping): self._all_high_level_calls = None self._all_library_calls = None self._all_low_level_calls = None + self._all_solidity_calls = None self._all_state_variables_read = None self._all_solidity_variables_read = None self._all_state_variables_written = None + self._all_slithir_variables = None self._all_conditional_state_variables_read = None self._all_conditional_state_variables_read_with_loop = None self._all_conditional_solidity_variables_read = None @@ -796,6 +798,14 @@ class Function(ChildContract, ChildInheritance, SourceMapping): lambda x: x.solidity_variables_read) return self._all_solidity_variables_read + def all_slithir_variables(self): + """ recursive version of slithir_variables + """ + if self._all_slithir_variables is None: + self._all_slithir_variables = self._explore_functions( + lambda x: x.slithir_variable) + return self._all_slithir_variables + def all_expressions(self): """ recursive version of variables_read """ @@ -846,6 +856,13 @@ class Function(ChildContract, ChildInheritance, SourceMapping): self._all_library_calls = self._explore_functions(lambda x: x.library_calls) return self._all_library_calls + def all_solidity_calls(self): + """ recursive version of solidity calls + """ + if self._all_solidity_calls is None: + self._all_solidity_calls = self._explore_functions(lambda x: x.solidity_calls) + return self._all_solidity_calls + @staticmethod def _explore_func_cond_read(func, include_loop): ret = [n.state_variables_read for n in func.nodes if n.is_conditional(include_loop)] diff --git a/slither/core/variables/variable.py b/slither/core/variables/variable.py index 8b37c6da1..4d7f26e03 100644 --- a/slither/core/variables/variable.py +++ b/slither/core/variables/variable.py @@ -78,6 +78,25 @@ class Variable(SourceMapping): assert isinstance(t, (Type, list)) or t is None self._type = t + @property + def function_name(self): + ''' + Return the name of the variable as a function signature + :return: + ''' + from slither.core.solidity_types import ArrayType, MappingType + variable_getter_args = "" + if type(self.type) is ArrayType: + length = 0 + v = self + while type(v.type) is ArrayType: + length += 1 + v = v.type + variable_getter_args = ','.join(["uint256"] * length) + elif type(self.type) is MappingType: + variable_getter_args = self.type.type_from + + return f"{self.name}({variable_getter_args})" def __str__(self): return self._name diff --git a/slither/printers/all_printers.py b/slither/printers/all_printers.py index 25f7a3ee0..02f83260d 100644 --- a/slither/printers/all_printers.py +++ b/slither/printers/all_printers.py @@ -13,4 +13,5 @@ from .summary.variable_order import VariableOrder from .summary.data_depenency import DataDependency from .summary.modifier_calls import Modifiers from .summary.require_calls import RequireOrAssert -from .summary.constructor_calls import ConstructorPrinter \ No newline at end of file +from .summary.constructor_calls import ConstructorPrinter +from .guidance.echidna import Echidna \ No newline at end of file diff --git a/slither/printers/guidance/__init__.py b/slither/printers/guidance/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/printers/guidance/echidna.py b/slither/printers/guidance/echidna.py new file mode 100644 index 000000000..371094db2 --- /dev/null +++ b/slither/printers/guidance/echidna.py @@ -0,0 +1,120 @@ +""" +""" + +import json +from collections import defaultdict +from slither.printers.abstract_printer import AbstractPrinter +from slither.core.declarations.solidity_variables import SolidityVariableComposed, SolidityFunction +from slither.slithir.operations.binary import Binary, BinaryType + +from slither.slithir.variables import Constant + + +def _extract_payable(slither): + ret = {} + for contract in slither.contracts: + payable_functions = [f.full_name for f in contract.functions_entry_points if f.payable] + if payable_functions: + ret[contract.name] = payable_functions + return ret + +def _extract_solidity_variable_usage(slither, sol_var): + ret = {} + for contract in slither.contracts: + functions_using_sol_var = [] + for f in contract.functions_entry_points: + for v in f.all_solidity_variables_read(): + if v == sol_var: + functions_using_sol_var.append(f.full_name) + break + if functions_using_sol_var: + ret[contract.name] = functions_using_sol_var + return ret + +def _extract_constant_functions(slither): + ret = {} + for contract in slither.contracts: + cst_functions = [f.full_name for f in contract.functions_entry_points if f.view or f.pure] + if cst_functions: + ret[contract.name] = cst_functions + return ret + +def _extract_assert(slither): + ret = {} + for contract in slither.contracts: + functions_using_assert = [] + for f in contract.functions_entry_points: + for v in f.all_solidity_calls(): + if v == SolidityFunction('assert(bool)'): + functions_using_assert.append(f.full_name) + break + if functions_using_assert: + ret[contract.name] = functions_using_assert + return ret + + +def _extract_constants(slither): + ret_cst_used = defaultdict(dict) + ret_cst_used_in_binary = defaultdict(dict) + for contract in slither.contracts: + for function in contract.functions_entry_points: + all_cst_used = [] + all_cst_used_in_binary = defaultdict(list) + for ir in function.all_slithir_operations(): + if isinstance(ir, Binary): + for r in ir.read: + if isinstance(r, Constant): + all_cst_used_in_binary[BinaryType.str(ir.type)].append(r.value) + for r in ir.read: + if isinstance(r, Constant): + all_cst_used.append(r.value) + if all_cst_used: + ret_cst_used[contract.name][function.full_name] = all_cst_used + if all_cst_used_in_binary: + ret_cst_used_in_binary[contract.name][function.full_name] = all_cst_used_in_binary + return (ret_cst_used, ret_cst_used_in_binary) + + + + +class Echidna(AbstractPrinter): + ARGUMENT = 'echidna' + HELP = 'todo' + + WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#echidna' + + + def output(self, filename): + """ + Output the inheritance relation + + _filename is not used + Args: + _filename(string) + """ + + payable = _extract_payable(self.slither) + timestamp = _extract_solidity_variable_usage(self.slither, + SolidityVariableComposed('block.timestamp')) + block_number = _extract_solidity_variable_usage(self.slither, + SolidityVariableComposed('block.number')) + msg_sender = _extract_solidity_variable_usage(self.slither, + SolidityVariableComposed('msg.sender')) + msg_gas = _extract_solidity_variable_usage(self.slither, + SolidityVariableComposed('msg.gas')) + assert_usage = _extract_assert(self.slither) + cst_functions = _extract_constant_functions(self.slither) + (cst_used, cst_used_in_binary) = _extract_constants(self.slither) + + + d = {'payable': payable, + 'timestamp': timestamp, + 'block_number': block_number, + 'msg_sender': msg_sender, + 'msg_gas': msg_gas, + 'assert': assert_usage, + 'constant_functions': cst_functions, + 'constants_used': cst_used, + 'constants_used_in_binary': cst_used_in_binary} + + print(json.dumps(d, indent=4)) \ No newline at end of file diff --git a/slither/printers/summary/function_ids.py b/slither/printers/summary/function_ids.py index 169a2a318..7d6539bf1 100644 --- a/slither/printers/summary/function_ids.py +++ b/slither/printers/summary/function_ids.py @@ -4,7 +4,6 @@ import collections from prettytable import PrettyTable -from slither.core.solidity_types import ArrayType, MappingType from slither.printers.abstract_printer import AbstractPrinter from slither.utils.colors import blue, green, magenta from slither.utils.function import get_function_id @@ -32,18 +31,8 @@ class FunctionIds(AbstractPrinter): table.add_row([function.full_name, hex(get_function_id(function.full_name))]) for variable in contract.state_variables: if variable.visibility in ['public']: - variable_getter_args = "" - if type(variable.type) is ArrayType: - length = 0 - v = variable - while type(v.type) is ArrayType: - length += 1 - v = v.type - variable_getter_args = ','.join(["uint256"]*length) - elif type(variable.type) is MappingType: - variable_getter_args = variable.type.type_from - - table.add_row([f"{variable.name}({variable_getter_args})", hex(get_function_id(f"{variable.name}({variable_getter_args})"))]) + sig = variable.function_name + table.add_row([sig, hex(get_function_id(sig))]) txt += str(table) + '\n' self.info(txt)