-
Notifications
You must be signed in to change notification settings - Fork 3
feat(#2): implement tree-based cross-chain permits #54
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
base: feat/tree-lib
Are you sure you want to change the base?
Conversation
Implement tree-based cross-chain permit system with UI transparency and gas-efficient on-chain verification using TreeNodeLib. Core changes: - Add PermitNode tree structure for UI-readable permits - Add PermitTree compact on-chain structure - Add Signature struct for cleaner API - New typehashes: PERMIT3_TYPEHASH, MULTICHAIN_PERMIT3_TYPEHASH, PERMIT_NODE_TYPEHASH - Refactor Permit3.sol to use tree reconstruction - Update PermitBase.sol for new structures - Update all permit-related interfaces Test coverage: - PermitNodeReconstruction.t.sol (540 lines) - Hash reconstruction tests - PermitTreeIntegration.t.sol (296 lines) - End-to-end integration - NestedStructure.t.sol (137 lines) - EIP-712 nested struct tests - Updated Permit3.t.sol, Permit3Edge.t.sol, Permit3Witness.t.sol - Updated MultiTokenPermit.t.sol and related tests JavaScript utilities: - permitNodeHelpers.js (1,192 lines) - Complete tree construction toolkit - test-permitNode.js (288 lines) - JS test suite - merkle-helpers.js (636 lines) - Legacy Merkle utilities - Comprehensive README documentation Key benefits: - Users sign complete tree structure (full transparency) - Contract receives compact proof (gas efficient) - O(log n) proof size with linear gas scaling - Three deterministic combination rules 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This pull request introduces comprehensive JavaScript utilities for Permit3's tree-based cross-chain permit system. The changes add implementation of EIP-712 PermitNode tree construction, proof generation, and Solidity test infrastructure to support the new tree-based permit architecture.
Key Changes:
- Implementation of
permitNodeHelpers.jswith tree encoding/proof generation algorithms matching on-chain Solidity reconstruction - Addition of
test-permitNode.jscomprehensive test suite with 6 test scenarios - New Solidity test helpers (
TreeNodeLibTester,PermitNodeLibTester) for testing generic tree reconstruction - Extensive integration tests for tree-based permit execution across various topologies
Reviewed Changes
Copilot reviewed 29 out of 30 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| utils/permitNodeHelpers.js | Core implementation of PermitNode tree hashing, proof generation, and encoding matching Solidity reconstruction algorithm |
| utils/test-permitNode.js | Comprehensive test script validating tree construction and proof generation for 2-5 chain scenarios |
| utils/merkle-helpers.js | Legacy Merkle tree utilities with backward compatibility aliases for permit node structures |
| utils/README.md | Documentation covering tree concepts, encoding format, usage examples, and migration guide |
| utils/package.json | Package configuration with ethers v5, merkletreejs, and keccak256 dependencies |
| test/utils/TreeNodeLibTester.sol | Generic tree node library test wrapper exposing internal combination functions |
| test/utils/PermitNodeLibTester.sol | Permit-specific wrapper around TreeNodeLib maintaining backward compatibility |
| test/utils/TestBase.sol | Enhanced base test with _hashPermitNode() helper and sorting utilities |
| test/TreeNodeLib.t.sol | Comprehensive tests (65 tests) for generic TreeNodeLib with both permit and nonce typehashes |
| test/PermitTreeIntegration.t.sol | End-to-end integration tests for tree-based permit execution with various topologies |
| test/PermitNodeReconstruction.t.sol | Tests for EIP-712 PermitNode hash reconstruction across 15 different tree structures |
| test/Permit3Witness.t.sol | Updated witness tests migrated from merkle proofs to tree structures |
| test/Permit3.t.sol | Updated core permit tests using new tree-based signature structure |
Files not reviewed (1)
- utils/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
# Conflicts: # test/ZeroAddressValidation.t.sol
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these supposed to be committed? If so, let's do all the JS things separately.
src/interfaces/IPermit3.sol
Outdated
| * @dev Used in EIP-712 signatures to provide transparency to users about what they're signing | ||
| * @dev Can represent either leaf nodes (ChainPermits) or internal tree nodes (nested levels) | ||
| * @dev Both arrays should be ordered by hash value as merkle tree construction requires | ||
| * @param levels Child tree nodes for internal nodes (ordered by hash value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No levels
src/interfaces/IPermit3.sol
Outdated
| * @param proofStructure Compact tree encoding | ||
| * @param currentChainPermits Permit operations for the current chain |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * @param proofStructure Compact tree encoding | |
| * @param currentChainPermits Permit operations for the current chain | |
| * @param currentChainPermits Permit operations for the current chain | |
| * @param proofStructure Compact tree encoding |
| * @param permits Array of permit operations to execute | ||
| * @param signature EIP-712 signature authorizing all permits in the batch | ||
| */ | ||
| function permit( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need this function? Why can't the single-chain permit be treated as a single-node tree?
| // processProof performs validation internally and provides granular error messages | ||
| params.merkleRoot = MerkleProof.processProof(proof, params.currentChainHash); | ||
| // Reconstruct the PermitNode hash from the proof and tree structure | ||
| bytes32 permitNodeHash = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| bytes32 permitNodeHash = | |
| bytes32 treeHash = |
| if (block.timestamp > sig.deadline) { | ||
| revert SignatureExpired(sig.deadline, uint48(block.timestamp)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm see this being repeated a lot. Consider moving it to a modifier or _verifySignature
| function _validateWitnessTypeString( | ||
| string calldata witnessTypeString | ||
| ) internal pure { | ||
| // Validate minimum length | ||
| if (bytes(witnessTypeString).length == 0) { | ||
| revert InvalidWitnessTypeString(witnessTypeString); | ||
| } | ||
|
|
||
| // Validate proper ending with closing parenthesis | ||
| uint256 witnessTypeStringLength = bytes(witnessTypeString).length; | ||
| if (bytes(witnessTypeString)[witnessTypeStringLength - 1] != ")") { | ||
| revert InvalidWitnessTypeString(witnessTypeString); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, I don't think this function is needed. It doesn't do much for us. There's much more that can go wrong than empty or the last char being not ")".
| * @param signature EIP-712 signature authorizing all permits with witness | ||
| * @param witness Witness data containing witness hash and type string | ||
| */ | ||
| function permitWitness( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand how this works, but what's the usecase. Who would need it?
| IPermit3.Signature calldata sig, | ||
| IPermit3.PermitTree calldata tree, | ||
| IPermit3.Witness calldata witness | ||
| ) internal view returns (TreeWitnessContext memory ctx) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| ) internal view returns (TreeWitnessContext memory ctx) { | |
| ) internal pure returns (TreeWitnessContext memory ctx) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, don't see why we need this struct TreeWitnessContext. Only .signedHash is ever used but the other two fields are never used.
…me7mv5nn2QUpN6XQ docs: fix documentation formatting in IPermit3
Summary
Implement tree-based cross-chain permit system with UI transparency and gas-efficient on-chain verification using TreeNodeLib.
Users sign a complete
PermitNodetree structure (full transparency), while the contract receives a compact proof (gas efficient).Dependencies
feat/tree-lib) - merge that first!Core Changes
New Structures
PermitNode- UI-readable tree structure for signingPermitTree- Compact on-chain structure (proofStructure + proof)Signature- Standardized signature data bundleNew Typehashes
PERMIT3_TYPEHASH- Single-chain permitMULTICHAIN_PERMIT3_TYPEHASH- Multi-chain tree permitPERMIT_NODE_TYPEHASH- Internal tree node hashingUpdated Files
src/Permit3.sol- Tree reconstruction using TreeNodeLibsrc/PermitBase.sol- Updated for new structuressrc/interfaces/IPermit3.sol- New structs and signaturessrc/MultiTokenPermit.sol- API adjustmentssrc/modules/ERC7579ApproverModule.sol- Integration updatesTest Coverage
New Tests:
test/PermitNodeReconstruction.t.sol(540 lines) - Hash reconstruction validationtest/PermitTreeIntegration.t.sol(296 lines) - End-to-end integrationtest/NestedStructure.t.sol(137 lines) - EIP-712 nested struct testsUpdated Tests:
test/Permit3.t.sol,test/Permit3Edge.t.sol,test/Permit3Witness.t.soltest/MultiTokenPermit.t.soland related testsTest Results:
JavaScript Utilities
Complete toolkit for tree construction:
utils/permitNodeHelpers.js(1,192 lines) - Tree construction & proof generationutils/test-permitNode.js(288 lines) - JS test suiteutils/merkle-helpers.js(636 lines) - Legacy Merkle utilitiesGas Performance
Key Benefits
✅ UI Transparency: Users see all chains/permits in wallet before signing
✅ Gas Efficient: O(log n) proof size with linear gas scaling
✅ Deterministic: Three clear combination rules prevent ambiguity
✅ Secure: Maximum depth validation, unused bit checks
API Example
Before (single-chain):
permit(owner, salt, deadline, timestamp, permits, signature);After (single-chain):
After (multi-chain):
Documentation
Updated README with:
🤖 Generated with Claude Code