From cb948adc0756b6ae3361a65748cec9f1609d65df Mon Sep 17 00:00:00 2001 From: Vectorized Date: Sat, 21 Jun 2025 08:02:15 +0000 Subject: [PATCH 01/10] MerkleTreeLib --- README.md | 1 + src/Milady.sol | 1 + src/utils/MerkleTreeLib.sol | 188 +++++++++++++++++++++++++++++++++ src/utils/g/MerkleTreeLib.sol | 192 ++++++++++++++++++++++++++++++++++ test/MerkleTreeLib.t.sol | 160 ++++++++++++++++++++++++++++ 5 files changed, 542 insertions(+) create mode 100644 src/utils/MerkleTreeLib.sol create mode 100644 src/utils/g/MerkleTreeLib.sol create mode 100644 test/MerkleTreeLib.t.sol diff --git a/README.md b/README.md index 6332f5867a..86fe13e998 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ utils ├─ LibZip — "Library for compressing and decompressing bytes" ├─ Lifebuoy — "Class that allows for rescue of ETH, ERC20, ERC721 tokens" ├─ MerkleProofLib — "Library for verification of Merkle proofs" +├─ MerkleTreeLib — "Library for generating Merkle trees" ├─ MetadataReaderLib — "Library for reading contract metadata robustly" ├─ MinHeapLib — "Library for managing a min-heap in storage or memory" ├─ Multicallable — "Contract that enables a single call to call multiple methods on itself" diff --git a/src/Milady.sol b/src/Milady.sol index 6aef5a3124..2e14615553 100644 --- a/src/Milady.sol +++ b/src/Milady.sol @@ -53,6 +53,7 @@ import "./utils/LibString.sol"; import "./utils/LibZip.sol"; import "./utils/Lifebuoy.sol"; import "./utils/MerkleProofLib.sol"; +import "./utils/MerkleTreeLib.sol"; import "./utils/MetadataReaderLib.sol"; import "./utils/MinHeapLib.sol"; import "./utils/Multicallable.sol"; diff --git a/src/utils/MerkleTreeLib.sol b/src/utils/MerkleTreeLib.sol new file mode 100644 index 0000000000..ef1ad8fad0 --- /dev/null +++ b/src/utils/MerkleTreeLib.sol @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Gas optimized verification of proof of inclusion for a leaf in a Merkle tree. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/MerkleTreeLib.sol) +/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/merkle-tree) +/// @author Modified from Murky (https://github.com/dmfxyz/murky) +library MerkleTreeLib { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* STRUCTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev A complete Merkle tree in memory. + /// To make it a full Merkle tree, use `build(pad(leafs))`. + struct MerkleTree { + bytes32[] nodes; + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev At least 1 leaf is required to build the tree. + error MerkleTreeLeafsEmpty(); + + /// @dev Attempt to access a node with an out-of-bounds index. + /// Check if the tree has been built and has sufficient leafs and nodes. + error MerkleTreeOutOfBoundsAccess(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MERKLE TREE OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Builds the tree. + /// If the tree has been already built, overwrites the existing tree. + function build(MerkleTree memory t, bytes32[] memory leafs) internal pure { + /// @solidity memory-safe-assembly + assembly { + let l := mload(leafs) + if iszero(l) { + mstore(0x00, 0x089aff6e) // `MerkleTreeLeafsEmpty()`. + revert(0x1c, 0x04) + } + let n := sub(add(l, l), 1) + let m := mload(0x40) // `nodes`. + mstore(t, m) // Set `t.nodes`. + mstore(m, n) // Store the length of the `nodes.length`. + m := add(m, 0x20) + let f := add(m, shl(5, n)) + mstore(0x40, f) // Allocate memory. + let e := add(0x20, shl(5, l)) + for { let i := 0x20 } 1 {} { + mstore(sub(f, i), mload(add(leafs, i))) + i := add(i, 0x20) + if eq(i, e) { break } + } + if iszero(lt(l, 2)) { + for { let i := shl(5, sub(l, 2)) } 1 {} { + let left := mload(add(m, add(add(i, i), 0x20))) + let right := mload(add(m, add(add(i, i), 0x40))) + let c := shl(5, lt(left, right)) + mstore(c, right) + mstore(xor(c, 0x20), left) + mstore(add(m, i), keccak256(0x00, 0x40)) + if iszero(i) { break } + i := sub(i, 0x20) + } + } + } + } + + /// @dev Returns the root. + function root(MerkleTree memory t) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(add(0x20, mload(t))) + if iszero(mload(mload(t))) { + mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Returns the number of leafs. + function numLeafs(MerkleTree memory t) internal pure returns (uint256) { + unchecked { + return t.nodes.length - (t.nodes.length >> 1); + } + } + + /// @dev Returns the number of internal nodes. + function numInternalNodes(MerkleTree memory t) internal pure returns (uint256) { + unchecked { + return t.nodes.length >> 1; + } + } + + /// @dev Returns the leaf at `leafIndex`. + function leaf(MerkleTree memory t, uint256 leafIndex) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let n := mload(mload(t)) + if iszero(lt(leafIndex, sub(n, shr(1, n)))) { + mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. + revert(0x1c, 0x04) + } + result := mload(add(mload(t), shl(5, sub(n, leafIndex)))) + } + } + + /// @dev Returns the proof for the leaf at `leafIndex`. + function leafProof(MerkleTree memory t, uint256 leafIndex) + internal + pure + returns (bytes32[] memory result) + { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let n := mload(mload(t)) + if iszero(lt(leafIndex, sub(n, shr(1, n)))) { + mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. + revert(0x1c, 0x04) + } + let o := add(result, 0x20) + for { let i := sub(n, add(1, leafIndex)) } i { i := shr(1, sub(i, 1)) } { + mstore(o, mload(add(mload(t), shl(5, add(i, shl(1, and(1, i))))))) + o := add(o, 0x20) + } + mstore(0x40, o) // Allocate memory. + mstore(result, shr(5, sub(o, add(result, 0x20)))) // Store length. + } + } + + /// @dev Returns the proof for the node at `nodeIndex`. + function nodeProof(MerkleTree memory t, uint256 nodeIndex) + internal + pure + returns (bytes32[] memory result) + { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let n := mload(mload(t)) + if iszero(lt(nodeIndex, n)) { + mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. + revert(0x1c, 0x04) + } + let o := add(result, 0x20) + for { let i := nodeIndex } i { i := shr(1, sub(i, 1)) } { + mstore(o, mload(add(mload(t), shl(5, add(i, shl(1, and(1, i))))))) + o := add(o, 0x20) + } + mstore(0x40, o) // Allocate memory. + mstore(result, shr(5, sub(o, add(result, 0x20)))) // Store length. + } + } + + /// @dev Returns a copy of leafs, with the length padded to a power of 2. + function pad(bytes32[] memory leafs, bytes32 defaultFill) + internal + pure + returns (bytes32[] memory result) + { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let l := mload(leafs) + if iszero(l) { + mstore(0x00, 0x089aff6e) // `MerkleTreeLeafsEmpty()`. + revert(0x1c, 0x04) + } + let p := 1 // Padded length. + for {} lt(p, l) {} { p := add(p, p) } + mstore(result, p) // Store length. + mstore(0x40, add(result, add(0x20, shl(5, p)))) // Allocate memory. + let d := sub(result, leafs) + let copyEnd := add(add(leafs, 0x20), shl(5, l)) + let end := add(add(leafs, 0x20), shl(5, p)) + mstore(0x00, defaultFill) + for { let i := add(leafs, 0x20) } 1 {} { + mstore(add(i, d), mload(mul(i, lt(i, copyEnd)))) + i := add(i, 0x20) + if eq(i, end) { break } + } + } + } +} diff --git a/src/utils/g/MerkleTreeLib.sol b/src/utils/g/MerkleTreeLib.sol new file mode 100644 index 0000000000..4357f97ee2 --- /dev/null +++ b/src/utils/g/MerkleTreeLib.sol @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +// This file is auto-generated. + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* STRUCTS */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + +/// @dev A complete Merkle tree in memory. +/// To make it a full Merkle tree, use `build(pad(leafs))`. +struct MerkleTree { + bytes32[] nodes; +} + +using MerkleTreeLib for MerkleTree global; + +/// @notice Gas optimized verification of proof of inclusion for a leaf in a Merkle tree. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/g/MerkleTreeLib.sol) +/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/merkle-tree) +/// @author Modified from Murky (https://github.com/dmfxyz/murky) +library MerkleTreeLib { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev At least 1 leaf is required to build the tree. + error MerkleTreeLeafsEmpty(); + + /// @dev Attempt to access a node with an out-of-bounds index. + /// Check if the tree has been built and has sufficient leafs and nodes. + error MerkleTreeOutOfBoundsAccess(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MERKLE TREE OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Builds the tree. + /// If the tree has been already built, overwrites the existing tree. + function build(MerkleTree memory t, bytes32[] memory leafs) internal pure { + /// @solidity memory-safe-assembly + assembly { + let l := mload(leafs) + if iszero(l) { + mstore(0x00, 0x089aff6e) // `MerkleTreeLeafsEmpty()`. + revert(0x1c, 0x04) + } + let n := sub(add(l, l), 1) + let m := mload(0x40) // `nodes`. + mstore(t, m) // Set `t.nodes`. + mstore(m, n) // Store the length of the `nodes.length`. + m := add(m, 0x20) + let f := add(m, shl(5, n)) + mstore(0x40, f) // Allocate memory. + let e := add(0x20, shl(5, l)) + for { let i := 0x20 } 1 {} { + mstore(sub(f, i), mload(add(leafs, i))) + i := add(i, 0x20) + if eq(i, e) { break } + } + if iszero(lt(l, 2)) { + for { let i := shl(5, sub(l, 2)) } 1 {} { + let left := mload(add(m, add(add(i, i), 0x20))) + let right := mload(add(m, add(add(i, i), 0x40))) + let c := shl(5, lt(left, right)) + mstore(c, right) + mstore(xor(c, 0x20), left) + mstore(add(m, i), keccak256(0x00, 0x40)) + if iszero(i) { break } + i := sub(i, 0x20) + } + } + } + } + + /// @dev Returns the root. + function root(MerkleTree memory t) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(add(0x20, mload(t))) + if iszero(mload(mload(t))) { + mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Returns the number of leafs. + function numLeafs(MerkleTree memory t) internal pure returns (uint256) { + unchecked { + return t.nodes.length - (t.nodes.length >> 1); + } + } + + /// @dev Returns the number of internal nodes. + function numInternalNodes(MerkleTree memory t) internal pure returns (uint256) { + unchecked { + return t.nodes.length >> 1; + } + } + + /// @dev Returns the leaf at `leafIndex`. + function leaf(MerkleTree memory t, uint256 leafIndex) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let n := mload(mload(t)) + if iszero(lt(leafIndex, sub(n, shr(1, n)))) { + mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. + revert(0x1c, 0x04) + } + result := mload(add(mload(t), shl(5, sub(n, leafIndex)))) + } + } + + /// @dev Returns the proof for the leaf at `leafIndex`. + function leafProof(MerkleTree memory t, uint256 leafIndex) + internal + pure + returns (bytes32[] memory result) + { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let n := mload(mload(t)) + if iszero(lt(leafIndex, sub(n, shr(1, n)))) { + mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. + revert(0x1c, 0x04) + } + let o := add(result, 0x20) + for { let i := sub(n, add(1, leafIndex)) } i { i := shr(1, sub(i, 1)) } { + mstore(o, mload(add(mload(t), shl(5, add(i, shl(1, and(1, i))))))) + o := add(o, 0x20) + } + mstore(0x40, o) // Allocate memory. + mstore(result, shr(5, sub(o, add(result, 0x20)))) // Store length. + } + } + + /// @dev Returns the proof for the node at `nodeIndex`. + function nodeProof(MerkleTree memory t, uint256 nodeIndex) + internal + pure + returns (bytes32[] memory result) + { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let n := mload(mload(t)) + if iszero(lt(nodeIndex, n)) { + mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. + revert(0x1c, 0x04) + } + let o := add(result, 0x20) + for { let i := nodeIndex } i { i := shr(1, sub(i, 1)) } { + mstore(o, mload(add(mload(t), shl(5, add(i, shl(1, and(1, i))))))) + o := add(o, 0x20) + } + mstore(0x40, o) // Allocate memory. + mstore(result, shr(5, sub(o, add(result, 0x20)))) // Store length. + } + } + + /// @dev Returns a copy of leafs, with the length padded to a power of 2. + function pad(bytes32[] memory leafs, bytes32 defaultFill) + internal + pure + returns (bytes32[] memory result) + { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let l := mload(leafs) + if iszero(l) { + mstore(0x00, 0x089aff6e) // `MerkleTreeLeafsEmpty()`. + revert(0x1c, 0x04) + } + let p := 1 // Padded length. + for {} lt(p, l) {} { p := add(p, p) } + mstore(result, p) // Store length. + mstore(0x40, add(result, add(0x20, shl(5, p)))) // Allocate memory. + let d := sub(result, leafs) + let copyEnd := add(add(leafs, 0x20), shl(5, l)) + let end := add(add(leafs, 0x20), shl(5, p)) + mstore(0x00, defaultFill) + for { let i := add(leafs, 0x20) } 1 {} { + mstore(add(i, d), mload(mul(i, lt(i, copyEnd)))) + i := add(i, 0x20) + if eq(i, end) { break } + } + } + } +} diff --git a/test/MerkleTreeLib.t.sol b/test/MerkleTreeLib.t.sol new file mode 100644 index 0000000000..f68339f168 --- /dev/null +++ b/test/MerkleTreeLib.t.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "./utils/SoladyTest.sol"; +import {MerkleTreeLib} from "../src/utils/MerkleTreeLib.sol"; +import {MerkleProofLib} from "../src/utils/MerkleProofLib.sol"; +import {EfficientHashLib} from "../src/utils/EfficientHashLib.sol"; + +contract MerkleTreeLibTest is SoladyTest { + using MerkleTreeLib for *; + + function testBuildCompleteMerkleTree(bytes32[] memory leafs, bytes32 r) public { + _maybeBrutalizeMemory(r); + if (leafs.length <= 1) { + leafs = new bytes32[](1); + leafs[0] = r; + } + MerkleTreeLib.MerkleTree memory t; + t.build(leafs); + assertEq(t.nodes.length, leafs.length * 2 - 1); + if (leafs.length == 1) { + assertEq(t.nodes[0], r); + } else { + assertNotEq(t.nodes[0], 0); + } + assertEq(t.root(), t.nodes[0]); + assertEq(leafs.length, t.numLeafs()); + assertEq(t.nodes.length, t.numLeafs() + t.numInternalNodes()); + _checkMemory(); + if (leafs.length >= 1) { + uint256 i = _randomUniform() % leafs.length; + assertEq(t.leaf(i), leafs[i]); + } + } + + function testPad(bytes32[] memory leafs, bytes32 defaultFill, uint256 r) public { + _maybeBrutalizeMemory(r); + if (leafs.length == 0) return; + assertEq(MerkleTreeLib.pad(leafs, defaultFill), _padOriginal(leafs, defaultFill)); + _checkMemory(); + } + + function _padOriginal(bytes32[] memory leafs, bytes32 defaultFill) + internal + pure + returns (bytes32[] memory result) + { + unchecked { + uint256 p = 1; + while (p < leafs.length) p = p << 1; + result = new bytes32[](p); + for (uint256 i; i < p; ++i) { + if (i < leafs.length) { + result[i] = leafs[i]; + } else { + result[i] = defaultFill; + } + } + } + } + + function _maybeBrutalizeMemory(uint256 r) internal view { + _maybeBrutalizeMemory(bytes32(r)); + } + + function _maybeBrutalizeMemory(bytes32 r) internal view { + uint256 h = uint256(EfficientHashLib.hash(r, "hehe")); + if (h & 0xf0 == 0) _misalignFreeMemoryPointer(); + if (h & 0x0f == 0) _brutalizeMemory(); + } + + function testBuildAndGetLeaf(bytes32[] memory leafs, uint256 leafIndex) public { + if (leafs.length == 0) return; + if (leafIndex < leafs.length) { + assertEq(this.buildAndGetLeaf(leafs, leafIndex), leafs[leafIndex]); + } else { + vm.expectRevert(MerkleTreeLib.MerkleTreeOutOfBoundsAccess.selector); + this.buildAndGetLeaf(leafs, leafIndex); + } + } + + function buildAndGetLeaf(bytes32[] memory leafs, uint256 leafIndex) + public + pure + returns (bytes32) + { + MerkleTreeLib.MerkleTree memory t; + t.build(leafs); + return t.leaf(leafIndex); + } + + function testBuildAndGetLeafProof(bytes32[] memory leafs, uint256 leafIndex) public { + if (leafs.length == 0) return _testBuildAndGetRoot(leafs); + MerkleTreeLib.MerkleTree memory t; + t.build(leafs); + if (leafIndex < leafs.length) { + bytes32[] memory proof = this.buildAndGetLeafProof(leafs, leafIndex); + assertTrue(MerkleProofLib.verify(proof, t.root(), leafs[leafIndex])); + } else { + vm.expectRevert(MerkleTreeLib.MerkleTreeOutOfBoundsAccess.selector); + this.buildAndGetLeafProof(leafs, leafIndex); + } + } + + function buildAndGetLeafProof(bytes32[] memory leafs, uint256 leafIndex) + public + pure + returns (bytes32[] memory proof) + { + MerkleTreeLib.MerkleTree memory t; + t.build(leafs); + proof = t.leafProof(leafIndex); + _checkMemory(); + } + + function testBuildAndGetNodeProof(bytes32[] memory leafs, uint256 nodeIndex) public { + if (leafs.length == 0) return _testBuildAndGetRoot(leafs); + MerkleTreeLib.MerkleTree memory t; + t.build(leafs); + if (nodeIndex < t.nodes.length) { + bytes32[] memory proof = this.buildAndGetNodeProof(leafs, nodeIndex); + assertTrue(MerkleProofLib.verify(proof, t.root(), t.nodes[nodeIndex])); + } else { + vm.expectRevert(MerkleTreeLib.MerkleTreeOutOfBoundsAccess.selector); + this.buildAndGetNodeProof(leafs, nodeIndex); + } + } + + function buildAndGetNodeProof(bytes32[] memory leafs, uint256 nodeIndex) + public + pure + returns (bytes32[] memory proof) + { + MerkleTreeLib.MerkleTree memory t; + t.build(leafs); + proof = t.nodeProof(nodeIndex); + _checkMemory(); + } + + function _testBuildAndGetRoot(bytes32[] memory leafs) internal { + vm.expectRevert(MerkleTreeLib.MerkleTreeLeafsEmpty.selector); + this.buildAndGetRoot(leafs); + } + + function buildAndGetRoot(bytes32[] memory leafs) public pure returns (bytes32) { + MerkleTreeLib.MerkleTree memory t; + t.build(leafs); + return t.root(); + } + + function testGetRootFromEmptyTree() public { + vm.expectRevert(MerkleTreeLib.MerkleTreeOutOfBoundsAccess.selector); + this.getRootFromEmptyTree(); + } + + function getRootFromEmptyTree() public pure returns (bytes32) { + MerkleTreeLib.MerkleTree memory t; + return t.root(); + } +} From 1d1bef7d8a3fcbef3b990cb027d46fa2025c2cc7 Mon Sep 17 00:00:00 2001 From: Vectorized Date: Sat, 21 Jun 2025 08:21:21 +0000 Subject: [PATCH 02/10] T --- src/utils/MerkleTreeLib.sol | 16 +++++----------- src/utils/g/MerkleTreeLib.sol | 16 +++++----------- test/MerkleTreeLib.t.sol | 2 +- 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/src/utils/MerkleTreeLib.sol b/src/utils/MerkleTreeLib.sol index ef1ad8fad0..ac65c6fc99 100644 --- a/src/utils/MerkleTreeLib.sol +++ b/src/utils/MerkleTreeLib.sol @@ -114,21 +114,16 @@ library MerkleTreeLib { pure returns (bytes32[] memory result) { + uint256 n = t.nodes.length; /// @solidity memory-safe-assembly assembly { - result := mload(0x40) - let n := mload(mload(t)) if iszero(lt(leafIndex, sub(n, shr(1, n)))) { mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. revert(0x1c, 0x04) } - let o := add(result, 0x20) - for { let i := sub(n, add(1, leafIndex)) } i { i := shr(1, sub(i, 1)) } { - mstore(o, mload(add(mload(t), shl(5, add(i, shl(1, and(1, i))))))) - o := add(o, 0x20) - } - mstore(0x40, o) // Allocate memory. - mstore(result, shr(5, sub(o, add(result, 0x20)))) // Store length. + } + unchecked { + result = nodeProof(t, n - (1 + leafIndex)); } } @@ -141,8 +136,7 @@ library MerkleTreeLib { /// @solidity memory-safe-assembly assembly { result := mload(0x40) - let n := mload(mload(t)) - if iszero(lt(nodeIndex, n)) { + if iszero(lt(nodeIndex, mload(mload(t)))) { mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. revert(0x1c, 0x04) } diff --git a/src/utils/g/MerkleTreeLib.sol b/src/utils/g/MerkleTreeLib.sol index 4357f97ee2..4e1f8a8c93 100644 --- a/src/utils/g/MerkleTreeLib.sol +++ b/src/utils/g/MerkleTreeLib.sol @@ -118,21 +118,16 @@ library MerkleTreeLib { pure returns (bytes32[] memory result) { + uint256 n = t.nodes.length; /// @solidity memory-safe-assembly assembly { - result := mload(0x40) - let n := mload(mload(t)) if iszero(lt(leafIndex, sub(n, shr(1, n)))) { mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. revert(0x1c, 0x04) } - let o := add(result, 0x20) - for { let i := sub(n, add(1, leafIndex)) } i { i := shr(1, sub(i, 1)) } { - mstore(o, mload(add(mload(t), shl(5, add(i, shl(1, and(1, i))))))) - o := add(o, 0x20) - } - mstore(0x40, o) // Allocate memory. - mstore(result, shr(5, sub(o, add(result, 0x20)))) // Store length. + } + unchecked { + result = nodeProof(t, n - (1 + leafIndex)); } } @@ -145,8 +140,7 @@ library MerkleTreeLib { /// @solidity memory-safe-assembly assembly { result := mload(0x40) - let n := mload(mload(t)) - if iszero(lt(nodeIndex, n)) { + if iszero(lt(nodeIndex, mload(mload(t)))) { mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. revert(0x1c, 0x04) } diff --git a/test/MerkleTreeLib.t.sol b/test/MerkleTreeLib.t.sol index f68339f168..c0b6820b6a 100644 --- a/test/MerkleTreeLib.t.sol +++ b/test/MerkleTreeLib.t.sol @@ -26,7 +26,7 @@ contract MerkleTreeLibTest is SoladyTest { assertEq(t.root(), t.nodes[0]); assertEq(leafs.length, t.numLeafs()); assertEq(t.nodes.length, t.numLeafs() + t.numInternalNodes()); - _checkMemory(); + _checkMemory(t.nodes); if (leafs.length >= 1) { uint256 i = _randomUniform() % leafs.length; assertEq(t.leaf(i), leafs[i]); From 023905e5963397c34e71ee914b17f798023797a5 Mon Sep 17 00:00:00 2001 From: Vectorized Date: Sat, 21 Jun 2025 09:42:35 +0000 Subject: [PATCH 03/10] T --- src/utils/MerkleTreeLib.sol | 20 +++++++++------- src/utils/g/MerkleTreeLib.sol | 20 +++++++++------- test/MerkleTreeLib.t.sol | 43 +++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 16 deletions(-) diff --git a/src/utils/MerkleTreeLib.sol b/src/utils/MerkleTreeLib.sol index ac65c6fc99..35fd49b00c 100644 --- a/src/utils/MerkleTreeLib.sol +++ b/src/utils/MerkleTreeLib.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.4; /// @notice Gas optimized verification of proof of inclusion for a leaf in a Merkle tree. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/MerkleTreeLib.sol) -/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/merkle-tree) +/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/merkle-tree/blob/master/src/core.ts) /// @author Modified from Murky (https://github.com/dmfxyz/murky) library MerkleTreeLib { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ @@ -90,9 +90,7 @@ library MerkleTreeLib { /// @dev Returns the number of internal nodes. function numInternalNodes(MerkleTree memory t) internal pure returns (uint256) { - unchecked { - return t.nodes.length >> 1; - } + return t.nodes.length >> 1; } /// @dev Returns the leaf at `leafIndex`. @@ -114,20 +112,26 @@ library MerkleTreeLib { pure returns (bytes32[] memory result) { - uint256 n = t.nodes.length; /// @solidity memory-safe-assembly assembly { + result := mload(0x40) + let n := mload(mload(t)) if iszero(lt(leafIndex, sub(n, shr(1, n)))) { mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. revert(0x1c, 0x04) } - } - unchecked { - result = nodeProof(t, n - (1 + leafIndex)); + let o := add(result, 0x20) + for { let i := sub(n, add(1, leafIndex)) } i { i := shr(1, sub(i, 1)) } { + mstore(o, mload(add(mload(t), shl(5, add(i, shl(1, and(1, i))))))) + o := add(o, 0x20) + } + mstore(0x40, o) // Allocate memory. + mstore(result, shr(5, sub(o, add(result, 0x20)))) // Store length. } } /// @dev Returns the proof for the node at `nodeIndex`. + /// This function can be used to prove the existence of internal nodes. function nodeProof(MerkleTree memory t, uint256 nodeIndex) internal pure diff --git a/src/utils/g/MerkleTreeLib.sol b/src/utils/g/MerkleTreeLib.sol index 4e1f8a8c93..2361670ebe 100644 --- a/src/utils/g/MerkleTreeLib.sol +++ b/src/utils/g/MerkleTreeLib.sol @@ -17,7 +17,7 @@ using MerkleTreeLib for MerkleTree global; /// @notice Gas optimized verification of proof of inclusion for a leaf in a Merkle tree. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/g/MerkleTreeLib.sol) -/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/merkle-tree) +/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/merkle-tree/blob/master/src/core.ts) /// @author Modified from Murky (https://github.com/dmfxyz/murky) library MerkleTreeLib { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ @@ -94,9 +94,7 @@ library MerkleTreeLib { /// @dev Returns the number of internal nodes. function numInternalNodes(MerkleTree memory t) internal pure returns (uint256) { - unchecked { - return t.nodes.length >> 1; - } + return t.nodes.length >> 1; } /// @dev Returns the leaf at `leafIndex`. @@ -118,20 +116,26 @@ library MerkleTreeLib { pure returns (bytes32[] memory result) { - uint256 n = t.nodes.length; /// @solidity memory-safe-assembly assembly { + result := mload(0x40) + let n := mload(mload(t)) if iszero(lt(leafIndex, sub(n, shr(1, n)))) { mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. revert(0x1c, 0x04) } - } - unchecked { - result = nodeProof(t, n - (1 + leafIndex)); + let o := add(result, 0x20) + for { let i := sub(n, add(1, leafIndex)) } i { i := shr(1, sub(i, 1)) } { + mstore(o, mload(add(mload(t), shl(5, add(i, shl(1, and(1, i))))))) + o := add(o, 0x20) + } + mstore(0x40, o) // Allocate memory. + mstore(result, shr(5, sub(o, add(result, 0x20)))) // Store length. } } /// @dev Returns the proof for the node at `nodeIndex`. + /// This function can be used to prove the existence of internal nodes. function nodeProof(MerkleTree memory t, uint256 nodeIndex) internal pure diff --git a/test/MerkleTreeLib.t.sol b/test/MerkleTreeLib.t.sol index c0b6820b6a..947df146e6 100644 --- a/test/MerkleTreeLib.t.sol +++ b/test/MerkleTreeLib.t.sol @@ -4,10 +4,13 @@ pragma solidity ^0.8.4; import "./utils/SoladyTest.sol"; import {MerkleTreeLib} from "../src/utils/MerkleTreeLib.sol"; import {MerkleProofLib} from "../src/utils/MerkleProofLib.sol"; +import {LibSort} from "../src/utils/LibSort.sol"; +import {LibPRNG} from "../src/utils/LibPRNG.sol"; import {EfficientHashLib} from "../src/utils/EfficientHashLib.sol"; contract MerkleTreeLibTest is SoladyTest { using MerkleTreeLib for *; + using LibPRNG for *; function testBuildCompleteMerkleTree(bytes32[] memory leafs, bytes32 r) public { _maybeBrutalizeMemory(r); @@ -71,6 +74,7 @@ contract MerkleTreeLibTest is SoladyTest { function testBuildAndGetLeaf(bytes32[] memory leafs, uint256 leafIndex) public { if (leafs.length == 0) return; + if (leafIndex < leafs.length) { assertEq(this.buildAndGetLeaf(leafs, leafIndex), leafs[leafIndex]); } else { @@ -157,4 +161,43 @@ contract MerkleTreeLibTest is SoladyTest { MerkleTreeLib.MerkleTree memory t; return t.root(); } + + function _generateUniqueLeafIndices(bytes32[] memory leafs) + internal + returns (uint256[] memory indices) + { + indices = new uint256[](leafs.length); + for (uint256 i; i < leafs.length; ++i) { + indices[i] = i; + } + LibPRNG.PRNG memory prng; + prng.seed(_randomUniform()); + prng.shuffle(indices); + uint256 n = _bound(_random(), 0, indices.length); + /// @solidity memory-safe-assembly + assembly { + mstore(indices, n) + } + } + + function _gatherLeafs(bytes32[] memory leafs, uint256[] memory indices) + internal + pure + returns (bytes32[] memory gathered) + { + gathered = new bytes32[](indices.length); + for (uint256 i; i < indices.length; ++i) { + gathered[i] = leafs[indices[i]]; + } + } + + /// @dev Returns proof and corresponding flags for multiple leafs. + function leafsMultiProof(MerkleTreeLib.MerkleTree memory t, uint256[] memory leafIndices) + internal + pure + returns (bytes32[] memory proof, bool[] memory flags) + { + /// @solidity memory-safe-assembly + assembly {} + } } From cef8add1010ac2cd0a26aba663d102a5c54b2ad5 Mon Sep 17 00:00:00 2001 From: Vectorized Date: Sat, 21 Jun 2025 09:44:26 +0000 Subject: [PATCH 04/10] T --- test/MerkleTreeLib.t.sol | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/test/MerkleTreeLib.t.sol b/test/MerkleTreeLib.t.sol index 947df146e6..6f1bf49fd4 100644 --- a/test/MerkleTreeLib.t.sol +++ b/test/MerkleTreeLib.t.sol @@ -191,13 +191,15 @@ contract MerkleTreeLibTest is SoladyTest { } } - /// @dev Returns proof and corresponding flags for multiple leafs. - function leafsMultiProof(MerkleTreeLib.MerkleTree memory t, uint256[] memory leafIndices) - internal - pure - returns (bytes32[] memory proof, bool[] memory flags) - { - /// @solidity memory-safe-assembly - assembly {} - } + // TODO: complete this in MerkleTreeLib.sol + + // /// @dev Returns proof and corresponding flags for multiple leafs. + // function leafsMultiProof(MerkleTree memory t, uint256[] memory leafIndices) + // internal + // pure + // returns (bytes32[] memory proof, bool[] memory flags) + // { + // /// @solidity memory-safe-assembly + // assembly {} + // } } From 82acd647f9cf32418565b7b01c62eac22fcaf9fd Mon Sep 17 00:00:00 2001 From: Vectorized Date: Sun, 22 Jun 2025 15:32:07 +0000 Subject: [PATCH 05/10] T --- src/utils/MerkleTreeLib.sol | 74 ++++++++++++++++++++++------------- src/utils/g/MerkleTreeLib.sol | 74 ++++++++++++++++++++++------------- test/MerkleTreeLib.t.sol | 56 ++++++++------------------ 3 files changed, 109 insertions(+), 95 deletions(-) diff --git a/src/utils/MerkleTreeLib.sol b/src/utils/MerkleTreeLib.sol index 35fd49b00c..c98ba9ec10 100644 --- a/src/utils/MerkleTreeLib.sol +++ b/src/utils/MerkleTreeLib.sol @@ -11,7 +11,7 @@ library MerkleTreeLib { /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev A complete Merkle tree in memory. - /// To make it a full Merkle tree, use `build(pad(leafs))`. + struct MerkleTree { bytes32[] nodes; } @@ -31,22 +31,21 @@ library MerkleTreeLib { /* MERKLE TREE OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ - /// @dev Builds the tree. - /// If the tree has been already built, overwrites the existing tree. - function build(MerkleTree memory t, bytes32[] memory leafs) internal pure { + /// @dev Builds and return a complete Merkle tree. + /// To make it a full Merkle tree, use `build(pad(leafs))`. + function build(bytes32[] memory leafs) internal pure returns (bytes32[] memory result) { /// @solidity memory-safe-assembly assembly { + result := mload(0x40) // `nodes`. let l := mload(leafs) if iszero(l) { mstore(0x00, 0x089aff6e) // `MerkleTreeLeafsEmpty()`. revert(0x1c, 0x04) } let n := sub(add(l, l), 1) - let m := mload(0x40) // `nodes`. - mstore(t, m) // Set `t.nodes`. - mstore(m, n) // Store the length of the `nodes.length`. - m := add(m, 0x20) - let f := add(m, shl(5, n)) + mstore(result, n) // `.length`. + let nodes := add(result, 0x20) + let f := add(nodes, shl(5, n)) mstore(0x40, f) // Allocate memory. let e := add(0x20, shl(5, l)) for { let i := 0x20 } 1 {} { @@ -56,12 +55,12 @@ library MerkleTreeLib { } if iszero(lt(l, 2)) { for { let i := shl(5, sub(l, 2)) } 1 {} { - let left := mload(add(m, add(add(i, i), 0x20))) - let right := mload(add(m, add(add(i, i), 0x40))) + let left := mload(add(nodes, add(add(i, i), 0x20))) + let right := mload(add(nodes, add(add(i, i), 0x40))) let c := shl(5, lt(left, right)) mstore(c, right) mstore(xor(c, 0x20), left) - mstore(add(m, i), keccak256(0x00, 0x40)) + mstore(add(nodes, i), keccak256(0x00, 0x40)) if iszero(i) { break } i := sub(i, 0x20) } @@ -70,11 +69,11 @@ library MerkleTreeLib { } /// @dev Returns the root. - function root(MerkleTree memory t) internal pure returns (bytes32 result) { + function root(bytes32[] memory t) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { - result := mload(add(0x20, mload(t))) - if iszero(mload(mload(t))) { + result := mload(add(0x20, t)) + if iszero(mload(t)) { mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. revert(0x1c, 0x04) } @@ -82,32 +81,32 @@ library MerkleTreeLib { } /// @dev Returns the number of leafs. - function numLeafs(MerkleTree memory t) internal pure returns (uint256) { + function numLeafs(bytes32[] memory t) internal pure returns (uint256) { unchecked { - return t.nodes.length - (t.nodes.length >> 1); + return t.length - (t.length >> 1); } } /// @dev Returns the number of internal nodes. - function numInternalNodes(MerkleTree memory t) internal pure returns (uint256) { - return t.nodes.length >> 1; + function numInternalNodes(bytes32[] memory t) internal pure returns (uint256) { + return t.length >> 1; } /// @dev Returns the leaf at `leafIndex`. - function leaf(MerkleTree memory t, uint256 leafIndex) internal pure returns (bytes32 result) { + function leaf(bytes32[] memory t, uint256 leafIndex) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { - let n := mload(mload(t)) + let n := mload(t) if iszero(lt(leafIndex, sub(n, shr(1, n)))) { mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. revert(0x1c, 0x04) } - result := mload(add(mload(t), shl(5, sub(n, leafIndex)))) + result := mload(add(t, shl(5, sub(n, leafIndex)))) } } /// @dev Returns the proof for the leaf at `leafIndex`. - function leafProof(MerkleTree memory t, uint256 leafIndex) + function leafProof(bytes32[] memory t, uint256 leafIndex) internal pure returns (bytes32[] memory result) @@ -115,14 +114,14 @@ library MerkleTreeLib { /// @solidity memory-safe-assembly assembly { result := mload(0x40) - let n := mload(mload(t)) + let n := mload(t) if iszero(lt(leafIndex, sub(n, shr(1, n)))) { mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. revert(0x1c, 0x04) } let o := add(result, 0x20) for { let i := sub(n, add(1, leafIndex)) } i { i := shr(1, sub(i, 1)) } { - mstore(o, mload(add(mload(t), shl(5, add(i, shl(1, and(1, i))))))) + mstore(o, mload(add(t, shl(5, add(i, shl(1, and(1, i))))))) o := add(o, 0x20) } mstore(0x40, o) // Allocate memory. @@ -132,7 +131,7 @@ library MerkleTreeLib { /// @dev Returns the proof for the node at `nodeIndex`. /// This function can be used to prove the existence of internal nodes. - function nodeProof(MerkleTree memory t, uint256 nodeIndex) + function nodeProof(bytes32[] memory t, uint256 nodeIndex) internal pure returns (bytes32[] memory result) @@ -140,13 +139,13 @@ library MerkleTreeLib { /// @solidity memory-safe-assembly assembly { result := mload(0x40) - if iszero(lt(nodeIndex, mload(mload(t)))) { + if iszero(lt(nodeIndex, mload(t))) { mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. revert(0x1c, 0x04) } let o := add(result, 0x20) for { let i := nodeIndex } i { i := shr(1, sub(i, 1)) } { - mstore(o, mload(add(mload(t), shl(5, add(i, shl(1, and(1, i))))))) + mstore(o, mload(add(t, shl(5, add(i, shl(1, and(1, i))))))) o := add(o, 0x20) } mstore(0x40, o) // Allocate memory. @@ -154,6 +153,25 @@ library MerkleTreeLib { } } + /// @dev Returns proof and corresponding flags for multiple leafs. + function leafsMultiProof(bytes32[] memory t, uint256[] memory leafIndices) + internal + pure + returns (bytes32[] memory proof, bool[] memory flags) + { + // /// @solidity memory-safe-assembly + // assembly { + // let nodes := mload(t) + // let n := mload(nodes) + // let m := mload(0x40) + // let flagsMap := m + // 10000 / 256 * 32 + + // let numIndices := mload(leafIndices) + // proof := add(0x2000, flagsMap) + // } + } + /// @dev Returns a copy of leafs, with the length padded to a power of 2. function pad(bytes32[] memory leafs, bytes32 defaultFill) internal diff --git a/src/utils/g/MerkleTreeLib.sol b/src/utils/g/MerkleTreeLib.sol index 2361670ebe..0f323171ed 100644 --- a/src/utils/g/MerkleTreeLib.sol +++ b/src/utils/g/MerkleTreeLib.sol @@ -8,7 +8,7 @@ pragma solidity ^0.8.13; /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev A complete Merkle tree in memory. -/// To make it a full Merkle tree, use `build(pad(leafs))`. + struct MerkleTree { bytes32[] nodes; } @@ -35,22 +35,21 @@ library MerkleTreeLib { /* MERKLE TREE OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ - /// @dev Builds the tree. - /// If the tree has been already built, overwrites the existing tree. - function build(MerkleTree memory t, bytes32[] memory leafs) internal pure { + /// @dev Builds and return a complete Merkle tree. + /// To make it a full Merkle tree, use `build(pad(leafs))`. + function build(bytes32[] memory leafs) internal pure returns (bytes32[] memory result) { /// @solidity memory-safe-assembly assembly { + result := mload(0x40) // `nodes`. let l := mload(leafs) if iszero(l) { mstore(0x00, 0x089aff6e) // `MerkleTreeLeafsEmpty()`. revert(0x1c, 0x04) } let n := sub(add(l, l), 1) - let m := mload(0x40) // `nodes`. - mstore(t, m) // Set `t.nodes`. - mstore(m, n) // Store the length of the `nodes.length`. - m := add(m, 0x20) - let f := add(m, shl(5, n)) + mstore(result, n) // `.length`. + let nodes := add(result, 0x20) + let f := add(nodes, shl(5, n)) mstore(0x40, f) // Allocate memory. let e := add(0x20, shl(5, l)) for { let i := 0x20 } 1 {} { @@ -60,12 +59,12 @@ library MerkleTreeLib { } if iszero(lt(l, 2)) { for { let i := shl(5, sub(l, 2)) } 1 {} { - let left := mload(add(m, add(add(i, i), 0x20))) - let right := mload(add(m, add(add(i, i), 0x40))) + let left := mload(add(nodes, add(add(i, i), 0x20))) + let right := mload(add(nodes, add(add(i, i), 0x40))) let c := shl(5, lt(left, right)) mstore(c, right) mstore(xor(c, 0x20), left) - mstore(add(m, i), keccak256(0x00, 0x40)) + mstore(add(nodes, i), keccak256(0x00, 0x40)) if iszero(i) { break } i := sub(i, 0x20) } @@ -74,11 +73,11 @@ library MerkleTreeLib { } /// @dev Returns the root. - function root(MerkleTree memory t) internal pure returns (bytes32 result) { + function root(bytes32[] memory t) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { - result := mload(add(0x20, mload(t))) - if iszero(mload(mload(t))) { + result := mload(add(0x20, t)) + if iszero(mload(t)) { mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. revert(0x1c, 0x04) } @@ -86,32 +85,32 @@ library MerkleTreeLib { } /// @dev Returns the number of leafs. - function numLeafs(MerkleTree memory t) internal pure returns (uint256) { + function numLeafs(bytes32[] memory t) internal pure returns (uint256) { unchecked { - return t.nodes.length - (t.nodes.length >> 1); + return t.length - (t.length >> 1); } } /// @dev Returns the number of internal nodes. - function numInternalNodes(MerkleTree memory t) internal pure returns (uint256) { - return t.nodes.length >> 1; + function numInternalNodes(bytes32[] memory t) internal pure returns (uint256) { + return t.length >> 1; } /// @dev Returns the leaf at `leafIndex`. - function leaf(MerkleTree memory t, uint256 leafIndex) internal pure returns (bytes32 result) { + function leaf(bytes32[] memory t, uint256 leafIndex) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { - let n := mload(mload(t)) + let n := mload(t) if iszero(lt(leafIndex, sub(n, shr(1, n)))) { mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. revert(0x1c, 0x04) } - result := mload(add(mload(t), shl(5, sub(n, leafIndex)))) + result := mload(add(t, shl(5, sub(n, leafIndex)))) } } /// @dev Returns the proof for the leaf at `leafIndex`. - function leafProof(MerkleTree memory t, uint256 leafIndex) + function leafProof(bytes32[] memory t, uint256 leafIndex) internal pure returns (bytes32[] memory result) @@ -119,14 +118,14 @@ library MerkleTreeLib { /// @solidity memory-safe-assembly assembly { result := mload(0x40) - let n := mload(mload(t)) + let n := mload(t) if iszero(lt(leafIndex, sub(n, shr(1, n)))) { mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. revert(0x1c, 0x04) } let o := add(result, 0x20) for { let i := sub(n, add(1, leafIndex)) } i { i := shr(1, sub(i, 1)) } { - mstore(o, mload(add(mload(t), shl(5, add(i, shl(1, and(1, i))))))) + mstore(o, mload(add(t, shl(5, add(i, shl(1, and(1, i))))))) o := add(o, 0x20) } mstore(0x40, o) // Allocate memory. @@ -136,7 +135,7 @@ library MerkleTreeLib { /// @dev Returns the proof for the node at `nodeIndex`. /// This function can be used to prove the existence of internal nodes. - function nodeProof(MerkleTree memory t, uint256 nodeIndex) + function nodeProof(bytes32[] memory t, uint256 nodeIndex) internal pure returns (bytes32[] memory result) @@ -144,13 +143,13 @@ library MerkleTreeLib { /// @solidity memory-safe-assembly assembly { result := mload(0x40) - if iszero(lt(nodeIndex, mload(mload(t)))) { + if iszero(lt(nodeIndex, mload(t))) { mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. revert(0x1c, 0x04) } let o := add(result, 0x20) for { let i := nodeIndex } i { i := shr(1, sub(i, 1)) } { - mstore(o, mload(add(mload(t), shl(5, add(i, shl(1, and(1, i))))))) + mstore(o, mload(add(t, shl(5, add(i, shl(1, and(1, i))))))) o := add(o, 0x20) } mstore(0x40, o) // Allocate memory. @@ -158,6 +157,25 @@ library MerkleTreeLib { } } + /// @dev Returns proof and corresponding flags for multiple leafs. + function leafsMultiProof(bytes32[] memory t, uint256[] memory leafIndices) + internal + pure + returns (bytes32[] memory proof, bool[] memory flags) + { + // /// @solidity memory-safe-assembly + // assembly { + // let nodes := mload(t) + // let n := mload(nodes) + // let m := mload(0x40) + // let flagsMap := m + // 10000 / 256 * 32 + + // let numIndices := mload(leafIndices) + // proof := add(0x2000, flagsMap) + // } + } + /// @dev Returns a copy of leafs, with the length padded to a power of 2. function pad(bytes32[] memory leafs, bytes32 defaultFill) internal diff --git a/test/MerkleTreeLib.t.sol b/test/MerkleTreeLib.t.sol index 6f1bf49fd4..155d1bdec5 100644 --- a/test/MerkleTreeLib.t.sol +++ b/test/MerkleTreeLib.t.sol @@ -9,7 +9,7 @@ import {LibPRNG} from "../src/utils/LibPRNG.sol"; import {EfficientHashLib} from "../src/utils/EfficientHashLib.sol"; contract MerkleTreeLibTest is SoladyTest { - using MerkleTreeLib for *; + using MerkleTreeLib for bytes32[]; using LibPRNG for *; function testBuildCompleteMerkleTree(bytes32[] memory leafs, bytes32 r) public { @@ -18,18 +18,17 @@ contract MerkleTreeLibTest is SoladyTest { leafs = new bytes32[](1); leafs[0] = r; } - MerkleTreeLib.MerkleTree memory t; - t.build(leafs); - assertEq(t.nodes.length, leafs.length * 2 - 1); + bytes32[] memory t = MerkleTreeLib.build(leafs); + assertEq(t.length, leafs.length * 2 - 1); if (leafs.length == 1) { - assertEq(t.nodes[0], r); + assertEq(t[0], r); } else { - assertNotEq(t.nodes[0], 0); + assertNotEq(t[0], 0); } - assertEq(t.root(), t.nodes[0]); + assertEq(t.root(), t[0]); assertEq(leafs.length, t.numLeafs()); - assertEq(t.nodes.length, t.numLeafs() + t.numInternalNodes()); - _checkMemory(t.nodes); + assertEq(t.length, t.numLeafs() + t.numInternalNodes()); + _checkMemory(t); if (leafs.length >= 1) { uint256 i = _randomUniform() % leafs.length; assertEq(t.leaf(i), leafs[i]); @@ -88,15 +87,12 @@ contract MerkleTreeLibTest is SoladyTest { pure returns (bytes32) { - MerkleTreeLib.MerkleTree memory t; - t.build(leafs); - return t.leaf(leafIndex); + return MerkleTreeLib.build(leafs).leaf(leafIndex); } function testBuildAndGetLeafProof(bytes32[] memory leafs, uint256 leafIndex) public { if (leafs.length == 0) return _testBuildAndGetRoot(leafs); - MerkleTreeLib.MerkleTree memory t; - t.build(leafs); + bytes32[] memory t = MerkleTreeLib.build(leafs); if (leafIndex < leafs.length) { bytes32[] memory proof = this.buildAndGetLeafProof(leafs, leafIndex); assertTrue(MerkleProofLib.verify(proof, t.root(), leafs[leafIndex])); @@ -111,19 +107,17 @@ contract MerkleTreeLibTest is SoladyTest { pure returns (bytes32[] memory proof) { - MerkleTreeLib.MerkleTree memory t; - t.build(leafs); + bytes32[] memory t = MerkleTreeLib.build(leafs); proof = t.leafProof(leafIndex); _checkMemory(); } function testBuildAndGetNodeProof(bytes32[] memory leafs, uint256 nodeIndex) public { if (leafs.length == 0) return _testBuildAndGetRoot(leafs); - MerkleTreeLib.MerkleTree memory t; - t.build(leafs); - if (nodeIndex < t.nodes.length) { + bytes32[] memory t = MerkleTreeLib.build(leafs); + if (nodeIndex < t.length) { bytes32[] memory proof = this.buildAndGetNodeProof(leafs, nodeIndex); - assertTrue(MerkleProofLib.verify(proof, t.root(), t.nodes[nodeIndex])); + assertTrue(MerkleProofLib.verify(proof, t.root(), t[nodeIndex])); } else { vm.expectRevert(MerkleTreeLib.MerkleTreeOutOfBoundsAccess.selector); this.buildAndGetNodeProof(leafs, nodeIndex); @@ -135,8 +129,7 @@ contract MerkleTreeLibTest is SoladyTest { pure returns (bytes32[] memory proof) { - MerkleTreeLib.MerkleTree memory t; - t.build(leafs); + bytes32[] memory t = MerkleTreeLib.build(leafs); proof = t.nodeProof(nodeIndex); _checkMemory(); } @@ -147,9 +140,7 @@ contract MerkleTreeLibTest is SoladyTest { } function buildAndGetRoot(bytes32[] memory leafs) public pure returns (bytes32) { - MerkleTreeLib.MerkleTree memory t; - t.build(leafs); - return t.root(); + return MerkleTreeLib.build(leafs).root(); } function testGetRootFromEmptyTree() public { @@ -158,8 +149,7 @@ contract MerkleTreeLibTest is SoladyTest { } function getRootFromEmptyTree() public pure returns (bytes32) { - MerkleTreeLib.MerkleTree memory t; - return t.root(); + return (new bytes32[](0)).root(); } function _generateUniqueLeafIndices(bytes32[] memory leafs) @@ -190,16 +180,4 @@ contract MerkleTreeLibTest is SoladyTest { gathered[i] = leafs[indices[i]]; } } - - // TODO: complete this in MerkleTreeLib.sol - - // /// @dev Returns proof and corresponding flags for multiple leafs. - // function leafsMultiProof(MerkleTree memory t, uint256[] memory leafIndices) - // internal - // pure - // returns (bytes32[] memory proof, bool[] memory flags) - // { - // /// @solidity memory-safe-assembly - // assembly {} - // } } From 90fb98cfaafca8187a7aab6136b2a6c586adddc0 Mon Sep 17 00:00:00 2001 From: Vectorized Date: Sun, 22 Jun 2025 15:32:24 +0000 Subject: [PATCH 06/10] T --- src/utils/MerkleTreeLib.sol | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/utils/MerkleTreeLib.sol b/src/utils/MerkleTreeLib.sol index c98ba9ec10..c773e63bf7 100644 --- a/src/utils/MerkleTreeLib.sol +++ b/src/utils/MerkleTreeLib.sol @@ -6,16 +6,6 @@ pragma solidity ^0.8.4; /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/merkle-tree/blob/master/src/core.ts) /// @author Modified from Murky (https://github.com/dmfxyz/murky) library MerkleTreeLib { - /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ - /* STRUCTS */ - /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ - - /// @dev A complete Merkle tree in memory. - - struct MerkleTree { - bytes32[] nodes; - } - /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ From 3950bc935bc1ecb0106dbee6174936f2928266b1 Mon Sep 17 00:00:00 2001 From: Vectorized Date: Sun, 22 Jun 2025 19:18:16 +0000 Subject: [PATCH 07/10] T --- src/utils/MerkleTreeLib.sol | 77 +++++++++++++++++++++++++++++++------ test/MerkleTreeLib.t.sol | 28 +++++++++++++- 2 files changed, 93 insertions(+), 12 deletions(-) diff --git a/src/utils/MerkleTreeLib.sol b/src/utils/MerkleTreeLib.sol index c773e63bf7..3f48cbeef5 100644 --- a/src/utils/MerkleTreeLib.sol +++ b/src/utils/MerkleTreeLib.sol @@ -17,6 +17,9 @@ library MerkleTreeLib { /// Check if the tree has been built and has sufficient leafs and nodes. error MerkleTreeOutOfBoundsAccess(); + /// @dev Leaf indices for multi proof must be strictly ascending and not empty. + error MerkleTreeInvalidLeafIndices(); + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* MERKLE TREE OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -144,22 +147,74 @@ library MerkleTreeLib { } /// @dev Returns proof and corresponding flags for multiple leafs. + /// The `leafIndices` must be non-empty and sorted in strictly ascending order. function leafsMultiProof(bytes32[] memory t, uint256[] memory leafIndices) internal pure returns (bytes32[] memory proof, bool[] memory flags) { - // /// @solidity memory-safe-assembly - // assembly { - // let nodes := mload(t) - // let n := mload(nodes) - // let m := mload(0x40) - // let flagsMap := m - // 10000 / 256 * 32 - - // let numIndices := mload(leafIndices) - // proof := add(0x2000, flagsMap) - // } + /// @solidity memory-safe-assembly + assembly { + function gen(leafIndices_, t_, proof_, flags_) -> _flagsLen, _proofLen { + let e_ := mload(leafIndices_) // End index of circular buffer. + let c_ := add(1, e_) // Capacity of circular buffer. + let q_ := mload(0x40) // Circular buffer. + if iszero(e_) { + mstore(0x00, 0xe9729976) // `MerkleTreeInvalidLeafIndices()`. + revert(0x1c, 0x04) + } + for { + let n_ := mload(t_) // Num nodes. + let l_ := sub(n_, shr(1, n_)) // Num leafs. + let p_ := 0 + let i_ := 0 + } 1 {} { + let j_ := mload(add(add(leafIndices_, 0x20), shl(5, i_))) // Leaf index. + if flags_ { + if iszero(lt(j_, l_)) { + mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. + revert(0x1c, 0x04) + } + if iszero(or(iszero(i_), gt(j_, p_))) { + mstore(0x00, 0xe9729976) // `MerkleTreeInvalidLeafIndices()`. + revert(0x1c, 0x04) + } + p_ := j_ + } + mstore(add(q_, shl(5, i_)), sub(n_, add(1, j_))) + i_ := add(i_, 1) + if eq(i_, e_) { break } + } + t_ := add(t_, 0x20) + let b_ := 0 // Start index of circular buffer. + for {} 1 {} { + if iszero(lt(b_, e_)) { break } + let j_ := mload(add(q_, shl(5, mod(b_, c_)))) // Current. + if iszero(j_) { break } + b_ := add(b_, 1) + let s_ := sub(j_, sub(1, shl(1, and(j_, 1)))) // Sibling. + _flagsLen := add(_flagsLen, 0x20) + let f_ := and(eq(s_, mload(add(q_, shl(5, mod(b_, c_))))), lt(b_, e_)) + b_ := add(b_, f_) + if flags_ { mstore(add(flags_, _flagsLen), f_) } + if iszero(f_) { + _proofLen := add(_proofLen, 0x20) + if flags_ { mstore(add(proof_, _proofLen), mload(add(t_, shl(5, s_)))) } + } + mstore(add(q_, shl(5, mod(e_, c_))), shr(1, sub(j_, 1))) + e_ := add(e_, 1) + } + _proofLen := shr(5, _proofLen) + _flagsLen := shr(5, _flagsLen) + } + let flagsLen, proofLen := gen(leafIndices, t, 0x00, 0x00) + proof := mload(0x40) + mstore(proof, proofLen) + flags := add(add(proof, 0x20), shl(5, proofLen)) + mstore(flags, flagsLen) + mstore(0x40, add(add(flags, 0x20), shl(5, flagsLen))) + flagsLen, proofLen := gen(leafIndices, t, proof, flags) + } } /// @dev Returns a copy of leafs, with the length padded to a power of 2. diff --git a/test/MerkleTreeLib.t.sol b/test/MerkleTreeLib.t.sol index 155d1bdec5..19d18bb724 100644 --- a/test/MerkleTreeLib.t.sol +++ b/test/MerkleTreeLib.t.sol @@ -152,6 +152,31 @@ contract MerkleTreeLibTest is SoladyTest { return (new bytes32[](0)).root(); } + struct TestMultiProofTemps { + bytes32[] leafs; + uint256[] leafIndices; + bytes32[] gathered; + bytes32[] tree; + bytes32[] proof; + bool[] flags; + } + + function testBuildAndGetLeafsMultiProof(bytes32) public { + TestMultiProofTemps memory t; + t.leafs = new bytes32[](_bound(_random(), 1, 32)); + for (uint256 i; i < t.leafs.length; ++i) { + t.leafs[i] = bytes32(_randomUniform()); + } + t.leafIndices = _generateUniqueLeafIndices(t.leafs); + + t.tree = MerkleTreeLib.build(t.leafs); + + (t.proof, t.flags) = t.tree.leafsMultiProof(t.leafIndices); + t.gathered = _gatherLeafs(t.leafs, t.leafIndices); + + assertTrue(MerkleProofLib.verifyMultiProof(t.proof, t.tree.root(), t.gathered, t.flags)); + } + function _generateUniqueLeafIndices(bytes32[] memory leafs) internal returns (uint256[] memory indices) @@ -163,11 +188,12 @@ contract MerkleTreeLibTest is SoladyTest { LibPRNG.PRNG memory prng; prng.seed(_randomUniform()); prng.shuffle(indices); - uint256 n = _bound(_random(), 0, indices.length); + uint256 n = _bound(_random(), 1, indices.length); /// @solidity memory-safe-assembly assembly { mstore(indices, n) } + LibSort.sort(indices); } function _gatherLeafs(bytes32[] memory leafs, uint256[] memory indices) From 196d273c004d47b44b0055f4a24388b32e6e5c0c Mon Sep 17 00:00:00 2001 From: Vectorized Date: Sun, 22 Jun 2025 19:18:43 +0000 Subject: [PATCH 08/10] T --- src/utils/g/MerkleTreeLib.sol | 208 ---------------------------------- 1 file changed, 208 deletions(-) delete mode 100644 src/utils/g/MerkleTreeLib.sol diff --git a/src/utils/g/MerkleTreeLib.sol b/src/utils/g/MerkleTreeLib.sol deleted file mode 100644 index 0f323171ed..0000000000 --- a/src/utils/g/MerkleTreeLib.sol +++ /dev/null @@ -1,208 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; - -// This file is auto-generated. - -/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ -/* STRUCTS */ -/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ - -/// @dev A complete Merkle tree in memory. - -struct MerkleTree { - bytes32[] nodes; -} - -using MerkleTreeLib for MerkleTree global; - -/// @notice Gas optimized verification of proof of inclusion for a leaf in a Merkle tree. -/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/g/MerkleTreeLib.sol) -/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/merkle-tree/blob/master/src/core.ts) -/// @author Modified from Murky (https://github.com/dmfxyz/murky) -library MerkleTreeLib { - /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ - /* CUSTOM ERRORS */ - /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ - - /// @dev At least 1 leaf is required to build the tree. - error MerkleTreeLeafsEmpty(); - - /// @dev Attempt to access a node with an out-of-bounds index. - /// Check if the tree has been built and has sufficient leafs and nodes. - error MerkleTreeOutOfBoundsAccess(); - - /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ - /* MERKLE TREE OPERATIONS */ - /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ - - /// @dev Builds and return a complete Merkle tree. - /// To make it a full Merkle tree, use `build(pad(leafs))`. - function build(bytes32[] memory leafs) internal pure returns (bytes32[] memory result) { - /// @solidity memory-safe-assembly - assembly { - result := mload(0x40) // `nodes`. - let l := mload(leafs) - if iszero(l) { - mstore(0x00, 0x089aff6e) // `MerkleTreeLeafsEmpty()`. - revert(0x1c, 0x04) - } - let n := sub(add(l, l), 1) - mstore(result, n) // `.length`. - let nodes := add(result, 0x20) - let f := add(nodes, shl(5, n)) - mstore(0x40, f) // Allocate memory. - let e := add(0x20, shl(5, l)) - for { let i := 0x20 } 1 {} { - mstore(sub(f, i), mload(add(leafs, i))) - i := add(i, 0x20) - if eq(i, e) { break } - } - if iszero(lt(l, 2)) { - for { let i := shl(5, sub(l, 2)) } 1 {} { - let left := mload(add(nodes, add(add(i, i), 0x20))) - let right := mload(add(nodes, add(add(i, i), 0x40))) - let c := shl(5, lt(left, right)) - mstore(c, right) - mstore(xor(c, 0x20), left) - mstore(add(nodes, i), keccak256(0x00, 0x40)) - if iszero(i) { break } - i := sub(i, 0x20) - } - } - } - } - - /// @dev Returns the root. - function root(bytes32[] memory t) internal pure returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { - result := mload(add(0x20, t)) - if iszero(mload(t)) { - mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. - revert(0x1c, 0x04) - } - } - } - - /// @dev Returns the number of leafs. - function numLeafs(bytes32[] memory t) internal pure returns (uint256) { - unchecked { - return t.length - (t.length >> 1); - } - } - - /// @dev Returns the number of internal nodes. - function numInternalNodes(bytes32[] memory t) internal pure returns (uint256) { - return t.length >> 1; - } - - /// @dev Returns the leaf at `leafIndex`. - function leaf(bytes32[] memory t, uint256 leafIndex) internal pure returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { - let n := mload(t) - if iszero(lt(leafIndex, sub(n, shr(1, n)))) { - mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. - revert(0x1c, 0x04) - } - result := mload(add(t, shl(5, sub(n, leafIndex)))) - } - } - - /// @dev Returns the proof for the leaf at `leafIndex`. - function leafProof(bytes32[] memory t, uint256 leafIndex) - internal - pure - returns (bytes32[] memory result) - { - /// @solidity memory-safe-assembly - assembly { - result := mload(0x40) - let n := mload(t) - if iszero(lt(leafIndex, sub(n, shr(1, n)))) { - mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. - revert(0x1c, 0x04) - } - let o := add(result, 0x20) - for { let i := sub(n, add(1, leafIndex)) } i { i := shr(1, sub(i, 1)) } { - mstore(o, mload(add(t, shl(5, add(i, shl(1, and(1, i))))))) - o := add(o, 0x20) - } - mstore(0x40, o) // Allocate memory. - mstore(result, shr(5, sub(o, add(result, 0x20)))) // Store length. - } - } - - /// @dev Returns the proof for the node at `nodeIndex`. - /// This function can be used to prove the existence of internal nodes. - function nodeProof(bytes32[] memory t, uint256 nodeIndex) - internal - pure - returns (bytes32[] memory result) - { - /// @solidity memory-safe-assembly - assembly { - result := mload(0x40) - if iszero(lt(nodeIndex, mload(t))) { - mstore(0x00, 0x7a856a38) // `MerkleTreeOutOfBoundsAccess()`. - revert(0x1c, 0x04) - } - let o := add(result, 0x20) - for { let i := nodeIndex } i { i := shr(1, sub(i, 1)) } { - mstore(o, mload(add(t, shl(5, add(i, shl(1, and(1, i))))))) - o := add(o, 0x20) - } - mstore(0x40, o) // Allocate memory. - mstore(result, shr(5, sub(o, add(result, 0x20)))) // Store length. - } - } - - /// @dev Returns proof and corresponding flags for multiple leafs. - function leafsMultiProof(bytes32[] memory t, uint256[] memory leafIndices) - internal - pure - returns (bytes32[] memory proof, bool[] memory flags) - { - // /// @solidity memory-safe-assembly - // assembly { - // let nodes := mload(t) - // let n := mload(nodes) - // let m := mload(0x40) - // let flagsMap := m - // 10000 / 256 * 32 - - // let numIndices := mload(leafIndices) - // proof := add(0x2000, flagsMap) - // } - } - - /// @dev Returns a copy of leafs, with the length padded to a power of 2. - function pad(bytes32[] memory leafs, bytes32 defaultFill) - internal - pure - returns (bytes32[] memory result) - { - /// @solidity memory-safe-assembly - assembly { - result := mload(0x40) - let l := mload(leafs) - if iszero(l) { - mstore(0x00, 0x089aff6e) // `MerkleTreeLeafsEmpty()`. - revert(0x1c, 0x04) - } - let p := 1 // Padded length. - for {} lt(p, l) {} { p := add(p, p) } - mstore(result, p) // Store length. - mstore(0x40, add(result, add(0x20, shl(5, p)))) // Allocate memory. - let d := sub(result, leafs) - let copyEnd := add(add(leafs, 0x20), shl(5, l)) - let end := add(add(leafs, 0x20), shl(5, p)) - mstore(0x00, defaultFill) - for { let i := add(leafs, 0x20) } 1 {} { - mstore(add(i, d), mload(mul(i, lt(i, copyEnd)))) - i := add(i, 0x20) - if eq(i, end) { break } - } - } - } -} From 951f18f6a6ea6371c39c2a06e0db2714badef9dd Mon Sep 17 00:00:00 2001 From: Vectorized Date: Sun, 22 Jun 2025 19:26:57 +0000 Subject: [PATCH 09/10] T --- docs/utils/merkletreelib.md | 135 ++++++++++++++++++++++++++++++++++++ src/utils/MerkleTreeLib.sol | 13 ++-- test/MerkleTreeLib.t.sol | 7 +- 3 files changed, 143 insertions(+), 12 deletions(-) create mode 100644 docs/utils/merkletreelib.md diff --git a/docs/utils/merkletreelib.md b/docs/utils/merkletreelib.md new file mode 100644 index 0000000000..6a22523ab1 --- /dev/null +++ b/docs/utils/merkletreelib.md @@ -0,0 +1,135 @@ +# MerkleTreeLib + +Gas optimized verification of proof of inclusion for a leaf in a Merkle tree. + + + + + + + + +## Custom Errors + +### MerkleTreeLeafsEmpty() + +```solidity +error MerkleTreeLeafsEmpty() +``` + +At least 1 leaf is required to build the tree. + +### MerkleTreeOutOfBoundsAccess() + +```solidity +error MerkleTreeOutOfBoundsAccess() +``` + +Attempt to access a node with an out-of-bounds index. +Check if the tree has been built and has sufficient leafs and nodes. + +### MerkleTreeInvalidLeafIndices() + +```solidity +error MerkleTreeInvalidLeafIndices() +``` + +Leaf indices for multi proof must be strictly ascending and not empty. + +## Merkle Tree Operations + +### build(bytes32[]) + +```solidity +function build(bytes32[] memory leafs) + internal + pure + returns (bytes32[] memory result) +``` + +Builds and return a complete Merkle tree. +To make it a full Merkle tree, use `build(pad(leafs))`. + +### root(bytes32[]) + +```solidity +function root(bytes32[] memory t) internal pure returns (bytes32 result) +``` + +Returns the root. + +### numLeafs(bytes32[]) + +```solidity +function numLeafs(bytes32[] memory t) internal pure returns (uint256) +``` + +Returns the number of leafs. + +### numInternalNodes(bytes32[]) + +```solidity +function numInternalNodes(bytes32[] memory t) + internal + pure + returns (uint256) +``` + +Returns the number of internal nodes. + +### leaf(bytes32[],uint256) + +```solidity +function leaf(bytes32[] memory t, uint256 leafIndex) + internal + pure + returns (bytes32 result) +``` + +Returns the leaf at `leafIndex`. + +### leafProof(bytes32[],uint256) + +```solidity +function leafProof(bytes32[] memory t, uint256 leafIndex) + internal + pure + returns (bytes32[] memory result) +``` + +Returns the proof for the leaf at `leafIndex`. + +### nodeProof(bytes32[],uint256) + +```solidity +function nodeProof(bytes32[] memory t, uint256 nodeIndex) + internal + pure + returns (bytes32[] memory result) +``` + +Returns the proof for the node at `nodeIndex`. +This function can be used to prove the existence of internal nodes. + +### leafsMultiProof(bytes32[],uint256[]) + +```solidity +function leafsMultiProof(bytes32[] memory t, uint256[] memory leafIndices) + internal + pure + returns (bytes32[] memory proof, bool[] memory flags) +``` + +Returns proof and corresponding flags for multiple leafs. +The `leafIndices` must be non-empty and sorted in strictly ascending order. + +### pad(bytes32[],bytes32) + +```solidity +function pad(bytes32[] memory leafs, bytes32 defaultFill) + internal + pure + returns (bytes32[] memory result) +``` + +Returns a copy of leafs, with the length padded to a power of 2. \ No newline at end of file diff --git a/src/utils/MerkleTreeLib.sol b/src/utils/MerkleTreeLib.sol index 3f48cbeef5..89aa17be00 100644 --- a/src/utils/MerkleTreeLib.sol +++ b/src/utils/MerkleTreeLib.sol @@ -156,9 +156,10 @@ library MerkleTreeLib { /// @solidity memory-safe-assembly assembly { function gen(leafIndices_, t_, proof_, flags_) -> _flagsLen, _proofLen { - let e_ := mload(leafIndices_) // End index of circular buffer. - let c_ := add(1, e_) // Capacity of circular buffer. let q_ := mload(0x40) // Circular buffer. + let c_ := mload(leafIndices_) // Capacity of circular buffer. + let e_ := mload(leafIndices_) // End index of circular buffer. + let b_ := 0 // Start index of circular buffer. if iszero(e_) { mstore(0x00, 0xe9729976) // `MerkleTreeInvalidLeafIndices()`. revert(0x1c, 0x04) @@ -185,16 +186,14 @@ library MerkleTreeLib { i_ := add(i_, 1) if eq(i_, e_) { break } } - t_ := add(t_, 0x20) - let b_ := 0 // Start index of circular buffer. for {} 1 {} { if iszero(lt(b_, e_)) { break } let j_ := mload(add(q_, shl(5, mod(b_, c_)))) // Current. if iszero(j_) { break } b_ := add(b_, 1) - let s_ := sub(j_, sub(1, shl(1, and(j_, 1)))) // Sibling. + let s_ := add(j_, shl(1, and(j_, 1))) // Sibling (+1). _flagsLen := add(_flagsLen, 0x20) - let f_ := and(eq(s_, mload(add(q_, shl(5, mod(b_, c_))))), lt(b_, e_)) + let f_ := and(eq(s_, add(1, mload(add(q_, shl(5, mod(b_, c_)))))), lt(b_, e_)) b_ := add(b_, f_) if flags_ { mstore(add(flags_, _flagsLen), f_) } if iszero(f_) { @@ -212,7 +211,7 @@ library MerkleTreeLib { mstore(proof, proofLen) flags := add(add(proof, 0x20), shl(5, proofLen)) mstore(flags, flagsLen) - mstore(0x40, add(add(flags, 0x20), shl(5, flagsLen))) + mstore(0x40, add(add(flags, 0x20), shl(5, flagsLen))) // Allocate memory. flagsLen, proofLen := gen(leafIndices, t, proof, flags) } } diff --git a/test/MerkleTreeLib.t.sol b/test/MerkleTreeLib.t.sol index 19d18bb724..7006490efd 100644 --- a/test/MerkleTreeLib.t.sol +++ b/test/MerkleTreeLib.t.sol @@ -163,17 +163,14 @@ contract MerkleTreeLibTest is SoladyTest { function testBuildAndGetLeafsMultiProof(bytes32) public { TestMultiProofTemps memory t; - t.leafs = new bytes32[](_bound(_random(), 1, 32)); + t.leafs = new bytes32[](_bound(_random(), 1, 128)); for (uint256 i; i < t.leafs.length; ++i) { - t.leafs[i] = bytes32(_randomUniform()); + t.leafs[i] = bytes32(_random()); } t.leafIndices = _generateUniqueLeafIndices(t.leafs); - t.tree = MerkleTreeLib.build(t.leafs); - (t.proof, t.flags) = t.tree.leafsMultiProof(t.leafIndices); t.gathered = _gatherLeafs(t.leafs, t.leafIndices); - assertTrue(MerkleProofLib.verifyMultiProof(t.proof, t.tree.root(), t.gathered, t.flags)); } From d0fc203aa2482ea87324e37ef14e57eb1eecf4a4 Mon Sep 17 00:00:00 2001 From: Vectorized Date: Sun, 22 Jun 2025 19:28:00 +0000 Subject: [PATCH 10/10] T --- docs/utils/merkletreelib.md | 2 +- src/utils/MerkleTreeLib.sol | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/utils/merkletreelib.md b/docs/utils/merkletreelib.md index 6a22523ab1..d8025e7b38 100644 --- a/docs/utils/merkletreelib.md +++ b/docs/utils/merkletreelib.md @@ -1,6 +1,6 @@ # MerkleTreeLib -Gas optimized verification of proof of inclusion for a leaf in a Merkle tree. +Library for generating Merkle trees. diff --git a/src/utils/MerkleTreeLib.sol b/src/utils/MerkleTreeLib.sol index 89aa17be00..e898f93709 100644 --- a/src/utils/MerkleTreeLib.sol +++ b/src/utils/MerkleTreeLib.sol @@ -1,10 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -/// @notice Gas optimized verification of proof of inclusion for a leaf in a Merkle tree. +/// @notice Library for generating Merkle trees. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/MerkleTreeLib.sol) /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/merkle-tree/blob/master/src/core.ts) -/// @author Modified from Murky (https://github.com/dmfxyz/murky) library MerkleTreeLib { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */