Skip to content

EIP-712 type hashing in Solidity tests

Hardhat provides three cheatcodes for EIP-712 hashing in Solidity tests:

The first two look up your project’s struct definitions by name, avoiding hand-written canonical type strings. eip712HashTypedData computes a digest from a self-describing JSON payload and works without any configuration.

eip712HashType and eip712HashStruct need a registry of struct types to resolve names. Enable this by adding an eip712Types block to your hardhat.config.ts:

hardhat.config.ts
import { defineConfig } from "hardhat/config";
export default defineConfig({
test: {
solidity: {
eip712Types: {
include: ["contracts/**", "test/contracts/**"],
// exclude: ["contracts/legacy/**"], // Optional
},
},
},
});

Note: To include structs from npm packages, match against the input source name used by solc (e.g. "npm/@uniswap/permit2@*/src/interfaces/ISignatureTransfer.sol"). For the full reference, see Solidity tests configuration.

If your bundled forge-std doesn’t expose these cheatcodes on its vm interface yet, declare a local interface and call them through the cheatcode address.

Given these struct definitions in contracts/Eip712Types.sol:

struct Person {
address wallet;
string name;
}
struct Mail {
Person from;
Person to;
string contents;
}

You can hash types and structs in your tests:

import "forge-std/Test.sol";
import "../../contracts/Eip712Types.sol";
interface IEip712Cheats {
function eip712HashType(string calldata) external pure returns (bytes32);
function eip712HashStruct(
string calldata,
bytes calldata
) external pure returns (bytes32);
function eip712HashTypedData(string calldata) external pure returns (bytes32);
}
contract Eip712Test is Test {
IEip712Cheats cheats = IEip712Cheats(address(vm));
function testMailTypeHash() public {
bytes32 expected = keccak256(
bytes(
"Mail(Person from,Person to,string contents)"
"Person(address wallet,string name)"
)
);
assertEq(cheats.eip712HashType("Mail"), expected);
}
function testMailStructHash() public {
Mail memory m = Mail({
from: Person({ wallet: address(0xA), name: "Alice" }),
to: Person({ wallet: address(0xB), name: "Bob" }),
contents: "hello"
});
bytes32 hash = cheats.eip712HashStruct("Mail", abi.encode(m));
// hash now equals the standard EIP-712 struct hash for `m`.
}
}

Each struct name must map to exactly one definition across the collected set. If two different structs share a name, the test run fails with error HHE818. To fix this, rename one of the structs or narrow your include/exclude globs. Identical definitions across multiple build infos are deduplicated silently.