Conversation
There was a problem hiding this comment.
Pull request overview
Adds an ERC-7730 v2 pipeline to the codebase: new v2 input/resolved Pydantic models, an input→resolved converter, v2-specific linters, and golden-file tests validating conversion output.
Changes:
- Introduce v2 input + resolved descriptor models (context/metadata/display) and discriminated unions.
- Implement v2 input→resolved conversion (selectors, references, params, constants/maps/enums).
- Add v2 linting entrypoints + linters and golden reference tests with sample v2 descriptors.
Reviewed changes
Copilot reviewed 66 out of 69 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/v2/convert/resolved/test_convert_input_to_resolved.py | Golden-file conversion tests for v2 input→resolved. |
| tests/v2/convert/resolved/data/calldata_with_extended_params_input.json | Input fixture for calldata params conversion. |
| tests/v2/convert/resolved/data/calldata_with_extended_params_resolved.json | Expected resolved output for calldata params conversion. |
| tests/v2/convert/resolved/data/deprecated_abi_ignored_input.json | Input fixture asserting deprecated ABI is ignored. |
| tests/v2/convert/resolved/data/deprecated_abi_ignored_resolved.json | Expected resolved output with deprecated ABI removed/ignored. |
| tests/v2/convert/resolved/data/deprecated_schemas_ignored_input.json | Input fixture asserting deprecated EIP-712 schemas are ignored. |
| tests/v2/convert/resolved/data/deprecated_schemas_ignored_resolved.json | Expected resolved output with deprecated schemas removed/ignored. |
| tests/v2/convert/resolved/data/field_group_basic_input.json | Input fixture for field-group scoping. |
| tests/v2/convert/resolved/data/field_group_basic_resolved.json | Expected resolved output for field-group scoping. |
| tests/v2/convert/resolved/data/field_group_with_iteration_input.json | Input fixture for field-group iteration. |
| tests/v2/convert/resolved/data/field_group_with_iteration_resolved.json | Expected resolved output for field-group iteration. |
| tests/v2/convert/resolved/data/field_group_with_label_input.json | Input fixture for field-group labels. |
| tests/v2/convert/resolved/data/field_group_with_label_resolved.json | Expected resolved output for field-group labels. |
| tests/v2/convert/resolved/data/field_with_encryption_input.json | Input fixture for encryption metadata on fields. |
| tests/v2/convert/resolved/data/field_with_encryption_resolved.json | Expected resolved output for encryption metadata. |
| tests/v2/convert/resolved/data/field_with_separator_input.json | Input fixture for array separator handling. |
| tests/v2/convert/resolved/data/field_with_separator_resolved.json | Expected resolved output for array separator handling. |
| tests/v2/convert/resolved/data/field_with_visibility_conditions_input.json | Input fixture for conditional visibility rules. |
| tests/v2/convert/resolved/data/field_with_visibility_conditions_resolved.json | Expected resolved output for conditional visibility rules. |
| tests/v2/convert/resolved/data/field_with_visibility_simple_input.json | Input fixture for simple visibility rules. |
| tests/v2/convert/resolved/data/field_with_visibility_simple_resolved.json | Expected resolved output for simple visibility rules. |
| tests/v2/convert/resolved/data/format_chain_id_input.json | Input fixture for chainId field format. |
| tests/v2/convert/resolved/data/format_chain_id_resolved.json | Expected resolved output for chainId field format. |
| tests/v2/convert/resolved/data/format_interoperable_address_name_input.json | Input fixture for interoperable address name format. |
| tests/v2/convert/resolved/data/format_interoperable_address_name_resolved.json | Expected resolved output for interoperable address name format. |
| tests/v2/convert/resolved/data/format_token_ticker_input.json | Input fixture for token ticker format. |
| tests/v2/convert/resolved/data/format_token_ticker_resolved.json | Expected resolved output for token ticker format. |
| tests/v2/convert/resolved/data/format_with_interpolated_intent_input.json | Input fixture for interpolated intent strings. |
| tests/v2/convert/resolved/data/format_with_interpolated_intent_resolved.json | Expected resolved output for interpolated intent strings. |
| tests/v2/convert/resolved/data/metadata_with_contract_name_input.json | Input fixture for metadata.contractName. |
| tests/v2/convert/resolved/data/metadata_with_contract_name_resolved.json | Expected resolved output for metadata.contractName. |
| tests/v2/convert/resolved/data/metadata_with_maps_input.json | Input fixture for metadata.maps. |
| tests/v2/convert/resolved/data/metadata_with_maps_resolved.json | Expected resolved output for metadata.maps. |
| tests/v2/convert/resolved/data/minimal_contract_v2_input.json | Minimal contract-context v2 input fixture. |
| tests/v2/convert/resolved/data/minimal_contract_v2_resolved.json | Expected resolved output for minimal contract-context fixture. |
| tests/v2/convert/resolved/data/minimal_eip712_v2_input.json | Minimal EIP-712-context v2 input fixture. |
| tests/v2/convert/resolved/data/minimal_eip712_v2_resolved.json | Expected resolved output for minimal EIP-712-context fixture. |
| tests/v2/convert/resolved/data/token_amount_with_chainid_input.json | Input fixture for tokenAmount + chainIdPath parameters. |
| tests/v2/convert/resolved/data/token_amount_with_chainid_resolved.json | Expected resolved output for tokenAmount + chainIdPath parameters. |
| tests/v2/convert/resolved/init.py | Test package init for v2 resolved conversion tests. |
| tests/v2/convert/init.py | Test package init for v2 conversion tests. |
| tests/v2/init.py | Test package init for v2 tests. |
| src/erc7730/model/input/v2/init.py | v2 input model package init. |
| src/erc7730/model/input/v2/context.py | v2 input context models (contract/EIP-712). |
| src/erc7730/model/input/v2/descriptor.py | v2 input descriptor root model. |
| src/erc7730/model/input/v2/display.py | v2 input display/field/params models (incl. groups, visibility, maps). |
| src/erc7730/model/input/v2/format.py | v2 field format enums. |
| src/erc7730/model/input/v2/metadata.py | v2 input metadata models (incl. maps/enums/constants). |
| src/erc7730/model/input/v2/unions.py | v2 discriminators for fields/params/visibility unions. |
| src/erc7730/model/resolved/v2/init.py | v2 resolved model package init. |
| src/erc7730/model/resolved/v2/context.py | v2 resolved context models with normalized addresses and deprecated fields dropped. |
| src/erc7730/model/resolved/v2/descriptor.py | v2 resolved descriptor root model. |
| src/erc7730/model/resolved/v2/display.py | v2 resolved display/field/params models. |
| src/erc7730/model/resolved/v2/metadata.py | v2 resolved metadata models (resolved enums/maps). |
| src/erc7730/convert/resolved/v2/init.py | v2 conversion helpers package init. |
| src/erc7730/convert/resolved/v2/constants.py | v2 constant provider + path resolution helpers (incl. map reference scaffolding). |
| src/erc7730/convert/resolved/v2/convert_erc7730_input_to_resolved.py | Main v2 input→resolved converter implementation. |
| src/erc7730/convert/resolved/v2/enums.py | Enum reference parsing/validation helpers for v2 conversion. |
| src/erc7730/convert/resolved/v2/parameters.py | v2 parameter resolution for field formats (token amount/ticker, calldata, etc.). |
| src/erc7730/convert/resolved/v2/references.py | v2 display definition reference inlining. |
| src/erc7730/convert/resolved/v2/values.py | v2 field value/path resolution + constant encoding. |
| src/erc7730/lint/v2/init.py | v2 linter base + MultiLinter runner. |
| src/erc7730/lint/v2/lint.py | v2 lint CLI/entrypoint functions (load→convert→lint). |
| src/erc7730/lint/v2/lint_transaction_type_classifier.py | v2 transaction classification linter (permit heuristics + ABI classifier hook). |
| src/erc7730/lint/v2/lint_validate_display_fields.py | v2 ABI-backed display path validation (via explorer ABI fetch). |
| src/erc7730/lint/v2/lint_validate_max_length.py | v2 max-length checks for Ledger device constraints. |
| src/erc7730/lint/v2/path_schemas.py | v2 schema-path extraction for linting. |
| specs/erc7730-v1.schema.json | Adds/records the v1 JSON schema file under specs/. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| token_resolved = resolve_path_or_constant_value( | ||
| prefix=prefix, | ||
| input_path=params.tokenPath, | ||
| input_value=token_value, | ||
| abi_type=ABIDataType.ADDRESS, | ||
| constants=constants, | ||
| out=out, | ||
| ) |
There was a problem hiding this comment.
resolve_path_or_constant_value(...) can return a ResolvedValuePath, but this branch only handles ResolvedValueConstant. As a result, when tokenPath is provided (the common case), the resolved path is ignored and the returned resolved params won’t carry the correctly prefixed/absolute token path.
| return ResolvedTokenAmountParameters( | ||
| tokenPath=params.tokenPath, | ||
| token=resolved_token, | ||
| nativeCurrencyAddress=resolved_addresses, | ||
| threshold=resolved_threshold, | ||
| message=constants.resolve_or_none(params.message, out), | ||
| chainId=resolved_chain_id, | ||
| chainIdPath=params.chainIdPath, | ||
| ) |
There was a problem hiding this comment.
The returned ResolvedTokenAmountParameters passes through tokenPath/chainIdPath from the input without resolving descriptor-path constants or applying the current prefix (unlike field path resolution). This can leave relative paths in the resolved descriptor and break downstream consumers/linting; consider populating these from the resolved path results (absolute/prefixed) and/or resolving via constants.resolve_path + prefix concatenation.
| case ResolvedCallDataParameters(): | ||
| pass | ||
| case ResolvedTokenAmountParameters(): | ||
| if field.params.tokenPath is not None: | ||
| add_path(field.params.tokenPath) | ||
| case ResolvedTokenTickerParameters(): | ||
| pass |
There was a problem hiding this comment.
compute_format_schema_paths ignores data paths referenced inside several parameter objects (e.g., ResolvedCallDataParameters.*Path, ResolvedTokenAmountParameters.chainIdPath, ResolvedTokenTickerParameters.chainIdPath). This makes ABI/path validation incomplete and can produce false "missing display field" warnings or miss invalid parameter paths; include these *Path attributes in the collected data_paths.
| pass | ||
| case _: | ||
| assert_never(field.params) | ||
| case ResolvedFieldGroup(): |
There was a problem hiding this comment.
For ResolvedFieldGroup, the group’s own path is not added to the collected schema paths. If the group scopes fields under an array/object path, failing to record it can hide invalid group paths and skew ABI/path coverage calculations; consider calling add_path(field.path) before recursing into field.fields.
| case ResolvedFieldGroup(): | |
| case ResolvedFieldGroup(): | |
| if field.path is not None: | |
| add_path(field.path) |
| encoded = value.to_bytes(length=(max(value.bit_length(), 1) + 7) // 8, signed=False) | ||
|
|
||
| case ABIDataType.INT: | ||
| if not isinstance(value, int): | ||
| return out.error(title="Invalid constant", message=f"""Value "{value}" is not an integer""") | ||
| encoded = value.to_bytes(length=(max(value.bit_length(), 1) + 7) // 8, signed=True) | ||
|
|
||
| case ABIDataType.BOOL: | ||
| if not isinstance(value, bool): | ||
| return out.error(title="Invalid constant", message=f"""Value "{value}" is not a boolean""") | ||
| encoded = value.to_bytes() |
There was a problem hiding this comment.
int.to_bytes(...) is called without the required byteorder argument, which will raise TypeError at runtime for non-hex INT constants. Encode using an explicit byteorder (and keep signed=True for negatives).
| encoded = value.to_bytes(length=(max(value.bit_length(), 1) + 7) // 8, signed=False) | |
| case ABIDataType.INT: | |
| if not isinstance(value, int): | |
| return out.error(title="Invalid constant", message=f"""Value "{value}" is not an integer""") | |
| encoded = value.to_bytes(length=(max(value.bit_length(), 1) + 7) // 8, signed=True) | |
| case ABIDataType.BOOL: | |
| if not isinstance(value, bool): | |
| return out.error(title="Invalid constant", message=f"""Value "{value}" is not a boolean""") | |
| encoded = value.to_bytes() | |
| encoded = value.to_bytes( | |
| length=(max(value.bit_length(), 1) + 7) // 8, | |
| byteorder="big", | |
| signed=False, | |
| ) | |
| case ABIDataType.INT: | |
| if not isinstance(value, int): | |
| return out.error(title="Invalid constant", message=f"""Value "{value}" is not an integer""") | |
| encoded = value.to_bytes( | |
| length=(max(value.bit_length(), 1) + 7) // 8, | |
| byteorder="big", | |
| signed=True, | |
| ) | |
| case ABIDataType.BOOL: | |
| if not isinstance(value, bool): | |
| return out.error(title="Invalid constant", message=f"""Value "{value}" is not a boolean""") | |
| encoded = int(value).to_bytes(length=1, byteorder="big", signed=False) |
| case ABIDataType.BOOL: | ||
| if not isinstance(value, bool): | ||
| return out.error(title="Invalid constant", message=f"""Value "{value}" is not a boolean""") | ||
| encoded = value.to_bytes() |
There was a problem hiding this comment.
value.to_bytes() on a bool will raise TypeError (the method requires length and byteorder). Encode booleans explicitly (e.g., b"\x01"/b"\x00" or int(value).to_bytes(1, byteorder=...)).
| encoded = value.to_bytes() | |
| encoded = b"\x01" if value else b"\x00" |
| from erc7730.common import client | ||
| from erc7730.common.abi import reduce_signature, signature_to_selector | ||
| from erc7730.common.output import ExceptionsToOutput, OutputAdder | ||
| from erc7730.convert import ERC7730Converter |
There was a problem hiding this comment.
Import of 'ERC7730Converter' is not used.
| ) | ||
|
|
||
| # noinspection PyUnreachableCode | ||
| return None |
There was a problem hiding this comment.
This statement is unreachable.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
The lint CLI command was hardcoded to use the v1 model, causing false errors when linting v2 descriptors. This adds auto-detection based on the $schema field and routes to the v2 linter accordingly. A --v2 flag is also available for explicit override.
Pure/view functions cannot produce transactions and don't need clear signing descriptors. Skip them by default in get_functions() to reduce noise in lint output. The generator still includes all functions via include_read_only=True.
rich.print wraps long strings at terminal width, inserting literal newlines into JSON string values (e.g. hex descriptor fields). This corrupts the output when piped or captured. Use builtins.print for schema, resolve, and calldata commands that output raw JSON.
The calldata command now auto-detects v2 descriptors (via $schema field) and builds ABI Function objects from human-readable format keys in display.formats, instead of requiring an embedded ABI in the contract context. Fields with visible="never" are skipped to match v1 behavior.
- `erc7730 resolve` now auto-detects v2 descriptors and uses the v2 resolver (or accepts --v2 flag). - `erc7730 convert erc7730-to-eip712` now detects v2 descriptors early and exits with a clear error: v2 drops embedded EIP-712 schemas, so conversion to legacy format is not supported.
Reconstruct EIP-712 schemas from v2 descriptors where the embedded schemas have been removed. The display.formats keys (encodeType strings) are parsed back into full EIP-712 type definitions and the EIP712Domain is rebuilt from the domain/deployment info. - Add `parse_encode_type()` utility to reverse encodeType strings - New `ERC7730V2toEIP712Converter` for v2 -> legacy EIP-712 conversion - Update `command_convert_erc7730_to_eip712` to dispatch to v2 converter - Add `validate_eip712_domain_fields()` to warn on non-standard or mis-ordered EIP712Domain fields in v1 descriptors - V2 domain reconstruction emits fields in canonical EIP-712 order: name, version, chainId, verifyingContract, salt
Context: https://github.com/ethereum/ERCs/blob/master/assets/erc-7730/erc7730-v2.schema.json