Source code for evmos.eip712.base

from __future__ import annotations

from collections.abc import Mapping, Sequence
from dataclasses import asdict, dataclass
from typing import TYPE_CHECKING, Any, TypedDict

from eth_typing import ChecksumAddress, HexAddress
from eth_utils import keccak

from evmos.utils.eip_712_hash import encode_data

if TYPE_CHECKING:
    from evmos.utils.eip_712_hash import _FieldDef


[docs] class WithValidator(TypedDict): """:class:`~typing.TypedDict` with string ``validator_address`` field.""" validator_address: str """Validator address."""
[docs] class MsgWithValidatorInterface(TypedDict): """Validator editing message.""" type: str """Type for ABI encoding.""" value: WithValidator """Message itself."""
[docs] class MsgInterface(TypedDict): """Validator editing message.""" type: str """Type for ABI encoding.""" value: Any """Message itself."""
[docs] @dataclass class Domain: """This describes ``domain`` field of :class:`EIPToSign`.""" name: str | None = None """Domain name.""" version: str | None = None """Version (usually ``1.0.0``).""" chainId: int | None = None # noqa: N815 """Chain ID.""" verifyingContract: HexAddress | ChecksumAddress | None = None # noqa: N815 """Verifying contract address.""" salt: str | None = None """Used salt (usually ``'0'``)."""
[docs] def pick_types(self) -> list[_FieldDef]: known_fields: list[_FieldDef] = [ {"name": "name", "type": "string"}, {"name": "version", "type": "string"}, {"name": "chainId", "type": "uint256"}, {"name": "verifyingContract", "type": "address"}, {"name": "salt", "type": "bytes32"}, ] return [f for f in known_fields if getattr(self, f["name"]) is not None]
[docs] def hash(self) -> bytes: return keccak( encode_data( "EIP712Domain", {"EIP712Domain": self.pick_types()}, asdict(self) ) )
[docs] class EIP712Signatures(TypedDict): domain: bytes message: bytes
[docs] @dataclass class EIPToSign: """EIP message to sign.""" types: dict[str, list[_FieldDef]] """Message types for ABI encoding, mapping name to type.""" primaryType: str # noqa: N815 """Type name of ``message`` attribute.""" domain: Domain """Domain.""" message: dict[str, Any] """Message to sign itself, mapping."""
[docs] def hash(self) -> EIP712Signatures: """Return the hashed V4 EIP-712 domain and struct objects to be signed. Throws on errors. """ try: return { "domain": self.domain.hash(), "message": self.hash_message(), } except (ValueError, TypeError, KeyError, IndexError) as e: raise ValueError("Could not hash EIP-712 object") from e
[docs] def hash_message(self: EIPToSign) -> bytes: """Hash message part of a EIP-712 message.""" return keccak( encode_data( self.primaryType, self.types, self.message, ) )
[docs] def create_eip712( types: dict[str, Any], chain_id: int, message: dict[str, Any] ) -> EIPToSign: """Create `EIP712 <https://eips.ethereum.org/EIPS/eip-712>`_ data.""" domain = Domain(chainId=chain_id) return EIPToSign( types=types | {"EIP712Domain": domain.pick_types()}, primaryType="Tx", domain=domain, message=message, )
[docs] def generate_message_with_multiple_transactions( account_number: str, sequence: str, chain_cosmos_id: str, memo: str, fee: dict[str, Any], msgs: Sequence[Mapping[str, Any]], ) -> dict[str, Any]: """Create a message with multiple transactions included.""" return { "account_number": account_number, "chain_id": chain_cosmos_id, "fee": fee, "memo": memo, "msgs": msgs, "sequence": sequence, }
[docs] def generate_message( account_number: str, sequence: str, chain_cosmos_id: str, memo: str, fee: dict[str, Any], msg: Mapping[str, Any], ) -> dict[str, Any]: """Create a message with one transaction included.""" return generate_message_with_multiple_transactions( account_number, sequence, chain_cosmos_id, memo, fee, [msg], )
[docs] def generate_types(msg_values: dict[str, Any]) -> dict[str, Any]: """Generate EIP-712 types.""" types = { "Tx": [ {"name": "account_number", "type": "string"}, {"name": "chain_id", "type": "string"}, {"name": "fee", "type": "Fee"}, {"name": "memo", "type": "string"}, {"name": "msgs", "type": "Msg[]"}, {"name": "sequence", "type": "string"}, ], "Fee": [ {"name": "feePayer", "type": "string"}, {"name": "amount", "type": "Coin[]"}, {"name": "gas", "type": "string"}, ], "Coin": [ {"name": "denom", "type": "string"}, {"name": "amount", "type": "string"}, ], "Msg": [ {"name": "type", "type": "string"}, {"name": "value", "type": "MsgValue"}, ], } types.update(msg_values) return types
[docs] def generate_fee(amount: str, denom: str, gas: str, fee_payer: str) -> dict[str, Any]: """Generate fee definition structure.""" return { "amount": [{"amount": amount, "denom": denom}], "gas": gas, "feePayer": fee_payer, }