fix(dyn-abi): allow self-referential struct types in EIP-712 canonicalize (#1103)#1105
Merged
DaniPopes merged 1 commit intoMay 23, 2026
Conversation
…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
DaniPopes
approved these changes
May 14, 2026
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! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
EncodeType::canonicalizerejected recursive struct types such asNode(uint256 value, Node[] children)withMissingType("primary component"), even though EIP-712 states "The standard supports recursive struct types."Resolver::non_dependent_typestreated a self-edge as making a type dependent on itself, so the primary component iterator came back empty andcanonicalizeerrored out before any cycle check.Fix
non_dependent_typesnow ignores self-edges when computing the dependent set, so a self-referential type is still recognised as a non-dependent (primary) component.Resolver::resolve, which builds aDynSolTypeand therefore cannot represent recursive types) and a permissive variant used byResolver::linearize/Resolver::encode_type. Cycles between distinct types are still rejected on both paths.canonicalizeno longer calls the redundantresolver.resolve(primary);encode_typealready walks the dependency graph vialinearizeand surfaces missing-type and circular-dependency errors.TypedData::eip712_signing_hashstill returnsCircularDependencyfor self-referential types because theencode_datapath goes throughresolve; only theencodeType/typeHashpath becomes permissive. The existingtest_hash_recursive_typescontinues to pass unchanged.How it was tested
New tests (all pass;
cargo fmtandcargo clippy -- -D warningsare 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_primaryeip712::resolver::tests::encode_type_round_trip_self_referentialeip712::resolver::tests::resolve_still_rejects_self_referential_typeseip712::resolver::tests::encode_type_still_rejects_mutual_recursionPre-existing
test_fails_encode_type_circular(mutual recursion through distinct types) andtest_hash_recursive_typescontinue to pass.Full
cargo test -p alloy-dyn-abi --all-features: 194 passed; 0 failed.Fixes #1103