Version of Hardhat
3.6.0
What happened?
test.solidity.eip712Types.include in Hardhat defines the include set only controls which struct names are emitted. The collector still walks every compiled source's AST and considers every struct it finds for name-collision detection. A struct in a file the user didn't include — and that nothing in the included scope imports — can therefore abort the test run with HHE818 if its name happens to clash with a selected struct, even though the user has no expectation that the tool would look at that file at all.
Forge's equivalent ([bind_json].include) behaves differently: structs in sources outside the include set never enter the registry.
Minimal reproduction steps
Self-contained dual-toolchain repro (forge test passes, npx hardhat test solidity throws HHE818 against the same source set): https://github.com/anaPerezGhiglia/repro-hh-eip712-include-scope.
Two small files plus a test:
contracts/A.sol — listed in eip712Types.include; defines library A { struct Order { address user; uint256 amount; } }.
contracts/B.sol — not included, not imported by A.sol or any test; defines a same-named struct Order { bytes32 id; bool active; }.
The repro repo's README has the full versions table, devcontainer setup, and an optional appendix that also validates the name-form path under forge bind-json.
Observed error
Error HHE818: Two different EIP-712 struct definitions named "Order" were found:
- contracts/A.sol
- contracts/B.sol
Thrown at collection time, before any test runs.
Expected behavior
include should bound which sources contribute structs to the registry. Conflict detection should fire only on selected-vs-selected conflicts (and on unselected conflicts reachable from a selected struct — the existing reachableFromSelected deferred-path machinery in canonicalize.ts already covers that). An unselected struct in an unimported file should not throw.
Environment
hardhat@3.6.0 (also reproduces on 3.5.1)
@nomicfoundation/edr@0.12.0-next.33
Search terms
eip712, EIP-712, HHE818
Version of Hardhat
3.6.0
What happened?
test.solidity.eip712Types.includein Hardhat defines the include set only controls which struct names are emitted. The collector still walks every compiled source's AST and considers every struct it finds for name-collision detection. A struct in a file the user didn't include — and that nothing in the included scope imports — can therefore abort the test run withHHE818if its name happens to clash with a selected struct, even though the user has no expectation that the tool would look at that file at all.Forge's equivalent (
[bind_json].include) behaves differently: structs in sources outside the include set never enter the registry.Minimal reproduction steps
Self-contained dual-toolchain repro (
forge testpasses,npx hardhat test soliditythrowsHHE818against the same source set): https://github.com/anaPerezGhiglia/repro-hh-eip712-include-scope.Two small files plus a test:
contracts/A.sol— listed ineip712Types.include; defineslibrary A { struct Order { address user; uint256 amount; } }.contracts/B.sol— not included, not imported byA.solor any test; defines a same-namedstruct Order { bytes32 id; bool active; }.The repro repo's README has the full versions table, devcontainer setup, and an optional appendix that also validates the name-form path under
forge bind-json.Observed error
Thrown at collection time, before any test runs.
Expected behavior
includeshould bound which sources contribute structs to the registry. Conflict detection should fire only on selected-vs-selected conflicts (and on unselected conflicts reachable from a selected struct — the existingreachableFromSelecteddeferred-path machinery incanonicalize.tsalready covers that). An unselected struct in an unimported file should not throw.Environment
hardhat@3.6.0(also reproduces on3.5.1)@nomicfoundation/edr@0.12.0-next.33Search terms
eip712, EIP-712, HHE818