Skip to content

feat: support for v2 rewards, do everything in safe #33

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

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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: 0 additions & 1 deletion contracts/deployments/kovan/.chainId

This file was deleted.

522 changes: 0 additions & 522 deletions contracts/deployments/kovan/MerkleRedeem.json

This file was deleted.

3 changes: 2 additions & 1 deletion snapshots/.env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
PNK_DROP_JSON_RPC_URL='<string>'
INFURA_ARB_ONE_RPC=https://arbitrum-mainnet.infura.io/v3/{apiKey}
PNK_DROP_JSON_RPC_URL=https://mainnet.infura.io/v3/{apiKey}
FILEBASE_TOKEN='<BUCKET_TOKEN>'
108 changes: 63 additions & 45 deletions snapshots/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,61 @@ import { hideBin } from "yargs/helpers";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc.js";
import { createSnapshotCreator } from "./src/create-snapshot-from-block-limits.js";
import { formatEther } from "ethers/lib/utils.js";
import fs from "fs";
import { fileToIpfs } from "./src/fileToIpfs.js";
import { addTransactionToBatch, writeTransactionBatch, createNewBatch } from "./src/helpers/tx-builder.js";

dotenv.config();

dayjs.extend(utc);

const chains = [
{
version: "v1",
chainId: 1,
chainShortName: "eth",
blocksPerSecond: 0.066667,
klerosLiquidAddress: "0x988b3a538b618c7a603e1c11ab82cd16dbe28069",
token: "0x93ed3fbe21207ec2e8f2d3c3de6e058cb73bc04d",
pnkDropRatio: BigNumber.from("900000000"),
pnkDropRatio: BigNumber.from("800000000"),
fromBlock: 7300000,
provider: getDefaultProvider(process.env.PNK_DROP_JSON_RPC_URL),
merkleDropAddress: "0xdbc3088Dfebc3cc6A84B0271DaDe2696DB00Af38",
safeAddress: "0x3CDe6e49AC61B268dBFce31B73DEA440c4E09162",
},
{
version: "v1",
chainId: 100,
chainShortName: "gno",
blocksPerSecond: 0.2,
klerosLiquidAddress: "0x9C1dA9A04925bDfDedf0f6421bC7EEa8305F9002",
token: "0xcb3231aBA3b451343e0Fddfc45883c842f223846",
pnkDropRatio: BigNumber.from("100000000"),
fromBlock: 16895601,
provider: getDefaultProvider("https://rpc.gnosischain.com"),
merkleDropAddress: "0xf1A9589880DbF393F32A5b2d5a0054Fa10385074",
safeAddress: "0x3CDe6e49AC61B268dBFce31B73DEA440c4E09162",
},
{
version: "v2",
chainId: 42161,
chainShortName: "arb",
blocksPerSecond: 0.26,
klerosCoreAddress: "0x991d2df165670b9cac3B022f4B68D65b664222ea",
token: "0x330bD769382cFc6d50175903434CCC8D206DCAE5",
pnkDropRatio: BigNumber.from("1000000000"),
fromBlock: 272063254,
provider: getDefaultProvider(process.env.INFURA_ARB_ONE_RPC),
merkleDropAddress: "0x2a23B84078b287753A91C522c3bB3b6B32f6F8f1",
safeAddress: "0x66e8DE9B42308c6Ca913D1EE041d6F6fD037A57e",
},
];

const argv = yargs(hideBin(process.argv))
.strict(true)
.locale("en")
.usage(`Usage: $0 --lastamount={n}`)
.epilogue("Alternatively you can set the same params in the .env file. Check .env.example.")
.epilogue("Alternatively set the same params in the .env file. Check .env.example.")
.option("lastamount", {
description: "The amount of tokens, in wei, that were distributed in the last period",
})
Expand All @@ -54,6 +75,8 @@ const normalizeArgs = ({ lastamount }) => ({

const { lastamount } = normalizeArgs(argv);

const formatDateMonth = (date) => dayjs(date).utc().format("YYYY-MM");

const getDatesAndPeriod = () => {
const currentDate = new Date(); // Current date in local time zone
const currentMonth = currentDate.getUTCMonth(); // Get current month in UTC
Expand All @@ -75,10 +98,11 @@ const getDatesAndPeriod = () => {
// target starts at 29 for January 2024 and increases by 1 each period
// maxes at 50
const target = BigNumber.from(Math.min(29 + monthDiff, 50)).mul(BigNumber.from("10000000"));
// mainnetPeriod starts at 35 for January 2024 and also increases by 1 each period
// gnosisPeriod starts at 30 for January 2024 and increases by 1 each period
// v1's mainnetPeriod starts at 35 for January 2024 and also increases by 1 each period
// v1's gnosisPeriod starts at 30 for January 2024 and increases by 1 each period
// v2's arbitrumPeriod starts at 1 for January 2024 and increases by 1 each period
// only used for _week argument in merkledrop.seedAllocations()
const periods = { 1: 35 + monthDiff, 100: 30 + monthDiff };
const periods = { 1: 35 + monthDiff, 100: 30 + monthDiff, 42161: 1 + monthDiff };

return { startDate, endDate, previousDate, target, periods };
};
Expand All @@ -100,6 +124,7 @@ const main = async () => {
const createSnapshot = await createSnapshotCreator({
provider: chain.provider,
klerosLiquidAddress: chain.klerosLiquidAddress,
klerosCoreAddress: chain.klerosCoreAddress,
droppedAmount: BigNumber.from(0), // we're not awarding anything, just counting.
});

Expand All @@ -124,12 +149,12 @@ const main = async () => {
const totalPNKStaked = await getTotalPNKStaked();

// lets compute the formula to figure out how much will be awarded in total this month
const pnkMainnet = new Contract(
const pnkSupplyChecker = new Contract(
chains[0].token,
["function totalSupply() view returns (uint256)"],
chains[0].provider
);
const totalSupply = await pnkMainnet.totalSupply();
const totalSupply = await pnkSupplyChecker.totalSupply();
console.log(
"Total PNK staked:",
BigNumber.from(totalPNKStaked).div(BigNumber.from("1000000000000000000")).toString(),
Expand All @@ -156,63 +181,56 @@ const main = async () => {
const snapshotInfos = [];
for (const c of chains) {
const droppedAmount = fullReward.mul(c.pnkDropRatio).div(basis);
console.log("> Chain [", c.chainId, "] ", droppedAmount.toString(), "PNK (wei) will be rewarded");
console.log(`> Chain [${c.chainId}] ${droppedAmount.toString()} PNK (wei) will be rewarded`);

const createSnapshot = await createSnapshotCreator({
provider: c.provider,
klerosLiquidAddress: c.klerosLiquidAddress,
klerosCoreAddress: c.klerosCoreAddress,
droppedAmount,
});
const snapshot = await createSnapshot({ fromBlock: c.fromBlock, startDate, endDate });

snapshotInfos.push({
// edit when arbitrum inclusion
filename: `${c.chainId == "1" ? "" : "xdai-"}snapshot-${startDate.toISOString().slice(0, 7)}.json`,
filename: `${c.chainShortName}-snapshot-${startDate.toISOString().slice(0, 7)}.json`,
chain: c,
snapshot,
period: periods[c.chainId],
});
}

// paste these into kleros/court
// paste these ipfs hashes into kleros/court's claim-modal file so people can claim the rewards.
for (const sinfo of snapshotInfos) {
const path = `.cache/${sinfo.filename}`;
fs.writeFileSync(path, JSON.stringify(sinfo.snapshot));
const ipfsPath = await fileToIpfs(path);
console.log(`https://cdn.kleros.link/ipfs/${ipfsPath}`);
}

// txs to run sequentially, hardcoded section.
//1. Approve `0xdbc3088Dfebc3cc6A84B0271DaDe2696DB00Af38` (mainnet) to spend 900k PNK (token address `0x93ed3fbe21207ec2e8f2d3c3de6e058cb73bc04d`)
// >>>> ignoring.
//2. Seed week X on Mainnet.
const merkleContractMainnet = new Contract("0xdbc3088Dfebc3cc6A84B0271DaDe2696DB00Af38", [
"function seedAllocations(uint _week, bytes32 _merkleRoot, uint _totalAllocation) external",
]);
const txToUrl = (tx, chainId) =>
`https://greenlucid.github.io/lame-tx-prompt/site?to=${tx.to}&data=${tx.data}&value=0&chainId=${chainId}`;
const tx1 = await merkleContractMainnet.populateTransaction.seedAllocations(
snapshotInfos[0].period,
snapshotInfos[0].snapshot.merkleTree.root,
snapshotInfos[0].snapshot.droppedAmount
);
console.log("PNK should be already approved to Merkle Drop");
console.log("1: ", txToUrl(tx1, 1));
console.log(
"2: ",
"https://omni.gnosischain.com/bridge",
" amount: ",
formatEther(snapshotInfos[1].snapshot.droppedAmount)
);
console.log("3: http://court.kleros.io and xPNK -> stPNK");
console.log("stPNK should be already approved to Merkle Drop");
const merkleContractGnosis = new Contract("0xf1A9589880DbF393F32A5b2d5a0054Fa10385074", [
"function seedAllocations(uint _week, bytes32 _merkleRoot, uint _totalAllocation) external",
]);
const tx2 = await merkleContractGnosis.populateTransaction.seedAllocations(
snapshotInfos[1].period,
snapshotInfos[1].snapshot.merkleTree.root,
snapshotInfos[1].snapshot.droppedAmount
);
console.log("4: ", txToUrl(tx2, 100));
// 1. For each chain, the Safe account must have approved the spending of an unlimited amount of PNK tokens to its MerkleRedeem contract.
// This means, approve it 1 time for Mainnet, 1 time for Gnosis, 1 time for Arbitrum, and you're all set.
console.log("Generating batched transactions...");
const merkleDropABI = [
"function seedAllocations(uint256 _period, bytes32 _merkleRoot, uint256 _totalAllocation) external",
];

for (const sinfo of snapshotInfos) {
const tx = await new Contract(sinfo.chain.merkleDropAddress, merkleDropABI).populateTransaction.seedAllocations(
sinfo.period,
sinfo.snapshot.merkleTree.root,
sinfo.snapshot.droppedAmount
);

createNewBatch();
addTransactionToBatch(tx);
writeTransactionBatch({
name: "Seed allocations",
chainId: sinfo.chain.chainId,
chainShortName: sinfo.chain.chainShortName,
safeAddress: sinfo.chain.safeAddress,
outputPath: `tx-batch-${sinfo.chain.chainShortName}-${formatDateMonth(startDate)}.json`,
});
}
};

main();
111 changes: 72 additions & 39 deletions snapshots/src/helpers/subgraph-events.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,70 @@
import { BigNumber, utils } from "ethers";
import fetch from "node-fetch";

const fetchStakeSets = async (blockStart, blockEnd, subgraphEndpoint, lastId) => {
const subgraphQuery = {
query: `
{
stakeSets(where: {
blocknumber_gte: ${blockStart},
blocknumber_lt: ${blockEnd},
id_gt: "${lastId}"
},
orderBy: id,
orderDirection: asc,
first: 1000) {
const isV2 = (chainId) => chainId === 42161;

const fetchStakeSets = async (blockStart, blockEnd, subgraphEndpoint, lastId, chainId) => {

const query = isV2(chainId)
? `
{
stakeSets(where: {
blocknumber_gte: ${blockStart},
blocknumber_lt: ${blockEnd},
id_gt: "${lastId}"
},
orderBy: id,
orderDirection: asc,
first: 1000) {
id
juror {
id
address
subcourtID
stake
newTotalStake
logIndex
blocknumber
}
courtID
stake
newTotalStake
logIndex
blocknumber
}
}
`
: `
{
stakeSets(where: {
blocknumber_gte: ${blockStart},
blocknumber_lt: ${blockEnd},
id_gt: "${lastId}"
},
orderBy: id,
orderDirection: asc,
first: 1000) {
id
address
subcourtID
stake
newTotalStake
logIndex
blocknumber
}
`,
};
}
`;

const response = await fetch(subgraphEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(subgraphQuery),
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query }),
});
const { data } = await response.json();
const stakeSets = data.stakeSets;

return stakeSets;
const { data } = await response.json();
return data.stakeSets;
};

const fetchAllStakeSets = async (blockStart, blockEnd, subgraphEndpoint) => {
const fetchAllStakeSets = async (blockStart, blockEnd, subgraphEndpoint, chainId) => {
const batches = [];
let lastId = "";
for (let i = 0; i < 1000; i++) {
//console.log("Stake sets batch", batches.length);
const sets = await fetchStakeSets(blockStart, blockEnd, subgraphEndpoint, lastId);
const sets = await fetchStakeSets(blockStart, blockEnd, subgraphEndpoint, lastId, chainId);
//console.log("Batch got length:", sets.length);
batches.push(sets);
if (sets.length < 1000) break;
Expand All @@ -51,15 +73,22 @@ const fetchAllStakeSets = async (blockStart, blockEnd, subgraphEndpoint) => {
return batches.flat(1);
};

const parseStakeSetsIntoEvents = (subgraphStakeSets) => {
const parseStakeSetsIntoEvents = (subgraphStakeSets, chainId) => {
return subgraphStakeSets.map((s) => {
return {
args: {
_address: utils.getAddress(s.address), // to checksum
_subcourtID: BigNumber.from(s.subcourtID),
_stake: BigNumber.from(s.stake),
_newTotalStake: BigNumber.from(s.newTotalStake),
},
args: isV2(chainId)
? {
_address: utils.getAddress(s.juror.id), // to checksum
_courtID: BigNumber.from(s.courtID),
_stake: BigNumber.from(s.stake),
_newTotalStake: BigNumber.from(s.newTotalStake),
}
: {
_address: utils.getAddress(s.address), // to checksum
_subcourtID: BigNumber.from(s.subcourtID),
_stake: BigNumber.from(s.stake),
_newTotalStake: BigNumber.from(s.newTotalStake),
},
logIndex: Number(s.logIndex),
blockNumber: Number(s.blocknumber),
};
Expand All @@ -72,15 +101,19 @@ export const getStakeSets = async (blockStart, blockEnd, chainId) => {
endpoint = "https://api.studio.thegraph.com/query/61738/kleros-display-mainnet/version/latest";
} else if (chainId === 100) {
endpoint = "https://api.studio.thegraph.com/query/61738/kleros-display-gnosis/version/latest";
} else if (chainId === 42161) {
endpoint = "https://api.studio.thegraph.com/query/44313/kleros-v2-neo-mainnet/version/latest";
} else {
throw new Error("Unsupported Chain, nor mainnet nor gnosis");
throw new Error("Unsupported chain");
}
const subgraphStakeSets = await fetchAllStakeSets(blockStart, blockEnd, endpoint);
const parsed = parseStakeSetsIntoEvents(subgraphStakeSets);

const subgraphStakeSets = await fetchAllStakeSets(blockStart, blockEnd, endpoint, chainId);
const parsed = parseStakeSetsIntoEvents(subgraphStakeSets, chainId);
const sorted = parsed.sort((a, b) => {
if (a.blockNumber === b.blockNumber) {
return a.logIndex - b.logIndex;
} else return a.blockNumber - b.blockNumber;
});

return sorted;
};
Loading