Skip to content

fix(dyn-abi): allow self-referential struct types in EIP-712 canonicalize (#1103)#1105

Merged
DaniPopes merged 1 commit into
alloy-rs:mainfrom
0xSoftBoi:fix/1103-eip712-recursive-canonicalize
May 23, 2026
Merged

fix(dyn-abi): allow self-referential struct types in EIP-712 canonicalize (#1103)#1105
DaniPopes merged 1 commit into
alloy-rs:mainfrom
0xSoftBoi:fix/1103-eip712-recursive-canonicalize

Conversation

@0xSoftBoi

Copy link
Copy Markdown
Contributor

Summary

  • EncodeType::canonicalize rejected recursive struct types such as Node(uint256 value, Node[] children) with MissingType("primary component"), even though EIP-712 states "The standard supports recursive struct types."
  • Resolver::non_dependent_types treated a self-edge as making a type dependent on itself, so the primary component iterator came back empty and canonicalize errored out before any cycle check.

Fix

  • non_dependent_types now ignores self-edges when computing the dependent set, so a self-referential type is still recognised as a non-dependent (primary) component.
  • Cycle detection is split into a strict variant (used by Resolver::resolve, which builds a DynSolType and therefore cannot represent recursive types) and a permissive variant used by Resolver::linearize / Resolver::encode_type. Cycles between distinct types are still rejected on both paths.
  • canonicalize no longer calls the redundant resolver.resolve(primary); encode_type already walks the dependency graph via linearize and surfaces missing-type and circular-dependency errors.

TypedData::eip712_signing_hash still returns CircularDependency for self-referential types because the encode_data path goes through resolve; only the encodeType / typeHash path becomes permissive. The existing test_hash_recursive_types continues to pass unchanged.

How it was tested

New tests (all pass; cargo fmt and cargo clippy -- -D warnings are clean):

  • eip712::parser::tests::canonicalize_self_referential_type — the exact repro from the issue.
  • eip712::parser::tests::canonicalize_self_referential_type_with_dependency — recursive primary with an extra leaf type.
  • eip712::resolver::tests::non_dependent_self_referential_type_is_primary
  • eip712::resolver::tests::encode_type_round_trip_self_referential
  • eip712::resolver::tests::resolve_still_rejects_self_referential_types
  • eip712::resolver::tests::encode_type_still_rejects_mutual_recursion

Pre-existing test_fails_encode_type_circular (mutual recursion through distinct types) and test_hash_recursive_types continue to pass.

Full cargo test -p alloy-dyn-abi --all-features: 194 passed; 0 failed.

Fixes #1103

…lize

EIP-712 explicitly permits recursive struct types
("The standard supports recursive struct types."), but
`EncodeType::canonicalize` previously rejected them with
`MissingType("primary component")`. A self-referential type was treated
as dependent on itself in `Resolver::non_dependent_types`, so the
iterator that identifies the primary component came back empty.

This change:

* Stops treating a self-edge as a dependency in `non_dependent_types`,
  so a type like `Node(uint256 value, Node[] children)` is correctly
  picked up as the primary component.
* Splits cycle detection into a strict variant (used by
  `Resolver::resolve`, which builds a `DynSolType` and therefore
  cannot represent recursive types) and a permissive variant used by
  `Resolver::linearize` / `Resolver::encode_type`. Cycles between
  distinct types are still rejected on both paths.
* Drops the redundant `resolver.resolve(primary)` call in
  `canonicalize` — `encode_type` already walks the dependency graph
  and surfaces missing-type and circular-dependency errors.

`TypedData::eip712_signing_hash` still returns `CircularDependency` for
self-referential types because it goes through `resolve` to build
encode_data; only the encodeType / typeHash path is now permissive.

Fixes alloy-rs#1103
@github-project-automation github-project-automation Bot moved this to Reviewed in Alloy May 14, 2026
@0xSoftBoi

Copy link
Copy Markdown
Contributor Author

Friendly ping — this is approved and mergeable. Happy to rebase if needed, otherwise let me know if anything else is required before merge. Thanks!

@DaniPopes
DaniPopes merged commit f7d1c53 into alloy-rs:main May 23, 2026
31 checks passed
@github-project-automation github-project-automation Bot moved this from Reviewed to Done in Alloy May 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Bug] EncodeType::canonicalize rejects EIP-712 recursive struct types

2 participants