Last active
December 14, 2023 12:34
-
-
Save KONFeature/410e039981497f3080644c3a7e455c36 to your computer and use it in GitHub Desktop.
Small gist used to reproduce voting MerkleTree generation & proof generation for a Jokerace contest. If you check out this gist, and find it useful, don't hesitate to vote for the Frak proposal aha
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
encodePacked, | |
getAddress, | |
isAddress, | |
keccak256, | |
parseUnits, | |
} from "viem"; | |
import { MerkleTree } from "merkletreejs"; | |
import fs from "fs"; | |
import csv from "csv-parser"; | |
export interface Recipient { | |
address: string; | |
numVotes: string; | |
} | |
// Frak proposal on the `village buidl on polygon#1` of Jokerace: | |
// https://jokerace.xyz/contest/polygon/0x317Fc116E3c524316da9e783eB88099f0397ab2E/submission/76584427412783342536093397040981532505068033608056882071147444248795741602076 | |
async function main() { | |
// Read the CSV and get all the address (first column) as address | |
const addresses: Set<string> = new Set<string>(); | |
const votesData: Record<string, number> = {}; | |
const rawRecipients: Recipient[] = []; | |
let duplicateCount: number = 0; | |
console.log("Reading CSV"); | |
fs.createReadStream("voters.csv") | |
.pipe(csv()) | |
.on("data", (data) => { | |
// Yeah strange encoding, but it works (I have update the header of the CSV to `addr`) | |
const address = data["addr"]; | |
const numberOfVotes = parseFloat(data["numVotes"]); | |
if (!address && !isAddress(address)) throw new Error("Invalid address"); | |
rawRecipients.push({ | |
address: getAddress(address), | |
numVotes: parseUnits(numberOfVotes.toString(), 18).toString(), | |
}); | |
if (addresses.has(address)) { | |
// Duplicate, exit | |
duplicateCount++; | |
console.log("Duplicate address", address); | |
return; | |
} | |
addresses.add(address); | |
if (numberOfVotes !== 0) { | |
votesData[address] = numberOfVotes; | |
} | |
}) | |
.on("end", () => { | |
console.log("CSV file successfully processed"); | |
console.log(addresses.size, "addresses"); | |
console.log(duplicateCount, "duplicates"); | |
console.log(Object.keys(votesData).length, "addressToNumVotes"); | |
// Generate the recipients stuff | |
const recipients = setupVoteRecipients(18, votesData); | |
console.log(recipients.length, "recipients"); | |
console.log(rawRecipients.length, "raw recipients"); | |
const merkleTree = createMerkleTree(rawRecipients); | |
console.log("Merkle tree generated"); | |
console.log("Root: ", merkleTree.getHexRoot()); | |
// Submission merkle root | |
// 0xc1b8ebc70ba8f951acf475ab234abe60990b6389a75d67d033e78b069860a760 | |
// Voting merkle root | |
// 0x36a5c5b58b73bf54ad852a1dc89839a942669cc8a14efd6dd62495392b325e1e | |
// Generated voting merkle root: | |
// 0x1f50a1b41f34951e354340178680616f615b268a734bcb9074edd0028bb672eb | |
// Proof | |
let proof = generateProof( | |
merkleTree, | |
"0xaE4e57b886541829BA70eFC84340653c41e2908C", | |
"1", | |
); | |
console.log("Proof: ", proof); | |
}); | |
} | |
await main(); | |
function generateLeaf(address: string, numVotes: string) { | |
// Exactly the same as etherjs but more performant | |
// It doesn't impact the merklee tree generation (same hash with both method) | |
const packed = encodePacked( | |
["address", "uint256"], | |
[getAddress(address), BigInt(numVotes)], | |
); | |
return keccak256(packed); | |
} | |
/** | |
* Generate Merkle Tree from recipients | |
* @param {Recipient[]} recipients array of recipients | |
* @returns {MerkleTree} merkle tree | |
*/ | |
const createMerkleTree = (recipients: Recipient[]): MerkleTree => { | |
return new MerkleTree( | |
recipients.map(({ address, numVotes }) => generateLeaf(address, numVotes)), | |
keccak256, | |
{ sortPairs: true }, | |
); | |
}; | |
const setupVoteRecipients = ( | |
decimals: number, | |
votesData: Record<string, number>, | |
): Recipient[] => { | |
const recipients: Recipient[] = []; | |
// For each entry | |
for (const [address, votes] of Object.entries(votesData)) { | |
recipients.push({ | |
address: getAddress(address), | |
numVotes: parseUnits(votes.toString(), decimals).toString(), | |
}); | |
} | |
return recipients; | |
}; | |
export const generateProof = ( | |
merkleTree: MerkleTree, | |
address: string, | |
numVotes: string, | |
): string[] => { | |
const leaf = generateLeaf(address, parseUnits(numVotes, 18).toString()); | |
return merkleTree.getHexProof(leaf); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment