Skip to content

Conversation

ernestognw
Copy link
Member

@ernestognw ernestognw commented Aug 30, 2025

Fixes #4318

PR Checklist

  • Tests
  • Documentation
  • Changeset entry (run npx changeset add)

Summary by Sourcery

Add support for dynamic EIP-712 domain separator generation using ERC-5267 bit flags

New Features:

  • Introduce MessageHashUtils.toDomainSeparator to compute EIP-712 domain separators dynamically based on ERC-5267
  • Implement assembly-based builders for conditional type hash and domain separator construction using bit-flag field selection

Copy link

changeset-bot bot commented Aug 30, 2025

🦋 Changeset detected

Latest commit: 129a8b3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
openzeppelin-solidity Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

sourcery-ai bot commented Aug 30, 2025

Reviewer's Guide

Introduces a dynamic EIP-712 domain separator generator that reads an eip712Domain return tuple and assembles both the type string and the domain separator entirely in memory using bit-flag dispatch and inline assembly.

Class diagram for updated MessageHashUtils library

classDiagram
    class MessageHashUtils {
        +toDomainSeparator(eip712Domain: function) bytes32
        -_buildDynamicTypeHash(ptr: bytes32, fields: bytes1) (typePtr: bytes32, hash: bytes32)
        -_buildDynamicDomainSeparatorHash(ptr: bytes32, typePtr: bytes32, typeHash: bytes32, fields: bytes1, name: string, version: string, chainId: uint256, verifyingContract: address, salt: bytes32, extensions: uint256[]) bytes32
    }

    MessageHashUtils ..> Memory : uses
    MessageHashUtils ..> Strings : uses
Loading

File-Level Changes

Change Details Files
Add dynamic domain separator generation based on ERC-5267
  • Import Memory library and use free memory pointer management
  • Implement toDomainSeparator function to invoke eip712Domain and orchestrate hashing
  • Add _buildDynamicTypeHash assembly helper to construct and hash the EIP712Domain type string based on bit flags
  • Add _buildDynamicDomainSeparatorHash assembly helper to pack and hash domain fields conditionally
  • Reset memory pointer after hash computation
contracts/utils/cryptography/MessageHashUtils.sol

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

coderabbitai bot commented Aug 30, 2025

Walkthrough

The update adds a changeset declaring a new minor version and introduces new MessageHashUtils helpers for constructing EIP-712 domain typehashes and separators with selectively enabled fields, aligning with ERC-5267. It adds an error ERC5267ExtensionsNotSupported and three internal pure functions: toDomainSeparator (two overloads) and toDomainTypeHash. Tests are expanded to validate all field subsets (name, version, chainId, verifyingContract, salt) against reference encoders, covering both input styles (string and pre-hashed name/version).

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The title accurately and concisely describes the primary addition of dynamic domain separator hash generation based on ERC-5267, matching the main functionality introduced in the pull request.
Linked Issues Check ✅ Passed The changes introduce dynamic EIP-712 domain separator and type hash generation including the salt field per EIP-5267, directly addressing the request in issue #4318 to incorporate bytes32 salt into domain separator construction.
Out of Scope Changes Check ✅ Passed All code changes, including the new helper functions in MessageHashUtils, the accompanying tests, and the changeset entry, are directly related to implementing dynamic domain separator generation per the linked issue objectives, with no unrelated or extraneous modifications present.
Description Check ✅ Passed The pull request description includes a summary of the implemented feature, references the linked issue, and outlines the new functionality for dynamic EIP-712 domain separator generation based on ERC-5267, making it relevant to the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Amxx Amxx added this to the 5.6 milestone Sep 25, 2025
@Amxx Amxx marked this pull request as ready for review October 9, 2025 12:04
@Amxx Amxx requested a review from a team as a code owner October 9, 2025 12:04
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
contracts/utils/cryptography/MessageHashUtils.sol (1)

181-227: Verify trailing comma removal logic and consider adding a clarifying comment.

The implementation correctly constructs the EIP-712 domain type string dynamically. The trailing comma removal at line 220 uses clever arithmetic but may benefit from a comment for maintainability.

The logic works as follows:

  • and(fields, 0x1f) returns non-zero if any field is set
  • Double iszero converts to boolean (0 or 1)
  • sub(ptr, 1) when fields are present removes the trailing comma
  • When no fields are set, ptr remains unchanged and ')' is added directly after '('

Consider adding an inline comment at line 220 to explain the trailing comma removal:

+            // Remove trailing comma if any field is set: sub(ptr, fields != 0 ? 1 : 0)
             ptr := sub(ptr, iszero(iszero(and(fields, 0x1f))))
test/utils/cryptography/MessageHashUtils.test.js (1)

99-148: Comprehensive test coverage, but consider adding extension bit revert test.

The test suite excellently covers all 32 possible field combinations (2^5) by iterating over the bitmask. Each combination tests:

  1. String-based toDomainSeparator against ethers.js reference
  2. Bytes32-based toDomainSeparator against ethers.js reference
  3. toDomainTypeHash against computed type string hash

This validates that:

  • Unused parameters are correctly ignored
  • Both input variants produce identical results
  • Empty domain (fields=0x00) works correctly

Consider adding an explicit test for the extension bit (0x20) revert case:

it('reverts on unsupported extensions', async function () {
  await expect(
    this.mock.$toDomainTypeHash(ethers.toBeHex(0x20))
  ).to.be.revertedWithCustomError(this.mock, 'ERC5267ExtensionsNotSupported');
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53bb340 and 7fdc8bd.

📒 Files selected for processing (3)
  • .changeset/blue-mirrors-agree.md (1 hunks)
  • contracts/utils/cryptography/MessageHashUtils.sol (2 hunks)
  • test/utils/cryptography/MessageHashUtils.test.js (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/utils/cryptography/MessageHashUtils.test.js (2)
test/helpers/eip712.js (1)
  • domain (11-17)
test/helpers/random.js (1)
  • generators (3-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: tests
  • GitHub Check: coverage
  • GitHub Check: slither
  • GitHub Check: tests-upgradeable
  • GitHub Check: tests-foundry
🔇 Additional comments (5)
contracts/utils/cryptography/MessageHashUtils.sol (4)

16-16: LGTM!

The error declaration follows Solidity conventions and will be used to signal unsupported ERC-5267 extensions.


102-134: LGTM!

The function correctly hashes string inputs and delegates to the core implementation. This provides a convenient interface for callers who haven't pre-hashed the name and version.


136-179: LGTM!

The core domain separator implementation correctly:

  • Retrieves the domain type hash based on field selection
  • Uses assembly for gas-efficient conditional field inclusion
  • Properly aligns the fields bitmask (line 149)
  • Sequentially stores enabled fields (lines 156-175)
  • Computes the final hash over the exact memory range

The bit-flag logic aligns with ERC-5267 specification.


183-183: LGTM!

The extension bit check (bit 5, value 0x20) correctly reverts when unsupported extensions are indicated, aligning with ERC-5267's extension mechanism.

test/utils/cryptography/MessageHashUtils.test.js (1)

5-6: LGTM!

The import additions correctly bring in domainType and generators needed for the new ERC-5267 tests.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
contracts/utils/cryptography/MessageHashUtils.sol (3)

16-16: — Consider enriching the revert for diagnosability

Include the offending fields in the error to simplify debugging and test assertions.

-    error ERC5267ExtensionsNotSupported();
+    error ERC5267ExtensionsNotSupported(bytes1 fields);

And emit it at call sites:

-        if (fields & 0x20 == 0x20) revert ERC5267ExtensionsNotSupported();
+        if ((fields & 0x20) != 0) revert ERC5267ExtensionsNotSupported(fields);

117-134: — Avoid hashing name/version when not used

Minor gas: only keccak256 name/version when corresponding bits are set.

-    function toDomainSeparator(
+    function toDomainSeparator(
         bytes1 fields,
         string memory name,
         string memory version,
         uint256 chainId,
         address verifyingContract,
         bytes32 salt
     ) internal pure returns (bytes32 hash) {
-        return
-            toDomainSeparator(
-                fields,
-                keccak256(bytes(name)),
-                keccak256(bytes(version)),
-                chainId,
-                verifyingContract,
-                salt
-            );
+        bytes32 nameHash;
+        bytes32 versionHash;
+        if ((fields & 0x01) != 0) nameHash = keccak256(bytes(name));
+        if ((fields & 0x02) != 0) versionHash = keccak256(bytes(version));
+        return toDomainSeparator(fields, nameHash, versionHash, chainId, verifyingContract, salt);
     }

181-187: — Clarify bitwise check precedence

Use explicit parentheses or compare against zero to avoid precedence ambiguities and improve readability.

-        if (fields & 0x20 == 0x20) revert ERC5267ExtensionsNotSupported();
+        if ((fields & 0x20) != 0) revert ERC5267ExtensionsNotSupported(fields);
.changeset/blue-mirrors-agree.md (1)

5-5: — Fix typo in changeset description

Change “selectivelly” → “selectively”.

-`MessageHashUtils`: Add helper functions to build EIP-712 domain typehash and separator with fields selectivelly enabled/disabled.
+`MessageHashUtils`: Add helper functions to build EIP-712 domain typehash and separator with fields selectively enabled/disabled.
test/utils/cryptography/MessageHashUtils.test.js (1)

99-148: — Add negative test for unsupported 0x20 flag

Current loop covers bits 0..4 only. Add a test asserting revert with ERC5267ExtensionsNotSupported when bit 0x20 is set.

   describe('ERC-5267', function () {
+    it('reverts on unsupported extensions bit (0x20)', async function () {
+      const fields = 0x20; // bit 5
+      await expect(this.mock.$toDomainTypeHash(ethers.toBeHex(fields)))
+        .to.be.revertedWithCustomError(this.mock, 'ERC5267ExtensionsNotSupported');
+      await expect(
+        this.mock.$toDomainSeparator(
+          ethers.toBeHex(fields),
+          ethers.Typed.string('n'),
+          ethers.Typed.string('v'),
+          1n,
+          ethers.Wallet.createRandom().address,
+          ethers.ZeroHash,
+        ),
+      ).to.be.revertedWithCustomError(this.mock, 'ERC5267ExtensionsNotSupported');
+    });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53bb340 and 7fdc8bd.

📒 Files selected for processing (3)
  • .changeset/blue-mirrors-agree.md (1 hunks)
  • contracts/utils/cryptography/MessageHashUtils.sol (2 hunks)
  • test/utils/cryptography/MessageHashUtils.test.js (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/utils/cryptography/MessageHashUtils.test.js (2)
test/helpers/eip712.js (1)
  • domain (11-17)
test/helpers/random.js (1)
  • generators (3-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: tests
  • GitHub Check: coverage
  • GitHub Check: slither
  • GitHub Check: tests-upgradeable
  • GitHub Check: tests-foundry
  • GitHub Check: Redirect rules - solidity-contracts
  • GitHub Check: Header rules - solidity-contracts
  • GitHub Check: Pages changed - solidity-contracts
🔇 Additional comments (3)
contracts/utils/cryptography/MessageHashUtils.sol (2)

137-179: Assembly encoding of domain separator looks correct

Field order, ABI word layout, and length computation match EIP‑712; ignoring non-selected fields is correct.


189-226: Type string builder is well-formed

String fragments, pointer math, trailing comma removal, and length computation are accurate.

test/utils/cryptography/MessageHashUtils.test.js (1)

5-6: Imports look correct and scoped to new tests

Brings in domainType and random generators needed for ERC‑5267 cases.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EIP712: _TYPE_HASH including salt

2 participants