|
|
|
@ -3,17 +3,19 @@ |
|
|
|
|
|
|
|
|
|
import json |
|
|
|
|
from collections import defaultdict |
|
|
|
|
from typing import Dict, List, Set, Tuple |
|
|
|
|
from typing import Dict, List, Set, Tuple, Union, NamedTuple |
|
|
|
|
|
|
|
|
|
from slither.analyses.data_dependency.data_dependency import is_dependent |
|
|
|
|
from slither.core.cfg.node import Node |
|
|
|
|
from slither.core.declarations import Function |
|
|
|
|
from slither.core.declarations.solidity_variables import SolidityVariableComposed, SolidityFunction, SolidityVariable |
|
|
|
|
from slither.core.expressions import NewContract |
|
|
|
|
from slither.core.slither_core import Slither |
|
|
|
|
from slither.core.variables.variable import Variable |
|
|
|
|
from slither.core.variables.state_variable import StateVariable |
|
|
|
|
from slither.printers.abstract_printer import AbstractPrinter |
|
|
|
|
from slither.core.declarations.solidity_variables import SolidityVariableComposed, SolidityFunction, SolidityVariable |
|
|
|
|
from slither.slithir.operations import Member, Operation |
|
|
|
|
from slither.slithir.operations import Member, Operation, SolidityCall, LowLevelCall, HighLevelCall, EventCall, Send, \ |
|
|
|
|
Transfer, InternalDynamicCall, InternalCall, TypeConversion, Balance |
|
|
|
|
from slither.slithir.operations.binary import Binary, BinaryType |
|
|
|
|
from slither.core.variables.state_variable import StateVariable |
|
|
|
|
from slither.slithir.variables import Constant |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -46,10 +48,54 @@ def _extract_solidity_variable_usage(slither: Slither, sol_var: SolidityVariable |
|
|
|
|
return ret |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _is_constant(f: Function) -> bool: |
|
|
|
|
""" |
|
|
|
|
Heuristic: |
|
|
|
|
- If view/pure with Solidity >= 0.4 -> Return true |
|
|
|
|
- If it contains assembly -> Return false (Slither doesn't analyze asm) |
|
|
|
|
- Otherwise check for the rules from |
|
|
|
|
https://solidity.readthedocs.io/en/v0.5.0/contracts.html?highlight=pure#view-functions |
|
|
|
|
with an exception: internal dynamic call are not correctly handled, so we consider them as non-constant |
|
|
|
|
:param f: |
|
|
|
|
:return: |
|
|
|
|
""" |
|
|
|
|
if f.view or f.pure: |
|
|
|
|
if not f.contract.slither.crytic_compile.compiler_version.version.startswith('0.4'): |
|
|
|
|
return True |
|
|
|
|
if f.payable: |
|
|
|
|
return False |
|
|
|
|
if not f.is_implemented: |
|
|
|
|
return False |
|
|
|
|
if f.contains_assembly: |
|
|
|
|
return False |
|
|
|
|
if f.all_state_variables_written(): |
|
|
|
|
return False |
|
|
|
|
for ir in f.all_slithir_operations(): |
|
|
|
|
if isinstance(ir, InternalDynamicCall): |
|
|
|
|
return False |
|
|
|
|
if isinstance(ir, (EventCall, NewContract, LowLevelCall, Send, Transfer)): |
|
|
|
|
return False |
|
|
|
|
if isinstance(ir, SolidityCall) and ir.function in [SolidityFunction('selfdestruct(address)'), |
|
|
|
|
SolidityFunction('suicide(address)')]: |
|
|
|
|
return False |
|
|
|
|
if isinstance(ir, HighLevelCall): |
|
|
|
|
if ir.function.view or ir.function.pure: |
|
|
|
|
# External call to constant functions are ensured to be constant only for solidity >= 0.5 |
|
|
|
|
if f.contract.slither.crytic_compile.compiler_version.version.startswith('0.4'): |
|
|
|
|
return False |
|
|
|
|
else: |
|
|
|
|
return False |
|
|
|
|
if isinstance(ir, InternalCall): |
|
|
|
|
# Storage write are not properly handled by all_state_variables_written |
|
|
|
|
if any(parameter.is_storage for parameter in ir.function.parameters): |
|
|
|
|
return False |
|
|
|
|
return True |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _extract_constant_functions(slither: Slither) -> Dict[str, List[str]]: |
|
|
|
|
ret: Dict[str, List[str]] = {} |
|
|
|
|
for contract in slither.contracts: |
|
|
|
|
cst_functions = [_get_name(f) for f in contract.functions_entry_points if f.view or f.pure] |
|
|
|
|
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']] |
|
|
|
|
if cst_functions: |
|
|
|
|
ret[contract.name] = cst_functions |
|
|
|
@ -70,21 +116,42 @@ def _extract_assert(slither: Slither) -> Dict[str, List[str]]: |
|
|
|
|
return ret |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Create a named tuple that is serialization in json |
|
|
|
|
def json_serializable(cls): |
|
|
|
|
def as_dict(self): |
|
|
|
|
yield {name: value for name, value in zip( |
|
|
|
|
self._fields, |
|
|
|
|
iter(super(cls, self).__iter__()))} |
|
|
|
|
|
|
|
|
|
cls.__iter__ = as_dict |
|
|
|
|
return cls |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@json_serializable |
|
|
|
|
class ConstantValue(NamedTuple): |
|
|
|
|
value: Union[str, int, bool] |
|
|
|
|
type: str |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _extract_constants_from_irs(irs: List[Operation], |
|
|
|
|
all_cst_used: List, |
|
|
|
|
all_cst_used_in_binary: Dict, |
|
|
|
|
all_cst_used: List[ConstantValue], |
|
|
|
|
all_cst_used_in_binary: Dict[str, List[ConstantValue]], |
|
|
|
|
context_explored: Set[Node]): |
|
|
|
|
for ir in irs: |
|
|
|
|
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) |
|
|
|
|
all_cst_used_in_binary[BinaryType.str(ir.type)].append(ConstantValue(r.value, str(r.type))) |
|
|
|
|
if isinstance(ir, TypeConversion): |
|
|
|
|
if isinstance(ir.variable, Constant): |
|
|
|
|
all_cst_used.append(ConstantValue(ir.variable.value, str(ir.type))) |
|
|
|
|
continue |
|
|
|
|
for r in ir.read: |
|
|
|
|
# Do not report struct_name in a.struct_name |
|
|
|
|
if isinstance(ir, Member): |
|
|
|
|
continue |
|
|
|
|
if isinstance(r, Constant): |
|
|
|
|
all_cst_used.append(r.value) |
|
|
|
|
all_cst_used.append(ConstantValue(r.value, str(r.type))) |
|
|
|
|
if isinstance(r, StateVariable): |
|
|
|
|
if r.node_initialization: |
|
|
|
|
if r.node_initialization.irs: |
|
|
|
@ -99,12 +166,14 @@ def _extract_constants_from_irs(irs: List[Operation], |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _extract_constants(slither: Slither) -> Tuple[Dict[str, Dict[str, List]], Dict[str, Dict[str, Dict]]]: |
|
|
|
|
ret_cst_used: Dict[str, Dict[str, List]] = defaultdict(dict) |
|
|
|
|
ret_cst_used_in_binary: Dict[str, Dict[str, Dict]] = defaultdict(dict) |
|
|
|
|
# contract -> function -> [ {"value": value, "type": type} ] |
|
|
|
|
ret_cst_used: Dict[str, Dict[str, List[ConstantValue]]] = defaultdict(dict) |
|
|
|
|
# contract -> function -> binary_operand -> [ {"value": value, "type": type ] |
|
|
|
|
ret_cst_used_in_binary: Dict[str, Dict[str, Dict[str, List[ConstantValue]]]] = defaultdict(dict) |
|
|
|
|
for contract in slither.contracts: |
|
|
|
|
for function in contract.functions_entry_points: |
|
|
|
|
all_cst_used = [] |
|
|
|
|
all_cst_used_in_binary = defaultdict(list) |
|
|
|
|
all_cst_used: List = [] |
|
|
|
|
all_cst_used_in_binary: Dict = defaultdict(list) |
|
|
|
|
|
|
|
|
|
context_explored = set() |
|
|
|
|
context_explored.add(function) |
|
|
|
@ -113,13 +182,100 @@ def _extract_constants(slither: Slither) -> Tuple[Dict[str, Dict[str, List]], Di |
|
|
|
|
all_cst_used_in_binary, |
|
|
|
|
context_explored) |
|
|
|
|
|
|
|
|
|
# Note: use list(set()) instead of set |
|
|
|
|
# As this is meant to be serialized in JSON, and JSON does not support set |
|
|
|
|
if all_cst_used: |
|
|
|
|
ret_cst_used[contract.name][function.full_name] = all_cst_used |
|
|
|
|
ret_cst_used[contract.name][_get_name(function)] = list(set(all_cst_used)) |
|
|
|
|
if all_cst_used_in_binary: |
|
|
|
|
ret_cst_used_in_binary[contract.name][function.full_name] = all_cst_used_in_binary |
|
|
|
|
ret_cst_used_in_binary[contract.name][_get_name(function)] = {k: list(set(v)) for k, v in |
|
|
|
|
all_cst_used_in_binary.items()} |
|
|
|
|
return ret_cst_used, ret_cst_used_in_binary |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _extract_function_relations(slither: Slither) -> Dict[str, Dict[str, Dict[str, List[str]]]]: |
|
|
|
|
# contract -> function -> [functions] |
|
|
|
|
ret: Dict[str, Dict[str, Dict[str, List[str]]]] = defaultdict(dict) |
|
|
|
|
for contract in slither.contracts: |
|
|
|
|
ret[contract.name] = defaultdict(dict) |
|
|
|
|
written = {_get_name(function): function.all_state_variables_written() |
|
|
|
|
for function in contract.functions_entry_points} |
|
|
|
|
read = {_get_name(function): function.all_state_variables_read() |
|
|
|
|
for function in contract.functions_entry_points} |
|
|
|
|
for function in contract.functions_entry_points: |
|
|
|
|
ret[contract.name][_get_name(function)] = {"impacts": [], |
|
|
|
|
"is_impacted_by": []} |
|
|
|
|
for candidate, varsWritten in written.items(): |
|
|
|
|
if any((r in varsWritten for r in function.all_state_variables_read())): |
|
|
|
|
ret[contract.name][_get_name(function)]["is_impacted_by"].append(candidate) |
|
|
|
|
for candidate, varsRead in read.items(): |
|
|
|
|
if any((r in varsRead for r in function.all_state_variables_written())): |
|
|
|
|
ret[contract.name][_get_name(function)]["impacts"].append(candidate) |
|
|
|
|
return ret |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _have_external_calls(slither: Slither) -> Dict[str, List[str]]: |
|
|
|
|
""" |
|
|
|
|
Detect the functions with external calls |
|
|
|
|
:param slither: |
|
|
|
|
:return: |
|
|
|
|
""" |
|
|
|
|
ret: Dict[str, List[str]] = defaultdict(list) |
|
|
|
|
for contract in slither.contracts: |
|
|
|
|
for function in contract.functions_entry_points: |
|
|
|
|
if function.all_high_level_calls() or function.all_low_level_calls(): |
|
|
|
|
ret[contract.name].append(_get_name(function)) |
|
|
|
|
if contract.name in ret: |
|
|
|
|
ret[contract.name] = list(set(ret[contract.name])) |
|
|
|
|
return ret |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _use_balance(slither: Slither) -> Dict[str, List[str]]: |
|
|
|
|
""" |
|
|
|
|
Detect the functions with external calls |
|
|
|
|
:param slither: |
|
|
|
|
:return: |
|
|
|
|
""" |
|
|
|
|
ret: Dict[str, List[str]] = defaultdict(list) |
|
|
|
|
for contract in slither.contracts: |
|
|
|
|
for function in contract.functions_entry_points: |
|
|
|
|
for ir in function.all_slithir_operations(): |
|
|
|
|
if isinstance(ir, Balance): |
|
|
|
|
ret[contract.name].append(_get_name(function)) |
|
|
|
|
if contract.name in ret: |
|
|
|
|
ret[contract.name] = list(set(ret[contract.name])) |
|
|
|
|
return ret |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _call_a_parameter(slither: Slither) -> Dict[str, List[Dict]]: |
|
|
|
|
""" |
|
|
|
|
Detect the functions with external calls |
|
|
|
|
:param slither: |
|
|
|
|
:return: |
|
|
|
|
""" |
|
|
|
|
# contract -> [ (function, idx, interface_called) ] |
|
|
|
|
ret: Dict[str, List[Dict]] = defaultdict(list) |
|
|
|
|
for contract in slither.contracts: |
|
|
|
|
for function in contract.functions_entry_points: |
|
|
|
|
for ir in function.all_slithir_operations(): |
|
|
|
|
if isinstance(ir, HighLevelCall): |
|
|
|
|
for idx, parameter in enumerate(function.parameters): |
|
|
|
|
if is_dependent(ir.destination, parameter, function): |
|
|
|
|
ret[contract.name].append({ |
|
|
|
|
"function": _get_name(function), |
|
|
|
|
"parameter_idx": idx, |
|
|
|
|
"signature": _get_name(ir.function) |
|
|
|
|
}) |
|
|
|
|
if isinstance(ir, LowLevelCall): |
|
|
|
|
for idx, parameter in enumerate(function.parameters): |
|
|
|
|
if is_dependent(ir.destination, parameter, function): |
|
|
|
|
ret[contract.name].append({ |
|
|
|
|
"function": _get_name(function), |
|
|
|
|
"parameter_idx": idx, |
|
|
|
|
"signature": None |
|
|
|
|
}) |
|
|
|
|
return ret |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Echidna(AbstractPrinter): |
|
|
|
|
ARGUMENT = 'echidna' |
|
|
|
|
HELP = 'Export Echidna guiding information' |
|
|
|
@ -148,6 +304,17 @@ class Echidna(AbstractPrinter): |
|
|
|
|
cst_functions = _extract_constant_functions(self.slither) |
|
|
|
|
(cst_used, cst_used_in_binary) = _extract_constants(self.slither) |
|
|
|
|
|
|
|
|
|
functions_relations = _extract_function_relations(self.slither) |
|
|
|
|
|
|
|
|
|
constructors = {contract.name: contract.constructor.full_name |
|
|
|
|
for contract in self.slither.contracts if contract.constructor} |
|
|
|
|
|
|
|
|
|
external_calls = _have_external_calls(self.slither) |
|
|
|
|
|
|
|
|
|
call_parameters = _call_a_parameter(self.slither) |
|
|
|
|
|
|
|
|
|
use_balance = _use_balance(self.slither) |
|
|
|
|
|
|
|
|
|
d = {'payable': payable, |
|
|
|
|
'timestamp': timestamp, |
|
|
|
|
'block_number': block_number, |
|
|
|
@ -156,7 +323,12 @@ class Echidna(AbstractPrinter): |
|
|
|
|
'assert': assert_usage, |
|
|
|
|
'constant_functions': cst_functions, |
|
|
|
|
'constants_used': cst_used, |
|
|
|
|
'constants_used_in_binary': cst_used_in_binary} |
|
|
|
|
'constants_used_in_binary': cst_used_in_binary, |
|
|
|
|
'functions_relations': functions_relations, |
|
|
|
|
'constructors': constructors, |
|
|
|
|
'have_external_calls': external_calls, |
|
|
|
|
'call_a_parameter': call_parameters, |
|
|
|
|
'use_balance': use_balance} |
|
|
|
|
|
|
|
|
|
self.info(json.dumps(d, indent=4)) |
|
|
|
|
|
|
|
|
|