-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Transaction events parser #67
Changes from all commits
2748344
a8ad731
87662b2
bea1305
4509a78
3f3ef9b
adfbe6e
bed5007
0f533b4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,12 @@ | ||
from copy import deepcopy | ||
from pathlib import Path | ||
from types import SimpleNamespace | ||
from typing import Any, Dict, List, cast | ||
|
||
from multiversx_sdk.abi.abi_definition import (AbiDefinition, | ||
EndpointDefinition, | ||
EnumDefinition, | ||
EnumDefinition, EventDefinition, | ||
EventTopicDefinition, | ||
ParameterDefinition, | ||
StructDefinition) | ||
from multiversx_sdk.abi.address_value import AddressValue | ||
|
@@ -39,6 +41,7 @@ def __init__(self, definition: AbiDefinition) -> None: | |
self.definition = definition | ||
self.custom_types_prototypes_by_name: Dict[str, Any] = {} | ||
self.endpoints_prototypes_by_name: Dict[str, EndpointPrototype] = {} | ||
self.events_prototypes_by_name: Dict[str, EventPrototype] = {} | ||
|
||
for name in definition.types.enums: | ||
self.custom_types_prototypes_by_name[name] = self._create_custom_type_prototype(name) | ||
|
@@ -67,6 +70,15 @@ def __init__(self, definition: AbiDefinition) -> None: | |
|
||
self.endpoints_prototypes_by_name[endpoint.name] = endpoint_prototype | ||
|
||
for event in definition.events: | ||
prototype = self._create_event_input_prototypes(event) | ||
|
||
event_prototype = EventPrototype( | ||
fields=prototype | ||
) | ||
|
||
self.events_prototypes_by_name[event.identifier] = event_prototype | ||
|
||
def _create_custom_type_prototype(self, name: str) -> Any: | ||
if name in self.definition.types.enums: | ||
definition = self.definition.types.enums[name] | ||
|
@@ -126,10 +138,23 @@ def _create_endpoint_output_prototypes(self, endpoint: EndpointDefinition) -> Li | |
|
||
return prototypes | ||
|
||
def _create_event_input_prototypes(self, event: EventDefinition) -> List[Any]: | ||
prototypes: List[Any] = [] | ||
|
||
for topic in event.inputs: | ||
event_field_prototype = EventField(name=topic.name, value=self._create_event_field_prototype(topic)) | ||
prototypes.append(event_field_prototype) | ||
|
||
return prototypes | ||
|
||
def _create_parameter_prototype(self, parameter: ParameterDefinition) -> Any: | ||
type_formula = self._type_formula_parser.parse_expression(parameter.type) | ||
return self._create_prototype(type_formula) | ||
|
||
def _create_event_field_prototype(self, parameter: EventTopicDefinition) -> Any: | ||
type_formula = self._type_formula_parser.parse_expression(parameter.type) | ||
return self._create_prototype(type_formula) | ||
|
||
def encode_constructor_input_parameters(self, values: List[Any]) -> List[bytes]: | ||
return self._do_encode_endpoint_input_parameters("constructor", self.constructor_prototype, values) | ||
|
||
|
@@ -163,6 +188,39 @@ def decode_endpoint_output_parameters(self, endpoint_name: str, encoded_values: | |
output_native_values = [value.get_payload() for value in output_values_as_native_object_holders] | ||
return output_native_values | ||
|
||
def decode_event(self, event_name: str, topics: List[bytes], data_items: List[bytes]) -> SimpleNamespace: | ||
result = SimpleNamespace() | ||
event_definition = self.definition.get_event_definition(event_name) | ||
event_prototype = self._get_event_prototype(event_name) | ||
|
||
indexed_inputs = [input for input in event_definition.inputs if input.indexed] | ||
indexed_inputs_names = [item.name for item in indexed_inputs] | ||
|
||
fields = deepcopy(event_prototype.fields) | ||
|
||
output_values = [field.value for field in fields if field.name in indexed_inputs_names] | ||
self._serializer.deserialize_parts(topics, output_values) | ||
|
||
output_values_as_native_object_holders = cast(List[IPayloadHolder], output_values) | ||
output_native_values = [value.get_payload() for value in output_values_as_native_object_holders] | ||
|
||
for i in range(len(indexed_inputs)): | ||
setattr(result, indexed_inputs[i].name, output_native_values[i]) | ||
|
||
non_indexed_inputs = [input for input in event_definition.inputs if not input.indexed] | ||
non_indexed_inputs_names = [item.name for item in non_indexed_inputs] | ||
|
||
output_values = [field.value for field in fields if field.name in non_indexed_inputs_names] | ||
self._serializer.deserialize_parts(data_items, output_values) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed. AFAIK, non indexed fields are matched with the event data items (not topics). |
||
|
||
output_values_as_native_object_holders = cast(List[IPayloadHolder], output_values) | ||
output_native_values = [value.get_payload() for value in output_values_as_native_object_holders] | ||
|
||
for i in range(len(non_indexed_inputs)): | ||
setattr(result, non_indexed_inputs[i].name, output_native_values[i]) | ||
|
||
return result | ||
|
||
def _get_custom_type_prototype(self, type_name: str) -> Any: | ||
type_prototype = self.custom_types_prototypes_by_name.get(type_name) | ||
|
||
|
@@ -179,6 +237,14 @@ def _get_endpoint_prototype(self, endpoint_name: str) -> 'EndpointPrototype': | |
|
||
return endpoint_prototype | ||
|
||
def _get_event_prototype(self, event_name: str) -> 'EventPrototype': | ||
event_prototype = self.events_prototypes_by_name.get(event_name) | ||
|
||
if not event_prototype: | ||
raise ValueError(f"event '{event_name}' not found") | ||
|
||
return event_prototype | ||
|
||
def _create_prototype(self, type_formula: TypeFormula) -> Any: | ||
name = type_formula.name | ||
|
||
|
@@ -248,3 +314,14 @@ class EndpointPrototype: | |
def __init__(self, input_parameters: List[Any], output_parameters: List[Any]) -> None: | ||
self.input_parameters = input_parameters | ||
self.output_parameters = output_parameters | ||
|
||
|
||
class EventField: | ||
def __init__(self, name: str, value: Any) -> None: | ||
self.name = name | ||
self.value = value | ||
|
||
|
||
class EventPrototype: | ||
def __init__(self, fields: List[EventField]) -> None: | ||
self.fields = fields |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
from types import SimpleNamespace | ||
from typing import List, Protocol | ||
|
||
from multiversx_sdk.core.transactions_outcome_parsers.resources import \ | ||
TransactionEvent | ||
|
||
|
||
class IAbi(Protocol): | ||
def decode_event(self, event_name: str, topics: List[bytes], data_items: List[bytes]) -> SimpleNamespace: | ||
... | ||
|
||
|
||
class TransactionEventsParser: | ||
def __init__(self, abi: IAbi, first_topic_as_identifier: bool = True) -> None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
self.abi = abi | ||
|
||
# By default, we consider that the first topic is the event identifier. | ||
# This is true for log entries emitted by smart contracts: | ||
# https://github.com/multiversx/mx-chain-vm-go/blob/v1.5.27/vmhost/contexts/output.go#L270 | ||
# https://github.com/multiversx/mx-chain-vm-go/blob/v1.5.27/vmhost/contexts/output.go#L283 | ||
self.first_topic_as_identifier = first_topic_as_identifier | ||
|
||
def parse_events(self, events: List[TransactionEvent]) -> List[SimpleNamespace]: | ||
return [self.parse_event(event) for event in events] | ||
|
||
def parse_event(self, event: TransactionEvent) -> SimpleNamespace: | ||
first_topic = event.topics[0].decode() if len(event.topics) else "" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if it's possible but... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i am not sure this is possible, but I've added the extra check. |
||
abi_identifier = first_topic if first_topic and self.first_topic_as_identifier else event.identifier | ||
|
||
topics = event.topics | ||
|
||
if self.first_topic_as_identifier: | ||
topics = topics[1:] | ||
|
||
return self.abi.decode_event( | ||
event_name=abi_identifier, | ||
topics=topics, | ||
data_items=event.data_items, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for field in event.fields
(and those who areindexed = true
will be matched to topics in the end, yes).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can't use
event.fields
, this isEventDefiniton
notEventPrototype
. Or perhaps did I understand something wrong?