Skip to content

Commit

Permalink
test: FullMerkle tests (#553)
Browse files Browse the repository at this point in the history
Co-authored-by: kelemeno <[email protected]>
Co-authored-by: Vlad Bochok <[email protected]>
  • Loading branch information
3 people committed Jun 29, 2024
1 parent 8c0b80c commit 1c4dce4
Show file tree
Hide file tree
Showing 10 changed files with 411 additions and 98 deletions.
16 changes: 12 additions & 4 deletions l1-contracts/contracts/common/libraries/FullMerkle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ library FullMerkle {
// solhint-disable-next-line gas-increment-by-one
uint256 index = self._leafNumber++;

if ((index == 1 << self._height)) {
if (index == 1 << self._height) {
uint256 newHeight = self._height.uncheckedInc();
self._height = newHeight;
bytes32 topZero = self._zeros[newHeight - 1];
Expand Down Expand Up @@ -100,12 +100,20 @@ library FullMerkle {
self._nodes[_height][0] = _newNodes[0];
return _newNodes[0];
}
bytes32[] memory _newRow;

uint256 newRowLength = (_newNodes.length + 1) / 2;
bytes32[] memory _newRow = new bytes32[](newRowLength);

uint256 length = _newNodes.length;
for (uint256 i; i < length; i = i.uncheckedAdd(2)) {
self._nodes[_height][i] = _newNodes[i];
self._nodes[_height][i + 1] = _newNodes[i + 1];
_newRow[i / 2] = Merkle.efficientHash(_newNodes[i], _newNodes[i + 1]);
if (i + 1 < length) {
self._nodes[_height][i + 1] = _newNodes[i + 1];
_newRow[i / 2] = Merkle.efficientHash(_newNodes[i], _newNodes[i + 1]);
} else {
// Handle odd number of nodes by hashing the last node with zero
_newRow[i / 2] = Merkle.efficientHash(_newNodes[i], self._zeros[_height]);
}
}
return updateAllNodesAtHeight(self, _height + 1, _newRow);
}
Expand Down
4 changes: 4 additions & 0 deletions l1-contracts/contracts/dev-contracts/test/FullMerkleTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ contract FullMerkleTest {
tree.updateAllLeaves(_items);
}

function updateAllNodesAtHeight(uint256 _height, bytes32[] memory _items) external {
tree.updateAllNodesAtHeight(_height, _items);
}

function root() external view returns (bytes32) {
return tree.root();
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {FullMerkleTest} from "./_FullMerkle_Shared.t.sol";

contract PushNewLeafTest is FullMerkleTest {
function test_oneLeaf() public {
// Inserting one leaf
bytes32 leaf0 = keccak256("Leaf 0");
merkleTest.pushNewLeaf(leaf0);

// Checking the tree structure
assertEq(merkleTest.height(), 0, "Height should be 0 after one insert");
assertEq(merkleTest.index(), 1, "Leaf number should be 1 after one insert");

// Checking leaf node
assertEq(merkleTest.node(0, 0), leaf0, "Node 0,0 should be correctly inserted");

// Chekcking zeros tree structure
assertEq(merkleTest.zeros(0), zeroHash, "Zero 0 should be correctly inserted");
}

function test_twoLeaves() public {
// Inserting two leaves
bytes32 leaf0 = keccak256("Leaf 0");
bytes32 leaf1 = keccak256("Leaf 1");
merkleTest.pushNewLeaf(leaf0);
merkleTest.pushNewLeaf(leaf1);

// Checking the tree structure
assertEq(merkleTest.height(), 1, "Height should be 1 after two inserts");
assertEq(merkleTest.index(), 2, "Leaf number should be 2 after two inserts");

// Checking leaf nodes
assertEq(merkleTest.node(0, 0), leaf0, "Node 0,0 should be correctly inserted");
assertEq(merkleTest.node(0, 1), leaf1, "Node 0,1 should be correctly inserted");

// Checking parent node
bytes32 l01Hashed = keccak(leaf0, leaf1);
assertEq(merkleTest.node(1, 0), l01Hashed, "Node 1,0 should be correctly inserted");

// Checking zeros
bytes32 zeroHashed = keccak(zeroHash, zeroHash);
assertEq(merkleTest.zeros(1), zeroHashed, "Zero 1 should be correctly inserted");
}

function test_threeLeaves() public {
// Insert three leaves
bytes32 leaf0 = keccak256("Leaf 0");
bytes32 leaf1 = keccak256("Leaf 1");
bytes32 leaf2 = keccak256("Leaf 2");
merkleTest.pushNewLeaf(leaf0);
merkleTest.pushNewLeaf(leaf1);
merkleTest.pushNewLeaf(leaf2);

// Checking the tree structure
assertEq(merkleTest.height(), 2, "Height should be 2 after three inserts");
assertEq(merkleTest.index(), 3, "Leaf number should be 3 after three inserts");

// Checking leaf nodes
assertEq(merkleTest.node(0, 0), leaf0, "Node 0,0 should be correctly inserted");
assertEq(merkleTest.node(0, 1), leaf1, "Node 0,1 should be correctly inserted");
assertEq(merkleTest.node(0, 2), leaf2, "Node 0,2 should be correctly inserted");

// Checking parent nodes
bytes32 l01Hashed = keccak(leaf0, leaf1);
assertEq(merkleTest.node(1, 0), l01Hashed, "Node 1,0 should be correctly inserted");
// there is no leaf3 so we hash leaf2 with zero
bytes32 l23Hashed = keccak(leaf2, merkleTest.zeros(0));
assertEq(merkleTest.node(1, 1), l23Hashed, "Node 1,1 should be correctly inserted");

// Checking root node
bytes32 l01l23Hashed = keccak(l01Hashed, l23Hashed);
assertEq(merkleTest.node(2, 0), l01l23Hashed, "Node 2,0 should be correctly inserted");

// Checking zero
bytes32 zeroHashed = keccak(zeroHash, zeroHash);
assertEq(merkleTest.zeros(1), zeroHashed, "Zero 1 should be correctly inserted");
bytes32 zhHashed = keccak(zeroHashed, zeroHashed);
assertEq(merkleTest.zeros(2), zhHashed, "Zero 2 should be correctly inserted");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {FullMerkleTest} from "./_FullMerkle_Shared.t.sol";

contract RootTest is FullMerkleTest {
function test_emptyTree() public view {
// Initially tree is empty, root is the zero hash
assertEq(merkleTest.root(), zeroHash, "Root should be zero hash initially");
}

function test_oneLeaf() public {
// Inserting one leaf
bytes32 leaf = keccak256("Leaf 0");
merkleTest.pushNewLeaf(leaf);

// With one leaf, root is the leaf itself
assertEq(merkleTest.root(), leaf, "Root should be the leaf hash");
}

function test_twoLeaves() public {
// Inserting two leaves
bytes32 leaf0 = keccak256("Leaf 0");
bytes32 leaf1 = keccak256("Leaf 1");
merkleTest.pushNewLeaf(leaf0);
merkleTest.pushNewLeaf(leaf1);

// Calculate expected root
bytes32 expectedRoot = keccak(leaf0, leaf1);
assertEq(merkleTest.root(), expectedRoot, "Root should be the hash of the two leaves");
}

function test_nodeCountAndRoot() public {
// Initially tree is empty
assertEq(merkleTest.nodeCount(0), 1, "Initial node count at height 0 should be 1");

// Inserting three leaves and checking counts and root
bytes32 leaf0 = keccak256("Leaf 0");
bytes32 leaf1 = keccak256("Leaf 1");
bytes32 leaf2 = keccak256("Leaf 2");
merkleTest.pushNewLeaf(leaf0);
merkleTest.pushNewLeaf(leaf1);
merkleTest.pushNewLeaf(leaf2);

assertEq(merkleTest.nodeCount(0), 3, "Node count at height 0 should be 3 after three inserts");
assertEq(merkleTest.nodeCount(1), 2, "Node count at height 1 should be 2");
assertEq(merkleTest.nodeCount(2), 1, "Node count at height 2 should be 1");

// Calculate expected root to verify correctness
bytes32 leftChild = keccak(leaf0, leaf1);
bytes32 rightChild = keccak(leaf2, merkleTest.zeros(0));
bytes32 expectedRoot = keccak(leftChild, rightChild);

assertEq(merkleTest.root(), expectedRoot, "Root should match expected value after inserts");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {FullMerkleTest} from "./_FullMerkle_Shared.t.sol";

contract SetupTest is FullMerkleTest {
function test_checkInit() public view {
assertEq(merkleTest.height(), 0, "Height should be 0");
assertEq(merkleTest.index(), 0, "Leaf number should be 0");
assertEq(merkleTest.zeros(0), zeroHash, "Zero hash should be correctly initialized");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {FullMerkleTest} from "./_FullMerkle_Shared.t.sol";

contract UpdateAllLeavesTest is FullMerkleTest {
function test_revertWhen_wrongLength() public {
// Inserting two leaves
bytes32 leaf0 = keccak256("Leaf 0");
bytes32 leaf1 = keccak256("Leaf 1");
merkleTest.pushNewLeaf(leaf0);
merkleTest.pushNewLeaf(leaf1);

// Preparing new leaves for full update
bytes32[] memory newLeaves = new bytes32[](3);
newLeaves[0] = keccak256("New Leaf 0");
newLeaves[1] = keccak256("New Leaf 1");
newLeaves[2] = keccak256("New Leaf 2");

// Updating all leaves with wrong length
vm.expectRevert(bytes("FMT, wrong length"));
merkleTest.updateAllLeaves(newLeaves);
}

function test_oneLeaf() public {
// Inserting one leaf
bytes32 leaf0 = keccak256("Leaf 0");
merkleTest.pushNewLeaf(leaf0);

// Preparing new leaves for full update
bytes32[] memory newLeaves = new bytes32[](1);
newLeaves[0] = keccak256("New Leaf 0");

// Updating all leaves
merkleTest.updateAllLeaves(newLeaves);

// Checking leaf nodes
assertEq(merkleTest.node(0, 0), newLeaves[0], "Node 0,0 should be correctly updated");
}

function test_twoLeaves() public {
// Inserting two leaves
bytes32 leaf0 = keccak256("Leaf 0");
bytes32 leaf1 = keccak256("Leaf 1");
merkleTest.pushNewLeaf(leaf0);
merkleTest.pushNewLeaf(leaf1);

// Preparing new leaves for full update
bytes32[] memory newLeaves = new bytes32[](2);
newLeaves[0] = keccak256("New Leaf 0");
newLeaves[1] = keccak256("New Leaf 1");

// Updating all leaves
merkleTest.updateAllLeaves(newLeaves);

// Checking leaf nodes
assertEq(merkleTest.node(0, 0), newLeaves[0], "Node 0,0 should be correctly updated");
assertEq(merkleTest.node(0, 1), newLeaves[1], "Node 0,1 should be correctly updated");

// Checking parent node
bytes32 l01Hashed = keccak(newLeaves[0], newLeaves[1]);
assertEq(merkleTest.node(1, 0), l01Hashed, "Node 1,0 should be correctly updated");
}

function test_threeLeaves() public {
// Inserting two leaves
bytes32 leaf0 = keccak256("Leaf 0");
bytes32 leaf1 = keccak256("Leaf 1");
bytes32 leaf2 = keccak256("Leaf 2");
merkleTest.pushNewLeaf(leaf0);
merkleTest.pushNewLeaf(leaf1);
merkleTest.pushNewLeaf(leaf2);

// Preparing new leaves for full update
bytes32[] memory newLeaves = new bytes32[](3);
newLeaves[0] = keccak256("New Leaf 0");
newLeaves[1] = keccak256("New Leaf 1");
newLeaves[2] = keccak256("New Leaf 2");

// Updating all leaves
merkleTest.updateAllLeaves(newLeaves);

// Checking leaf nodes
assertEq(merkleTest.node(0, 0), newLeaves[0], "Node 0,0 should be correctly updated");
assertEq(merkleTest.node(0, 1), newLeaves[1], "Node 0,1 should be correctly updated");
assertEq(merkleTest.node(0, 2), newLeaves[2], "Node 0,2 should be correctly updated");

// Checking parent nodes
bytes32 l01Hashed = keccak(newLeaves[0], newLeaves[1]);
assertEq(merkleTest.node(1, 0), l01Hashed, "Node 1,0 should be correctly updated");
// There is no leaf3 so we hash leaf2 with zero
bytes32 l23Hashed = keccak(newLeaves[2], merkleTest.zeros(0));
assertEq(merkleTest.node(1, 1), l23Hashed, "Node 1,1 should be correctly updated");

// Checking root node
bytes32 l01l23Hashed = keccak(l01Hashed, l23Hashed);
assertEq(merkleTest.node(2, 0), l01l23Hashed, "Node 2,0 should be correctly updated");
}
}
Loading

0 comments on commit 1c4dce4

Please sign in to comment.