Skip to content

Commit 1daeaa4

Browse files
committed
add IncrementalMerkleTree#pop function
1 parent 3bb20aa commit 1daeaa4

File tree

3 files changed

+67
-0
lines changed

3 files changed

+67
-0
lines changed

contracts/data/IncrementalMerkleTree.sol

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,37 @@ library IncrementalMerkleTree {
8787
}
8888
}
8989

90+
function pop(Tree storage t) internal {
91+
uint256 treeHeight = t.height();
92+
uint256 treeSize = t.size() - 1;
93+
94+
// remove layer if tree has excess capacity
95+
96+
if (treeSize == (1 << treeHeight) >> 2) {
97+
treeHeight--;
98+
// TODO: is this pop necessary, or will the row be properly deleted?
99+
t.nodes[treeHeight].pop();
100+
t.nodes.pop();
101+
}
102+
103+
// remove columns if rows are too long
104+
105+
uint256 row;
106+
uint256 col = treeSize;
107+
108+
while (row < treeHeight && t.nodes[row].length > col) {
109+
t.nodes[row].pop();
110+
row++;
111+
col = (col + 1) >> 1;
112+
}
113+
114+
// recalculate hashes
115+
116+
if (treeSize > 0) {
117+
t.set(treeSize - 1, t.at(treeSize - 1));
118+
}
119+
}
120+
90121
/**
91122
* @notice update existing element in tree
92123
* @param t Tree struct storage reference

contracts/data/IncrementalMerkleTreeMock.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ contract IncrementalMerkleTreeMock {
3131
tree.push(hash);
3232
}
3333

34+
function pop() external {
35+
tree.pop();
36+
}
37+
3438
function set(uint256 index, bytes32 hash) external {
3539
tree.set(index, hash);
3640
}

test/data/IncrementalMerkleTree.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,38 @@ describe('IncrementalMerkleTree', function () {
133133
});
134134
});
135135

136+
describe('#pop', () => {
137+
it('updates Merkle root', async () => {
138+
const hashes = [];
139+
140+
for (let i = 0; i < 10; i++) {
141+
hashes.push(randomHash());
142+
await instance.push(hashes[i]);
143+
}
144+
145+
for (let i = 0; i < hashes.length; i++) {
146+
await instance.pop();
147+
148+
const tree = new MerkleTree(
149+
hashes.slice(0, hashes.length - 1 - i),
150+
keccak256,
151+
);
152+
153+
// MerkleTree library returns truncated zero hash, so must use hexEqual matcher
154+
155+
expect(await instance.callStatic.root()).to.hexEqual(tree.getHexRoot());
156+
}
157+
});
158+
159+
describe('reverts if', () => {
160+
it('tree is size zero', async () => {
161+
await expect(instance.pop()).to.be.revertedWithPanic(
162+
PANIC_CODES.ARITHMETIC_UNDER_OR_OVERFLOW,
163+
);
164+
});
165+
});
166+
});
167+
136168
describe('#set', () => {
137169
it('updates Merkle root', async () => {
138170
const hashes = [];

0 commit comments

Comments
 (0)