Skip to content

Commit 167cabb

Browse files
authored
improve: Import MerkleTree from @uma/common instead of redefining (#210)
* improve: Import MerkleTree from @uma/common instead of redefining * update uma/common * Update MerkleTree.ts * Fix tests
1 parent bd5a08b commit 167cabb

File tree

5 files changed

+516
-173
lines changed

5 files changed

+516
-173
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"@defi-wonderland/smock": "^2.0.7",
3636
"@eth-optimism/contracts": "^0.5.11",
3737
"@openzeppelin/contracts": "^4.7.3",
38-
"@uma/common": "^2.28.4",
38+
"@uma/common": "^2.29.0",
3939
"@uma/contracts-node": "^0.3.18",
4040
"@uma/core": "^2.41.0",
4141
"arb-bridge-eth": "^0.7.4",

test/MerkleLib.utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,5 +125,5 @@ export async function buildSlowRelayTree(relays: RelayData[]) {
125125
const hashFn = (input: RelayData) => {
126126
return keccak256(defaultAbiCoder.encode([paramType!], [input]));
127127
};
128-
return new MerkleTree(relays, hashFn);
128+
return new MerkleTree<RelayData>(relays, hashFn);
129129
}

test/merkle-distributor/MerkleDistributor.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
/* eslint-disable no-unused-expressions */
22

3-
import { ethers, getContractFactory, SignerWithAddress, Contract, toWei, toBN, expect } from "../utils";
3+
import {
4+
ethers,
5+
getContractFactory,
6+
SignerWithAddress,
7+
Contract,
8+
toWei,
9+
toBN,
10+
expect,
11+
keccak256,
12+
defaultAbiCoder,
13+
} from "../utils";
414
import { deployErc20 } from "../gas-analytics/utils";
515
import { MAX_UINT_VAL, MerkleTree } from "@uma/common";
616

@@ -19,7 +29,9 @@ let contractCreator: SignerWithAddress;
1929
let otherAddress: SignerWithAddress;
2030

2131
// Test variables
22-
let merkleTree: MerkleTree;
32+
let merkleTree: MerkleTree<Buffer>;
33+
const hashFn = (input: Buffer) => input.toString("hex");
34+
2335
let windowIndex: number;
2436
const sampleIpfsHash = "";
2537

@@ -58,7 +70,8 @@ describe("AcrossMerkleDistributor", () => {
5870
amount: totalRewardAmount,
5971
accountIndex: 0,
6072
});
61-
merkleTree = new MerkleTree([leaf]);
73+
74+
merkleTree = new MerkleTree<Buffer>([leaf], hashFn);
6275
// Expect this merkle root to be at the first index.
6376
windowIndex = 0;
6477
// Seed the merkleDistributor with the root of the tree and additional information.
@@ -118,7 +131,7 @@ describe("AcrossMerkleDistributor", () => {
118131
amount: totalRewardAmount,
119132
accountIndex: 1,
120133
});
121-
merkleTree = new MerkleTree([leaf1, leaf2]);
134+
merkleTree = new MerkleTree<Buffer>([leaf1, leaf2], hashFn);
122135
// Expect this merkle root to be at the first index.
123136
windowIndex = 0;
124137
// Seed the merkleDistributor with the root of the tree and additional information.
@@ -171,7 +184,7 @@ describe("AcrossMerkleDistributor", () => {
171184
amount: totalRewardAmount,
172185
accountIndex: 0,
173186
});
174-
merkleTree = new MerkleTree([leaf]);
187+
merkleTree = new MerkleTree<Buffer>([leaf], hashFn);
175188
// Expect this merkle root to be at the first index.
176189
windowIndex = 0;
177190
// Seed the merkleDistributor with the root of the tree and additional information.

utils/MerkleTree.ts

Lines changed: 1 addition & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,145 +1 @@
1-
// This script provides some useful methods for building MerkleTrees. It is essentially the uniswap implementation
2-
// https://github.com/Uniswap/merkle-distributor/blob/master/src/merkle-tree.ts with some added convenience methods
3-
// to take the leaves and conversion functions, so the user never has to work with buffers.
4-
import { bufferToHex, keccak256 } from "ethereumjs-util";
5-
6-
export const EMPTY_MERKLE_ROOT = "0x0000000000000000000000000000000000000000000000000000000000000000";
7-
export class MerkleTree<T> {
8-
private readonly elements: Buffer[];
9-
private readonly bufferElementPositionIndex: { [hexElement: string]: number };
10-
private readonly layers: Buffer[][];
11-
12-
constructor(leaves: T[], public readonly hashFn: (element: T) => string) {
13-
this.elements = leaves.map((leaf) => this.leafToBuf(leaf));
14-
// Sort elements
15-
this.elements.sort(Buffer.compare);
16-
// Deduplicate elements
17-
this.elements = MerkleTree.bufDedup(this.elements);
18-
19-
this.bufferElementPositionIndex = this.elements.reduce<{ [hexElement: string]: number }>((memo, el, index) => {
20-
memo[bufferToHex(el)] = index;
21-
return memo;
22-
}, {});
23-
24-
// Create layers
25-
this.layers = this.getLayers(this.elements);
26-
}
27-
28-
isEmpty(): boolean {
29-
return this.layers.length === 0;
30-
}
31-
32-
getLayers(elements: Buffer[]): Buffer[][] {
33-
const layers: Buffer[][] = [];
34-
if (elements.length === 0) return layers;
35-
36-
layers.push(elements);
37-
38-
// Get next layer until we reach the root
39-
while (layers[layers.length - 1].length > 1) {
40-
layers.push(this.getNextLayer(layers[layers.length - 1]));
41-
}
42-
43-
return layers;
44-
}
45-
46-
getNextLayer(elements: Buffer[]): Buffer[] {
47-
return elements.reduce<Buffer[]>((layer, el, idx, arr) => {
48-
if (idx % 2 === 0) {
49-
// Hash the current element with its pair element
50-
layer.push(MerkleTree.combinedHash(el, arr[idx + 1]));
51-
}
52-
53-
return layer;
54-
}, []);
55-
}
56-
57-
static combinedHash(first: Buffer, second: Buffer): Buffer {
58-
if (!first) {
59-
return second;
60-
}
61-
if (!second) {
62-
return first;
63-
}
64-
65-
return keccak256(MerkleTree.sortAndConcat(first, second));
66-
}
67-
68-
getRoot(): Buffer {
69-
return this.layers[this.layers.length - 1][0];
70-
}
71-
72-
getHexRoot(): string {
73-
if (this.isEmpty()) return EMPTY_MERKLE_ROOT;
74-
return bufferToHex(this.getRoot());
75-
}
76-
77-
getProof(leaf: T) {
78-
return this.getProofRawBuf(this.leafToBuf(leaf));
79-
}
80-
81-
getHexProof(leaf: T) {
82-
return this.getHexProofRawBuf(this.leafToBuf(leaf));
83-
}
84-
85-
leafToBuf(element: T): Buffer {
86-
const hash = this.hashFn(element);
87-
const hexString = hash.startsWith("0x") ? hash.substring(2) : hash;
88-
return Buffer.from(hexString.toLowerCase(), "hex");
89-
}
90-
91-
// Methods that take the raw buffers (hashes).
92-
getProofRawBuf(element: Buffer) {
93-
let idx = this.bufferElementPositionIndex[bufferToHex(element)];
94-
95-
if (typeof idx !== "number") {
96-
throw new Error("Element does not exist in Merkle tree");
97-
}
98-
99-
return this.layers.reduce((proof, layer) => {
100-
const pairElement = MerkleTree.getPairElement(idx, layer);
101-
102-
if (pairElement) {
103-
proof.push(pairElement);
104-
}
105-
106-
idx = Math.floor(idx / 2);
107-
108-
return proof;
109-
}, []);
110-
}
111-
112-
getHexProofRawBuf(el: Buffer): string[] {
113-
const proof = this.getProofRawBuf(el);
114-
115-
return MerkleTree.bufArrToHexArr(proof);
116-
}
117-
118-
private static getPairElement(idx: number, layer: Buffer[]): Buffer | null {
119-
const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1;
120-
121-
if (pairIdx < layer.length) {
122-
return layer[pairIdx];
123-
} else {
124-
return null;
125-
}
126-
}
127-
128-
private static bufDedup(elements: Buffer[]): Buffer[] {
129-
return elements.filter((el, idx) => {
130-
return idx === 0 || !elements[idx - 1].equals(el);
131-
});
132-
}
133-
134-
private static bufArrToHexArr(arr: Buffer[]): string[] {
135-
if (arr.some((el) => !Buffer.isBuffer(el))) {
136-
throw new Error("Array is not an array of buffers");
137-
}
138-
139-
return arr.map((el) => "0x" + el.toString("hex"));
140-
}
141-
142-
private static sortAndConcat(...args: Buffer[]): Buffer {
143-
return Buffer.concat([...args].sort(Buffer.compare));
144-
}
145-
}
1+
export { MerkleTree, EMPTY_MERKLE_ROOT } from "@uma/common";

0 commit comments

Comments
 (0)