Skip to content

Commit

Permalink
feat(BACK-8208): tooling: Allow generation from string inputs for ABI…
Browse files Browse the repository at this point in the history
…s and and schemas
  • Loading branch information
fsamier committed Jan 9, 2025
1 parent 961f433 commit 2367963
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 27 deletions.
35 changes: 16 additions & 19 deletions src/erc7730/generate/generate.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from collections.abc import Generator
from pathlib import Path
from typing import Any, assert_never

from caseswitcher import to_title
Expand Down Expand Up @@ -47,29 +46,29 @@
def generate_descriptor(
chain_id: int,
contract_address: Address,
abi_file: Path | None = None,
eip712_schema_file: Path | None = None,
abi: str | bytes | None = None,
eip712_schema: str | bytes | None = None,
owner: str | None = None,
legal_name: str | None = None,
url: HttpUrl | None = None,
) -> InputERC7730Descriptor:
"""
Generate an ERC-7730 descriptor.
If an EIP-712 schema file is provided, an EIP-712 descriptor is generated for this schema, otherwise a calldata
descriptor. If no ABI file is supplied, the ABIs are fetched from Etherscan using the chain id / contract address.
If an EIP-712 schema is provided, an EIP-712 descriptor is generated for this schema, otherwise a calldata
descriptor. If no ABI is supplied, the ABIs are fetched from Etherscan using the chain id / contract address.
:param chain_id: contract chain id
:param contract_address: contract address
:param abi_file: path to a JSON ABI file (to generate a calldata descriptor)
:param eip712_schema_file: path to an EIP-712 schema (to generate an EIP-712 descriptor)
:param abi: JSON ABI string or buffer representation (to generate a calldata descriptor)
:param eip712_schema: JSON EIP-712 schema string or buffer representation (to generate an EIP-712 descriptor)
:param owner: the display name of the owner or target of the contract / message to be clear signed
:param legal_name: the full legal name of the owner if different from the owner field
:param url: URL with more info on the entity the user interacts with
:return: a generated ERC-7730 descriptor
"""

context, trees = _generate_context(chain_id, contract_address, abi_file, eip712_schema_file)
context, trees = _generate_context(chain_id, contract_address, abi, eip712_schema)
metadata = _generate_metadata(legal_name, owner, url)
display = _generate_display(trees)

Expand All @@ -82,18 +81,17 @@ def _generate_metadata(owner: str | None, legal_name: str | None, url: HttpUrl |


def _generate_context(
chain_id: int, contract_address: Address, abi_file: Path | None, eip712_schema_file: Path | None
chain_id: int, contract_address: Address, abi: str | bytes | None, eip712_schema: str | bytes | None
) -> tuple[InputContractContext | InputEIP712Context, dict[str, SchemaTree]]:
if eip712_schema_file is not None:
return _generate_context_eip712(chain_id, contract_address, eip712_schema_file)
return _generate_context_calldata(chain_id, contract_address, abi_file)
if eip712_schema is not None:
return _generate_context_eip712(chain_id, contract_address, eip712_schema)
return _generate_context_calldata(chain_id, contract_address, abi)


def _generate_context_eip712(
chain_id: int, contract_address: Address, eip712_schema_file: Path
chain_id: int, contract_address: Address, eip712_schema: str | bytes
) -> tuple[InputEIP712Context, dict[str, SchemaTree]]:
with open(eip712_schema_file, "rb") as f:
schemas = TypeAdapter(list[EIP712Schema]).validate_json(f.read())
schemas = TypeAdapter(list[EIP712Schema]).validate_json(eip712_schema)

context = InputEIP712Context(
eip712=InputEIP712(schemas=schemas, deployments=[InputDeployment(chainId=chain_id, address=contract_address)])
Expand All @@ -105,11 +103,10 @@ def _generate_context_eip712(


def _generate_context_calldata(
chain_id: int, contract_address: Address, abi_file: Path | None
chain_id: int, contract_address: Address, abi: str | bytes | None
) -> tuple[InputContractContext, dict[str, SchemaTree]]:
if abi_file is not None:
with open(abi_file, "rb") as f:
abis = TypeAdapter(list[ABI]).validate_json(f.read())
if abi is not None:
abis = TypeAdapter(list[ABI]).validate_json(abi)

elif (abis := get_contract_abis(chain_id, contract_address)) is None:
raise Exception("Failed to fetch contract ABIs")
Expand Down
17 changes: 15 additions & 2 deletions src/erc7730/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,24 @@ def command_generate(
legal_name: Annotated[str | None, Option(help="The full legal name of the owner")] = None,
url: Annotated[str | None, Option(help="URL with more info on the entity interacted with")] = None,
) -> None:
if schema is not None and abi is not None:
print("Cannot specify both ABI and schema.")
raise Exit(1)
schema_buffer = None
abi_buffer = None

if schema is not None:
with open(schema, "rb") as f:
schema_buffer = f.read()
elif abi is not None:
with open(abi, "rb") as f:
abi_buffer = f.read()

descriptor = generate_descriptor(
chain_id=chain_id,
contract_address=address,
abi_file=abi,
eip712_schema_file=schema,
abi=abi_buffer,
eip712_schema=schema_buffer,
owner=owner,
legal_name=legal_name,
url=HttpUrl(url) if url is not None else None,
Expand Down
38 changes: 32 additions & 6 deletions tests/generate/test_generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,49 @@ def test_generate_from_contract_address(label: str, chain_id: int, contract_addr


@pytest.mark.parametrize("test_file", sorted(glob(str(DATA / "abis*.json"))), ids=lambda f: Path(f).stem)
def test_generate_from_abis(test_file: str) -> None:
def test_generate_from_abis_buffer(test_file: str) -> None:
"""Generate descriptor using a provided ABI file."""
_assert_descriptor_valid(
generate_descriptor(
chain_id=1, contract_address=Address("0x0000000000000000000000000000000000000000"), abi_file=Path(test_file)
with open(test_file, "rb") as f:
_assert_descriptor_valid(
generate_descriptor(
chain_id=1, contract_address=Address("0x0000000000000000000000000000000000000000"), abi=f.read()
)
)


@pytest.mark.parametrize("test_file", sorted(glob(str(DATA / "abis*.json"))), ids=lambda f: Path(f).stem)
def test_generate_from_abis_string(test_file: str) -> None:
"""Generate descriptor using a provided ABI file."""
with open(test_file, "rb") as f:
abi = f.read().decode("utf-8")
_assert_descriptor_valid(
generate_descriptor(chain_id=1, contract_address=Address("0x0000000000000000000000000000000000000000"), abi=abi)
)


@pytest.mark.parametrize("test_file", sorted(glob(str(DATA / "schemas*.json"))), ids=lambda f: Path(f).stem)
def test_generate_from_eip712_schemas(test_file: str) -> None:
def test_generate_from_eip712_schemas_buffer(test_file: str) -> None:
"""Generate descriptor using a provided EIP-712 file."""
with open(test_file, "rb") as f:
_assert_descriptor_valid(
generate_descriptor(
chain_id=1,
contract_address=Address("0x0000000000000000000000000000000000000000"),
eip712_schema=f.read(),
)
)


@pytest.mark.parametrize("test_file", sorted(glob(str(DATA / "schemas*.json"))), ids=lambda f: Path(f).stem)
def test_generate_from_eip712_schemas_string(test_file: str) -> None:
"""Generate descriptor using a provided EIP-712 file."""
with open(test_file, "rb") as f:
schema = f.read().decode("utf-8")
_assert_descriptor_valid(
generate_descriptor(
chain_id=1,
contract_address=Address("0x0000000000000000000000000000000000000000"),
eip712_schema_file=Path(test_file),
eip712_schema=schema,
)
)

Expand Down

0 comments on commit 2367963

Please sign in to comment.