Skip to content
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

refactor: split model in input/resolved #40

Merged
merged 9 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/erc7730/common/properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from typing import Any


def has_property(target: Any, name: str) -> bool:
"""
Check if the target has a property with the given name.

:param target: object of dict like
:param name: attribute name
:return: true if the target has the property
"""
if isinstance(target, dict):
return name in target
return hasattr(target, name)
25 changes: 11 additions & 14 deletions src/erc7730/convert/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
from abc import ABC, abstractmethod
from collections.abc import Callable
from enum import IntEnum, auto
from typing import Generic, TypeVar

from pydantic import BaseModel

from erc7730.model.descriptor import ERC7730Descriptor

InputType = TypeVar("InputType", bound=BaseModel)
OutputType = TypeVar("OutputType", bound=BaseModel)

Expand Down Expand Up @@ -39,22 +36,22 @@ class Error(BaseModel):
class Level(IntEnum):
"""ERC7730Converter error level."""

ERROR = auto()
WARNING = auto()
"""Indicates a non-fatal error: descriptor can be partially converted, but some parts will be lost."""

FATAL = auto()
ERROR = auto()
"""Indicates a fatal error: descriptor cannot be converted."""

level: Level = Level.ERROR
level: Level
message: str

ErrorAdder = Callable[[Error], None]
"""ERC7730Converter output sink."""


class FromERC7730Converter(ERC7730Converter[ERC7730Descriptor, OutputType], ABC):
"""Converter from ERC-7730 to another format."""
class ErrorAdder(ABC):
"""ERC7730Converter output sink."""

@abstractmethod
def warning(self, message: str) -> None:
raise NotImplementedError()

class ToERC7730Converter(ERC7730Converter[InputType, ERC7730Descriptor], ABC):
"""Converter from another format to ERC-7730."""
@abstractmethod
def error(self, message: str) -> None:
raise NotImplementedError()
13 changes: 10 additions & 3 deletions src/erc7730/convert/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,20 @@ def convert_and_print_errors(
"""
errors: list[ERC7730Converter.Error] = []

result = converter.convert(input_descriptor, errors.append)
class ErrorAdder(ERC7730Converter.ErrorAdder):
def warning(self, message: str) -> None:
errors.append(ERC7730Converter.Error(level=ERC7730Converter.Error.Level.WARNING, message=message))

def error(self, message: str) -> None:
errors.append(ERC7730Converter.Error(level=ERC7730Converter.Error.Level.ERROR, message=message))

result = converter.convert(input_descriptor, ErrorAdder())

for error in errors:
match error.level:
case ERC7730Converter.Error.Level.ERROR:
case ERC7730Converter.Error.Level.WARNING:
print(f"[yellow][bold]{error.level}: [/bold]{error.message}[/yellow]")
case ERC7730Converter.Error.Level.FATAL:
case ERC7730Converter.Error.Level.ERROR:
print(f"[red][bold]{error.level}: [/bold]{error.message}[/red]")

return result
90 changes: 46 additions & 44 deletions src/erc7730/convert/convert_eip712_to_erc7730.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,47 @@
)
from pydantic import AnyUrl

from erc7730.convert import ERC7730Converter, ToERC7730Converter
from erc7730.model.context import EIP712, Deployment, Deployments, Domain, EIP712Context, EIP712JsonSchema, NameType
from erc7730.model.descriptor import ERC7730Descriptor
from erc7730.convert import ERC7730Converter
from erc7730.model.context import Deployment, Domain, EIP712JsonSchema, NameType
from erc7730.model.display import (
Display,
Field,
FieldDescription,
FieldFormat,
Format,
TokenAmountParameters,
)
from erc7730.model.input.context import InputEIP712, InputEIP712Context
from erc7730.model.input.descriptor import InputERC7730Descriptor
from erc7730.model.input.display import (
InputDisplay,
InputFieldDescription,
InputFormat,
InputNestedFields,
InputReference,
)
from erc7730.model.metadata import Metadata
from erc7730.model.types import ContractAddress


@final
class EIP712toERC7730Converter(ToERC7730Converter[EIP712DAppDescriptor]):
class EIP712toERC7730Converter(ERC7730Converter[EIP712DAppDescriptor, InputERC7730Descriptor]):
"""Converts Ledger legacy EIP-712 descriptor to ERC-7730 descriptor."""

@override
def convert(self, descriptor: EIP712DAppDescriptor, error: ERC7730Converter.ErrorAdder) -> ERC7730Descriptor | None:
# FIXME this code flattens all messages in first contract
def convert(
self, descriptor: EIP712DAppDescriptor, error: ERC7730Converter.ErrorAdder
) -> InputERC7730Descriptor | None:
# FIXME this code flattens all messages in first contract.
# converter must be changed to output a list[InputERC7730Descriptor]
# 1 output InputERC7730Descriptor per input contract

verifying_contract: ContractAddress | None = None
contract_name = descriptor.name
if len(descriptor.contracts) > 0:
verifying_contract = descriptor.contracts[0].address # FIXME
contract_name = descriptor.contracts[0].name # FIXME

if verifying_contract is None:
return error(
ERC7730Converter.Error(
level=ERC7730Converter.Error.Level.FATAL, message="verifying_contract is undefined"
)
)
return error.error("verifying_contract is undefined")

formats = dict[str, Format]()
formats = dict[str, InputFormat]()
schemas = list[EIP712JsonSchema | AnyUrl]()
for contract in descriptor.contracts:
for message in contract.messages:
Expand All @@ -56,27 +61,25 @@ def convert(self, descriptor: EIP712DAppDescriptor, error: ERC7730Converter.Erro
types=schema,
)
)
fields = [Field(self._convert_field(field)) for field in mapper.fields]
formats[mapper.label] = Format(
fields = [self._convert_field(field) for field in mapper.fields]
formats[mapper.label] = InputFormat(
intent=None, # FIXME
fields=fields,
required=None, # FIXME
screens=None,
)

return ERC7730Descriptor(
context=(
EIP712Context(
eip712=EIP712(
domain=Domain(
name=descriptor.name,
version=None, # FIXME
chainId=descriptor.chain_id,
verifyingContract=verifying_contract,
),
schemas=schemas,
deployments=Deployments([Deployment(chainId=descriptor.chain_id, address=verifying_contract)]),
)
return InputERC7730Descriptor(
context=InputEIP712Context(
eip712=InputEIP712(
domain=Domain(
name=descriptor.name,
version=None, # FIXME
chainId=descriptor.chain_id,
verifyingContract=verifying_contract,
),
schemas=schemas,
deployments=[Deployment(chainId=descriptor.chain_id, address=verifying_contract)],
)
),
metadata=Metadata(
Expand All @@ -86,28 +89,27 @@ def convert(self, descriptor: EIP712DAppDescriptor, error: ERC7730Converter.Erro
constants=None, # FIXME
enums=None, # FIXME
),
display=Display(
display=InputDisplay(
definitions=None, # FIXME
formats=formats,
),
)

@classmethod
def _convert_field(cls, field: EIP712Field) -> FieldDescription:
def _convert_field(cls, field: EIP712Field) -> InputFieldDescription | InputReference | InputNestedFields:
match field.format:
case EIP712Format.AMOUNT if field.assetPath is not None:
return InputFieldDescription(
label=field.label,
format=FieldFormat.TOKEN_AMOUNT,
params=TokenAmountParameters(tokenPath=field.assetPath),
path=field.path,
)
case EIP712Format.AMOUNT:
if field.assetPath is not None:
return FieldDescription(
label=field.label,
format=FieldFormat.TOKEN_AMOUNT,
params=TokenAmountParameters(tokenPath=field.assetPath),
path=field.path,
)
else:
return FieldDescription(label=field.label, format=FieldFormat.AMOUNT, params=None, path=field.path)
return InputFieldDescription(label=field.label, format=FieldFormat.AMOUNT, params=None, path=field.path)
case EIP712Format.DATETIME:
return FieldDescription(label=field.label, format=FieldFormat.DATE, params=None, path=field.path)
return InputFieldDescription(label=field.label, format=FieldFormat.DATE, params=None, path=field.path)
case EIP712Format.RAW | None:
return FieldDescription(label=field.label, format=FieldFormat.RAW, params=None, path=field.path)
return InputFieldDescription(label=field.label, format=FieldFormat.RAW, params=None, path=field.path)
case _:
assert_never(field.format)
Loading
Loading