From efbedef21c698b6d8746fcf468d8542b1de48772 Mon Sep 17 00:00:00 2001 From: venugopalanvip <102638505+venugopalanvip@users.noreply.github.com> Date: Sun, 20 Sep 2026 18:55:49 +0530 Subject: [PATCH] fix(calldata): name the descriptor when conversion fails The catch-all around v2 conversion reported: Error processing v2 ERC-7730 file None, skipping it `source` is an optional URL and is None for a local file, which is what the CLI passes, so the message named nothing. It is the only line that says which descriptor was abandoned -- the exception detail that follows it carries no file or name -- so a run over more than one descriptor gave no way to tell which one had failed. It now falls back to what the descriptor calls itself, using the same identifier the converted output carries as its contract name: Error processing v2 ERC-7730 descriptor pFT NFT, skipping it Error processing v2 ERC-7730 descriptor Ekubo Swap Router, skipping it A source is kept beside the name when both are known, since a batch run wants the path as well. With neither, it says so rather than printing None. Co-Authored-By: Claude Opus 5 --- .../convert_erc7730_v2_input_to_calldata.py | 19 ++++++- tests/v2/convert/calldata/test_describe.py | 51 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 tests/v2/convert/calldata/test_describe.py diff --git a/src/erc7730/convert/calldata/convert_erc7730_v2_input_to_calldata.py b/src/erc7730/convert/calldata/convert_erc7730_v2_input_to_calldata.py index 5b7f6d00..25cfef5e 100644 --- a/src/erc7730/convert/calldata/convert_erc7730_v2_input_to_calldata.py +++ b/src/erc7730/convert/calldata/convert_erc7730_v2_input_to_calldata.py @@ -99,6 +99,23 @@ ADDRESS_LENGTH = 20 +def _describe(input_descriptor: InputERC7730Descriptor, source: HttpUrl | None) -> str: + """Name a descriptor in a diagnostic. + + ``source`` is optional and is None for a local file, which is the usual case for the CLI, + so fall back to what the descriptor calls itself -- the same identifier the converted + output carries as its contract name. + + :param input_descriptor: descriptor being converted + :param source: source URL, if one was supplied + :return: the most specific identifier available + """ + name = input_descriptor.metadata.contractName or getattr(input_descriptor.context, "id", None) + if name is not None and source is not None: + return f"{name} ({source})" + return str(name or source or "") + + def erc7730_v2_descriptor_to_calldata_descriptors( input_descriptor: InputERC7730Descriptor, source: HttpUrl | None = None, @@ -186,7 +203,7 @@ def erc7730_v2_descriptor_to_calldata_descriptors( return output_descriptors except Exception as e: - out.warning(f"Error processing v2 ERC-7730 file {source}, skipping it") + out.warning(f"Error processing v2 ERC-7730 descriptor {_describe(input_descriptor, source)}, skipping it") exception_to_output(e, out) return [] diff --git a/tests/v2/convert/calldata/test_describe.py b/tests/v2/convert/calldata/test_describe.py new file mode 100644 index 00000000..6ccf2497 --- /dev/null +++ b/tests/v2/convert/calldata/test_describe.py @@ -0,0 +1,51 @@ +from typing import Any + +import pytest +from pydantic_string_url import HttpUrl + +from erc7730.convert.calldata.convert_erc7730_v2_input_to_calldata import _describe +from erc7730.model.input.v2.descriptor import InputERC7730Descriptor + +SOURCE = HttpUrl("https://example.com/calldata-MyContract.json") + + +def descriptor(*, context_id: str | None = None, contract_name: str | None = None) -> InputERC7730Descriptor: + context: dict[str, Any] = { + "contract": {"deployments": [{"chainId": 1, "address": "0x0000000000000000000000000000000000000001"}]} + } + if context_id is not None: + context["$id"] = context_id + metadata: dict[str, Any] = {"owner": "Someone"} + if contract_name is not None: + metadata["contractName"] = contract_name + return InputERC7730Descriptor.model_validate( + {"context": context, "metadata": metadata, "display": {"formats": {}}}, strict=True + ) + + +def test_the_contract_name_is_preferred() -> None: + assert _describe(descriptor(context_id="Fallback", contract_name="MyContract"), None) == "MyContract" + + +def test_the_context_id_is_the_fallback() -> None: + """A descriptor need not set contractName, and $id is what the output falls back to as well.""" + assert _describe(descriptor(context_id="MyContract"), None) == "MyContract" + + +def test_a_source_is_kept_beside_the_name() -> None: + """Both identify it, and a batch run over many files wants the path as well as the name.""" + assert _describe(descriptor(contract_name="MyContract"), SOURCE) == f"MyContract ({SOURCE})" + + +def test_the_source_alone_is_used_when_the_descriptor_names_nothing() -> None: + assert _describe(descriptor(), SOURCE) == str(SOURCE) + + +def test_neither_is_stated_rather_than_printing_none() -> None: + """The old message read "file None", which named nothing at all.""" + assert _describe(descriptor(), None) == "" + + +@pytest.mark.parametrize("source", [None, SOURCE]) +def test_it_never_returns_none_as_text(source: HttpUrl | None) -> None: + assert "None" not in _describe(descriptor(), source)