From 4b7541f8e3fca3e5e3f667dbde74f40e03e5b8cb Mon Sep 17 00:00:00 2001 From: Josselin Feist Date: Wed, 3 Aug 2022 15:36:19 +0200 Subject: [PATCH 1/3] Improve generation of solidity signature - Better support nested types/array/structure/mapping - Clarify the API for full_name versus solidity_signature - Addd contract.get_function_from_full_name - Use the same API for function and variable - Add tests Replace #483 Fix #1077 --- .github/workflows/features.yml | 1 + slither/core/declarations/contract.py | 21 ++- slither/core/declarations/function.py | 14 +- slither/core/declarations/structure.py | 4 +- .../core/solidity_types/elementary_type.py | 2 +- slither/core/variables/state_variable.py | 32 +--- slither/core/variables/variable.py | 61 ++++--- .../formatters/attributes/const_functions.py | 2 +- .../formatters/functions/external_function.py | 2 +- .../naming_convention/naming_convention.py | 4 +- slither/printers/guidance/echidna.py | 5 +- slither/printers/summary/function_ids.py | 2 +- .../upgradeability/checks/functions_ids.py | 2 +- slither/utils/type.py | 164 ++++++++++++++---- tests/printers/functions_ids.sol | 64 +++++++ tests/test_functions_ids.py | 60 +++++++ 16 files changed, 338 insertions(+), 102 deletions(-) create mode 100644 tests/printers/functions_ids.sol create mode 100644 tests/test_functions_ids.py diff --git a/.github/workflows/features.yml b/.github/workflows/features.yml index 8757d654a..18d5d49b4 100644 --- a/.github/workflows/features.yml +++ b/.github/workflows/features.yml @@ -47,3 +47,4 @@ jobs: pytest tests/test_features.py pytest tests/test_constant_folding_unary.py pytest tests/slithir/test_ternary_expressions.py + pytest tests/test_functions_ids.py diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index 333d906fa..0dfaa273c 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -650,6 +650,21 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods """ return [f for f in self.functions if f.is_writing(variable)] + def get_function_from_full_name(self, full_name: str) -> Optional["Function"]: + """ + Return a function from a full name + The full name differs from the solidity's signature are the type are conserved + For example contract type are kept, structure are not unrolled, etc + Args: + full_name (str): signature of the function (without return statement) + Returns: + Function + """ + return next( + (f for f in self.functions if f.full_name == full_name and not f.is_shadowed), + None, + ) + def get_function_from_signature(self, function_signature: str) -> Optional["Function"]: """ Return a function from a signature @@ -659,7 +674,11 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods Function """ return next( - (f for f in self.functions if f.full_name == function_signature and not f.is_shadowed), + ( + f + for f in self.functions + if f.solidity_signature == function_signature and not f.is_shadowed + ), None, ) diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 8ec5a00ae..674fe1cce 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -20,11 +20,11 @@ from slither.core.expressions import ( MemberAccess, UnaryOperation, ) -from slither.core.solidity_types import UserDefinedType from slither.core.solidity_types.type import Type from slither.core.source_mapping.source_mapping import SourceMapping from slither.core.variables.local_variable import LocalVariable from slither.core.variables.state_variable import StateVariable +from slither.utils.type import convert_type_for_solidity_signature_to_string from slither.utils.utils import unroll # pylint: disable=import-outside-toplevel,too-many-instance-attributes,too-many-statements,too-many-lines @@ -265,6 +265,8 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu """ str: func_name(type1,type2) Return the function signature without the return values + The difference between this function and solidity_function is that full_name does not translate the underlying + type (ex: structure, contract to address, ...) """ if self._full_name is None: name, parameters, _ = self.signature @@ -952,14 +954,6 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu ################################################################################### ################################################################################### - @staticmethod - def _convert_type_for_solidity_signature(t: Type): - from slither.core.declarations import Contract - - if isinstance(t, UserDefinedType) and isinstance(t.type, Contract): - return "address" - return str(t) - @property def solidity_signature(self) -> str: """ @@ -969,7 +963,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu """ if self._solidity_signature is None: parameters = [ - self._convert_type_for_solidity_signature(x.type) for x in self.parameters + convert_type_for_solidity_signature_to_string(x.type) for x in self.parameters ] self._solidity_signature = self.name + "(" + ",".join(parameters) + ")" return self._solidity_signature diff --git a/slither/core/declarations/structure.py b/slither/core/declarations/structure.py index eb5f5e00d..39b1948ee 100644 --- a/slither/core/declarations/structure.py +++ b/slither/core/declarations/structure.py @@ -1,4 +1,4 @@ -from typing import List, TYPE_CHECKING, Dict +from typing import List, TYPE_CHECKING, Dict, Optional from slither.core.source_mapping.source_mapping import SourceMapping @@ -10,7 +10,7 @@ if TYPE_CHECKING: class Structure(SourceMapping): def __init__(self, compilation_unit: "SlitherCompilationUnit"): super().__init__() - self._name = None + self._name: Optional[str] = None self._canonical_name = None self._elems: Dict[str, "StructureVariable"] = {} # Name of the elements in the order of declaration diff --git a/slither/core/solidity_types/elementary_type.py b/slither/core/solidity_types/elementary_type.py index ce782a5e6..24eeb39ae 100644 --- a/slither/core/solidity_types/elementary_type.py +++ b/slither/core/solidity_types/elementary_type.py @@ -151,7 +151,7 @@ class NonElementaryType(Exception): class ElementaryType(Type): - def __init__(self, t): + def __init__(self, t: str) -> None: if t not in ElementaryTypeName: raise NonElementaryType super().__init__() diff --git a/slither/core/variables/state_variable.py b/slither/core/variables/state_variable.py index 3b37d57c3..c9a90f36b 100644 --- a/slither/core/variables/state_variable.py +++ b/slither/core/variables/state_variable.py @@ -1,8 +1,7 @@ -from typing import Optional, TYPE_CHECKING, Tuple, List +from typing import Optional, TYPE_CHECKING -from slither.core.variables.variable import Variable from slither.core.children.child_contract import ChildContract -from slither.utils.type import export_nested_types_from_variable +from slither.core.variables.variable import Variable if TYPE_CHECKING: from slither.core.cfg.node import Node @@ -22,33 +21,6 @@ class StateVariable(ChildContract, Variable): """ return self.contract == contract - ################################################################################### - ################################################################################### - # region Signature - ################################################################################### - ################################################################################### - - @property - def signature(self) -> Tuple[str, List[str], str]: - """ - Return the signature of the state variable as a function signature - :return: (str, list(str), list(str)), as (name, list parameters type, list return values type) - """ - return ( - self.name, - [str(x) for x in export_nested_types_from_variable(self)], - str(self.type), - ) - - @property - def signature_str(self) -> str: - """ - Return the signature of the state variable as a function signature - :return: str: func_name(type1,type2) returns(type3) - """ - name, parameters, returnVars = self.signature - return name + "(" + ",".join(parameters) + ") returns(" + ",".join(returnVars) + ")" - # endregion ################################################################################### ################################################################################### diff --git a/slither/core/variables/variable.py b/slither/core/variables/variable.py index 5f94bc922..0558be52c 100644 --- a/slither/core/variables/variable.py +++ b/slither/core/variables/variable.py @@ -1,7 +1,7 @@ """ Variable module """ -from typing import Optional, TYPE_CHECKING, List, Union +from typing import Optional, TYPE_CHECKING, List, Union, Tuple from slither.core.source_mapping.source_mapping import SourceMapping from slither.core.solidity_types.type import Type @@ -44,7 +44,7 @@ class Variable(SourceMapping): return self._initial_expression @expression.setter - def expression(self, expr: "Expression"): + def expression(self, expr: "Expression") -> None: self._initial_expression = expr @property @@ -70,6 +70,7 @@ class Variable(SourceMapping): """ str: variable name """ + assert self._name return self._name @name.setter @@ -97,7 +98,7 @@ class Variable(SourceMapping): return self._is_reentrant @is_reentrant.setter - def is_reentrant(self, is_reentrant: bool): + def is_reentrant(self, is_reentrant: bool) -> None: self._is_reentrant = is_reentrant @property @@ -105,7 +106,7 @@ class Variable(SourceMapping): return self._write_protection @write_protection.setter - def write_protection(self, write_protection: List[str]): + def write_protection(self, write_protection: List[str]) -> None: self._write_protection = write_protection @property @@ -116,12 +117,13 @@ class Variable(SourceMapping): return self._visibility @visibility.setter - def visibility(self, v: str): + def visibility(self, v: str) -> None: self._visibility = v - def set_type(self, t): + def set_type(self, t: Optional[Union[List, Type, str]]) -> None: if isinstance(t, str): - t = ElementaryType(t) + self._type = ElementaryType(t) + return assert isinstance(t, (Type, list)) or t is None self._type = t @@ -135,27 +137,46 @@ class Variable(SourceMapping): return self._is_immutable @is_immutable.setter - def is_immutable(self, immutablility: bool): + def is_immutable(self, immutablility: bool) -> None: self._is_immutable = immutablility + ################################################################################### + ################################################################################### + # region Signature + ################################################################################### + ################################################################################### + @property - def function_name(self): + def signature(self) -> Tuple[str, List[str], List[str]]: """ - Return the name of the variable as a function signature - :return: + Return the signature of the state variable as a function signature + :return: (str, list(str), list(str)), as (name, list parameters type, list return values type) """ # pylint: disable=import-outside-toplevel - from slither.core.solidity_types import ArrayType, MappingType - from slither.utils.type import export_nested_types_from_variable + from slither.utils.type import ( + export_nested_types_from_variable, + export_return_type_from_variable, + ) - variable_getter_args = "" - return_type = self.type - assert return_type + return ( + self.name, + [str(x) for x in export_nested_types_from_variable(self)], + [str(x) for x in export_return_type_from_variable(self)], + ) - if isinstance(return_type, (ArrayType, MappingType)): - variable_getter_args = ",".join(map(str, export_nested_types_from_variable(self))) + @property + def signature_str(self) -> str: + """ + Return the signature of the state variable as a function signature + :return: str: func_name(type1,type2) returns(type3) + """ + name, parameters, returnVars = self.signature + return name + "(" + ",".join(parameters) + ") returns(" + ",".join(returnVars) + ")" - return f"{self.name}({variable_getter_args})" + @property + def solidity_signature(self) -> str: + name, parameters, _ = self.signature + return f'{name}({",".join(parameters)})' - def __str__(self): + def __str__(self) -> str: return self._name diff --git a/slither/formatters/attributes/const_functions.py b/slither/formatters/attributes/const_functions.py index 2f28ac615..7be42ac04 100644 --- a/slither/formatters/attributes/const_functions.py +++ b/slither/formatters/attributes/const_functions.py @@ -16,7 +16,7 @@ def custom_format(compilation_unit: SlitherCompilationUnit, result): element["type_specific_fields"]["parent"]["name"] ) if target_contract: - function = target_contract.get_function_from_signature( + function = target_contract.get_function_from_full_name( element["type_specific_fields"]["signature"] ) if function: diff --git a/slither/formatters/functions/external_function.py b/slither/formatters/functions/external_function.py index 07316abed..67fdf66e9 100644 --- a/slither/formatters/functions/external_function.py +++ b/slither/formatters/functions/external_function.py @@ -12,7 +12,7 @@ def custom_format(compilation_unit: SlitherCompilationUnit, result): element["type_specific_fields"]["parent"]["name"] ) if target_contract: - function = target_contract.get_function_from_signature( + function = target_contract.get_function_from_full_name( element["type_specific_fields"]["signature"] ) if function: diff --git a/slither/formatters/naming_convention/naming_convention.py b/slither/formatters/naming_convention/naming_convention.py index d7720cc5a..9b4de3830 100644 --- a/slither/formatters/naming_convention/naming_convention.py +++ b/slither/formatters/naming_convention/naming_convention.py @@ -254,7 +254,7 @@ def _patch(compilation_unit: SlitherCompilationUnit, result, element, _target): ] param_name = element["name"] contract = scope.get_contract_from_name(contract_name) - function = contract.get_function_from_signature(function_sig) + function = contract.get_function_from_full_name(function_sig) target = function.get_local_variable_from_name(param_name) elif _target in ["variable", "variable_constant"]: @@ -268,7 +268,7 @@ def _patch(compilation_unit: SlitherCompilationUnit, result, element, _target): ] var_name = element["name"] contract = scope.get_contract_from_name(contract_name) - function = contract.get_function_from_signature(function_sig) + function = contract.get_function_from_full_name(function_sig) target = function.get_local_variable_from_name(var_name) # State variable else: diff --git a/slither/printers/guidance/echidna.py b/slither/printers/guidance/echidna.py index 2ae7c4612..dbfa54121 100644 --- a/slither/printers/guidance/echidna.py +++ b/slither/printers/guidance/echidna.py @@ -37,8 +37,7 @@ def _get_name(f: Union[Function, Variable]) -> str: if isinstance(f, Function): if f.is_fallback or f.is_receive: return "()" - return f.solidity_signature - return f.function_name + return f.solidity_signature def _extract_payable(slither: SlitherCore) -> Dict[str, List[str]]: @@ -117,7 +116,7 @@ def _extract_constant_functions(slither: SlitherCore) -> Dict[str, List[str]]: for contract in slither.contracts: cst_functions = [_get_name(f) for f in contract.functions_entry_points if _is_constant(f)] cst_functions += [ - v.function_name for v in contract.state_variables if v.visibility in ["public"] + v.solidity_signature for v in contract.state_variables if v.visibility in ["public"] ] if cst_functions: ret[contract.name] = cst_functions diff --git a/slither/printers/summary/function_ids.py b/slither/printers/summary/function_ids.py index 6c998880d..c3738ac5f 100644 --- a/slither/printers/summary/function_ids.py +++ b/slither/printers/summary/function_ids.py @@ -31,7 +31,7 @@ class FunctionIds(AbstractPrinter): table.add_row([function.solidity_signature, f"{function_id:#0{10}x}"]) for variable in contract.state_variables: if variable.visibility in ["public"]: - sig = variable.function_name + sig = variable.solidity_signature function_id = get_function_id(sig) table.add_row([sig, f"{function_id:#0{10}x}"]) txt += str(table) + "\n" diff --git a/slither/tools/upgradeability/checks/functions_ids.py b/slither/tools/upgradeability/checks/functions_ids.py index 255ae01ec..a7a45f405 100644 --- a/slither/tools/upgradeability/checks/functions_ids.py +++ b/slither/tools/upgradeability/checks/functions_ids.py @@ -22,7 +22,7 @@ def get_signatures(c): def _get_function_or_variable(contract, signature): - f = contract.get_function_from_signature(signature) + f = contract.get_function_from_full_name(signature) if f: return f diff --git a/slither/utils/type.py b/slither/utils/type.py index a70ee754b..1ce5fc158 100644 --- a/slither/utils/type.py +++ b/slither/utils/type.py @@ -1,21 +1,93 @@ +import math from typing import List, Union -from slither.core.solidity_types import ArrayType, MappingType, ElementaryType +from slither.core.solidity_types import ArrayType, MappingType, ElementaryType, UserDefinedType from slither.core.solidity_types.type import Type from slither.core.variables.variable import Variable -def _add_mapping_parameter(t: Type, l: List[Type]): - while isinstance(t, MappingType): - l.append(t.type_from) - t = t.type_to - _add_array_parameter(t, l) +def _convert_type_for_solidity_signature_to_string(types: Union[Type, List[Type]]) -> str: + if isinstance(types, Type): + # Array might be struct, so we need to go again through the conversion here + # We could have this logic in convert_type_for_solidity_signature + # But the slither type system is not straightforward to manipulate here + # And it would require to create a new ArrayType, which a potential List[Type] as input + # Which is currently not supported. This comes down to (uint, uint)[] not being possible in Solidity + # While having an array of a struct of two uint leads to a (uint, uint)[] signature + if isinstance(types, ArrayType): + underlying_type = convert_type_for_solidity_signature(types.type) + underlying_type_str = _convert_type_for_solidity_signature_to_string(underlying_type) + return underlying_type_str + "[]" + return str(types) -def _add_array_parameter(t: Type, l: List[Type]): - while isinstance(t, ArrayType): - l.append(ElementaryType("uint256")) - t = t.type + first_item = True + + ret = "(" + for underlying_type in types: + if first_item: + ret += _convert_type_for_solidity_signature_to_string(underlying_type) + else: + ret += "," + _convert_type_for_solidity_signature_to_string(underlying_type) + first_item = False + + ret += ")" + return ret + + +def convert_type_for_solidity_signature_to_string(t: Type) -> str: + types = convert_type_for_solidity_signature(t) + return _convert_type_for_solidity_signature_to_string(types) + + +def convert_type_for_solidity_signature(t: Type) -> Union[Type, List[Type]]: + # pylint: disable=import-outside-toplevel + from slither.core.declarations import Contract, Enum, Structure + + if isinstance(t, UserDefinedType): + underlying_type = t.type + if isinstance(underlying_type, Contract): + return ElementaryType("address") + if isinstance(underlying_type, Enum): + number_values = len(underlying_type.values) + # IF below 65536, avoid calling log2 + if number_values <= 256: + uint = "8" + elif number_values <= 65536: + uint = "16" + else: + uint = str(int(math.log2(number_values))) + return ElementaryType(f"uint{uint}") + if isinstance(underlying_type, Structure): + # We can't have recursive types for structure, so recursion is ok here + types = [ + convert_type_for_solidity_signature(x.type) for x in underlying_type.elems_ordered + ] + return types + + return t + + +def _export_nested_types_from_variable(current_type: Type, ret: List[Type]) -> None: + """ + Export the list of nested types (mapping/array) + :param variable: + :return: list(Type) + """ + if isinstance(current_type, MappingType): + underlying_type = convert_type_for_solidity_signature(current_type.type_from) + if isinstance(underlying_type, list): + ret.extend(underlying_type) + else: + ret.append(underlying_type) + next_type = current_type.type_to + + elif isinstance(current_type, ArrayType): + ret.append(ElementaryType("uint256")) + next_type = current_type.type + else: + return + _export_nested_types_from_variable(next_type, ret) def export_nested_types_from_variable(variable: Variable) -> List[Type]: @@ -25,33 +97,67 @@ def export_nested_types_from_variable(variable: Variable) -> List[Type]: :return: list(Type) """ l: List[Type] = [] - if isinstance(variable.type, MappingType): - t = variable.type - _add_mapping_parameter(t, l) + _export_nested_types_from_variable(variable.type, l) + return l - if isinstance(variable.type, ArrayType): - v = variable - _add_array_parameter(v.type, l) - return l +def _export_return_type_from_variable(underlying_type: Type, all_types: bool) -> List[Type]: + # pylint: disable=import-outside-toplevel + from slither.core.declarations import Structure + if isinstance(underlying_type, MappingType): + if not all_types: + return [] + return export_return_type_from_variable(underlying_type.type_to) -def export_return_type_from_variable(variable: Union[Type, Variable]): + if isinstance(underlying_type, ArrayType): + if not all_types: + return [] + return export_return_type_from_variable(underlying_type.type) + + if isinstance(underlying_type, UserDefinedType) and isinstance(underlying_type.type, Structure): + ret = [] + for r in underlying_type.type.elems_ordered: + ret += export_return_type_from_variable(r, all_types=False) + + return ret + + return [underlying_type] + + +def export_return_type_from_variable( + variable_or_type: Union[Type, Variable], all_types: bool = True +) -> List[Type]: """ - Return the type returned by a variable - :param variable + Return the type returned by a variable. + If all_types set to false, filter array/mapping. This is useful as the mapping/array in a structure are not + returned by solidity + :param variable_or_type + :param all_types :return: Type """ - if isinstance(variable, MappingType): - return export_return_type_from_variable(variable.type_to) + # pylint: disable=import-outside-toplevel + from slither.core.declarations import Structure + + if isinstance(variable_or_type, Type): + return _export_return_type_from_variable(variable_or_type, all_types) - if isinstance(variable, ArrayType): - return variable.type + if isinstance(variable_or_type.type, MappingType): + if not all_types: + return [] + return export_return_type_from_variable(variable_or_type.type.type_to) - if isinstance(variable.type, MappingType): - return export_return_type_from_variable(variable.type.type_to) + if isinstance(variable_or_type.type, ArrayType): + if not all_types: + return [] + return export_return_type_from_variable(variable_or_type.type.type) - if isinstance(variable.type, ArrayType): - return variable.type.type + if isinstance(variable_or_type.type, UserDefinedType) and isinstance( + variable_or_type.type.type, Structure + ): + ret = [] + for r in variable_or_type.type.type.elems_ordered: + ret += export_return_type_from_variable(r, all_types=False) + return ret - return variable.type + return [variable_or_type.type] diff --git a/tests/printers/functions_ids.sol b/tests/printers/functions_ids.sol new file mode 100644 index 000000000..2f4eea6a7 --- /dev/null +++ b/tests/printers/functions_ids.sol @@ -0,0 +1,64 @@ +pragma experimental ABIEncoderV2; + +contract Contract{} + +contract C{ + + mapping(uint => address)[] public arrayOfMappings; + + mapping(uint => address[]) public normalMappingArrayField; + + enum State{a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39,a40,a41,a42,a43,a44,a45,a46,a47,a48,a49,a50,a51,a52,a53,a54,a55,a56,a57,a58,a59,a60,a61,a62,a63,a64,a65,a66,a67,a68,a69,a70,a71,a72,a73,a74,a75,a76,a77,a78,a79,a80,a81,a82,a83,a84,a85,a86,a87,a88,a89,a90,a91,a92,a93,a94,a95,a96,a97,a98,a99,a100,a101,a102,a103,a104,a105,a106,a107,a108,a109,a110,a111,a112,a113,a114,a115,a116,a117,a118,a119,a120,a121,a122,a123,a124,a125,a126,a127,a128,a129,a130,a131,a132,a133,a134,a135,a136,a137,a138,a139,a140,a141,a142,a143,a144,a145,a146,a147,a148,a149,a150,a151,a152,a153,a154,a155,a156,a157,a158,a159,a160,a161,a162,a163,a164,a165,a166,a167,a168,a169,a170,a171,a172,a173,a174,a175,a176,a177,a178,a179,a180,a181,a182,a183,a184,a185,a186,a187,a188,a189,a190,a191,a192,a193,a194,a195,a196,a197,a198,a199,a200,a201,a202,a203,a204,a205,a206,a207,a208,a209,a210,a211,a212,a213,a214,a215,a216,a217,a218,a219,a220,a221,a222,a223,a224,a225,a226,a227,a228,a229,a230,a231,a232,a233,a234,a235,a236,a237,a238,a239,a240,a241,a242,a243,a244,a245,a246,a247,a248,a249,a250,a251,a252,a253,a254,a255,a256} + mapping(State => uint) public stateMap; + + mapping(Contract => uint) public contractMap; + + uint[][] public multiDimensionalArray; + + struct Simple { + uint a; + uint b; + mapping(uint => uint) c; + uint[] d; + function(uint) external returns (uint) e; + } + + Simple public simple; + + struct Inner { + uint a; + uint b; + } + + struct Outer { + Inner inner; + uint c; + } + + Outer public outer; + + + struct A { + Inner inner; + uint a; + } + struct B { + uint a; + Inner inner; + } + + A public a; + A[] public a_array; + mapping(address => B[]) public b_mapping_of_array; + + function function_with_struct(A memory a) public{} + + function function_with_array(A[] memory array, B memory b) public {} + + struct AnotherStruct{ + B b; + A[] a; + } + mapping(address => AnotherStruct[][]) public mapping_of_double_array_of_struct; +} + diff --git a/tests/test_functions_ids.py b/tests/test_functions_ids.py new file mode 100644 index 000000000..49818347f --- /dev/null +++ b/tests/test_functions_ids.py @@ -0,0 +1,60 @@ +from solc_select import solc_select +from slither import Slither + +# % solc functions_ids.sol --hashes +# ======= functions_ids.sol:C ======= +# Function signatures: +# 0dbe671f: a() +# 4a1f689d: a_array(uint256) +# 98fc2aa5: arrayOfMappings(uint256,uint256) +# 4ea7a557: b_mapping_of_array(address,uint256) +# 3c0af344: contractMap(address) +# 20969954: function_with_array(((uint256,uint256),uint256)[],(uint256,(uint256,uint256))) +# 1c039831: function_with_struct(((uint256,uint256),uint256)) +# 37e66bae: mapping_of_double_array_of_struct(address,uint256,uint256) +# f29872a8: multiDimensionalArray(uint256,uint256) +# 9539e3c8: normalMappingArrayField(uint256,uint256) +# 87c3dbb6: outer() +# df201a46: simple() +# 5a20851f: stateMap(uint16) + +# {"contracts":{"functions_ids.sol:C":{"hashes":{"a()":"0dbe671f","a_array(uint256)":"4a1f689d","arrayOfMappings(uint256,uint256)":"98fc2aa5","b_mapping_of_array(address,uint256)":"4ea7a557","contractMap(address)":"3c0af344","function_with_array(((uint256,uint256),uint256)[],(uint256,(uint256,uint256)))":"20969954","function_with_struct(((uint256,uint256),uint256))":"1c039831","mapping_of_double_array_of_struct(address,uint256,uint256)":"37e66bae","multiDimensionalArray(uint256,uint256)":"f29872a8","normalMappingArrayField(uint256,uint256)":"9539e3c8","outer()":"87c3dbb6","simple()":"df201a46","stateMap(uint16)":"5a20851f"}},"functions_ids.sol:Contract":{"hashes":{}}},"version":"0.7.0+commit.9e61f92b.Darwin.appleclang"} +from slither.utils.function import get_function_id + +signatures = { + "a()": "0dbe671f", + "a_array(uint256)": "4a1f689d", + "arrayOfMappings(uint256,uint256)": "98fc2aa5", + "b_mapping_of_array(address,uint256)": "4ea7a557", + "contractMap(address)": "3c0af344", + "function_with_array(((uint256,uint256),uint256)[],(uint256,(uint256,uint256)))": "20969954", + "function_with_struct(((uint256,uint256),uint256))": "1c039831", + "mapping_of_double_array_of_struct(address,uint256,uint256)": "37e66bae", + "multiDimensionalArray(uint256,uint256)": "f29872a8", + "normalMappingArrayField(uint256,uint256)": "9539e3c8", + "outer()": "87c3dbb6", + "simple()": "df201a46", + "stateMap(uint16)": "5a20851f", +} + + +def test_functions_ids() -> None: + solc_select.switch_global_version("0.7.0") + sl = Slither("tests/printers/functions_ids.sol") + contracts_c = sl.get_contract_from_name("C") + assert len(contracts_c) == 1 + contract_c = contracts_c[0] + + for sig, hashes in signatures.items(): + func = contract_c.get_function_from_signature(sig) + if not func: + var_name = sig[: sig.find("(")] + var = contract_c.get_state_variable_from_name(var_name) + assert var + assert get_function_id(var.solidity_signature) == int(hashes, 16) + else: + assert get_function_id(func.solidity_signature) == int(hashes, 16) + + +if __name__ == "__main__": + test_functions_ids() From ce1fc4f60a952632149b8e407b982e73d1466f79 Mon Sep 17 00:00:00 2001 From: Josselin Feist Date: Wed, 3 Aug 2022 16:23:27 +0200 Subject: [PATCH 2/3] Fix type on variable.name --- slither/core/variables/variable.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/slither/core/variables/variable.py b/slither/core/variables/variable.py index 0558be52c..5fda02e93 100644 --- a/slither/core/variables/variable.py +++ b/slither/core/variables/variable.py @@ -66,11 +66,10 @@ class Variable(SourceMapping): return not self._initialized @property - def name(self) -> str: + def name(self) -> Optional[str]: """ str: variable name """ - assert self._name return self._name @name.setter From 350b26596da5c470263dac9ba05dc97b914abb81 Mon Sep 17 00:00:00 2001 From: Josselin Feist Date: Wed, 3 Aug 2022 16:56:42 +0200 Subject: [PATCH 3/3] Minor --- slither/tools/erc_conformance/erc/ercs.py | 2 +- tests/test_functions_ids.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/tools/erc_conformance/erc/ercs.py b/slither/tools/erc_conformance/erc/ercs.py index afc9a698a..ef459eef9 100644 --- a/slither/tools/erc_conformance/erc/ercs.py +++ b/slither/tools/erc_conformance/erc/ercs.py @@ -51,7 +51,7 @@ def _check_signature(erc_function, contract, ret): ret["missing_function"].append(missing_func.data) return - function_return_type = [export_return_type_from_variable(state_variable_as_function)] + function_return_type = export_return_type_from_variable(state_variable_as_function) function = state_variable_as_function function_view = True diff --git a/tests/test_functions_ids.py b/tests/test_functions_ids.py index 49818347f..eacbf4930 100644 --- a/tests/test_functions_ids.py +++ b/tests/test_functions_ids.py @@ -39,7 +39,7 @@ signatures = { def test_functions_ids() -> None: - solc_select.switch_global_version("0.7.0") + solc_select.switch_global_version("0.7.0", always_install=True) sl = Slither("tests/printers/functions_ids.sol") contracts_c = sl.get_contract_from_name("C") assert len(contracts_c) == 1