Skip to content

Commit

Permalink
Merge pull request #2219 from crytic/dev-tp-events
Browse files Browse the repository at this point in the history
Add support top level events
  • Loading branch information
0xalpharush authored Feb 16, 2024
2 parents 029d5db + 4a33541 commit 65fcb4b
Show file tree
Hide file tree
Showing 18 changed files with 105 additions and 45 deletions.
6 changes: 6 additions & 0 deletions slither/core/compilation_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
)
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.event_top_level import EventTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel
from slither.core.declarations.using_for_top_level import UsingForTopLevel
Expand Down Expand Up @@ -57,6 +58,7 @@ def __init__(self, core: "SlitherCore", crytic_compilation_unit: CompilationUnit
self.contracts: List[Contract] = []
self._structures_top_level: List[StructureTopLevel] = []
self._enums_top_level: List[EnumTopLevel] = []
self._events_top_level: List[EventTopLevel] = []
self._variables_top_level: List[TopLevelVariable] = []
self._functions_top_level: List[FunctionTopLevel] = []
self._using_for_top_level: List[UsingForTopLevel] = []
Expand Down Expand Up @@ -234,6 +236,10 @@ def structures_top_level(self) -> List[StructureTopLevel]:
def enums_top_level(self) -> List[EnumTopLevel]:
return self._enums_top_level

@property
def events_top_level(self) -> List[EventTopLevel]:
return self._events_top_level

@property
def variables_top_level(self) -> List[TopLevelVariable]:
return self._variables_top_level
Expand Down
2 changes: 2 additions & 0 deletions slither/core/declarations/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from .contract import Contract
from .enum import Enum
from .event import Event
from .event_contract import EventContract
from .event_top_level import EventTopLevel
from .function import Function
from .import_directive import Import
from .modifier import Modifier
Expand Down
12 changes: 6 additions & 6 deletions slither/core/declarations/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from slither.utils.type_helpers import LibraryCallType, HighLevelCallType, InternalCallType
from slither.core.declarations import (
Enum,
Event,
EventContract,
Modifier,
EnumContract,
StructureContract,
Expand Down Expand Up @@ -73,7 +73,7 @@ def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope

self._enums: Dict[str, "EnumContract"] = {}
self._structures: Dict[str, "StructureContract"] = {}
self._events: Dict[str, "Event"] = {}
self._events: Dict[str, "EventContract"] = {}
# map accessible variable from name -> variable
# do not contain private variables inherited from contract
self._variables: Dict[str, "StateVariable"] = {}
Expand Down Expand Up @@ -278,28 +278,28 @@ def enums_as_dict(self) -> Dict[str, "EnumContract"]:
###################################################################################

@property
def events(self) -> List["Event"]:
def events(self) -> List["EventContract"]:
"""
list(Event): List of the events
"""
return list(self._events.values())

@property
def events_inherited(self) -> List["Event"]:
def events_inherited(self) -> List["EventContract"]:
"""
list(Event): List of the inherited events
"""
return [e for e in self.events if e.contract != self]

@property
def events_declared(self) -> List["Event"]:
def events_declared(self) -> List["EventContract"]:
"""
list(Event): List of the events declared within the contract (not inherited)
"""
return [e for e in self.events if e.contract == self]

@property
def events_as_dict(self) -> Dict[str, "Event"]:
def events_as_dict(self) -> Dict[str, "EventContract"]:
return self._events

# endregion
Expand Down
24 changes: 2 additions & 22 deletions slither/core/declarations/event.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
from typing import List, Tuple, TYPE_CHECKING
from typing import List, Tuple

from slither.core.declarations.contract_level import ContractLevel
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.event_variable import EventVariable

if TYPE_CHECKING:
from slither.core.declarations import Contract


class Event(ContractLevel, SourceMapping):
class Event(SourceMapping):
def __init__(self) -> None:
super().__init__()
self._name = None
Expand Down Expand Up @@ -39,25 +35,9 @@ def full_name(self) -> str:
name, parameters = self.signature
return name + "(" + ",".join(parameters) + ")"

@property
def canonical_name(self) -> str:
"""Return the function signature as a str
Returns:
str: contract.func_name(type1,type2)
"""
return self.contract.name + "." + self.full_name

@property
def elems(self) -> List["EventVariable"]:
return self._elems

def is_declared_by(self, contract: "Contract") -> bool:
"""
Check if the element is declared by the contract
:param contract:
:return:
"""
return self.contract == contract

def __str__(self) -> str:
return self.name
25 changes: 25 additions & 0 deletions slither/core/declarations/event_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from typing import TYPE_CHECKING

from slither.core.declarations.contract_level import ContractLevel
from slither.core.declarations import Event

if TYPE_CHECKING:
from slither.core.declarations import Contract


class EventContract(Event, ContractLevel):
def is_declared_by(self, contract: "Contract") -> bool:
"""
Check if the element is declared by the contract
:param contract:
:return:
"""
return self.contract == contract

@property
def canonical_name(self) -> str:
"""Return the function signature as a str
Returns:
str: contract.func_name(type1,type2)
"""
return self.contract.name + "." + self.full_name
13 changes: 13 additions & 0 deletions slither/core/declarations/event_top_level.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from typing import TYPE_CHECKING

from slither.core.declarations import Event
from slither.core.declarations.top_level import TopLevel

if TYPE_CHECKING:
from slither.core.scope.scope import FileScope


class EventTopLevel(Event, TopLevel):
def __init__(self, scope: "FileScope") -> None:
super().__init__()
self.file_scope: "FileScope" = scope
7 changes: 6 additions & 1 deletion slither/core/scope/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from slither.core.declarations import Contract, Import, Pragma
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.event_top_level import EventTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.using_for_top_level import UsingForTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel
Expand Down Expand Up @@ -35,6 +36,7 @@ def __init__(self, filename: Filename) -> None:
# So we simplify the logic and have the scope fields all populated
self.custom_errors: Set[CustomErrorTopLevel] = set()
self.enums: Dict[str, EnumTopLevel] = {}
self.events: Dict[str, EventTopLevel] = {}
# Functions is a list instead of a dict
# Because we parse the function signature later on
# So we simplify the logic and have the scope fields all populated
Expand All @@ -54,7 +56,7 @@ def __init__(self, filename: Filename) -> None:
# Name -> type alias
self.type_aliases: Dict[str, TypeAlias] = {}

def add_accesible_scopes(self) -> bool:
def add_accesible_scopes(self) -> bool: # pylint: disable=too-many-branches
"""
Add information from accessible scopes. Return true if new information was obtained
Expand All @@ -74,6 +76,9 @@ def add_accesible_scopes(self) -> bool:
if not _dict_contain(new_scope.enums, self.enums):
self.enums.update(new_scope.enums)
learn_something = True
if not _dict_contain(new_scope.events, self.events):
self.events.update(new_scope.events)
learn_something = True
if not new_scope.functions.issubset(self.functions):
self.functions |= new_scope.functions
learn_something = True
Expand Down
2 changes: 2 additions & 0 deletions slither/core/slither_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ def _compute_offsets_to_ref_impl_decl(self): # pylint: disable=too-many-branche
self._compute_offsets_from_thing(event)
for enum in compilation_unit.enums_top_level:
self._compute_offsets_from_thing(enum)
for event in compilation_unit.events_top_level:
self._compute_offsets_from_thing(event)
for function in compilation_unit.functions_top_level:
self._compute_offsets_from_thing(function)
for st in compilation_unit.structures_top_level:
Expand Down
8 changes: 4 additions & 4 deletions slither/solc_parsing/declarations/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from slither.core.declarations import (
Modifier,
Event,
EventContract,
EnumContract,
StructureContract,
Function,
Expand Down Expand Up @@ -747,12 +747,12 @@ def analyze_events(self) -> None:
self._contract.events_as_dict.update(father.events_as_dict)

for event_to_parse in self._eventsNotParsed:
event = Event()
event = EventContract()
event.set_contract(self._contract)
event.set_offset(event_to_parse["src"], self._contract.compilation_unit)

event_parser = EventSolc(event, event_to_parse, self) # type: ignore
event_parser.analyze(self) # type: ignore
event_parser = EventSolc(event, event_to_parse, self._slither_parser) # type: ignore
event_parser.analyze() # type: ignore
self._contract.events_as_dict[event.full_name] = event
except (VariableNotFound, KeyError) as e:
self.log_incorrect_parsing(f"Missing event {e}")
Expand Down
19 changes: 9 additions & 10 deletions slither/solc_parsing/declarations/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@
from slither.core.declarations.event import Event

if TYPE_CHECKING:
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc


class EventSolc:
"""
Event class
"""

def __init__(self, event: Event, event_data: Dict, contract_parser: "ContractSolc") -> None:
def __init__(
self, event: Event, event_data: Dict, slither_parser: "SlitherCompilationUnitSolc"
) -> None:

self._event = event
event.set_contract(contract_parser.underlying_contract)
self._parser_contract = contract_parser
self._slither_parser = slither_parser

if self.is_compact_ast:
self._event.name = event_data["name"]
Expand All @@ -41,18 +42,16 @@ def __init__(self, event: Event, event_data: Dict, contract_parser: "ContractSol

@property
def is_compact_ast(self) -> bool:
return self._parser_contract.is_compact_ast
return self._slither_parser.is_compact_ast

def analyze(self, contract: "ContractSolc") -> None:
def analyze(self) -> None:
for elem_to_parse in self._elemsNotParsed:
elem = EventVariable()
# Todo: check if the source offset is always here
if "src" in elem_to_parse:
elem.set_offset(
elem_to_parse["src"], self._parser_contract.underlying_contract.compilation_unit
)
elem.set_offset(elem_to_parse["src"], self._slither_parser.compilation_unit)
elem_parser = EventVariableSolc(elem, elem_to_parse)
elem_parser.analyze(contract)
elem_parser.analyze(self._slither_parser)

self._event.elems.append(elem)

Expand Down
3 changes: 3 additions & 0 deletions slither/solc_parsing/expressions/find_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ def find_top_level(
if var_name in scope.enums:
return scope.enums[var_name], False

if var_name in scope.events:
return scope.events[var_name], False

for import_directive in scope.imports:
if import_directive.alias == var_name:
new_val = SolidityImportPlaceHolder(import_directive)
Expand Down
11 changes: 11 additions & 0 deletions slither/solc_parsing/slither_compilation_unit_solc.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from slither.core.declarations import Contract
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.event_top_level import EventTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.import_directive import Import
from slither.core.declarations.pragma_directive import Pragma
Expand All @@ -23,6 +24,7 @@
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.event import EventSolc
from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc
from slither.solc_parsing.declarations.using_for_top_level import UsingForTopLevelSolc
from slither.solc_parsing.exceptions import VariableNotFound
Expand Down Expand Up @@ -347,6 +349,15 @@ def parse_top_level_items(self, data_loaded: Dict, filename: str) -> None:
self._compilation_unit.type_aliases[alias] = type_alias
scope.type_aliases[alias] = type_alias

elif top_level_data[self.get_key()] == "EventDefinition":
event = EventTopLevel(scope)
event.set_offset(top_level_data["src"], self._compilation_unit)

event_parser = EventSolc(event, top_level_data, self) # type: ignore
event_parser.analyze() # type: ignore
scope.events[event.full_name] = event
self._compilation_unit.events_top_level.append(event)

else:
raise SlitherException(f"Top level {top_level_data[self.get_key()]} not supported")

Expand Down
1 change: 1 addition & 0 deletions slither/tools/flattening/flattening.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def __init__(

self._get_source_code_top_level(compilation_unit.structures_top_level)
self._get_source_code_top_level(compilation_unit.enums_top_level)
self._get_source_code_top_level(compilation_unit.events_top_level)
self._get_source_code_top_level(compilation_unit.custom_errors)
self._get_source_code_top_level(compilation_unit.variables_top_level)
self._get_source_code_top_level(compilation_unit.functions_top_level)
Expand Down
4 changes: 2 additions & 2 deletions slither/vyper_parsing/declarations/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from slither.vyper_parsing.variables.state_variable import StateVariableVyper
from slither.vyper_parsing.declarations.function import FunctionVyper
from slither.core.declarations.function_contract import FunctionContract
from slither.core.declarations import Contract, StructureContract, EnumContract, Event
from slither.core.declarations import Contract, StructureContract, EnumContract, EventContract

from slither.core.variables.state_variable import StateVariable

Expand Down Expand Up @@ -478,7 +478,7 @@ def parse_state_variables(self) -> None:

def parse_events(self) -> None:
for event_to_parse in self._eventsNotParsed:
event = Event()
event = EventContract()
event.set_contract(self._contract)
event.set_offset(event_to_parse.src, self._contract.compilation_unit)

Expand Down
1 change: 1 addition & 0 deletions tests/e2e/solc_parsing/test_ast_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ def make_version(minor: int, patch_min: int, patch_max: int) -> List[str]:
Test("aliasing/main.sol", ["0.8.19"]),
Test("type-aliases.sol", ["0.8.19"]),
Test("enum-max-min.sol", ["0.8.19"]),
Test("event-top-level.sol", ["0.8.22"]),
]
# create the output folder if needed
try:
Expand Down
Binary file not shown.
7 changes: 7 additions & 0 deletions tests/e2e/solc_parsing/test_data/event-top-level.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
event MyEvent(uint256 a);

contract T {
function a() public {
emit MyEvent(2);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"T": {
"a()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

0 comments on commit 65fcb4b

Please sign in to comment.