diff --git a/docs/pages/usage_library.md b/docs/pages/usage_library.md index 489c86ce..94759241 100644 --- a/docs/pages/usage_library.md +++ b/docs/pages/usage_library.md @@ -46,6 +46,7 @@ Descriptors can be manipulated in 2 forms: - References have been inlined - Constants have been inlined - Field definitions have been inlined + - Nested fields have been flattened where possible - Selectors have been converted to 4 bytes form This form is the most adapted to be used by tools and applications. diff --git a/src/erc7730/convert/resolved/convert_erc7730_input_to_resolved.py b/src/erc7730/convert/resolved/convert_erc7730_input_to_resolved.py index 022e43d6..740652a3 100644 --- a/src/erc7730/convert/resolved/convert_erc7730_input_to_resolved.py +++ b/src/erc7730/convert/resolved/convert_erc7730_input_to_resolved.py @@ -36,7 +36,7 @@ ) from erc7730.model.input.metadata import InputMetadata from erc7730.model.metadata import EnumDefinition -from erc7730.model.paths import ROOT_DATA_PATH, ContainerPath, DataPath +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.resolved.context import ( ResolvedContract, @@ -70,6 +70,7 @@ class ERC7730InputToResolved(ERC7730Converter[InputERC7730Descriptor, ResolvedER - References have been inlined - Constants have been inlined - Field definitions have been inlined + - Nested fields have been flattened where possible - Selectors have been converted to 4 bytes form """ @@ -385,7 +386,7 @@ def _resolve_fields( for input_format in fields: if (resolved_field := cls._resolve_field(prefix, input_format, definitions, enums, constants, out)) is None: return None - resolved_fields.append(resolved_field) + resolved_fields.extend(resolved_field) return resolved_fields @classmethod @@ -397,16 +398,28 @@ def _resolve_field( enums: dict[Id, EnumDefinition], constants: ConstantProvider, out: OutputAdder, - ) -> ResolvedField | None: + ) -> list[ResolvedField] | None: + resolved_fields: list[ResolvedField] = [] match field: case InputReference(): - return resolve_reference(prefix, field, definitions, enums, constants, out) + if (resolved_field := resolve_reference(prefix, field, definitions, enums, constants, out)) is None: + return None + resolved_fields.append(resolved_field) case InputFieldDescription(): - return cls._resolve_field_description(prefix, field, enums, constants, out) + if (resolved_field := cls._resolve_field_description(prefix, field, enums, constants, out)) is None: + return None + resolved_fields.append(resolved_field) case InputNestedFields(): - return cls._resolve_nested_fields(prefix, field, definitions, enums, constants, out) + if ( + resolved_nested_fields := cls._resolve_nested_fields( + prefix, field, definitions, enums, constants, out + ) + ) is None: + return None + resolved_fields.extend(resolved_nested_fields) case _: assert_never(field) + return resolved_fields @classmethod def _resolve_nested_fields( @@ -417,7 +430,7 @@ def _resolve_nested_fields( enums: dict[Id, EnumDefinition], constants: ConstantProvider, out: OutputAdder, - ) -> ResolvedNestedFields | None: + ) -> list[ResolvedNestedFields | ResolvedFieldDescription] | None: path: DataPath match constants.resolve_path(fields.path, out): case None: @@ -432,7 +445,22 @@ def _resolve_nested_fields( case _: assert_never(fields.path) - if (resolved_fields := cls._resolve_fields(path, fields.fields, definitions, enums, constants, out)) is None: + if ( + resolved_fields := cls._resolve_fields( + prefix=path, fields=fields.fields, definitions=definitions, enums=enums, constants=constants, out=out + ) + ) is None: return None - return ResolvedNestedFields(path=path, fields=resolved_fields) + match path.elements[-1]: + case Field() | ArrayElement(): + return resolved_fields + case ArraySlice(): + return out.error( + title="Invalid nested fields", + message="Using nested fields on an array slice is not allowed.", + ) + case Array(): + return [ResolvedNestedFields(path=path, fields=resolved_fields)] + case _: + assert_never(path.elements[-1]) diff --git a/src/erc7730/main.py b/src/erc7730/main.py index 0ac31c00..d8112c0f 100644 --- a/src/erc7730/main.py +++ b/src/erc7730/main.py @@ -84,6 +84,7 @@ def lint( - References inlined - Constants inlined - Field definitions inlined + - Nested fields flattened where possible - Selectors converted to 4 bytes form See `erc7730 schema resolved` for the resolved descriptor schema. diff --git a/src/erc7730/model/input/descriptor.py b/src/erc7730/model/input/descriptor.py index 2fedba8b..71cdec19 100644 --- a/src/erc7730/model/input/descriptor.py +++ b/src/erc7730/model/input/descriptor.py @@ -7,6 +7,7 @@ - References have not been inlined - Constants have not been inlined - Field definitions have not been inlined + - Nested fields have been flattened where possible - Selectors have been converted to 4 bytes form """ diff --git a/src/erc7730/model/resolved/__init__.py b/src/erc7730/model/resolved/__init__.py index 261f57cc..360025ef 100644 --- a/src/erc7730/model/resolved/__init__.py +++ b/src/erc7730/model/resolved/__init__.py @@ -7,5 +7,6 @@ - References have been inlined - Constants have been inlined - Field definitions have been inlined + - Nested fields have been flattened where possible - Selectors have been converted to 4 bytes form """ diff --git a/src/erc7730/model/resolved/descriptor.py b/src/erc7730/model/resolved/descriptor.py index 2060a521..44ac9e06 100644 --- a/src/erc7730/model/resolved/descriptor.py +++ b/src/erc7730/model/resolved/descriptor.py @@ -7,6 +7,7 @@ - References have been inlined - Constants have been inlined - Field definitions have been inlined + - Nested fields have been flattened where possible - Selectors have been converted to 4 bytes form """ @@ -30,6 +31,7 @@ class ResolvedERC7730Descriptor(Model): - References have been inlined - Constants have been inlined - Field definitions have been inlined + - Nested fields have been flattened where possible - Selectors have been converted to 4 bytes form Specification: https://github.com/LedgerHQ/clear-signing-erc7730-registry/tree/master/specs diff --git a/tests/convert/resolved/data/nested_fields_eip712_array_input.json b/tests/convert/resolved/data/nested_fields_eip712_array_input.json new file mode 100644 index 00000000..8646d1be --- /dev/null +++ b/tests/convert/resolved/data/nested_fields_eip712_array_input.json @@ -0,0 +1,74 @@ +{ + "$schema": "../../../registries/clear-signing-erc7730-registry/specs/erc7730-v1.schema.json", + "context": { + "eip712": { + "deployments": [ + { + "chainId": 1, + "address": "0x0000000000000000000000000000000000000000" + } + ], + "schemas": [ + { + "primaryType": "TestPrimaryType", + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "TestSubtype": [ + { + "name": "subparam1", + "type": "string" + }, + { + "name": "subparam2", + "type": "string" + } + ], + "TestPrimaryType": [ + { + "name": "param1", + "type": "TestSubtype[]" + } + ] + } + } + ] + } + }, + "metadata": {}, + "display": { + "formats": { + "TestPrimaryType": { + "fields": [ + { + "path": "param1.[]", + "fields": [ + { + "path": "subparam1", + "label": "Subparam 1", + "format": "raw" + }, + { + "path": "subparam2", + "label": "Subparam 2", + "format": "raw" + } + ] + } + ] + } + } + } +} diff --git a/tests/convert/resolved/data/nested_fields_eip712_array_resolved.json b/tests/convert/resolved/data/nested_fields_eip712_array_resolved.json new file mode 100644 index 00000000..e7146098 --- /dev/null +++ b/tests/convert/resolved/data/nested_fields_eip712_array_resolved.json @@ -0,0 +1,53 @@ +{ + "context": { + "eip712": { + "deployments": [{ "chainId": 1, "address": "0x0000000000000000000000000000000000000000" }], + "schemas": [ + { + "primaryType": "TestPrimaryType", + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "TestSubtype": [{ "name": "subparam1", "type": "string" }, { "name": "subparam2", "type": "string" }], + "TestPrimaryType": [{ "name": "param1", "type": "TestSubtype[]" }] + } + } + ] + } + }, + "metadata": { "enums": {} }, + "display": { + "formats": { + "TestPrimaryType": { + "fields": [ + { + "path": { "type": "data", "absolute": true, "elements": [{ "type": "field", "identifier": "param1" }, { "type": "array" }] }, + "fields": [ + { + "path": { + "type": "data", + "absolute": true, + "elements": [{ "type": "field", "identifier": "param1" }, { "type": "array" }, { "type": "field", "identifier": "subparam1" }] + }, + "label": "Subparam 1", + "format": "raw" + }, + { + "path": { + "type": "data", + "absolute": true, + "elements": [{ "type": "field", "identifier": "param1" }, { "type": "array" }, { "type": "field", "identifier": "subparam2" }] + }, + "label": "Subparam 2", + "format": "raw" + } + ] + } + ] + } + } + } +} diff --git a/tests/convert/resolved/data/nested_fields_eip712_struct_input.json b/tests/convert/resolved/data/nested_fields_eip712_struct_input.json new file mode 100644 index 00000000..67f6b7d8 --- /dev/null +++ b/tests/convert/resolved/data/nested_fields_eip712_struct_input.json @@ -0,0 +1,74 @@ +{ + "$schema": "../../../registries/clear-signing-erc7730-registry/specs/erc7730-v1.schema.json", + "context": { + "eip712": { + "deployments": [ + { + "chainId": 1, + "address": "0x0000000000000000000000000000000000000000" + } + ], + "schemas": [ + { + "primaryType": "TestPrimaryType", + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "TestSubtype": [ + { + "name": "subparam1", + "type": "string" + }, + { + "name": "subparam2", + "type": "string" + } + ], + "TestPrimaryType": [ + { + "name": "param1", + "type": "TestSubtype" + } + ] + } + } + ] + } + }, + "metadata": {}, + "display": { + "formats": { + "TestPrimaryType": { + "fields": [ + { + "path": "param1", + "fields": [ + { + "path": "subparam1", + "label": "Subparam 1", + "format": "raw" + }, + { + "path": "subparam2", + "label": "Subparam 2", + "format": "raw" + } + ] + } + ] + } + } + } +} diff --git a/tests/convert/resolved/data/nested_fields_eip712_struct_resolved.json b/tests/convert/resolved/data/nested_fields_eip712_struct_resolved.json new file mode 100644 index 00000000..2cb55364 --- /dev/null +++ b/tests/convert/resolved/data/nested_fields_eip712_struct_resolved.json @@ -0,0 +1,48 @@ +{ + "context": { + "eip712": { + "deployments": [{ "chainId": 1, "address": "0x0000000000000000000000000000000000000000" }], + "schemas": [ + { + "primaryType": "TestPrimaryType", + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "TestSubtype": [{ "name": "subparam1", "type": "string" }, { "name": "subparam2", "type": "string" }], + "TestPrimaryType": [{ "name": "param1", "type": "TestSubtype" }] + } + } + ] + } + }, + "metadata": { "enums": {} }, + "display": { + "formats": { + "TestPrimaryType": { + "fields": [ + { + "path": { + "type": "data", + "absolute": true, + "elements": [{ "type": "field", "identifier": "param1" }, { "type": "field", "identifier": "subparam1" }] + }, + "label": "Subparam 1", + "format": "raw" + }, + { + "path": { + "type": "data", + "absolute": true, + "elements": [{ "type": "field", "identifier": "param1" }, { "type": "field", "identifier": "subparam2" }] + }, + "label": "Subparam 2", + "format": "raw" + } + ] + } + } + } +} diff --git a/tests/convert/resolved/test_convert_input_to_resolved.py b/tests/convert/resolved/test_convert_input_to_resolved.py index 3abed90a..da74a4a9 100644 --- a/tests/convert/resolved/test_convert_input_to_resolved.py +++ b/tests/convert/resolved/test_convert_input_to_resolved.py @@ -253,6 +253,16 @@ def test_registry_files(input_file: Path) -> None: description="use of a valid reference to a valid display definition, with invalid overrides", error="Extra inputs are not permitted", ), + TestCase( + id="nested_fields_eip712_array", + label="nested fields - array parameter (EIP-712 descriptor)", + description="use of nested fields on an array parameter (EIP-712 descriptor)", + ), + TestCase( + id="nested_fields_eip712_struct", + label="nested fields - struct parameter (EIP-712 descriptor)", + description="use of nested fields on a struct parameter (EIP-712 descriptor)", + ), ], ids=case_id, )