Skip to content

chore(forge): cheat eip712 struct hash #10626

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 29, 2025

Conversation

0xrusowsky
Copy link
Contributor

@0xrusowsky 0xrusowsky commented May 25, 2025

Adds a new cheatcode to safely generate struct hashes following the EIP-712 spec, further enhancing Foundry's support.
Complementary of:

implementation details

ref #4818

the cheatcode accepts two primary arguments:

  1. primary type identifier:
    • the type name, which will we will attempt to retrieve from the bindings generated by forge bind-json (recommended)
    • the full string representation of the type definition. In this case, we leverage alloy's eip712 support to generate the canonical representation of the type even if users pass malformed types (unsorted or with extra whitespaces).
  2. abi-encoded data of the EIP-712 struct.

internally, the cheatcode implementation leverages ⁠alloy to encode the data following the EIP-712 spec.

context

while ⁠vm.eip712HashType offers a reliable method for obtaining the type hash, developers also need to compute the ⁠hashStruct(message) to prepare data for signing or to verify signatures. Manually implementing the ⁠data encoding logic in Solidity is a significant source of errors.

vm.eip712HashStruct abstracts this complexity. It provides a dependable way to obtain the correct struct hash for any given EIP-712 compliant data directly within Foundry.

this new cheatcode aims to provide robustness to the codebases, and peace of mind for developers, giving them guarantees that the encoding scheme that they used is correct.

example usage

using the built-in json bindings to avoid passing the full type definition:

function testHashPermitSingle() public {
        PermitDetails memory details = PermitDetails({
            token: 0x1111111111111111111111111111111111111111,
            amount: 1000 ether,
            expiration: 12345,
            nonce: 1
        });

        PermitSingle memory permit = PermitSingle({
            details: details,
            spender: 0x2222222222222222222222222222222222222222,
            sigDeadline: 12345
        });

        // user-computed permit (using uniswap hash library)
        bytes32 userStructHash = PermitHash.hash(permit);

        // cheatcode-computed permit
        // can figure out the canonical type from the previously generated bindings with `forge bind-json`
        bytes32 cheatStructHash = vm.eip712HashStruct("PermitSingle", abi.encode(permit));
        console.log("PermitSingle struct hash from cheatcode:");
        console.logBytes32(cheatStructHash);

        assertEq(cheatStructHash, userStructHash, "permit struct hash mismatch");

        // cheatcode-computed permit,
        // can figure out the canonical type from a messy string representation of the type,
        // with an invalid order and extra whitespaces
        cheatStructHash = vm.eip712HashStruct("PermitDetails(address token, uint160 amount, uint48 expiration, uint48 nonce) PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)", abi.encode(permit));
        console.log("PermitSingle struct hash from cheatcode:");
        console.logBytes32(cheatStructHash);

        assertEq(cheatStructHash, userStructHash, "permit struct hash mismatch");
    }

important note

when using vm.⁠eip712HashType:

  • Passing a type string representation: validates EIP-712 formatting, canonicalizing messy inputs. However, it does NOT guarantee the string perfectly matches the smart contract's struct. For instance, a typo like ⁠amont instead of ⁠amount in the string will still produce a valid EIP-712 hash, but for the incorrect type, leading to runtime signature verification errors.
  • Passing a type name: (RECOMMENDED METHOD) uses ⁠forge bind-json generated bindings, ensuring the hash corresponds to your actual compiled contract structs. This provides a guarantee of alignment, catching discrepancies that the string method would miss.

@0xrusowsky 0xrusowsky self-assigned this May 25, 2025
@0xrusowsky 0xrusowsky added the A-cheatcodes Area: cheatcodes label May 25, 2025
@0xrusowsky 0xrusowsky linked an issue May 25, 2025 that may be closed by this pull request
@0xrusowsky 0xrusowsky changed the title chore: cheat eip712 struct hash chore(forge): cheat eip712 struct hash May 26, 2025
@jenpaff jenpaff moved this to Ready For Review in Foundry May 27, 2025
@0xrusowsky
Copy link
Contributor Author

@Philogy @PatrickAlphaC how would u feel about this complimentary cheatcode?

@Philogy
Copy link
Contributor

Philogy commented May 27, 2025

@0xrusowsky Where does this magic permit.serialize method come from? Why accept JSON serialized data and not abi-encoded?

@0xrusowsky
Copy link
Contributor Author

@0xrusowsky Where does this magic permit.serialize method come from? Why accept JSON serialized data and not abi-encoded?

it is thanks to bindings that are auto-generated when running forge bind-json.

for full ctx see: #8345 (comment)

@Philogy
Copy link
Contributor

Philogy commented May 27, 2025

@0xrusowsky I see, still seems less convenient than just being able to directly do abi.encode on the struct. Invoking a separate command and ensuring you re-run it to keep it in sync seems tedious.

@0xrusowsky 0xrusowsky marked this pull request as draft May 28, 2025 12:56
@0xrusowsky 0xrusowsky marked this pull request as ready for review May 28, 2025 14:01
Copy link
Collaborator

@grandizzy grandizzy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

Copy link
Member

@yash-atreya yash-atreya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests look good! Can be added as examples in the book as well, under the guides section perhaps?

Copy link
Member

@zerosnacks zerosnacks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

@0xrusowsky 0xrusowsky merged commit 4b23502 into chore/cheat-eip712 May 29, 2025
20 of 22 checks passed
@0xrusowsky 0xrusowsky deleted the chore/cheat-eip712-struct-hash branch May 29, 2025 12:22
@github-project-automation github-project-automation bot moved this from Ready For Review to Done in Foundry May 29, 2025
@grandizzy grandizzy moved this from Done to Completed in Foundry Jun 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-cheatcodes Area: cheatcodes
Projects
Status: Completed
Development

Successfully merging this pull request may close these issues.

Cheatcode: EIP712 canonical hashing
7 participants