Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/merkle root scripts #16

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ src/types/*
!src/types/factories
src/types/factories/*
!src/types/factories/Greeter__factory.ts
**/.DS_Store
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,33 @@ TBD

## Usage

### Generating random drops for testing

```sh
npx hardhat generate-dummy-amounts --output output2.json --tokens 0x0000000000000000000000000000000000000000,0x0000000000000000000000000000000000000001,0x0000000000000000000000000000000000000002
```

### Generate cumalative amounts from previous drops

```sh
npx hardhat generate-cummalitive-amounts --prev prev.json --new new.json --output cummalative.json
```

### Generating merle tree files

```sh
# Seperate files for each address
npx hardhat generate-merkle-tree --input output.json --output tree --seperate
# One file with all the proofs
npx hardhat generate-merkle-tree --input output.json --output tree
```

### Uploading to ipfs through pinata
```sh
npx hardhat pinata-pin --input ./tree --pinata-key PINATA_KEY --pinata-secret PINATA_SECRET
```


### Pre Requisites

Before running any command, you need to create a `.env` file and set a BIP-39 compatible mnemonic as an environment
Expand Down
1 change: 1 addition & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "solidity-coverage";

import "./tasks/accounts";
import "./tasks/deploy";
import "./tasks/merkle";

import { resolve } from "path";

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"typechain": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat typechain"
},
"dependencies": {
"@openzeppelin/contracts": "^4.5.0"
"@openzeppelin/contracts": "^4.5.0",
"@pinata/sdk": "^1.1.26"
}
}
151 changes: 151 additions & 0 deletions tasks/merkle/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { BigNumber } from "ethers";
import { parseEther } from "ethers/lib/utils";
import { task } from "hardhat/config";
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { readdir, writeFile } from "node:fs/promises";
import pinataSDK from "@pinata/sdk";
import ChannelMerkleTree, { ChannelMerkleTreeEntry } from "../../utils/ChannelMerkleTree";

interface Amounts {
[address: string]: {
[token: string]: string;
};
}

interface Entries {
[address: string]: {
[token: string]: {
cumulativeAmount: string;
proof: string[];
};
}
}

task("generate-dummy-amounts", "example: npx hardhat generate-dummy-amounts --output output.json --tokens 0x0000000000000000000000000000000000000000,0x0000000000000000000000000000000000000001,0x0000000000000000000000000000000000000002")
.addParam("output")
.addParam("tokens", "tokens to generate amounts for comma separated ie: 0x00000000.....11111,0x0x0000000....2222")
.setAction(async(taskArgs, { ethers }) => {
const signers = await ethers.getSigners();

const amounts: Amounts = {};
const tokens = taskArgs.tokens.split(",").map((token: string) => token.trim());

for(let signer of signers) {
const tokensShuffled = tokens.sort(() => 0.5 - Math.random());
const tokensRandomSubset = tokensShuffled.slice(0, Math.floor(Math.random() * tokensShuffled.length));
// Do nothing if there are no entries for this signer
if(tokensRandomSubset.length === 0) {
continue;
}

amounts[signer.address] = {};

for(let token of tokensRandomSubset) {
amounts[signer.address][token] = parseEther(
(Math.random() * 1000000).toFixed(18).toString()
).toString();
}
}

const output = JSON.stringify(amounts, null, 2);
writeFileSync(taskArgs.output, output);
});

task("generate-cumalitive-amounts")
.addParam("prev")
.addParam("new")
.addParam("output")
.setAction(async(taskArgs, { ethers }) => {
const prevAmounts: Amounts = JSON.parse(readFileSync(taskArgs.prev).toString());
const newAmounts: Amounts = JSON.parse(readFileSync(taskArgs.new).toString());

const cumalitiveAmounts: Amounts = {...prevAmounts};

for(let address in newAmounts) {
// If not present yet create new entry
if(!cumalitiveAmounts[address]) {
cumalitiveAmounts[address] = {...newAmounts[address]};
continue;
}
// Otherwise add new amounts to existing entry
for(let token in newAmounts[address]) {
if(!cumalitiveAmounts[address][token]) {
cumalitiveAmounts[address][token] = newAmounts[address][token];
continue;
}
cumalitiveAmounts[address][token] = BigNumber.from(prevAmounts[address][token]).add(BigNumber.from(newAmounts[address][token])).toString();
}
}

const output = JSON.stringify(cumalitiveAmounts, null, 2);
writeFileSync(taskArgs.output, output);
});

task("generate-merkle-tree")
.addParam("input")
.addParam("output")
.addFlag("seperate")
.setAction(async(taskArgs, { ethers }) => {
const input: Amounts = JSON.parse(readFileSync(taskArgs.input).toString());
const merkleEntries: ChannelMerkleTreeEntry[] = [];

for(let address in input) {
for(let token in input[address]) {
merkleEntries.push({
token,
address,
cumulativeAmount: input[address][token],
});
}
}

const merkleTree = new ChannelMerkleTree(merkleEntries);
const result: Entries = {};

for(let address in input) {
result[address] = {};
for(let token in input[address]) {
result[address][token] = {
cumulativeAmount: input[address][token],
proof: merkleTree.getProof(address, token, input[address][token]),
};
}
}

if(existsSync(taskArgs.output)) {
rmSync(taskArgs.output, { recursive: true });
}
mkdirSync(taskArgs.output, { recursive: true });

await writeFile(`${taskArgs.output}/root.json`, JSON.stringify(merkleTree.merkleTree.getRoot(), null, 2));

if(!taskArgs.seperate) {
const output = JSON.stringify(result, null, 2);
writeFileSync(`${taskArgs.output}/tree.json`, output);
return;
}

const writePromises: Promise<void>[] = [];
for(let address in result) {
const output = JSON.stringify(result[address], null, 2);
// console.log(output);
writePromises.push(writeFile(`${taskArgs.output}/${address}.json`, output));
}



await Promise.all(writePromises);
});

task("pinata-pin")
.addParam("input")
// .addParam("ipfsNode")
.addParam("pinataKey")
.addParam("pinataSecret")
.setAction(async(taskArgs, { ethers }) => {
const files = await readdir(taskArgs.input);
const pinata = pinataSDK(taskArgs.pinataKey, taskArgs.pinataSecret);
const response = await pinata.pinFromFS(taskArgs.input);

console.log(response);
});
19 changes: 15 additions & 4 deletions utils/ChannelMerkleTree.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
import { ethers } from "ethers";
import { BigNumberish, ethers } from "ethers";
import { MerkleTree } from "./MerkleTree";

export interface ChannelMerkleTreeEntry {
token: string;
address: string;
cumulativeAmount: BigNumberish;
}

export default class ChannelMerkleTree {
merkleTree: MerkleTree;

constructor(entries: { token: string; address: string; cumulativeAmount: number }[]) {
constructor(entries: ChannelMerkleTreeEntry[]) {
const hashes = entries.map(({ token, address, cumulativeAmount }) =>
this.hashEntry(address, token, cumulativeAmount),
);

this.merkleTree = new MerkleTree(hashes);
}

hashEntry(address: string, token: string, cumulativeAmount: number) {
hashEntry(address: string, token: string, cumulativeAmount: BigNumberish) {
return ethers.utils.solidityKeccak256(["address", "address", "uint256"], [address, token, cumulativeAmount]);
}

getProof = (address: string, token: string, cumulativeAmount: number) => {
getProof = (address: string, token: string, cumulativeAmount: BigNumberish) => {
const hash = this.hashEntry(address, token, cumulativeAmount);

return this.merkleTree.getProof(hash);
};

getProofFromEntry = (entry: ChannelMerkleTreeEntry) => {
return this.getProof(entry.address, entry.token, entry.cumulativeAmount);
}

}
Loading