From 4b7365b0f099b9ec41f28ee2e7eb608381b5f758 Mon Sep 17 00:00:00 2001 From: Eshaan Bansal Date: Mon, 4 Nov 2024 17:45:17 +0530 Subject: [PATCH] fixup --- .../Avail unification cup MRU.png | Bin src/index.ts | 3 +- src/stackr/state.ts | 32 ++--- src/stackr/utils.ts | 120 +++++++++++++++++- 4 files changed, 136 insertions(+), 19 deletions(-) rename Avail unification cup MRU.png => assets/Avail unification cup MRU.png (100%) diff --git a/Avail unification cup MRU.png b/assets/Avail unification cup MRU.png similarity index 100% rename from Avail unification cup MRU.png rename to assets/Avail unification cup MRU.png diff --git a/src/index.ts b/src/index.ts index 4cb14f6..019a879 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import { MicroRollup, } from "@stackr/sdk"; import express, { Request, Response } from "express"; + import { stackrConfig } from "../stackr.config.ts"; import { leagueMachine, @@ -412,4 +413,4 @@ const main = async () => { }); }; -main(); +main(); \ No newline at end of file diff --git a/src/stackr/state.ts b/src/stackr/state.ts index 155bab1..b3ccc4e 100644 --- a/src/stackr/state.ts +++ b/src/stackr/state.ts @@ -1,6 +1,6 @@ import { State } from "@stackr/sdk/machine"; -import { solidityPacked, solidityPackedKeccak256 } from "ethers"; -import { createMT } from "./utils"; +import { keccak256, solidityPacked } from "ethers"; +import { createMMR, createMT } from "./utils"; type TournamentMeta = { round: number; @@ -73,7 +73,7 @@ export class League extends State { ) ); - const matchesMMR = createMT(matches, (m) => { + const matchesMMR = createMMR(matches, (m) => { const teamIds = Object.keys(m.scores).map((k) => parseInt(k)); const scores = teamIds.map((id) => m.scores[id]); return solidityPacked( @@ -102,23 +102,25 @@ export class League extends State { ); }); - const logsMMR = createMT(logs, (l) => + const logsMMR = createMMR(logs, (l) => solidityPacked( ["uint256", "uint256", "string", "uint256"], [l.playerId, l.timestamp, l.action, l.matchId || 0] ) ); - const metaHash = solidityPackedKeccak256( - ["uint256", "uint256", "uint256", "uint256", "string"], - Object.values(meta).map((v) => { - if (typeof v === "number") { - return v; - } - return Object.entries(v) - .map(([k, v]) => `${k}:${v}`) - .join(","); - }) + const metaHash = keccak256( + solidityPacked( + ["uint256", "uint256", "uint256", "uint256", "string"], + Object.values(meta).map((v) => { + if (typeof v === "number") { + return v; + } + return Object.entries(v) + .map(([k, v]) => `${k}:${v}`) + .join(","); + }) + ) ); const finalMerkleTree = createMT([ @@ -132,4 +134,4 @@ export class League extends State { return finalMerkleTree.rootHash; } -} +} \ No newline at end of file diff --git a/src/stackr/utils.ts b/src/stackr/utils.ts index 540d9e3..d9471a7 100644 --- a/src/stackr/utils.ts +++ b/src/stackr/utils.ts @@ -1,5 +1,31 @@ -import { keccak256, ZeroHash } from "ethers"; -import { MerkleTree } from "merkletreejs"; +import { + hexlify, + isHexString, + keccak256, + solidityPackedKeccak256, + ZeroHash, +} from "ethers"; +import { MerkleMountainRange, MerkleTree } from "merkletreejs"; + +export interface MMRResponse { + rootHash: string; + merkleProof: (leaf: string) => + | { + root: string; + width: number; + index: number; + peakBagging: string[]; + siblings: string[]; + } + | undefined; + verifyProof: ( + leafIndex: number, + leaf: string, + width: number, + peakBagging: string[], + siblings: string[] + ) => boolean; +} export interface MTResponse { rootHash: string; @@ -12,6 +38,94 @@ export interface MTResponse { verifyProof: (leaf: string, proof: string[]) => boolean; } +const hashLeafFn = (index: number, dataHash: Buffer): string => + solidityPackedKeccak256(["uint256", "bytes32"], [index, hexlify(dataHash)]); + +const peakBaggingFn = (size: number, peaks: Buffer[]): string => + solidityPackedKeccak256( + ["uint256", "bytes32"], + [ + size, + solidityPackedKeccak256( + ["uint256", "bytes32[]"], + [size, peaks.map((peak) => hexlify(peak))] + ), + ] + ); + +const hashBranchFn = (index: number, left: Buffer, right: Buffer): string => + solidityPackedKeccak256( + ["uint256", "bytes32", "bytes32"], + [index, hexlify(left), hexlify(right)] + ); + +/** + * 7 + * 3 6 10 + * 1 2 4 5 8 9 11 + * 1 2 3 4 5 6 7 + * @param items List of items to be included in the Merkle Mountain Range + * @param serializer Optional function to serialize the items + * @returns `MMRResponse` object + */ +export const createMMR = ( + items: ConvertibleItem[], + serializer?: (item: ConvertibleItem) => string +): MMRResponse => { + const mmr = new MerkleMountainRange( + keccak256, + serializer ? items.map(serializer) : items, + hashLeafFn, + peakBaggingFn, + hashBranchFn + ); + + const rootHash = mmr.getHexRoot(); + + return { + rootHash: rootHash === "0x" ? ZeroHash : rootHash, + merkleProof: (leaf: string) => { + const hashes = Object.keys(mmr.data); + const itemIndex = hashes.indexOf(keccak256(leaf)); + if (itemIndex === -1) { + // Leaf not found + return undefined; + } + const leafIndex = mmr.getLeafIndex(itemIndex + 1); // indexing in tree starts from 1 + const mmrProof = mmr.getMerkleProof(leafIndex); + return { + root: mmr.bufferToHex(mmrProof.root), + width: mmrProof.width, + index: leafIndex, + peakBagging: mmrProof.peakBagging.map((peak) => mmr.bufferToHex(peak)), + siblings: mmrProof.siblings.map((sibling) => mmr.bufferToHex(sibling)), + }; + }, + verifyProof: ( + leafIndex: number, + leaf: string, + width: number, + peaks: string[], + siblings: string[] + ): boolean => { + try { + return mmr.verify( + mmr.getRoot(), + width, + leafIndex, + leaf, + peaks.map((peak) => (isHexString(peak) ? mmr.bufferify(peak) : peak)), + siblings.map((sibling) => + isHexString(sibling) ? mmr.bufferify(sibling) : sibling + ) + ); + } catch (error) { + return false; + } + }, + }; +}; + /** * @param items List of items to be included in the Merkle Tree * @param serializer Optional function to serialize the items @@ -54,4 +168,4 @@ export const createMT = ( } }, }; -}; +}; \ No newline at end of file