Skip to content

Commit

Permalink
Refactor ABI processing, fix filtering events by signature (#1080)
Browse files Browse the repository at this point in the history
  • Loading branch information
droserasprout authored Aug 5, 2024
1 parent a63070e commit 7e34379
Show file tree
Hide file tree
Showing 21 changed files with 552 additions and 347 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic

- cli: Fixed progress estimation when there are indexes with `last_level` option set.
- cli: Don't save reports for successful test runs.
- evm: Fixed crash when contract ABI contains overloaded methods.
- tezos.operations: Fixed `sr_cement` operation index subscription.

### Changed

- config: When filtering EVM transactions by signature, use `signature` field instead of `method`.
- ctx: Signatures of `fire_handler` and `fire_hook` methods have changed.
- ctx: `ctx.logger` is a regular `logging.Logger` instead of pre-configured `FormattedLogger`.

Expand Down
3 changes: 2 additions & 1 deletion docs/7.references/2.config.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ description: "Config file reference"

## dipdup.config.evm_transactions.EvmTransactionsHandlerConfig

<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">dipdup.config.evm_transactions.</span></span><span class="sig-name descname"><span class="pre">EvmTransactionsHandlerConfig</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">from_</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">to</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">method</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span></dt>
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">dipdup.config.evm_transactions.</span></span><span class="sig-name descname"><span class="pre">EvmTransactionsHandlerConfig</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">from_</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">to</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">method</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">signature</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span></dt>
<dd><p>Subsquid transaction handler</p>
<dl class="field-list simple">
<dt class="field-odd" style="color: var(--txt-primary);">Parameters<span class="colon">:</span></dt>
Expand All @@ -286,6 +286,7 @@ description: "Config file reference"
<li><p><strong>from</strong> – Transaction sender</p></li>
<li><p><strong>to</strong> (<em>str</em><em> | </em><a class="reference internal" href="#dipdupconfigevmevmcontractconfig" title="dipdup.config.evm.EvmContractConfig" target="_self"><em>EvmContractConfig</em></a><em> | </em><em>None</em>) – Transaction receiver</p></li>
<li><p><strong>method</strong> (<em>str</em><em> | </em><em>None</em>) – Method name</p></li>
<li><p><strong>signature</strong> (<em>str</em><em> | </em><em>None</em>) – Method signature</p></li>

<li><p><strong>from_</strong> (<em>str</em><em> | </em><a class="reference internal" href="#dipdupconfigevmevmcontractconfig" title="dipdup.config.evm.EvmContractConfig" target="_self"><em>EvmContractConfig</em></a><em> | </em><em>None</em>)</p></li>
</ul>
Expand Down
2 changes: 2 additions & 0 deletions docs/9.release-notes/_8.0_changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
- evm.events: Fixed matching logs when filtering by topic0.
- evm.events: Improve fetching event batches from node.
- evm.subsquid: Fixed typo in `iter_events` method name.
- evm: Fixed crash when contract ABI contains overloaded methods.
- models: Fixed `CachedModel` preloading.
- models: Fixed setting default value for `Meta.maxsize`.
- performance: Fixed estimation indexing speed in levels per second.
Expand All @@ -45,6 +46,7 @@
- config: Index configs accept `datasources` list instead of `datasource` field.
- config: Index kinds have been renamed and grouped by the network.
- config: Index template values now can be any JSON-serializable object.
- config: When filtering EVM transactions by signature, use `signature` field instead of `method`.
- ctx: Signatures of `fire_handler` and `fire_hook` methods have changed.
- ctx: `ctx.logger` is a regular `logging.Logger` instead of pre-configured `FormattedLogger`.
- deps: Python 3.12 is now required to run DipDup.
Expand Down
13 changes: 13 additions & 0 deletions schemas/dipdup-3.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,19 @@
"title": "method",
"description": "Method name"
},
"signature": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "signature",
"description": "Method signature"
},
"from": {
"anyOf": [
{
Expand Down
16 changes: 16 additions & 0 deletions src/dipdup/abi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from __future__ import annotations

from abc import ABC
from abc import abstractmethod
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from dipdup.package import DipDupPackage


class AbiManager(ABC):
def __init__(self, package: DipDupPackage) -> None:
self._package = package

@abstractmethod
def load(self) -> None: ...
144 changes: 144 additions & 0 deletions src/dipdup/abi/cairo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
from __future__ import annotations

from functools import cache
from typing import TYPE_CHECKING
from typing import Any
from typing import TypedDict

import orjson

from dipdup.abi import AbiManager
from dipdup.exceptions import FrameworkException
from dipdup.utils import json_dumps
from dipdup.utils import touch

if TYPE_CHECKING:
from starknet_py.abi.v2 import Abi # type: ignore[import-untyped]
from starknet_py.cairo.data_types import CairoType # type: ignore[import-untyped]
from starknet_py.cairo.data_types import EventType
from starknet_py.serialization import PayloadSerializer # type: ignore[import-untyped]

from dipdup.package import DipDupPackage


class CairoEventAbi(TypedDict):
name: str
event_identifier: str
members: dict[str, CairoType]
serializer: PayloadSerializer


class CairoAbi(TypedDict):
events: list[CairoEventAbi]


def _convert_type(type_: CairoType) -> str:
# TODO: Support all types
return {
'FeltType': 'integer',
'UintType': 'integer',
'BoolType': 'boolean',
}[type.__class__.__name__]


def _jsonschema_from_event(event: EventType) -> dict[str, Any]:
# TODO: Unpack nested types (starknet.py could do that)
return {
'$schema': 'http://json-schema.org/draft/2019-09/schema#',
'type': 'object',
'properties': {key: {'type': _convert_type(value)} for key, value in event.types.items()},
'required': tuple(event.types.keys()),
'additionalProperties': False,
}


def sn_keccak(x: str) -> str:
from Crypto.Hash import keccak

# NOTE: Create keccak256 hash in bytes and return hex representation of the first 250 bits.
keccak_hash = keccak.new(data=x.encode('ascii'), digest_bits=256).digest()
return f'0x{int.from_bytes(keccak_hash, "big") & (1 << 248) - 1:x}'


@cache
def _loaded_abis(package: DipDupPackage) -> dict[str, Abi]:

from starknet_py.abi.v2 import AbiParser
from starknet_py.abi.v2 import AbiParsingError

result = {}
for abi_path in package.cairo_abi_paths:
abi = orjson.loads(abi_path.read_bytes())

try:
parsed_abi = AbiParser(abi).parse()
except AbiParsingError as e:
raise e

parsed_abi.events = {k.split('::')[-1]: v for k, v in parsed_abi.events.items()}
result[abi_path.parent.stem] = parsed_abi
return result


def convert_abi(package: DipDupPackage) -> dict[str, CairoAbi]:
from starknet_py.serialization import serializer_for_event

abi_by_typename: dict[str, CairoAbi] = {}

for contract_typename, parsed_abi in _loaded_abis(package).items():
converted_abi: CairoAbi = {
'events': [],
}

for name, event_type in parsed_abi.events.items():
if name in converted_abi['events']:
raise NotImplementedError('Multiple events with the same name are not supported')
converted_abi['events'].append(
CairoEventAbi(
name=name,
event_identifier=sn_keccak(name),
members=event_type.types,
serializer=serializer_for_event(event_type),
)
)
abi_by_typename[contract_typename] = converted_abi

return abi_by_typename


def abi_to_jsonschemas(
package: DipDupPackage,
events: set[str],
) -> None:
for contract_typename, parsed_abi in _loaded_abis(package).items():
for event_name in events:
if event_name not in parsed_abi.events:
continue

schema = _jsonschema_from_event(parsed_abi.events[event_name])
schema_path = package.schemas / contract_typename / 'starknet_events' / f'{event_name}.json'
touch(schema_path)
schema_path.write_bytes(json_dumps(schema))


class CairoAbiManager(AbiManager):
def __init__(self, package: DipDupPackage) -> None:
super().__init__(package)
self._abis: dict[str, CairoAbi] = {}
self.get_event_abi = cache(self.get_event_abi) # type: ignore[method-assign]

def load(self) -> None:
self._abis = convert_abi(self._package)

def get_event_abi(
self,
typename: str,
name: str,
) -> CairoEventAbi:
typename_abi = self._abis[typename]
for event_abi in typename_abi['events']:
if name != event_abi['name']:
continue
return event_abi

raise FrameworkException(f'Event `{name}` not found in `{typename}`')
Loading

0 comments on commit 7e34379

Please sign in to comment.