Merge pull request #1323 from crytic/dev-functions-sig

Improve generation of solidity signature
pull/1343/head
Feist Josselin 2 years ago committed by GitHub
commit 3a934a997b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .github/workflows/features.yml
  2. 21
      slither/core/declarations/contract.py
  3. 14
      slither/core/declarations/function.py
  4. 4
      slither/core/declarations/structure.py
  5. 2
      slither/core/solidity_types/elementary_type.py
  6. 32
      slither/core/variables/state_variable.py
  7. 62
      slither/core/variables/variable.py
  8. 2
      slither/formatters/attributes/const_functions.py
  9. 2
      slither/formatters/functions/external_function.py
  10. 4
      slither/formatters/naming_convention/naming_convention.py
  11. 5
      slither/printers/guidance/echidna.py
  12. 2
      slither/printers/summary/function_ids.py
  13. 2
      slither/tools/erc_conformance/erc/ercs.py
  14. 2
      slither/tools/upgradeability/checks/functions_ids.py
  15. 164
      slither/utils/type.py
  16. 64
      tests/printers/functions_ids.sol
  17. 60
      tests/test_functions_ids.py

@ -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

@ -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,
)

@ -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

@ -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

@ -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__()

@ -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
###################################################################################
###################################################################################

@ -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
@ -66,7 +66,7 @@ class Variable(SourceMapping):
return not self._initialized
@property
def name(self) -> str:
def name(self) -> Optional[str]:
"""
str: variable name
"""
@ -97,7 +97,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 +105,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 +116,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 +136,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

@ -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:

@ -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:

@ -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:

@ -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

@ -33,7 +33,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"

@ -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

@ -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

@ -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]

@ -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;
}

@ -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", always_install=True)
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()
Loading…
Cancel
Save