From 1c4dce474def04e9b6e6202448f7ab3ae86c252f Mon Sep 17 00:00:00 2001 From: Bence Haromi <56651250+benceharomi@users.noreply.github.com> Date: Sat, 29 Jun 2024 12:30:55 +0100 Subject: [PATCH] test: FullMerkle tests (#553) Co-authored-by: kelemeno Co-authored-by: Vlad Bochok <41153528+vladbochok@users.noreply.github.com> --- .../contracts/common/libraries/FullMerkle.sol | 16 ++- .../dev-contracts/test/FullMerkleTest.sol | 4 + .../libraries/FullMerkle/FullMerkle.t.sol | 94 ------------------ .../libraries/FullMerkle/PushNewLeaf.t.sol | 82 +++++++++++++++ .../common/libraries/FullMerkle/Root.t.sol | 56 +++++++++++ .../common/libraries/FullMerkle/Setup.t.sol | 12 +++ .../FullMerkle/UpdateAllLeaves.t.sol | 99 +++++++++++++++++++ .../FullMerkle/UpdateAllNodesAtHeight.t.sol | 82 +++++++++++++++ .../libraries/FullMerkle/UpdateLeaf.t.sol | 41 ++++++++ .../FullMerkle/_FullMerkle_Shared.t.sol | 23 +++++ 10 files changed, 411 insertions(+), 98 deletions(-) delete mode 100644 l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/FullMerkle.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/PushNewLeaf.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/Root.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/Setup.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/UpdateAllLeaves.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/UpdateAllNodesAtHeight.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/UpdateLeaf.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/_FullMerkle_Shared.t.sol diff --git a/l1-contracts/contracts/common/libraries/FullMerkle.sol b/l1-contracts/contracts/common/libraries/FullMerkle.sol index fc4161f17..39f94fb22 100644 --- a/l1-contracts/contracts/common/libraries/FullMerkle.sol +++ b/l1-contracts/contracts/common/libraries/FullMerkle.sol @@ -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]; @@ -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); } diff --git a/l1-contracts/contracts/dev-contracts/test/FullMerkleTest.sol b/l1-contracts/contracts/dev-contracts/test/FullMerkleTest.sol index 78f5160d5..0a7245800 100644 --- a/l1-contracts/contracts/dev-contracts/test/FullMerkleTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/FullMerkleTest.sol @@ -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(); } diff --git a/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/FullMerkle.t.sol b/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/FullMerkle.t.sol deleted file mode 100644 index 8bb4c191d..000000000 --- a/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/FullMerkle.t.sol +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import {Test} from "forge-std/Test.sol"; - -import {FullMerkleTest} from "contracts/dev-contracts/test/FullMerkleTest.sol"; - -contract FullMerkleTestTest is Test { - FullMerkleTest private merkleTest; - bytes32 constant zeroHash = keccak256(abi.encodePacked("ZERO")); - - function setUp() public { - merkleTest = new FullMerkleTest(zeroHash); - } - - function testCheckSetup() public { - assertEq(merkleTest.height(), 0, "Height should be correctly set to 3"); - assertEq(merkleTest.zeros(0), zeroHash, "Zero hash should be correctly initialized"); - } - - function testPushOneNewLeaf() public { - merkleTest.pushNewLeaf(bytes32(0)); - assertEq(merkleTest.height(), 0, "Height should be 0 after three inserts"); - assertEq(merkleTest.index(), 1, "Leaf number should be 1 after one insert"); - assertEq(merkleTest.node(0, 0), bytes32(0), "Node 1,0 should be correctly inserted"); - } - - function testPushTwoLeaves() public { - // Set up initial state with two leaves - merkleTest.pushNewLeaf(bytes32(0)); - merkleTest.pushNewLeaf(bytes32(uint256(1))); - assertEq(merkleTest.height(), 1, "Height should be 1 after three inserts"); - assertEq(merkleTest.index(), 2, "Leaf number should be 2 after two inserts"); - assertEq(merkleTest.node(0, 0), bytes32(0), "Node 0,0 should be correctly inserted"); - assertEq(merkleTest.node(0, 1), bytes32(uint256(1)), "Node 0,1 should be correctly inserted"); - bytes32 node = keccak(bytes32(0), bytes32(uint256(1))); - bytes32 zeroHashed = keccak(zeroHash, zeroHash); - assertEq(merkleTest.node(1, 0), node, "Node 1,0 should be correctly inserted"); - assertEq(merkleTest.zeros(1), zeroHashed, "Zero 1 should be correctly inserted"); - } - - function testPushThreeLeaves() public { - // Set up initial state with two leaves - merkleTest.pushNewLeaf(bytes32(0)); - merkleTest.pushNewLeaf(bytes32(uint256(1))); - merkleTest.pushNewLeaf(bytes32(uint256(2))); - assertEq(merkleTest.height(), 2, "Height should be 2 after three inserts"); - assertEq(merkleTest.index(), 3, "Leaf number should be 2 after two inserts"); - assertEq(merkleTest.node(0, 0), bytes32(0), "Node 0,0 should be correctly inserted"); - assertEq(merkleTest.node(0, 1), bytes32(uint256(1)), "Node 0,1 should be correctly inserted"); - assertEq(merkleTest.node(0, 2), bytes32(uint256(2)), "Node 0,2 should be correctly inserted"); - bytes32 node = keccak(bytes32(0), bytes32(uint256(1))); - bytes32 node2 = keccak(bytes32(uint256(2)), merkleTest.zeros(0)); - assertEq(merkleTest.node(1, 0), node, "Node 1,0 should be correctly inserted"); - assertEq(merkleTest.node(1, 1), node2, "Node 1,1 should be correctly inserted"); - node = keccak(node, node2); - bytes32 zeroHashed = keccak(merkleTest.zeros(1), merkleTest.zeros(1)); - assertEq(merkleTest.zeros(2), zeroHashed, "Zero 2 should be correctly inserted"); - assertEq(merkleTest.node(2, 0), node, "Node 2,0 should be correctly inserted"); - } - - function testUpdateLeaf() public { - // Update second leaf - merkleTest.pushNewLeaf(bytes32(0)); - merkleTest.pushNewLeaf(bytes32(uint256(1))); - merkleTest.updateLeaf(1, bytes32(uint256(2))); - assertEq(merkleTest.node(0, 0), bytes32(0), "Node 0,0 should be correctly inserted"); - assertEq(merkleTest.node(0, 1), bytes32(uint256(2)), "Node 0,1 should be correctly inserted"); - bytes32 node = keccak(bytes32(0), bytes32(uint256(2))); - assertEq(merkleTest.node(1, 0), node, "Node 1,0 should be correctly inserted"); - } - - // function testUpdateAllLeaves() public { - // // Setup initial leaves - // merkleTest.pushNewLeaf(keccak256("Leaf 1")); - // merkleTest.pushNewLeaf(keccak256("Leaf 2")); - - // // Prepare new leaves for full update - // bytes32[] memory newLeaves = new bytes32[](2); - // newLeaves[0] = keccak256("New Leaf 1"); - // newLeaves[1] = keccak256("New Leaf 2"); - - // // Update all leaves and verify root - // merkleTest.updateAllLeaves(newLeaves); - - // // bytes32 expectedRoot = merkleTest._efficientHash(newLeaves[0], newLeaves[1]); - // // bytes32 actualRoot = merkleTest._nodes[tree._height - 1][0]; - // // assertEq(actualRoot, expectedRoot, "Root should match expected hash after full update"); - // } - - function keccak(bytes32 left, bytes32 right) public pure returns (bytes32) { - return keccak256(abi.encodePacked(left, right)); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/PushNewLeaf.t.sol b/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/PushNewLeaf.t.sol new file mode 100644 index 000000000..dc08fde8a --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/PushNewLeaf.t.sol @@ -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"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/Root.t.sol b/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/Root.t.sol new file mode 100644 index 000000000..3ae519259 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/Root.t.sol @@ -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"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/Setup.t.sol b/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/Setup.t.sol new file mode 100644 index 000000000..6b6af0bc4 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/Setup.t.sol @@ -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"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/UpdateAllLeaves.t.sol b/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/UpdateAllLeaves.t.sol new file mode 100644 index 000000000..5bb127685 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/UpdateAllLeaves.t.sol @@ -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"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/UpdateAllNodesAtHeight.t.sol b/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/UpdateAllNodesAtHeight.t.sol new file mode 100644 index 000000000..be93cd032 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/UpdateAllNodesAtHeight.t.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {FullMerkleTest} from "./_FullMerkle_Shared.t.sol"; + +contract UpdateAllNodesAtHeightTest is FullMerkleTest { + function test_height0() 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 nodes at height 0 + merkleTest.updateAllNodesAtHeight(0, 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_height1() 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[](2); + newLeaves[0] = keccak256("New Leaf 0"); + newLeaves[1] = keccak256("New Leaf 1"); + + // Updating all nodes at height 1 + merkleTest.updateAllNodesAtHeight(1, newLeaves); + + // 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 + assertEq(merkleTest.node(1, 0), newLeaves[0], "Node 1,0 should be correctly updated"); + assertEq(merkleTest.node(1, 1), newLeaves[1], "Node 1,1 should be correctly updated"); + } + + function test_height2() 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[](1); + newLeaves[0] = keccak256("New Leaf 0"); + + // Updating all nodes at height 2 + merkleTest.updateAllNodesAtHeight(2, newLeaves); + + // 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 + assertEq(merkleTest.node(1, 0), keccak(leaf0, leaf1), "Node 1,0 should be correctly inserted"); + assertEq(merkleTest.node(2, 0), newLeaves[0], "Node 2,0 should be correctly updated"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/UpdateLeaf.t.sol b/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/UpdateLeaf.t.sol new file mode 100644 index 000000000..ae29641b8 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/UpdateLeaf.t.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {FullMerkleTest} from "./_FullMerkle_Shared.t.sol"; + +contract UpdateLeafTest is FullMerkleTest { + function test_revertWhen_wrongIndex() public { + // Inserting two leaves + bytes32 leaf0 = keccak256("Leaf 0"); + bytes32 leaf1 = keccak256("Leaf 1"); + merkleTest.pushNewLeaf(leaf0); + merkleTest.pushNewLeaf(leaf1); + + // Preparing new leaf 1 + bytes32 newLeaf1 = keccak256("New Leaf 1"); + + // Updating leaf 1 with wrong index + vm.expectRevert(bytes("FMT, wrong index")); + merkleTest.updateLeaf(2, newLeaf1); + } + + function test_updateLeaf() public { + // Inserting two leaves + bytes32 leaf0 = keccak256("Leaf 0"); + bytes32 leaf1 = keccak256("Leaf 1"); + merkleTest.pushNewLeaf(leaf0); + merkleTest.pushNewLeaf(leaf1); + + // Updating leaf 1 + bytes32 newLeaf1 = keccak256("New Leaf 1"); + merkleTest.updateLeaf(1, newLeaf1); + + // Checking leaf nodes + assertEq(merkleTest.node(0, 0), leaf0, "Node 0,0 should be correctly inserted"); + assertEq(merkleTest.node(0, 1), newLeaf1, "Node 0,1 should be correctly inserted"); + + // Checking parent node + bytes32 l01Hashed = keccak(leaf0, newLeaf1); + assertEq(merkleTest.node(1, 0), l01Hashed, "Node 1,0 should be correctly inserted"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/_FullMerkle_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/_FullMerkle_Shared.t.sol new file mode 100644 index 000000000..29c271edd --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/common/libraries/FullMerkle/_FullMerkle_Shared.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test} from "forge-std/Test.sol"; + +import {FullMerkleTest as FullMerkleTestContract} from "contracts/dev-contracts/test/FullMerkleTest.sol"; + +contract FullMerkleTest is Test { + // add this to be excluded from coverage report + function test() internal {} + + FullMerkleTestContract internal merkleTest; + bytes32 constant zeroHash = keccak256(abi.encodePacked("ZERO")); + + function setUp() public { + merkleTest = new FullMerkleTestContract(zeroHash); + } + + // ### Helper functions ### + function keccak(bytes32 left, bytes32 right) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(left, right)); + } +}