Source code for genlayer.chain

__all__ = ('ON', 'IAccount', 'Account', 'Event', 'id')

from abc import ABCMeta, abstractmethod
import typing

from genlayer.types import Address, u256
import genlayer.calldata as calldata
import genlayer._internal.on_chain.gl_call as gl_call
from genlayer import IS_IN_VM

type ON = typing.Literal['accepted', 'finalized']
"""When the transaction message should be applied: ``'accepted'`` or ``'finalized'``"""


[docs] @typing.runtime_checkable class IAccount(typing.Protocol): """ Protocol defining the interface for on-chain accounts. Be that a contract or an EoA. """ @property def address(self) -> Address: """ Return the address of this account. """ ... @property def balance(self) -> u256: """ Returns current balance of this account. """ ...
[docs] def emit_transfer(self, value: u256, *, on: ON = 'finalized') -> None: """ Emit a transfer message to this account's address. :param value: amount to transfer :param on: transaction stage at which the transfer is applied """ ...
[docs] class Account(IAccount): """ Class for on-chain accounts. """ __slots__ = ('_address',)
[docs] def __init__(self, address: Address, /): """ Get account at given address. Can be used to emit transfer messages to any address, even if there is no contract deployed at it. :param address: target account address :returns: account instance for the given address """ self._address = address
@property def address(self) -> Address: """ Return the address of this account. """ return self._address @property def balance(self) -> u256: """ Return current balance of this account. """ from genlayer.contract import get_at return get_at(self.address).balance
[docs] def emit_transfer(self, value: u256, *, on: ON = 'finalized') -> None: """ Emit a transfer message to this account's address. :param value: amount to transfer :param on: transaction stage at which the transfer is applied """ from genlayer.contract import get_at get_at(self.address).emit_transfer(value, on=on)
from genlayer.types.keccak import Keccak256 from .vm import public_abi as ABI from genlayer._internal import reflect import inspect
[docs] class Event: # editorconfig-checker-disable """ .. code-block:: python class TransferOccurredEvent(gl.Event): def __init__(self, sender: Address, to: Address, /): ... class TransferOccurredEvent(gl.Event): def __init__(self, sender: Address, to: Address, /, **blob): ... """ # editorconfig-checker-enable
[docs] def __init__(self): """ Base class cannot be instantiated directly, it is only used for inheritance """ raise NotImplementedError()
signature: str """ Event signature (built-in/main topic). Consists of name and indexed fields in parenthesis, **sorted** Example: ``TransferOccurredEvent(from,to)`` """ name: str """ Event name. If not overridden it will be set to ``__name__`` """ indexed: tuple[str, ...] """ tuple of indexed arguments name in **sorted** order """ _blob: dict[str, calldata.Encodable] __slots__ = ('_blob',)
[docs] @staticmethod def emit_raw( topics: list[bytes], blob: calldata.Encodable, /, ) -> None: """ Emit a raw event with the given topics and blob of data. :param topics: list of topic byte strings (first is typically the event signature hash) :param blob: calldata-encodable payload """ gl_call.gl_call_generic( { 'EmitEvent': { 'topics': topics, 'blob': blob, } }, lambda _x: None, ).get()
@staticmethod def _do_init(klass) -> None: old_init = klass.__init__ assert old_init is not Event.__init__ klass.__slots__ = ('_blob',) sig = inspect.signature(old_init) indexed_args_lst: list[str] = [] event_name = getattr(klass, 'name', klass.__name__) for i, (name, param) in enumerate(sig.parameters.items()): with reflect.context_notes(f'parameter `{name}`'): if i == 0: if name != 'self': raise TypeError('first argument must be `self`') continue match param.kind: case inspect.Parameter.VAR_POSITIONAL: raise TypeError('`*args` is forbidden') case inspect.Parameter.KEYWORD_ONLY: raise TypeError('keyword-only arguments are forbidden') case inspect.Parameter.POSITIONAL_OR_KEYWORD: raise TypeError('specify `/` after indexed fields') case inspect.Parameter.VAR_KEYWORD: pass case inspect.Parameter.POSITIONAL_ONLY: indexed_args_lst.append(name) indexed_args = tuple(sorted(indexed_args_lst)) def __init__(self, *args, **kwargs): if len(args) != len(indexed_args): raise TypeError( f'indexed fields mismatch, expected {indexed_args}, but got {len(args)} positional arguments' ) for name, val in zip(indexed_args, args): if name in kwargs: raise TypeError(f'indexed field `{name}` must not be present in blob') kwargs[name] = val self._blob = kwargs signature = event_name signature += '(' signature += ','.join(indexed_args) signature += ')' if len(indexed_args) > ABI.EVENT_MAX_TOPICS: import warnings warnings.warn( f'event has too many indexed fields, it may not be emitted correctly: `{signature}`' ) klass.name = event_name klass.signature = signature klass.indexed = indexed_args klass.__init__ = __init__ def __init_subclass__(cls) -> None: with reflect.context_notes('generating event class'): with reflect.context_type(cls): Event._do_init(cls)
[docs] def emit(self) -> None: """ Emit this event. """ topics = [Keccak256(self.signature.encode('utf-8')).digest()] for i in self.indexed: d = self._blob[i] as_cd = calldata.encode(d) if len(as_cd) > 32: as_cd = Keccak256(as_cd).digest() else: as_cd = as_cd + b'\x00' * (32 - len(as_cd)) topics.append(as_cd) Event.emit_raw(topics, self._blob)
if typing.TYPE_CHECKING or not IS_IN_VM: id: u256 = ... # type: ignore """ Chain ID. It is the same as in the message """ else: from genlayer.message import chain_id as id