Skip to content

Commit

Permalink
Merge branch 'dev' into HEAD
Browse files Browse the repository at this point in the history
  • Loading branch information
0xalpharush committed Mar 29, 2024
2 parents ecf8c3d + ef1fd7e commit 3181a45
Show file tree
Hide file tree
Showing 26 changed files with 666 additions and 144 deletions.
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"packaging",
"prettytable>=3.3.0",
"pycryptodome>=3.4.6",
"crytic-compile>=0.3.5,<0.4.0",
# "crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile",
# "crytic-compile>=0.3.5,<0.4.0",
"crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile",
"web3>=6.0.0",
"eth-abi>=4.0.0",
"eth-typing>=3.0.0",
Expand Down
29 changes: 25 additions & 4 deletions slither/core/declarations/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope
self._is_interface: bool = False
self._is_library: bool = False
self._is_fully_implemented: bool = False
self._is_abstract: bool = False

self._signatures: Optional[List[str]] = None
self._signatures_declared: Optional[List[str]] = None
Expand Down Expand Up @@ -199,12 +200,34 @@ def comments(self, comments: str):

@property
def is_fully_implemented(self) -> bool:
"""
bool: True if the contract defines all functions.
In modern Solidity, virtual functions can lack an implementation.
Prior to Solidity 0.6.0, functions like the following would be not fully implemented:
```solidity
contract ImplicitAbstract{
function f() public;
}
```
"""
return self._is_fully_implemented

@is_fully_implemented.setter
def is_fully_implemented(self, is_fully_implemented: bool):
self._is_fully_implemented = is_fully_implemented

@property
def is_abstract(self) -> bool:
"""
Note for Solidity < 0.6.0 it will always be false
bool: True if the contract is abstract.
"""
return self._is_abstract

@is_abstract.setter
def is_abstract(self, is_abstract: bool):
self._is_abstract = is_abstract

# endregion
###################################################################################
###################################################################################
Expand Down Expand Up @@ -983,16 +1006,14 @@ def get_enum_from_canonical_name(self, enum_name: str) -> Optional["Enum"]:

def get_functions_overridden_by(self, function: "Function") -> List["Function"]:
"""
Return the list of functions overriden by the function
Return the list of functions overridden by the function
Args:
(core.Function)
Returns:
list(core.Function)
"""
candidatess = [c.functions_declared for c in self.inheritance]
candidates = [candidate for sublist in candidatess for candidate in sublist]
return [f for f in candidates if f.full_name == function.full_name]
return function.overrides

# endregion
###################################################################################
Expand Down
49 changes: 47 additions & 2 deletions slither/core/declarations/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
HighLevelCallType,
LibraryCallType,
)
from slither.core.declarations import Contract
from slither.core.declarations import Contract, FunctionContract
from slither.core.cfg.node import Node, NodeType
from slither.core.variables.variable import Variable
from slither.slithir.variables.variable import SlithIRVariable
Expand All @@ -46,7 +46,6 @@
from slither.slithir.operations import Operation
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.scope.scope import FileScope
from slither.slithir.variables.state_variable import StateIRVariable

LOGGER = logging.getLogger("Function")
ReacheableNode = namedtuple("ReacheableNode", ["node", "ir"])
Expand Down Expand Up @@ -126,6 +125,9 @@ def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None:
self._pure: bool = False
self._payable: bool = False
self._visibility: Optional[str] = None
self._virtual: bool = False
self._overrides: List["FunctionContract"] = []
self._overridden_by: List["FunctionContract"] = []

self._is_implemented: Optional[bool] = None
self._is_empty: Optional[bool] = None
Expand Down Expand Up @@ -441,6 +443,49 @@ def payable(self) -> bool:
def payable(self, p: bool):
self._payable = p

# endregion
###################################################################################
###################################################################################
# region Virtual
###################################################################################
###################################################################################

@property
def is_virtual(self) -> bool:
"""
Note for Solidity < 0.6.0 it will always be false
bool: True if the function is virtual
"""
return self._virtual

@is_virtual.setter
def is_virtual(self, v: bool):
self._virtual = v

@property
def is_override(self) -> bool:
"""
Note for Solidity < 0.6.0 it will always be false
bool: True if the function overrides a base function
"""
return len(self._overrides) > 0

@property
def overridden_by(self) -> List["FunctionContract"]:
"""
List["FunctionContract"]: List of functions in child contracts that override this function
This may include distinct instances of the same function due to inheritance
"""
return self._overridden_by

@property
def overrides(self) -> List["FunctionContract"]:
"""
List["FunctionContract"]: List of functions in parent contracts that this function overrides
This may include distinct instances of the same function due to inheritance
"""
return self._overrides

# endregion
###################################################################################
###################################################################################
Expand Down
55 changes: 41 additions & 14 deletions slither/core/slither_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from slither.slithir.variables import Constant
from slither.utils.colors import red
from slither.utils.sarif import read_triage_info
from slither.utils.source_mapping import get_definition, get_references, get_implementation
from slither.utils.source_mapping import get_definition, get_references, get_all_implementations

logger = logging.getLogger("Slither")
logging.basicConfig()
Expand Down Expand Up @@ -204,41 +204,53 @@ def offset_to_objects(self, filename_str: str, offset: int) -> Set[SourceMapping
def _compute_offsets_from_thing(self, thing: SourceMapping):
definition = get_definition(thing, self.crytic_compile)
references = get_references(thing)
implementation = get_implementation(thing)
implementations = get_all_implementations(thing, self.contracts)

for offset in range(definition.start, definition.end + 1):

if (
isinstance(thing, TopLevel)
isinstance(thing, (TopLevel, Contract))
or (
isinstance(thing, FunctionContract)
and thing.contract_declarer == thing.contract
)
or (isinstance(thing, ContractLevel) and not isinstance(thing, FunctionContract))
):

self._offset_to_objects[definition.filename][offset].add(thing)

self._offset_to_definitions[definition.filename][offset].add(definition)
self._offset_to_implementations[definition.filename][offset].add(implementation)
self._offset_to_implementations[definition.filename][offset].update(implementations)
self._offset_to_references[definition.filename][offset] |= set(references)

for ref in references:
for offset in range(ref.start, ref.end + 1):

is_declared_function = (
isinstance(thing, FunctionContract)
and thing.contract_declarer == thing.contract
)
if (
isinstance(thing, TopLevel)
or (
isinstance(thing, FunctionContract)
and thing.contract_declarer == thing.contract
)
or is_declared_function
or (
isinstance(thing, ContractLevel) and not isinstance(thing, FunctionContract)
)
):
self._offset_to_objects[definition.filename][offset].add(thing)

self._offset_to_definitions[ref.filename][offset].add(definition)
self._offset_to_implementations[ref.filename][offset].add(implementation)
if is_declared_function:
# Only show the nearest lexical definition for declared contract-level functions
if (
thing.contract.source_mapping.start
< offset
< thing.contract.source_mapping.end
):

self._offset_to_definitions[ref.filename][offset].add(definition)

else:
self._offset_to_definitions[ref.filename][offset].add(definition)

self._offset_to_implementations[ref.filename][offset].update(implementations)
self._offset_to_references[ref.filename][offset] |= set(references)

def _compute_offsets_to_ref_impl_decl(self): # pylint: disable=too-many-branches
Expand All @@ -251,15 +263,18 @@ def _compute_offsets_to_ref_impl_decl(self): # pylint: disable=too-many-branche
for contract in compilation_unit.contracts:
self._compute_offsets_from_thing(contract)

for function in contract.functions:
for function in contract.functions_declared:
self._compute_offsets_from_thing(function)
for variable in function.local_variables:
self._compute_offsets_from_thing(variable)
for modifier in contract.modifiers:
for modifier in contract.modifiers_declared:
self._compute_offsets_from_thing(modifier)
for variable in modifier.local_variables:
self._compute_offsets_from_thing(variable)

for var in contract.state_variables:
self._compute_offsets_from_thing(var)

for st in contract.structures:
self._compute_offsets_from_thing(st)

Expand All @@ -268,6 +283,10 @@ def _compute_offsets_to_ref_impl_decl(self): # pylint: disable=too-many-branche

for event in contract.events:
self._compute_offsets_from_thing(event)

for typ in contract.type_aliases:
self._compute_offsets_from_thing(typ)

for enum in compilation_unit.enums_top_level:
self._compute_offsets_from_thing(enum)
for event in compilation_unit.events_top_level:
Expand All @@ -276,6 +295,14 @@ def _compute_offsets_to_ref_impl_decl(self): # pylint: disable=too-many-branche
self._compute_offsets_from_thing(function)
for st in compilation_unit.structures_top_level:
self._compute_offsets_from_thing(st)
for var in compilation_unit.variables_top_level:
self._compute_offsets_from_thing(var)
for typ in compilation_unit.type_aliases.values():
self._compute_offsets_from_thing(typ)
for err in compilation_unit.custom_errors:
self._compute_offsets_from_thing(err)
for event in compilation_unit.events_top_level:
self._compute_offsets_from_thing(event)
for import_directive in compilation_unit.import_directives:
self._compute_offsets_from_thing(import_directive)
for pragma in compilation_unit.pragma_directives:
Expand Down
12 changes: 7 additions & 5 deletions slither/printers/summary/declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,20 @@ def output(self, _filename: str) -> Output:
txt += "\n# Contracts\n"
for contract in compilation_unit.contracts:
txt += f"# {contract.name}\n"
txt += f"\t- Declaration: {get_definition(contract, compilation_unit.core.crytic_compile).to_detailed_str()}\n"
txt += f"\t- Implementation: {get_implementation(contract).to_detailed_str()}\n"
contract_def = get_definition(contract, compilation_unit.core.crytic_compile)
txt += f"\t- Declaration: {contract_def.to_detailed_str()}\n"
txt += f"\t- Implementation(s): {[x.to_detailed_str() for x in list(self.slither.offset_to_implementations(contract.source_mapping.filename.absolute, contract_def.start))]}\n"
txt += (
f"\t- References: {[x.to_detailed_str() for x in get_references(contract)]}\n"
)

txt += "\n\t## Function\n"

for func in contract.functions:
for func in contract.functions_declared:
txt += f"\t\t- {func.canonical_name}\n"
txt += f"\t\t\t- Declaration: {get_definition(func, compilation_unit.core.crytic_compile).to_detailed_str()}\n"
txt += f"\t\t\t- Implementation: {get_implementation(func).to_detailed_str()}\n"
function_def = get_definition(func, compilation_unit.core.crytic_compile)
txt += f"\t\t\t- Declaration: {function_def.to_detailed_str()}\n"
txt += f"\t\t\t- Implementation(s): {[x.to_detailed_str() for x in list(self.slither.offset_to_implementations(func.source_mapping.filename.absolute, function_def.start))]}\n"
txt += f"\t\t\t- References: {[x.to_detailed_str() for x in get_references(func)]}\n"

txt += "\n\t## State variables\n"
Expand Down
12 changes: 7 additions & 5 deletions slither/slithir/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -873,9 +873,7 @@ def propagate_types(ir: Operation, node: "Node"): # pylint: disable=too-many-lo
elif isinstance(ir, NewArray):
ir.lvalue.set_type(ir.array_type)
elif isinstance(ir, NewContract):
contract = node.file_scope.get_contract_from_name(ir.contract_name)
assert contract
ir.lvalue.set_type(UserDefinedType(contract))
ir.lvalue.set_type(ir.contract_name)
elif isinstance(ir, NewElementaryType):
ir.lvalue.set_type(ir.type)
elif isinstance(ir, NewStructure):
Expand Down Expand Up @@ -1166,7 +1164,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
return n

if isinstance(ins.ori, TmpNewContract):
op = NewContract(Constant(ins.ori.contract_name), ins.lvalue)
op = NewContract(ins.ori.contract_name, ins.lvalue)
op.set_expression(ins.expression)
op.call_id = ins.call_id
if ins.call_value:
Expand Down Expand Up @@ -1211,7 +1209,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
internalcall.set_expression(ins.expression)
return internalcall

raise Exception(f"Not extracted {type(ins.called)} {ins}") # pylint: disable=bad-option-value
raise SlithIRError(f"Not extracted {type(ins.called)} {ins}")


# endregion
Expand Down Expand Up @@ -1724,6 +1722,7 @@ def convert_type_of_high_and_internal_level_call(
Returns:
Potential new IR
"""

func = None
if isinstance(ir, InternalCall):
candidates: List[Function]
Expand Down Expand Up @@ -2019,6 +2018,9 @@ def _find_source_mapping_references(irs: List[Operation]) -> None:
if isinstance(ir, NewContract):
ir.contract_created.references.append(ir.expression.source_mapping)

if isinstance(ir, HighLevelCall):
ir.function.references.append(ir.expression.source_mapping)


# endregion
###################################################################################
Expand Down
14 changes: 7 additions & 7 deletions slither/slithir/operations/new_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from slither.core.declarations import Function
from slither.core.declarations.contract import Contract
from slither.core.variables import Variable
from slither.core.solidity_types import UserDefinedType
from slither.slithir.operations import Call, OperationWithLValue
from slither.slithir.utils.utils import is_valid_lvalue
from slither.slithir.variables.constant import Constant
Expand All @@ -13,7 +14,7 @@
class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instance-attributes
def __init__(
self,
contract_name: Constant,
contract_name: UserDefinedType,
lvalue: Union[TemporaryVariableSSA, TemporaryVariable],
names: Optional[List[str]] = None,
) -> None:
Expand All @@ -23,7 +24,9 @@ def __init__(
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
assert isinstance(contract_name, Constant)
assert isinstance(
contract_name.type, Contract
), f"contract_name is {contract_name} of type {type(contract_name)}"
assert is_valid_lvalue(lvalue)
super().__init__(names=names)
self._contract_name = contract_name
Expand Down Expand Up @@ -58,7 +61,7 @@ def call_salt(self, s):
self._call_salt = s

@property
def contract_name(self) -> Constant:
def contract_name(self) -> UserDefinedType:
return self._contract_name

@property
Expand All @@ -69,10 +72,7 @@ def read(self) -> List[Any]:

@property
def contract_created(self) -> Contract:
contract_name = self.contract_name
contract_instance = self.node.file_scope.get_contract_from_name(contract_name)
assert contract_instance
return contract_instance
return self.contract_name.type

###################################################################################
###################################################################################
Expand Down
Loading

0 comments on commit 3181a45

Please sign in to comment.