Skip to content

Commit

Permalink
feat(BACK-8135): add support for constant values (#168)
Browse files Browse the repository at this point in the history
* feat(BACK-8135): add support for constant values

Update to align with LedgerHQ/clear-signing-erc7730-registry#109

- [x] add support for constant `value` instead of `path` in all fields
- [ ] add support for constant `token` instead of `tokenPath` when using `tokenAmount` format
- [ ] add support for constant `collection` instead of `collectionPath` when using `nftName` format
- [ ] add support for constant `callee` instead of `calleePath` when using `calldata` format

* feat(BACK-8135): add support for constants in format parameters

* feat(BACK-8135): change constant encoding to align with app

* revert registry to main branch

* feat(BACK-8135): cosmetic fixes
  • Loading branch information
jnicoulaud-ledger authored Dec 17, 2024
1 parent 900318f commit 45c2c49
Show file tree
Hide file tree
Showing 55 changed files with 804 additions and 180 deletions.
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ repos:
name: sync pre-commit dependencies

- repo: https://github.com/pdm-project/pdm
rev: 2.19.2
rev: 2.21.0
hooks:
- id: pdm-lock-check
name: check pdm lock file
Expand Down Expand Up @@ -72,7 +72,7 @@ repos:
name: apply walrus operator

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.7.0
rev: v0.8.2
hooks:
- id: ruff
name: lint code (ruff)
Expand All @@ -93,8 +93,8 @@ repos:
- types-requests
- types-setuptools
- types-protobuf
- pydantic==2.9.2
- pytest==8.3.3
- pydantic==2.10.3
- pytest==8.3.4

- repo: https://github.com/PyCQA/bandit
rev: 1.7.8
Expand Down
11 changes: 11 additions & 0 deletions src/erc7730/common/properties.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
from typing import Any


def has_any_property(target: Any, *names: str) -> bool:
"""
Check if the target has a property with any of the given names.
:param target: object of dict like
:param names: attribute names
:return: true if the target has the property
"""
return any(has_property(target, n) for n in names)


def has_property(target: Any, name: str) -> bool:
"""
Check if the target has a property with the given name.
Expand Down
70 changes: 45 additions & 25 deletions src/erc7730/convert/ledger/eip712/convert_erc7730_to_eip712.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
ResolvedFieldDescription,
ResolvedNestedFields,
ResolvedTokenAmountParameters,
ResolvedValueConstant,
ResolvedValuePath,
)


Expand Down Expand Up @@ -134,22 +136,29 @@ def convert_field_description(
field_format: EIP712Format | None = None
in_array: bool = False

match field.path:
case DataPath() as field_path:
field_path = data_path_concat(prefix, field_path)

for element in field_path.elements:
match element:
case Array():
in_array = True
break
case _:
pass

case ContainerPath() as container_path:
return out.error(f"Path {container_path} is not supported")
match field.value:
case ResolvedValueConstant():
return out.error("Constant values are not supported")

case ResolvedValuePath(path=path):
match path:
case DataPath() as field_path:
field_path = data_path_concat(prefix, field_path)

for element in field_path.elements:
match element:
case Array():
in_array = True
break
case _:
pass

case ContainerPath() as container_path:
return out.error(f"Path {container_path} is not supported")
case _:
assert_never(field.value)
case _:
assert_never(field.path)
assert_never(field.value)

match field.format:
case None:
Expand Down Expand Up @@ -179,18 +188,29 @@ def convert_field_description(
else:
field_format = EIP712Format.AMOUNT
if field.params is not None and isinstance(field.params, ResolvedTokenAmountParameters):
match field.params.tokenPath:
match field.params.token:
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")
return out.error("Token path or reference must be set")

case ResolvedValueConstant():
return out.error("Constant values are not supported")

case ResolvedValuePath(path=path):
match path:
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(path)
case _:
assert_never(field.params.tokenPath)
assert_never(field.value)
case _:
assert_never(field.format)

Expand Down
2 changes: 1 addition & 1 deletion src/erc7730/convert/resolved/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def assert_not_address(path: DataPath | ContainerPath) -> bool:
title="Invalid data path",
message=f""""{path}" is invalid, it must contain a data path to the address in the """
"transaction data. It seems you are trying to use a constant address value instead, please "
"note this feature is not supported (yet).",
"use the adequate parameter to provide a constant value.",
)
return False
except ValidationError:
Expand Down
16 changes: 12 additions & 4 deletions src/erc7730/convert/resolved/convert_erc7730_input_to_resolved.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from erc7730.convert.resolved.constants import ConstantProvider, DefaultConstantProvider
from erc7730.convert.resolved.parameters import resolve_field_parameters
from erc7730.convert.resolved.references import resolve_reference
from erc7730.convert.resolved.values import resolve_field_value
from erc7730.model.abi import ABI
from erc7730.model.context import EIP712Schema
from erc7730.model.display import (
Expand Down Expand Up @@ -37,7 +38,7 @@
from erc7730.model.input.metadata import InputMetadata
from erc7730.model.metadata import EnumDefinition
from erc7730.model.paths import ROOT_DATA_PATH, Array, ArrayElement, ArraySlice, ContainerPath, DataPath, Field
from erc7730.model.paths.path_ops import data_or_container_path_concat, data_path_concat
from erc7730.model.paths.path_ops import data_path_concat
from erc7730.model.resolved.context import (
ResolvedContract,
ResolvedContractContext,
Expand All @@ -54,6 +55,7 @@
ResolvedFieldDescription,
ResolvedFormat,
ResolvedNestedFields,
ResolvedValuePath,
)
from erc7730.model.resolved.metadata import ResolvedMetadata
from erc7730.model.types import Address, Id, Selector
Expand Down Expand Up @@ -312,13 +314,13 @@ def _resolve_field_description(

params = resolve_field_parameters(prefix, definition.params, enums, constants, out)

if (path := constants.resolve_path(definition.path, out)) is None:
if (value := resolve_field_value(prefix, definition, definition.format, constants, out)) is None:
return None

return ResolvedFieldDescription.model_validate(
{
"$id": definition.id,
"path": data_or_container_path_concat(prefix, path),
"value": value,
"label": constants.resolve(definition.label, out),
"format": FieldFormat(definition.format) if definition.format is not None else None,
"params": params,
Expand Down Expand Up @@ -431,6 +433,12 @@ def _resolve_nested_fields(
constants: ConstantProvider,
out: OutputAdder,
) -> list[ResolvedNestedFields | ResolvedFieldDescription] | None:
if fields.path is None:
return out.error(
title="Unsupported nested fields value",
message="Nested fields are only supported with data paths and not constant values.",
)

path: DataPath
match constants.resolve_path(fields.path, out):
case None:
Expand Down Expand Up @@ -461,6 +469,6 @@ def _resolve_nested_fields(
message="Using nested fields on an array slice is not allowed.",
)
case Array():
return [ResolvedNestedFields(path=path, fields=resolved_fields)]
return [ResolvedNestedFields(value=ResolvedValuePath(path=path), fields=resolved_fields)]
case _:
assert_never(path.elements[-1])
42 changes: 35 additions & 7 deletions src/erc7730/convert/resolved/parameters.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from typing import assert_never, cast

from erc7730.common.abi import ABIDataType
from erc7730.common.output import OutputAdder
from erc7730.convert.resolved.constants import ConstantProvider
from erc7730.convert.resolved.enums import get_enum, get_enum_id
from erc7730.convert.resolved.values import resolve_path_or_constant_value
from erc7730.model.input.display import (
InputAddressNameParameters,
InputCallDataParameters,
Expand All @@ -16,7 +18,6 @@
from erc7730.model.input.path import DescriptorPathStr
from erc7730.model.metadata import EnumDefinition
from erc7730.model.paths import DataPath
from erc7730.model.paths.path_ops import data_or_container_path_concat
from erc7730.model.resolved.display import (
ResolvedAddressNameParameters,
ResolvedCallDataParameters,
Expand Down Expand Up @@ -69,18 +70,35 @@ def resolve_address_name_parameters(
def resolve_calldata_parameters(
prefix: DataPath, params: InputCallDataParameters, constants: ConstantProvider, out: OutputAdder
) -> ResolvedCallDataParameters | None:
if (callee_path := constants.resolve_path(params.calleePath, out)) is None:
if (
callee := resolve_path_or_constant_value(
prefix=prefix,
input_path=params.calleePath,
input_value=params.callee,
abi_type=ABIDataType.ADDRESS,
constants=constants,
out=out,
)
) is None:
return None

return ResolvedCallDataParameters(
selector=constants.resolve_or_none(params.selector, out),
calleePath=data_or_container_path_concat(prefix, callee_path),
callee=callee,
)


def resolve_token_amount_parameters(
prefix: DataPath, params: InputTokenAmountParameters, constants: ConstantProvider, out: OutputAdder
) -> ResolvedTokenAmountParameters | None:
token_path = constants.resolve_path_or_none(params.tokenPath, out)
token = resolve_path_or_constant_value(
prefix=prefix,
input_path=params.tokenPath,
input_value=params.token,
abi_type=ABIDataType.ADDRESS,
constants=constants,
out=out,
)

input_addresses = cast(
list[DescriptorPathStr | MixedCaseAddress] | MixedCaseAddress | None,
Expand Down Expand Up @@ -111,7 +129,7 @@ def resolve_token_amount_parameters(
resolved_threshold = None

return ResolvedTokenAmountParameters(
tokenPath=None if token_path is None else data_or_container_path_concat(prefix, token_path),
token=token,
nativeCurrencyAddress=resolved_addresses,
threshold=resolved_threshold,
message=constants.resolve_or_none(params.message, out),
Expand All @@ -121,9 +139,19 @@ def resolve_token_amount_parameters(
def resolve_nft_parameters(
prefix: DataPath, params: InputNftNameParameters, constants: ConstantProvider, out: OutputAdder
) -> ResolvedNftNameParameters | None:
if (collection_path := constants.resolve_path(params.collectionPath, out)) is None:
if (
collection := resolve_path_or_constant_value(
prefix=prefix,
input_path=params.collectionPath,
input_value=params.collection,
abi_type=ABIDataType.ADDRESS,
constants=constants,
out=out,
)
) is None:
return None
return ResolvedNftNameParameters(collectionPath=data_or_container_path_concat(prefix, collection_path))

return ResolvedNftNameParameters(collection=collection)


def resolve_date_parameters(
Expand Down
7 changes: 4 additions & 3 deletions src/erc7730/convert/resolved/references.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from erc7730.common.pydantic import model_to_json_str
from erc7730.convert.resolved.constants import ConstantProvider
from erc7730.convert.resolved.parameters import resolve_field_parameters
from erc7730.convert.resolved.values import resolve_field_value
from erc7730.model.display import (
FieldFormat,
)
Expand All @@ -18,7 +19,7 @@
)
from erc7730.model.metadata import EnumDefinition
from erc7730.model.paths import DataPath, DescriptorPath, Field
from erc7730.model.paths.path_ops import data_or_container_path_concat, descriptor_path_strip_prefix
from erc7730.model.paths.path_ops import descriptor_path_strip_prefix
from erc7730.model.resolved.display import (
ResolvedField,
ResolvedFieldDescription,
Expand Down Expand Up @@ -60,11 +61,11 @@ def resolve_reference(
if (resolved_params := resolve_field_parameters(prefix, input_params, enums, constants, out)) is None:
return None

if (path := constants.resolve_path(reference.path, out)) is None:
if (value := resolve_field_value(prefix, reference, definition.format, constants, out)) is None:
return None

return ResolvedFieldDescription(
path=data_or_container_path_concat(prefix, path),
value=value,
label=str(constants.resolve(label, out)),
format=FieldFormat(definition.format),
params=resolved_params,
Expand Down
Loading

0 comments on commit 45c2c49

Please sign in to comment.