Skip to content

Commit

Permalink
feat: use typed paths in model, implement constants/enums resolution (#…
Browse files Browse the repository at this point in the history
…92)

* chore: split display fields into input/resolved

* migrate code to typed paths (WIP)

* migrate code to typed paths (WIP)

* migrate code to typed paths (WIP)

* migrate code to typed paths (WIP)

* migrate code to typed paths (WIP)

* migrate code to typed paths (WIP)

* migrate code to typed paths (WIP)

* migrate code to typed paths (WIP)

* tests / fixes

* tests / fixes

* tests / fixes

* tests / fixes

* tests / fixes

* tests / fixes

* tests / fixes

* tests / fixes

* tests / fixes

* tests / fixes

* tests / fixes

* tests / fixes

* tests / fixes

* tests / fixes

* update refs

* tests / fixes

* tests / fixes

* tests / fixes

* tests / fixes

* tests / fixes

* tests / fixes

* feat: implement resolution of constants / descriptor paths (#104)

* feat: implement resolution of constants / descriptor paths

* feat: split metadata input/resolved, implement enums

* feat: fix linter

* feat: update tests

* fix enum constraints

* merge main

* fix doc
  • Loading branch information
jnicoulaud-ledger authored Oct 21, 2024
1 parent 2de30ee commit c0329f7
Show file tree
Hide file tree
Showing 89 changed files with 4,346 additions and 991 deletions.
21 changes: 0 additions & 21 deletions src/erc7730/common/abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,27 +70,6 @@ def type(self, ast: Any) -> str:
return value + array


def _append_path(root: str, path: str) -> str:
return f"{root}.{path}" if root else path


def compute_paths(abi: Function) -> set[str]:
"""Compute the sets of valid paths for a Function."""

def append_paths(path: str, params: list[InputOutput] | list[Component] | None, paths: set[str]) -> None:
if params:
for param in params:
name = param.name + ".[]" if param.type.endswith("[]") else param.name
if param.components:
append_paths(_append_path(path, name), param.components, paths) # type: ignore
else:
paths.add(_append_path(path, name))

paths: set[str] = set()
append_paths("", abi.inputs, paths)
return paths


def compute_signature(abi: Function) -> str:
"""Compute the signature of a Function."""
abi_function = cast(ABIFunction, abi.model_dump())
Expand Down
13 changes: 13 additions & 0 deletions src/erc7730/common/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,16 @@ def has_property(target: Any, name: str) -> bool:
if isinstance(target, dict):
return name in target
return hasattr(target, name)


def get_property(target: Any, name: str) -> Any:
"""
Get the property with the given name on target object.
:param target: object of dict like
:param name: attribute name
:return: value for property on target object
"""
if isinstance(target, dict):
return target[name]
return getattr(target, name)
2 changes: 1 addition & 1 deletion src/erc7730/common/pydantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def model_from_json_file_with_includes_or_none(path: Path, model: type[_BaseMode


def model_to_json_dict(obj: _BaseModel) -> dict[str, Any]:
"""Serialize a pydantic model into a JSON string."""
"""Serialize a pydantic model into a JSON dict."""
return obj.model_dump(by_alias=True, exclude_none=True)


Expand Down
15 changes: 8 additions & 7 deletions src/erc7730/convert/ledger/eip712/convert_eip712_to_erc7730.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,21 @@
from erc7730.model.context import Deployment, Domain, EIP712JsonSchema
from erc7730.model.display import (
DateEncoding,
DateParameters,
FieldFormat,
TokenAmountParameters,
)
from erc7730.model.input.context import InputEIP712, InputEIP712Context
from erc7730.model.input.descriptor import InputERC7730Descriptor
from erc7730.model.input.display import (
InputDateParameters,
InputDisplay,
InputFieldDescription,
InputFormat,
InputNestedFields,
InputReference,
InputTokenAmountParameters,
)
from erc7730.model.metadata import Metadata
from erc7730.model.input.metadata import InputMetadata
from erc7730.model.paths import ContainerField, ContainerPath


@final
Expand Down Expand Up @@ -72,7 +73,7 @@ def convert(
deployments=[Deployment(chainId=descriptor.chainId, address=contract.address)],
)
),
metadata=Metadata(
metadata=InputMetadata(
owner=contract.contractName,
info=None,
token=None,
Expand Down Expand Up @@ -100,21 +101,21 @@ def _convert_field(
path=field.path,
label=field.label,
format=FieldFormat.TOKEN_AMOUNT,
params=TokenAmountParameters(tokenPath=field.assetPath),
params=InputTokenAmountParameters(tokenPath=field.assetPath),
)
case EIP712Format.AMOUNT:
return InputFieldDescription(
path=field.path,
label=field.label,
format=FieldFormat.TOKEN_AMOUNT,
params=TokenAmountParameters(tokenPath="@.to"),
params=InputTokenAmountParameters(tokenPath=ContainerPath(field=ContainerField.TO)),
)
case EIP712Format.DATETIME:
return InputFieldDescription(
path=field.path,
label=field.label,
format=FieldFormat.DATE,
params=DateParameters(encoding=DateEncoding.TIMESTAMP),
params=InputDateParameters(encoding=DateEncoding.TIMESTAMP),
)
case _:
assert_never(field.format)
Expand Down
106 changes: 69 additions & 37 deletions src/erc7730/convert/ledger/eip712/convert_erc7730_to_eip712.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@
from erc7730.model.context import Deployment, EIP712JsonSchema
from erc7730.model.display import (
FieldFormat,
TokenAmountParameters,
)
from erc7730.model.paths import ContainerField, ContainerPath, DataPath
from erc7730.model.paths.path_ops import data_path_concat, to_relative
from erc7730.model.resolved.context import ResolvedEIP712Context
from erc7730.model.resolved.descriptor import ResolvedERC7730Descriptor
from erc7730.model.resolved.display import (
ResolvedField,
ResolvedFieldDescription,
ResolvedNestedFields,
ResolvedTokenAmountParameters,
)


Expand Down Expand Up @@ -54,18 +56,14 @@ def convert(

label = format.intent if isinstance(format.intent, str) else primary_type

output_fields = []
for input_field in format.fields:
if (out_field := self.convert_field(input_field, None, out)) is None:
return None
output_fields.extend(out_field)

messages.append(
InputEIP712Message(
schema=schema,
mapper=InputEIP712Mapper(
label=label,
fields=[
out_field
for in_field in format.fields
for out_field in self.convert_field(in_field, prefix=None)
],
),
)
InputEIP712Message(schema=schema, mapper=InputEIP712Mapper(label=label, fields=output_fields))
)

descriptors: dict[str, InputEIP712DAppDescriptor] = {}
Expand Down Expand Up @@ -106,32 +104,50 @@ def _get_schema(
return out.error(f"schema for type {primary_type} not found")

@classmethod
def convert_field(cls, field: ResolvedField, prefix: str | None) -> list[InputEIP712MapperField]:
if isinstance(field, ResolvedNestedFields):
field_prefix = field.path if prefix is None else f"{prefix}.{field.path}"
return [out_field for in_field in field.fields for out_field in cls.convert_field(in_field, field_prefix)]
return [cls.convert_field_description(field, prefix)]
def convert_field(
cls, field: ResolvedField, prefix: DataPath | None, out: OutputAdder
) -> list[InputEIP712MapperField] | None:
match field:
case ResolvedFieldDescription():
if (output_field := cls.convert_field_description(field, prefix, out)) is None:
return None
return [output_field]
case ResolvedNestedFields():
output_fields = []
for in_field in field.fields:
if (output_field := cls.convert_field(in_field, prefix, out)) is None:
return None
output_fields.extend(output_field)
return output_fields
case _:
assert_never(field)

@classmethod
def convert_field_description(cls, field: ResolvedFieldDescription, prefix: str | None) -> InputEIP712MapperField:
asset_path: str | None = None
def convert_field_description(
cls,
field: ResolvedFieldDescription,
prefix: DataPath | None,
out: OutputAdder,
) -> InputEIP712MapperField | None:
field_path: DataPath
asset_path: DataPath | None = None
field_format: EIP712Format | None = None
match field.format:
case FieldFormat.TOKEN_AMOUNT:
if field.params is not None and isinstance(field.params, TokenAmountParameters):
asset_path = field.params.tokenPath if prefix is None else f"{prefix}.{field.params.tokenPath}"

# FIXME edge case for referencing verifyingContract, this will be handled cleanly in #65
if asset_path == "@.to":
asset_path = None
match field.path:
case DataPath() as field_path:
field_path = data_path_concat(prefix, field_path)
case ContainerPath() as container_path:
return out.error(f"Path {container_path} is not supported")
case _:
assert_never(field.path)

field_format = EIP712Format.AMOUNT
case FieldFormat.AMOUNT:
field_format = EIP712Format.AMOUNT
case FieldFormat.DATE:
field_format = EIP712Format.DATETIME
match field.format:
case None:
field_format = None
case FieldFormat.ADDRESS_NAME:
field_format = EIP712Format.RAW
case FieldFormat.RAW:
field_format = EIP712Format.RAW
case FieldFormat.ENUM:
field_format = EIP712Format.RAW
case FieldFormat.UNIT:
Expand All @@ -142,15 +158,31 @@ def convert_field_description(cls, field: ResolvedFieldDescription, prefix: str
field_format = EIP712Format.RAW
case FieldFormat.CALL_DATA:
field_format = EIP712Format.RAW
case FieldFormat.RAW:
field_format = EIP712Format.RAW
case None:
field_format = None
case FieldFormat.DATE:
field_format = EIP712Format.DATETIME
case FieldFormat.AMOUNT:
field_format = EIP712Format.AMOUNT
case FieldFormat.TOKEN_AMOUNT:
field_format = EIP712Format.AMOUNT
if field.params is not None and isinstance(field.params, ResolvedTokenAmountParameters):
match field.params.tokenPath:
case None:
pass
case DataPath() as token_path:
asset_path = data_path_concat(prefix, token_path)
case ContainerPath() as container_path if container_path.field == ContainerField.TO:
# In EIP-712 protocol, format=token with no token path => refers to verifyingContract
asset_path = None
case ContainerPath() as container_path:
return out.error(f"Path {container_path} is not supported")
case _:
assert_never(field.params.tokenPath)
case _:
assert_never(field.format)

return InputEIP712MapperField(
path=field.path if prefix is None else f"{prefix}.{field.path}",
path=str(to_relative(field_path)),
label=field.label,
assetPath=asset_path,
assetPath=None if asset_path is None else str(to_relative(asset_path)),
format=field_format,
)
Loading

0 comments on commit c0329f7

Please sign in to comment.