Skip to content

Commit

Permalink
refactor: split model in input/resolved (#40)
Browse files Browse the repository at this point in the history
* chore: create packages

* split model in input/resolved

* split model in input/resolved (WIP)

* lint/fixes

* lint/fixes

* lint/fixes

* lint/fixes

* lint/fixes
  • Loading branch information
jnicoulaud-ledger authored Oct 2, 2024
1 parent dc8d411 commit d268342
Show file tree
Hide file tree
Showing 38 changed files with 1,068 additions and 580 deletions.
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

0 comments on commit d268342

Please sign in to comment.