feat(#2): implement tree-based cross-chain permits#54
feat(#2): implement tree-based cross-chain permits#54re1ro wants to merge 12 commits intofeat/tree-libfrom
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.
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.
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) |
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.
| * @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.
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.
| bytes32 permitNodeHash = | |
| bytes32 treeHash = |
| if (block.timestamp > sig.deadline) { | ||
| revert SignatureExpired(sig.deadline, uint48(block.timestamp)); | ||
| } |
There was a problem hiding this comment.
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.
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.
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.
| ) internal view returns (TreeWitnessContext memory ctx) { | |
| ) internal pure returns (TreeWitnessContext memory ctx) { |
There was a problem hiding this comment.
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