Skip to content
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

Add a MerkleTree builder #3617

Merged
merged 54 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
6b079ee
Add a (complete) MerkleTree structure
Amxx Aug 14, 2022
42c695b
optimize array access & remove depth/length constrains
Amxx Aug 14, 2022
ca83cde
gas optimization
Amxx Aug 14, 2022
f4f46ca
limit tree depth to 255 to avoid issues (255 is enough for any realis…
Amxx Aug 14, 2022
af7cb9c
fix lint
Amxx Aug 14, 2022
45eae37
reason
Amxx Aug 14, 2022
ac648c6
coverage
Amxx Aug 14, 2022
1b184c9
comments
Amxx Aug 14, 2022
4863418
documentation & changelog entry
Amxx Aug 16, 2022
3c19dcf
Merge branch 'master' into structure/merkletree
Amxx Dec 13, 2023
9fc7f31
update
Amxx Dec 13, 2023
652c8a1
add changeset
Amxx Dec 13, 2023
c74ab55
fix lint
Amxx Dec 14, 2023
a9932c9
fix lint
Amxx Dec 14, 2023
d8bdfd0
fix codespell
Amxx Dec 14, 2023
4d0ed52
Merge branch 'master' into structure/merkletree
Amxx Jan 4, 2024
b131354
update @openzeppelin/merkle-tree dependency
Amxx Jan 28, 2024
6422af6
fix lint
Amxx Jan 28, 2024
3ab0d21
Merge branch 'master' into structure/merkletree
Amxx Feb 5, 2024
5639d7c
up
Amxx Feb 5, 2024
24c829a
minimize changes
Amxx Feb 5, 2024
f954a98
Panic with RESOURCE_ERROR when inserting in a full tree
Amxx Feb 5, 2024
acdc6a9
Merge branch 'master' into structure/merkletree
Amxx Feb 6, 2024
cebdc2a
improve coverage
Amxx Feb 6, 2024
d4ced94
test looparound property of memory arrays
Amxx Feb 6, 2024
a3a813c
rename initialize → setUp
Amxx Feb 7, 2024
ec05d19
Update contracts/utils/structs/MerkleTree.sol
Amxx Feb 7, 2024
8ecc790
Merge branch 'master' into structure/merkletree
Amxx Feb 12, 2024
ec3d96b
fix lint
Amxx Feb 12, 2024
bcc0667
cleanup
Amxx Feb 12, 2024
b50ebee
Merge branch 'master' into structure/merkletree
Amxx Feb 16, 2024
5b15205
remove root history from the MerkleTree structure
Amxx Feb 19, 2024
b390790
Add Hashes.sol
Amxx Feb 19, 2024
e331674
fix-lint
Amxx Feb 19, 2024
91f7057
rename to reflect removal of history
Amxx Feb 19, 2024
088fa8c
rename setUp → setup
Amxx Feb 19, 2024
2d869b7
doc
Amxx Feb 20, 2024
567cd3e
Update contracts/utils/structs/MerkleTree.sol
Amxx Feb 20, 2024
a13237a
Update MerkleTree.sol
Amxx Feb 20, 2024
03bea3e
Update changesets and fix some comments
ernestognw Feb 21, 2024
c475bad
Simplify
ernestognw Feb 21, 2024
6a9e873
Add Merkle Tree to the docs
ernestognw Feb 21, 2024
1e59539
Remove merkletree.test.js
ernestognw Feb 21, 2024
051107b
Recover MerkleTree.test.js
ernestognw Feb 21, 2024
a1dd158
prefix variables with underscore to mark them as private (similar do …
Amxx Feb 21, 2024
7a21c4e
test reseting the tree using setup
Amxx Feb 21, 2024
01c2879
rename hashing functions
Amxx Feb 21, 2024
2494680
return index and root when inserting a leaf
Amxx Feb 21, 2024
0a2bfce
rename structure and functions
Amxx Feb 21, 2024
08c9a3c
Apply PR suggestions
ernestognw Mar 5, 2024
31712fb
Update contracts/utils/cryptography/Hashes.sol
Amxx Mar 5, 2024
55853be
rename the standard node hash
Amxx Mar 6, 2024
eca27fc
fix lint
Amxx Mar 6, 2024
eca9085
Fix NatSpec weird error
ernestognw Mar 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/odd-files-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`Hashes`: A library with commonly used hash functions.
5 changes: 5 additions & 0 deletions .changeset/warm-sheep-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`MerkleTree`: A data structure that allows inserting elements into a merkle tree and updating its root hash.
24 changes: 24 additions & 0 deletions contracts/mocks/ArraysMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ contract Uint256ArraysMock {
function _reverse(uint256 a, uint256 b) private pure returns (bool) {
return a > b;
}

function unsafeSetLength(uint256 newLength) external {
_array.unsafeSetLength(newLength);
}

function length() external view returns (uint256) {
return _array.length;
}
}

contract AddressArraysMock {
Expand All @@ -74,6 +82,14 @@ contract AddressArraysMock {
function _reverse(address a, address b) private pure returns (bool) {
return uint160(a) > uint160(b);
}

function unsafeSetLength(uint256 newLength) external {
_array.unsafeSetLength(newLength);
}

function length() external view returns (uint256) {
return _array.length;
}
}

contract Bytes32ArraysMock {
Expand All @@ -100,4 +116,12 @@ contract Bytes32ArraysMock {
function _reverse(bytes32 a, bytes32 b) private pure returns (bool) {
return uint256(a) > uint256(b);
}

function unsafeSetLength(uint256 newLength) external {
_array.unsafeSetLength(newLength);
}

function length() external view returns (uint256) {
return _array.length;
}
}
43 changes: 43 additions & 0 deletions contracts/mocks/MerkleTreeMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {MerkleTree} from "../utils/structs/MerkleTree.sol";

contract MerkleTreeMock {
using MerkleTree for MerkleTree.Bytes32PushTree;

MerkleTree.Bytes32PushTree private _tree;

event LeafInserted(bytes32 leaf, uint256 index, bytes32 root);

function setup(uint8 _depth, bytes32 _zero) public {
_tree.setup(_depth, _zero);
}

function push(bytes32 leaf) public {
(uint256 leafIndex, bytes32 currentRoot) = _tree.push(leaf);
emit LeafInserted(leaf, leafIndex, currentRoot);
}

function root() public view returns (bytes32) {
return _tree.root();
}

function depth() public view returns (uint256) {
return _tree.depth();
}

// internal state
function nextLeafIndex() public view returns (uint256) {
return _tree._nextLeafIndex;
}

function sides(uint256 i) public view returns (bytes32) {
return _tree._sides[i];
}

function zeros(uint256 i) public view returns (bytes32) {
return _tree._zeros[i];
}
}
33 changes: 33 additions & 0 deletions contracts/utils/Arrays.sol
Original file line number Diff line number Diff line change
Expand Up @@ -440,4 +440,37 @@ library Arrays {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}
}

/**
* @dev Helper to set the length of an dynamic array. Directly writing to `.length` is forbidden.
*
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
*/
function unsafeSetLength(address[] storage array, uint256 len) internal {
assembly {
sstore(array.slot, len)
}
}

/**
* @dev Helper to set the length of an dynamic array. Directly writing to `.length` is forbidden.
*
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
*/
function unsafeSetLength(bytes32[] storage array, uint256 len) internal {
assembly {
sstore(array.slot, len)
}
}

/**
* @dev Helper to set the length of an dynamic array. Directly writing to `.length` is forbidden.
*
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
*/
function unsafeSetLength(uint256[] storage array, uint256 len) internal {
assembly {
sstore(array.slot, len)
}
}
}
10 changes: 8 additions & 2 deletions contracts/utils/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
* {SafeCast}: Checked downcasting functions to avoid silent truncation.
* {ECDSA}, {MessageHashUtils}: Libraries for interacting with ECDSA signatures.
* {SignatureChecker}: A library helper to support regular ECDSA from EOAs as well as ERC-1271 signatures for smart contracts.
* {Hashes}: Commonly used hash functions.
* {MerkleProof}: Functions for verifying https://en.wikipedia.org/wiki/Merkle_tree[Merkle Tree] proofs.
* {EIP712}: Contract with functions to allow processing signed typed structure data according to https://eips.ethereum.org/EIPS/eip-712[EIP-712].
* {ReentrancyGuard}: A modifier that can prevent reentrancy during certain functions.
Expand All @@ -20,6 +21,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
* {EnumerableSet}: Like {EnumerableMap}, but for https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets]. Can be used to store privileged accounts, issued IDs, etc.
* {DoubleEndedQueue}: An implementation of a https://en.wikipedia.org/wiki/Double-ended_queue[double ended queue] whose values can be removed added or remove from both sides. Useful for FIFO and LIFO structures.
* {Checkpoints}: A data structure to store values mapped to an strictly increasing key. Can be used for storing and accessing values over time.
* {MerkleTree}: A library with https://wikipedia.org/wiki/Merkle_Tree[Merkle Tree] data structures and helper functions.
* {Create2}: Wrapper around the https://blog.openzeppelin.com/getting-the-most-out-of-create2/[`CREATE2` EVM opcode] for safe use without having to deal with low-level assembly.
* {Address}: Collection of functions for overloading Solidity's https://docs.soliditylang.org/en/latest/types.html#address[`address`] type.
* {Arrays}: Collection of functions that operate on https://docs.soliditylang.org/en/latest/types.html#arrays[`arrays`].
Expand Down Expand Up @@ -48,13 +50,15 @@ Because Solidity does not support generic types, {EnumerableMap} and {Enumerable

{{ECDSA}}

{{EIP712}}

{{MessageHashUtils}}

{{SignatureChecker}}

{{MerkleProof}}
{{Hashes}}

{{EIP712}}
{{MerkleProof}}

== Security

Expand Down Expand Up @@ -88,6 +92,8 @@ Ethereum contracts have no native concept of an interface, so applications must

{{Checkpoints}}

{{MerkleTree}}

== Libraries

{{Create2}}
Expand Down
29 changes: 29 additions & 0 deletions contracts/utils/cryptography/Hashes.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
* @dev Library of standard hash functions.
*/
library Hashes {
/**
* @dev Commutative Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs.
*
* NOTE: Equivalent to the `standardNodeHash` in our https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
*/
function commutativeKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32) {
return a < b ? _efficientKeccak256(a, b) : _efficientKeccak256(b, a);
}

/**
* @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
*/
function _efficientKeccak256(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
29 changes: 6 additions & 23 deletions contracts/utils/cryptography/MerkleProof.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

pragma solidity ^0.8.20;

import {Hashes} from "./Hashes.sol";

/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
Expand Down Expand Up @@ -49,7 +51,7 @@ library MerkleProof {
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
}
return computedHash;
}
Expand All @@ -60,7 +62,7 @@ library MerkleProof {
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
}
return computedHash;
}
Expand Down Expand Up @@ -138,7 +140,7 @@ library MerkleProof {
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = _hashPair(a, b);
hashes[i] = Hashes.commutativeKeccak256(a, b);
}

if (totalHashes > 0) {
Expand Down Expand Up @@ -194,7 +196,7 @@ library MerkleProof {
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = _hashPair(a, b);
hashes[i] = Hashes.commutativeKeccak256(a, b);
}

if (totalHashes > 0) {
Expand All @@ -210,23 +212,4 @@ library MerkleProof {
return proof[0];
}
}

/**
* @dev Sorts the pair (a, b) and hashes the result.
*/
function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}

/**
* @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
*/
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
Loading
Loading