From b4b3bb1e95daa4208368654adfba75ba27d88074 Mon Sep 17 00:00:00 2001 From: Frederic Samier Date: Thu, 6 Aug 2026 14:18:46 +0200 Subject: [PATCH] test: consolidate v2 calldata conversion tests Enrich the v2 calldata conversion suite to cover all implemented field formats (raw, amount, duration, chainId, tokenTicker, addressName, tokenAmount, nftName, calldata, enum, date, unit) and consolidate the per-format files into a single parametrized module with an inline descriptor helper. --- .../convert/calldata/test_convert_chain_id.py | 52 ----- .../convert/calldata/test_convert_fields.py | 215 ++++++++++++++++++ .../calldata/test_convert_token_ticker.py | 53 ----- 3 files changed, 215 insertions(+), 105 deletions(-) delete mode 100644 tests/v2/convert/calldata/test_convert_chain_id.py create mode 100644 tests/v2/convert/calldata/test_convert_fields.py delete mode 100644 tests/v2/convert/calldata/test_convert_token_ticker.py diff --git a/tests/v2/convert/calldata/test_convert_chain_id.py b/tests/v2/convert/calldata/test_convert_chain_id.py deleted file mode 100644 index b2e34e72..00000000 --- a/tests/v2/convert/calldata/test_convert_chain_id.py +++ /dev/null @@ -1,52 +0,0 @@ -from erc7730.convert.calldata.convert_erc7730_v2_input_to_calldata import ( - erc7730_v2_descriptor_to_calldata_descriptors, -) -from erc7730.convert.calldata.v1.tlv import tlv_field -from erc7730.model.calldata.v1.param import ( - CalldataDescriptorParamNetworkV1, - CalldataDescriptorParamType, -) -from erc7730.model.input.v2.descriptor import InputERC7730Descriptor - -CHAIN_ID_DESCRIPTOR = """ -{ - "$schema": "specs/erc7730-v2.schema.json", - "context": { - "$id": "chain-id-test", - "contract": { - "deployments": [ - { "chainId": 1, "address": "0x0000000000000000000000000000000000000001" } - ] - } - }, - "metadata": { "owner": "Test Owner" }, - "display": { - "formats": { - "bridge(uint256 chainId)": { - "intent": "Bridge to network", - "fields": [ - { "path": "chainId", "label": "Network", "format": "chainId" } - ] - } - } - } -} -""" - - -def test_convert_chain_id() -> None: - descriptor = InputERC7730Descriptor.model_validate_json(CHAIN_ID_DESCRIPTOR) - - descriptors = erc7730_v2_descriptor_to_calldata_descriptors(descriptor, chain_id=1) - - assert len(descriptors) == 1 - fields = descriptors[0].fields - assert len(fields) == 1 - - field = fields[0] - assert field.name == "Network" - assert isinstance(field.param, CalldataDescriptorParamNetworkV1) - - # PARAM_TYPE tag (0x02) must serialize the NETWORK parameter type (0x0b) - tlv = tlv_field(field) - assert bytes([0x02, 0x01, CalldataDescriptorParamType.NETWORK]) in tlv diff --git a/tests/v2/convert/calldata/test_convert_fields.py b/tests/v2/convert/calldata/test_convert_fields.py new file mode 100644 index 00000000..de41eff9 --- /dev/null +++ b/tests/v2/convert/calldata/test_convert_fields.py @@ -0,0 +1,215 @@ +import json +from typing import Any + +import pytest + +from erc7730.convert.calldata.convert_erc7730_v2_input_to_calldata import ( + erc7730_v2_descriptor_to_calldata_descriptors, +) +from erc7730.convert.calldata.v1.tlv import CalldataDescriptorFieldTag, tlv_field +from erc7730.model.calldata.v1.instruction import CalldataDescriptorInstructionFieldV1 +from erc7730.model.calldata.v1.param import ( + CalldataDescriptorDateType, + CalldataDescriptorParamAmountV1, + CalldataDescriptorParamCalldataV1, + CalldataDescriptorParamDatetimeV1, + CalldataDescriptorParamDurationV1, + CalldataDescriptorParamEnumV1, + CalldataDescriptorParamNetworkV1, + CalldataDescriptorParamNFTV1, + CalldataDescriptorParamRawV1, + CalldataDescriptorParamTokenAmountV1, + CalldataDescriptorParamTokenV1, + CalldataDescriptorParamTrustedNameV1, + CalldataDescriptorParamType, + CalldataDescriptorParamUnitV1, + CalldataDescriptorParamV1, +) +from erc7730.model.input.v2.descriptor import InputERC7730Descriptor + +DEFAULT_CHAIN_ID = 1 +DEFAULT_ADDRESS = "0x0000000000000000000000000000000000000001" + + +def convert_field( + signature: str, + field: dict[str, Any], + *, + metadata: dict[str, Any] | None = None, + chain_id: int = DEFAULT_CHAIN_ID, +) -> CalldataDescriptorInstructionFieldV1: + """Build a minimal v2 descriptor around a single display field and return the converted calldata field.""" + meta: dict[str, Any] = {"owner": "Test Owner"} + if metadata is not None: + meta.update(metadata) + + descriptor = InputERC7730Descriptor.model_validate_json( + json.dumps( + { + "$schema": "specs/erc7730-v2.schema.json", + "context": { + "$id": "test", + "contract": {"deployments": [{"chainId": chain_id, "address": DEFAULT_ADDRESS}]}, + }, + "metadata": meta, + "display": {"formats": {signature: {"intent": "Test intent", "fields": [field]}}}, + } + ) + ) + + descriptors = erc7730_v2_descriptor_to_calldata_descriptors(descriptor, chain_id=chain_id) + assert len(descriptors) == 1 + assert len(descriptors[0].fields) == 1 + return descriptors[0].fields[0] + + +def assert_serializes_param_type( + field: CalldataDescriptorInstructionFieldV1, + param_type: CalldataDescriptorParamType, +) -> None: + """Assert the field TLV serialization carries the expected PARAM_TYPE tag / value.""" + tlv = tlv_field(field) + assert bytes([CalldataDescriptorFieldTag.PARAM_TYPE, 0x01, param_type]) in tlv + + +# Homogeneous cases: signature + display field -> expected param class / param type. +FIELD_CASES: list[tuple[str, str, dict[str, Any], type[CalldataDescriptorParamV1], CalldataDescriptorParamType]] = [ + ( + "raw", + "store(uint256 value)", + {"path": "value", "label": "Value", "format": "raw"}, + CalldataDescriptorParamRawV1, + CalldataDescriptorParamType.RAW, + ), + ( + "amount", + "pay(uint256 amount)", + {"path": "amount", "label": "Amount", "format": "amount"}, + CalldataDescriptorParamAmountV1, + CalldataDescriptorParamType.AMOUNT, + ), + ( + "duration", + "lock(uint256 period)", + {"path": "period", "label": "Duration", "format": "duration"}, + CalldataDescriptorParamDurationV1, + CalldataDescriptorParamType.DURATION, + ), + ( + "chainId", + "bridge(uint256 chainId)", + {"path": "chainId", "label": "Network", "format": "chainId"}, + CalldataDescriptorParamNetworkV1, + CalldataDescriptorParamType.NETWORK, + ), + ( + "tokenTicker", + "getTokenTicker(address token)", + {"path": "token", "label": "Token", "format": "tokenTicker"}, + CalldataDescriptorParamTokenV1, + CalldataDescriptorParamType.TOKEN, + ), + ( + "addressName", + "transfer(address recipient)", + {"path": "recipient", "label": "Recipient", "format": "addressName", "params": {"types": ["contract"]}}, + CalldataDescriptorParamTrustedNameV1, + CalldataDescriptorParamType.TRUSTED_NAME, + ), + ( + "tokenAmount", + "transfer(address token, uint256 amount)", + {"path": "amount", "label": "Amount", "format": "tokenAmount", "params": {"tokenPath": "token"}}, + CalldataDescriptorParamTokenAmountV1, + CalldataDescriptorParamType.TOKEN_AMOUNT, + ), + ( + "nftName", + "transferNft(address collection, uint256 tokenId)", + {"path": "tokenId", "label": "NFT", "format": "nftName", "params": {"collectionPath": "collection"}}, + CalldataDescriptorParamNFTV1, + CalldataDescriptorParamType.NFT, + ), + ( + "calldata", + "execute(address target, bytes data)", + {"path": "data", "label": "Embedded call", "format": "calldata", "params": {"calleePath": "target"}}, + CalldataDescriptorParamCalldataV1, + CalldataDescriptorParamType.CALLDATA, + ), + ( + "enum", + "setMode(uint8 mode)", + {"path": "mode", "label": "Mode", "format": "enum", "params": {"$ref": "$.metadata.enums.interestRateMode"}}, + CalldataDescriptorParamEnumV1, + CalldataDescriptorParamType.ENUM, + ), +] + +_ENUM_METADATA = {"enums": {"interestRateMode": {"1": "stable", "2": "variable"}}} + + +@pytest.mark.parametrize( + ("signature", "field", "param_class", "param_type"), + [(sig, field, cls, ptype) for _, sig, field, cls, ptype in FIELD_CASES], + ids=[case_id for case_id, *_ in FIELD_CASES], +) +def test_convert_field( + signature: str, + field: dict[str, Any], + param_class: type[CalldataDescriptorParamV1], + param_type: CalldataDescriptorParamType, +) -> None: + metadata = _ENUM_METADATA if field["format"] == "enum" else None + output = convert_field(signature, field, metadata=metadata) + + assert output.name == field["label"] + assert isinstance(output.param, param_class) + assert_serializes_param_type(output, param_type) + + +@pytest.mark.parametrize( + ("encoding", "expected"), + [("timestamp", CalldataDescriptorDateType.UNIX), ("blockheight", CalldataDescriptorDateType.BLOCK_HEIGHT)], +) +def test_convert_date_encoding(encoding: str, expected: CalldataDescriptorDateType) -> None: + field = convert_field( + "schedule(uint256 deadline)", + {"path": "deadline", "label": "Deadline", "format": "date", "params": {"encoding": encoding}}, + ) + + assert isinstance(field.param, CalldataDescriptorParamDatetimeV1) + assert field.param.date_type == expected + assert_serializes_param_type(field, CalldataDescriptorParamType.DATETIME) + + +def test_convert_unit_params() -> None: + field = convert_field( + "setRate(uint256 rate)", + {"path": "rate", "label": "Rate", "format": "unit", "params": {"base": "%", "decimals": 4, "prefix": False}}, + ) + + assert isinstance(field.param, CalldataDescriptorParamUnitV1) + assert field.param.base == "%" + assert field.param.decimals == 4 + assert field.param.prefix is False + + +def test_convert_token_ticker_has_no_native_currencies() -> None: + field = convert_field( + "getTokenTicker(address token)", + {"path": "token", "label": "Token", "format": "tokenTicker"}, + ) + + assert isinstance(field.param, CalldataDescriptorParamTokenV1) + assert field.param.native_currencies is None + + +def test_convert_token_amount_resolves_token_path() -> None: + field = convert_field( + "transfer(address token, uint256 amount)", + {"path": "amount", "label": "Amount", "format": "tokenAmount", "params": {"tokenPath": "token"}}, + ) + + assert isinstance(field.param, CalldataDescriptorParamTokenAmountV1) + assert field.param.token is not None diff --git a/tests/v2/convert/calldata/test_convert_token_ticker.py b/tests/v2/convert/calldata/test_convert_token_ticker.py deleted file mode 100644 index fd96c8a4..00000000 --- a/tests/v2/convert/calldata/test_convert_token_ticker.py +++ /dev/null @@ -1,53 +0,0 @@ -from erc7730.convert.calldata.convert_erc7730_v2_input_to_calldata import ( - erc7730_v2_descriptor_to_calldata_descriptors, -) -from erc7730.convert.calldata.v1.tlv import tlv_field -from erc7730.model.calldata.v1.param import ( - CalldataDescriptorParamTokenV1, - CalldataDescriptorParamType, -) -from erc7730.model.input.v2.descriptor import InputERC7730Descriptor - -TOKEN_TICKER_DESCRIPTOR = """ -{ - "$schema": "specs/erc7730-v2.schema.json", - "context": { - "$id": "token-ticker-test", - "contract": { - "deployments": [ - { "chainId": 1, "address": "0x0000000000000000000000000000000000000001" } - ] - } - }, - "metadata": { "owner": "Test Owner" }, - "display": { - "formats": { - "getTokenTicker(address token)": { - "intent": "Get token ticker", - "fields": [ - { "path": "token", "label": "Token", "format": "tokenTicker" } - ] - } - } - } -} -""" - - -def test_convert_token_ticker() -> None: - descriptor = InputERC7730Descriptor.model_validate_json(TOKEN_TICKER_DESCRIPTOR) - - descriptors = erc7730_v2_descriptor_to_calldata_descriptors(descriptor, chain_id=1) - - assert len(descriptors) == 1 - fields = descriptors[0].fields - assert len(fields) == 1 - - field = fields[0] - assert field.name == "Token" - assert isinstance(field.param, CalldataDescriptorParamTokenV1) - assert field.param.native_currencies is None - - # PARAM_TYPE tag (0x02) must serialize the TOKEN parameter type (0x0a) - tlv = tlv_field(field) - assert bytes([0x02, 0x01, CalldataDescriptorParamType.TOKEN]) in tlv