Merge pull request #2219 from crytic/dev-tp-events

Add support top level events
pull/2311/head
alpharush 9 months ago committed by GitHub
commit 65fcb4bf58
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      slither/core/compilation_unit.py
  2. 2
      slither/core/declarations/__init__.py
  3. 12
      slither/core/declarations/contract.py
  4. 24
      slither/core/declarations/event.py
  5. 25
      slither/core/declarations/event_contract.py
  6. 13
      slither/core/declarations/event_top_level.py
  7. 7
      slither/core/scope/scope.py
  8. 2
      slither/core/slither_core.py
  9. 8
      slither/solc_parsing/declarations/contract.py
  10. 19
      slither/solc_parsing/declarations/event.py
  11. 3
      slither/solc_parsing/expressions/find_variable.py
  12. 11
      slither/solc_parsing/slither_compilation_unit_solc.py
  13. 1
      slither/tools/flattening/flattening.py
  14. 4
      slither/vyper_parsing/declarations/contract.py
  15. 1
      tests/e2e/solc_parsing/test_ast_parsing.py
  16. BIN
      tests/e2e/solc_parsing/test_data/compile/event-top-level.sol-0.8.22-compact.zip
  17. 7
      tests/e2e/solc_parsing/test_data/event-top-level.sol
  18. 5
      tests/e2e/solc_parsing/test_data/expected/event-top-level.sol-0.8.22-compact.json

@ -16,6 +16,7 @@ from slither.core.declarations import (
) )
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel 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.function_top_level import FunctionTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel from slither.core.declarations.structure_top_level import StructureTopLevel
from slither.core.declarations.using_for_top_level import UsingForTopLevel from slither.core.declarations.using_for_top_level import UsingForTopLevel
@ -57,6 +58,7 @@ class SlitherCompilationUnit(Context):
self.contracts: List[Contract] = [] self.contracts: List[Contract] = []
self._structures_top_level: List[StructureTopLevel] = [] self._structures_top_level: List[StructureTopLevel] = []
self._enums_top_level: List[EnumTopLevel] = [] self._enums_top_level: List[EnumTopLevel] = []
self._events_top_level: List[EventTopLevel] = []
self._variables_top_level: List[TopLevelVariable] = [] self._variables_top_level: List[TopLevelVariable] = []
self._functions_top_level: List[FunctionTopLevel] = [] self._functions_top_level: List[FunctionTopLevel] = []
self._using_for_top_level: List[UsingForTopLevel] = [] self._using_for_top_level: List[UsingForTopLevel] = []
@ -234,6 +236,10 @@ class SlitherCompilationUnit(Context):
def enums_top_level(self) -> List[EnumTopLevel]: def enums_top_level(self) -> List[EnumTopLevel]:
return self._enums_top_level return self._enums_top_level
@property
def events_top_level(self) -> List[EventTopLevel]:
return self._events_top_level
@property @property
def variables_top_level(self) -> List[TopLevelVariable]: def variables_top_level(self) -> List[TopLevelVariable]:
return self._variables_top_level return self._variables_top_level

@ -1,6 +1,8 @@
from .contract import Contract from .contract import Contract
from .enum import Enum from .enum import Enum
from .event import Event from .event import Event
from .event_contract import EventContract
from .event_top_level import EventTopLevel
from .function import Function from .function import Function
from .import_directive import Import from .import_directive import Import
from .modifier import Modifier from .modifier import Modifier

@ -33,7 +33,7 @@ if TYPE_CHECKING:
from slither.utils.type_helpers import LibraryCallType, HighLevelCallType, InternalCallType from slither.utils.type_helpers import LibraryCallType, HighLevelCallType, InternalCallType
from slither.core.declarations import ( from slither.core.declarations import (
Enum, Enum,
Event, EventContract,
Modifier, Modifier,
EnumContract, EnumContract,
StructureContract, StructureContract,
@ -73,7 +73,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._enums: Dict[str, "EnumContract"] = {} self._enums: Dict[str, "EnumContract"] = {}
self._structures: Dict[str, "StructureContract"] = {} self._structures: Dict[str, "StructureContract"] = {}
self._events: Dict[str, "Event"] = {} self._events: Dict[str, "EventContract"] = {}
# map accessible variable from name -> variable # map accessible variable from name -> variable
# do not contain private variables inherited from contract # do not contain private variables inherited from contract
self._variables: Dict[str, "StateVariable"] = {} self._variables: Dict[str, "StateVariable"] = {}
@ -278,28 +278,28 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
################################################################################### ###################################################################################
@property @property
def events(self) -> List["Event"]: def events(self) -> List["EventContract"]:
""" """
list(Event): List of the events list(Event): List of the events
""" """
return list(self._events.values()) return list(self._events.values())
@property @property
def events_inherited(self) -> List["Event"]: def events_inherited(self) -> List["EventContract"]:
""" """
list(Event): List of the inherited events list(Event): List of the inherited events
""" """
return [e for e in self.events if e.contract != self] return [e for e in self.events if e.contract != self]
@property @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) list(Event): List of the events declared within the contract (not inherited)
""" """
return [e for e in self.events if e.contract == self] return [e for e in self.events if e.contract == self]
@property @property
def events_as_dict(self) -> Dict[str, "Event"]: def events_as_dict(self) -> Dict[str, "EventContract"]:
return self._events return self._events
# endregion # endregion

@ -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.source_mapping.source_mapping import SourceMapping
from slither.core.variables.event_variable import EventVariable from slither.core.variables.event_variable import EventVariable
if TYPE_CHECKING:
from slither.core.declarations import Contract
class Event(SourceMapping):
class Event(ContractLevel, SourceMapping):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self._name = None self._name = None
@ -39,25 +35,9 @@ class Event(ContractLevel, SourceMapping):
name, parameters = self.signature name, parameters = self.signature
return name + "(" + ",".join(parameters) + ")" 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 @property
def elems(self) -> List["EventVariable"]: def elems(self) -> List["EventVariable"]:
return self._elems 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: def __str__(self) -> str:
return self.name return self.name

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

@ -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,6 +7,7 @@ from crytic_compile.utils.naming import Filename
from slither.core.declarations import Contract, Import, Pragma from slither.core.declarations import Contract, Import, Pragma
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel 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.function_top_level import FunctionTopLevel
from slither.core.declarations.using_for_top_level import UsingForTopLevel from slither.core.declarations.using_for_top_level import UsingForTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel from slither.core.declarations.structure_top_level import StructureTopLevel
@ -35,6 +36,7 @@ class FileScope:
# So we simplify the logic and have the scope fields all populated # So we simplify the logic and have the scope fields all populated
self.custom_errors: Set[CustomErrorTopLevel] = set() self.custom_errors: Set[CustomErrorTopLevel] = set()
self.enums: Dict[str, EnumTopLevel] = {} self.enums: Dict[str, EnumTopLevel] = {}
self.events: Dict[str, EventTopLevel] = {}
# Functions is a list instead of a dict # Functions is a list instead of a dict
# Because we parse the function signature later on # Because we parse the function signature later on
# So we simplify the logic and have the scope fields all populated # So we simplify the logic and have the scope fields all populated
@ -54,7 +56,7 @@ class FileScope:
# Name -> type alias # Name -> type alias
self.type_aliases: Dict[str, TypeAlias] = {} 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 Add information from accessible scopes. Return true if new information was obtained
@ -74,6 +76,9 @@ class FileScope:
if not _dict_contain(new_scope.enums, self.enums): if not _dict_contain(new_scope.enums, self.enums):
self.enums.update(new_scope.enums) self.enums.update(new_scope.enums)
learn_something = True 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): if not new_scope.functions.issubset(self.functions):
self.functions |= new_scope.functions self.functions |= new_scope.functions
learn_something = True learn_something = True

@ -269,6 +269,8 @@ class SlitherCore(Context):
self._compute_offsets_from_thing(event) self._compute_offsets_from_thing(event)
for enum in compilation_unit.enums_top_level: for enum in compilation_unit.enums_top_level:
self._compute_offsets_from_thing(enum) 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: for function in compilation_unit.functions_top_level:
self._compute_offsets_from_thing(function) self._compute_offsets_from_thing(function)
for st in compilation_unit.structures_top_level: for st in compilation_unit.structures_top_level:

@ -4,7 +4,7 @@ from typing import Any, List, Dict, Callable, TYPE_CHECKING, Union, Set, Sequenc
from slither.core.declarations import ( from slither.core.declarations import (
Modifier, Modifier,
Event, EventContract,
EnumContract, EnumContract,
StructureContract, StructureContract,
Function, Function,
@ -747,12 +747,12 @@ class ContractSolc(CallerContextExpression):
self._contract.events_as_dict.update(father.events_as_dict) self._contract.events_as_dict.update(father.events_as_dict)
for event_to_parse in self._eventsNotParsed: for event_to_parse in self._eventsNotParsed:
event = Event() event = EventContract()
event.set_contract(self._contract) event.set_contract(self._contract)
event.set_offset(event_to_parse["src"], self._contract.compilation_unit) event.set_offset(event_to_parse["src"], self._contract.compilation_unit)
event_parser = EventSolc(event, event_to_parse, self) # type: ignore event_parser = EventSolc(event, event_to_parse, self._slither_parser) # type: ignore
event_parser.analyze(self) # type: ignore event_parser.analyze() # type: ignore
self._contract.events_as_dict[event.full_name] = event self._contract.events_as_dict[event.full_name] = event
except (VariableNotFound, KeyError) as e: except (VariableNotFound, KeyError) as e:
self.log_incorrect_parsing(f"Missing event {e}") self.log_incorrect_parsing(f"Missing event {e}")

@ -8,7 +8,7 @@ from slither.solc_parsing.variables.event_variable import EventVariableSolc
from slither.core.declarations.event import Event from slither.core.declarations.event import Event
if TYPE_CHECKING: if TYPE_CHECKING:
from slither.solc_parsing.declarations.contract import ContractSolc from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
class EventSolc: class EventSolc:
@ -16,11 +16,12 @@ class EventSolc:
Event class 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 self._event = event
event.set_contract(contract_parser.underlying_contract) self._slither_parser = slither_parser
self._parser_contract = contract_parser
if self.is_compact_ast: if self.is_compact_ast:
self._event.name = event_data["name"] self._event.name = event_data["name"]
@ -41,18 +42,16 @@ class EventSolc:
@property @property
def is_compact_ast(self) -> bool: 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: for elem_to_parse in self._elemsNotParsed:
elem = EventVariable() elem = EventVariable()
# Todo: check if the source offset is always here # Todo: check if the source offset is always here
if "src" in elem_to_parse: if "src" in elem_to_parse:
elem.set_offset( elem.set_offset(elem_to_parse["src"], self._slither_parser.compilation_unit)
elem_to_parse["src"], self._parser_contract.underlying_contract.compilation_unit
)
elem_parser = EventVariableSolc(elem, elem_to_parse) elem_parser = EventVariableSolc(elem, elem_to_parse)
elem_parser.analyze(contract) elem_parser.analyze(self._slither_parser)
self._event.elems.append(elem) self._event.elems.append(elem)

@ -134,6 +134,9 @@ def find_top_level(
if var_name in scope.enums: if var_name in scope.enums:
return scope.enums[var_name], False return scope.enums[var_name], False
if var_name in scope.events:
return scope.events[var_name], False
for import_directive in scope.imports: for import_directive in scope.imports:
if import_directive.alias == var_name: if import_directive.alias == var_name:
new_val = SolidityImportPlaceHolder(import_directive) new_val = SolidityImportPlaceHolder(import_directive)

@ -10,6 +10,7 @@ from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.declarations import Contract from slither.core.declarations import Contract
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel 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.function_top_level import FunctionTopLevel
from slither.core.declarations.import_directive import Import from slither.core.declarations.import_directive import Import
from slither.core.declarations.pragma_directive import Pragma from slither.core.declarations.pragma_directive import Pragma
@ -23,6 +24,7 @@ from slither.solc_parsing.declarations.caller_context import CallerContextExpres
from slither.solc_parsing.declarations.contract import ContractSolc from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
from slither.solc_parsing.declarations.function import FunctionSolc 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.structure_top_level import StructureTopLevelSolc
from slither.solc_parsing.declarations.using_for_top_level import UsingForTopLevelSolc from slither.solc_parsing.declarations.using_for_top_level import UsingForTopLevelSolc
from slither.solc_parsing.exceptions import VariableNotFound from slither.solc_parsing.exceptions import VariableNotFound
@ -347,6 +349,15 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
self._compilation_unit.type_aliases[alias] = type_alias self._compilation_unit.type_aliases[alias] = type_alias
scope.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: else:
raise SlitherException(f"Top level {top_level_data[self.get_key()]} not supported") raise SlitherException(f"Top level {top_level_data[self.get_key()]} not supported")

@ -77,6 +77,7 @@ class Flattening:
self._get_source_code_top_level(compilation_unit.structures_top_level) 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.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.custom_errors)
self._get_source_code_top_level(compilation_unit.variables_top_level) self._get_source_code_top_level(compilation_unit.variables_top_level)
self._get_source_code_top_level(compilation_unit.functions_top_level) self._get_source_code_top_level(compilation_unit.functions_top_level)

@ -24,7 +24,7 @@ from slither.vyper_parsing.declarations.struct import StructVyper
from slither.vyper_parsing.variables.state_variable import StateVariableVyper from slither.vyper_parsing.variables.state_variable import StateVariableVyper
from slither.vyper_parsing.declarations.function import FunctionVyper from slither.vyper_parsing.declarations.function import FunctionVyper
from slither.core.declarations.function_contract import FunctionContract 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 from slither.core.variables.state_variable import StateVariable
@ -478,7 +478,7 @@ class ContractVyper: # pylint: disable=too-many-instance-attributes
def parse_events(self) -> None: def parse_events(self) -> None:
for event_to_parse in self._eventsNotParsed: for event_to_parse in self._eventsNotParsed:
event = Event() event = EventContract()
event.set_contract(self._contract) event.set_contract(self._contract)
event.set_offset(event_to_parse.src, self._contract.compilation_unit) event.set_offset(event_to_parse.src, self._contract.compilation_unit)

@ -463,6 +463,7 @@ ALL_TESTS = [
Test("aliasing/main.sol", ["0.8.19"]), Test("aliasing/main.sol", ["0.8.19"]),
Test("type-aliases.sol", ["0.8.19"]), Test("type-aliases.sol", ["0.8.19"]),
Test("enum-max-min.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 # create the output folder if needed
try: try:

@ -0,0 +1,7 @@
event MyEvent(uint256 a);
contract T {
function a() public {
emit MyEvent(2);
}
}

@ -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"
}
}
Loading…
Cancel
Save