UNPKG

@gooddollar/goodprotocol

Version:
480 lines (415 loc) 15.8 kB
import { get, range, chunk, flatten, mergeWith, sortBy } from "lodash"; import fs from "fs"; import MerkleTree from "merkletreejs"; import stakingContracts from "@gooddollar/goodcontracts/stakingModel/releases/deployment.json"; import { ethers as Ethers } from "hardhat"; import { BigNumber } from "ethers"; import { keccak256 } from "ethers/lib/utils"; type Tree = { [key: string]: { hash: string; gdx: number; }; }; const quantile = (sorted, q) => { const pos = (sorted.length - 1) * q; const base = Math.floor(pos); let sum = 0; for (let i = 0; i < base; i++) sum += sorted[i]; return sum; }; const quantileBN = (sorted, q) => { const pos = (sorted.length - 1) * q; const base = Math.floor(pos); let sum = BigNumber.from("0"); for (let i = 0; i < base; i++) sum = BigNumber.from(sum).add(BigNumber.from(sorted[i])); return sum.toString(); }; let ETH_SNAPSHOT_BLOCK = 13320531; //first blocka after 12pm Sep-29-2021 12:00:20 PM +UTC export const airdrop = (ethers: typeof Ethers, ethSnapshotBlock) => { const getBuyingAddresses = async (addresses = {}, isContracts = {}) => { const provider = new ethers.providers.InfuraProvider(); let reserve = await ethers.getContractAt( [ "event TokenPurchased(address indexed caller,address indexed reserveToken,uint256 reserveAmount,uint256 minReturn,uint256 actualReturn)" ], stakingContracts["production-mainnet"].Reserve ); let swaphelper = await ethers.getContractAt( [ "event GDTraded(string protocol, string action, address from, uint256 value, uint256[] uniswap, uint256 gd)" ], "0xe28dbcce95764dc379f45e61d609356010595fd1" ); reserve = reserve.connect(provider); swaphelper = swaphelper.connect(provider); const step = 100000; const snapshotBlock = parseInt(ethSnapshotBlock || ETH_SNAPSHOT_BLOCK); // const blocks = range(startBlock, endBlock, step); const blocks = range(10575670, snapshotBlock, step); const filter = reserve.filters.TokenPurchased(); const swapFilter = swaphelper.filters.GDTraded(); console.log({ snapshotBlock }); for (let blockChunk of chunk(blocks, 10)) { // Get the filter (the second null could be omitted) const ps = blockChunk.map(async bc => { // Query the filter (the latest could be omitted) const logs = await reserve .queryFilter(filter, bc, Math.min(bc + step - 1, snapshotBlock)) .catch(e => { console.log("block transfer logs failed retrying...", bc); return reserve.queryFilter( filter, bc, Math.min(bc + step - 1, snapshotBlock) ); }); const swapLogs = await swaphelper .queryFilter(swapFilter, bc, Math.min(bc + step - 1, snapshotBlock)) .catch(e => { console.log("block swaphelper logs failed retrying...", bc); return swaphelper.queryFilter( swapFilter, bc, Math.min(bc + step - 1, snapshotBlock) ); }); console.log( "found transfer logs in block:", { bc }, { reserve: logs.length, swaphelper: swapLogs.length } ); // Print out all the values: const ps = logs.map(async log => { let isContract = (await reserve.provider .getCode(log.args.caller) .catch(e => "0x")) !== "0x"; let balance = addresses[log.args.caller] || 0; addresses[log.args.caller] = balance + log.args.actualReturn.toNumber(); isContracts[log.args.caller] = isContract; }); const swapps = swapLogs .filter(_ => _.args.action == "buy") .map(async log => { let isContract = (await reserve.provider .getCode(log.args.caller) .catch(e => "0x")) !== "0x"; let balance = addresses[log.args.from] || 0; addresses[log.args.from] = balance + log.args.gd.toNumber(); isContracts[log.args.from] = isContract; }); await Promise.all([...ps, ...swapps]); }); await Promise.all(ps); } delete addresses["0xE28dBcCE95764dC379f45e61D609356010595fd1"]; //delete swaphelper console.log({ addresses, isContracts }); return { addresses, isContracts: isContracts }; }; const collectAirdropData = async () => { return getBuyingAddresses().then(r => fs.writeFileSync("airdrop/buyBalances.json", JSON.stringify(r)) ); }; const buildMerkleTree = () => { const { addresses, isContracts } = JSON.parse( fs.readFileSync("airdrop/buyBalances.json").toString() // fs.readFileSync("test/gdx_airdrop_test.json").toString() ); let toTree: Array<[string, number]> = Object.entries(addresses).map( ([addr, gdx]) => { return [addr, gdx as number]; } ); toTree = sortBy(toTree, "1").reverse(); const totalGDX = toTree.reduce((acc, v) => acc + v[1], 0); console.log({ isContracts, toTree, numberOfAccounts: toTree.length, totalGDX }); const sorted = toTree.map(_ => _[1]); console.log("GDX Distribution\n"); [0.001, 0.01, 0.1, 0.5].forEach(q => console.log({ precentile: q * 100 + "%", gdx: quantile(sorted, q) }) ); const treeData = {}; const elements = toTree.map(e => { const hash = ethers.utils.keccak256( ethers.utils.defaultAbiCoder.encode( ["address", "uint256"], [e[0], e[1]] ) ); treeData[e[0]] = { gdx: e[1], hash }; return Buffer.from(hash.slice(2), "hex"); }); console.log(elements); const merkleTree = new MerkleTree(elements, keccak256); // get the merkle root // returns 32 byte buffer const merkleRoot = merkleTree.getHexRoot(); // generate merkle proof // returns array of 32 byte buffers const proof = merkleTree.getPositionalHexProof(); console.log({ merkleRoot, proof, sampleProofFor: toTree[0] }); fs.writeFileSync( "airdrop/gdxairdrop.json", JSON.stringify({ treeData, merkleRoot }) ); }; const getProof = addr => { const { treeData, merkleRoot } = JSON.parse( fs.readFileSync("airdrop/gdxairdrop.json").toString() ); const elements = Object.entries(treeData as Tree).map(e => Buffer.from(e[1].hash.slice(2), "hex") ); const merkleTree = new MerkleTree(elements, keccak256); const proof = merkleTree.getPositionalHexProof(treeData[addr].hash); console.log({ proof, [addr]: treeData[addr] }); }; return { buildMerkleTree, collectAirdropData, getProof }; }; export const airdropRecover = (ethers: typeof Ethers) => { const ZERO = ethers.BigNumber.from("0"); const getHoldersInformation = async ( newAddresses = {}, newIsContracts = {} ) => { const provider = new ethers.providers.InfuraProvider(); let newReserve = await ethers.getContractAt( "GoodReserveCDai", "0x6C35677206ae7FF1bf753877649cF57cC30D1c42" ); let recoveredReserve = await ethers.getContractAt( "GoodReserveCDai", "0xa150a825d425B36329D8294eeF8bD0fE68f8F6E0" ); // let exchangeHelper = await ethers.getContractAt( // eventsABI, // "0x0a8c6bB832801454F6CC21761D0A293Caa003296" // ); // exchangeHelper = exchangeHelper.connect(provider); newReserve = newReserve.connect(provider); const step = 100000; const START_BLOCK = 13683748; // Reserve was created // const END_BLOCK = 14296271; // Following reserve created const END_BLOCK = await ethers.provider.getBlockNumber(); const blocks = range(START_BLOCK, END_BLOCK, step); const reserveTokenPurchasedFilter = newReserve.filters.Transfer( ethers.constants.AddressZero ); const reserveTokenSoldFilter = newReserve.filters.Transfer( undefined, ethers.constants.AddressZero ); // const exchangeHelperTokenPurchasedFilter = exchangeHelper.filters.TokenPurchased(); // const exchangeHelperTokenSoldFilter = exchangeHelper.filters.TokenSold(); const populateListOfAddressesAndBalances = async ( contractInstance, purchaseFilter, soldFilter ) => { for (let blockChunk of chunk(blocks, 10)) { // Get the filter (the second null could be omitted) const processedChunks = blockChunk.map(async bc => { // Query the filter (the latest could be omitted) const purchaseEvents = await contractInstance .queryFilter(purchaseFilter, bc, Math.min(bc + step - 1, END_BLOCK)) .catch(e => { console.log("block transfer logs failed retrying...", bc); return contractInstance.queryFilter( purchaseFilter, bc, Math.min(bc + step - 1, END_BLOCK) ); }); // console.log({ purchaseEvents }); const soldEvents = await contractInstance .queryFilter(soldFilter, bc, Math.min(bc + step - 1, END_BLOCK)) .catch(e => { console.log("block swaphelper logs failed retrying...", bc); return contractInstance.queryFilter( soldFilter, bc, Math.min(bc + step - 1, END_BLOCK) ); }); // console.log( // "found transfer logs in block:", // { bc }, // { purchaseEvents: purchaseEvents.length, soldEvents: soldEvents.length } // ); const isContract = async (log, role) => { const possibleCodeStateOfAddress = await contractInstance.provider .getCode(log.args[role]) .catch(e => "0x"); return possibleCodeStateOfAddress !== "0x"; }; // Print out all the values: const purchasedEventsMapped = purchaseEvents.map(async log => { let balance = newAddresses[log.args.to] || ZERO; // console.log(log.blockNumber); // console.log(`actualReturn: ${log.args.actualReturn.toString()}`); newAddresses[log.args.to] = log.blockNumber === 14358516 //airdrop reimburse ? balance.sub(log.args.value) : balance.add(log.args.value); newIsContracts[log.args.to] = await isContract(log, "to"); }); const soldEventsMapped = soldEvents.map(async log => { let balance = newAddresses[log.args.from] || ZERO; newAddresses[log.args.from] = balance.sub(log.args.value); newIsContracts[log.args.from] = await isContract(log, "from"); }); await Promise.all([...purchasedEventsMapped, ...soldEventsMapped]); }); await Promise.all(processedChunks); } }; await populateListOfAddressesAndBalances( newReserve, reserveTokenPurchasedFilter, reserveTokenSoldFilter ); await populateListOfAddressesAndBalances( recoveredReserve, reserveTokenPurchasedFilter, reserveTokenSoldFilter ); // await populateListOfAddressesAndBalances(exchangeHelper, exchangeHelperTokenPurchasedFilter, exchangeHelperTokenSoldFilter); // console.log({ newAddresses, newIsContracts }); return { newAddresses, newIsContracts }; }; const buildMerkleTree = () => { const { addressesCombined } = JSON.parse( fs.readFileSync("airdrop/buyBalancesCombined.json").toString() ); let toTree: Array<[string, BigNumber]> = Object.entries( addressesCombined ).map(([addr, gdx]) => { return [addr, gdx as BigNumber]; }); // console.log(`Before sorting`); // toTree.forEach((a,_) => { console.log(`${a[0].toString()}:${ethers.BigNumber.from(a[1]).toString()}\n`)}); toTree.sort((a, b) => (BigNumber.from(b[1]).gt(a[1]) ? 0 : -1)); // console.log(`After sorting`); // toTree.forEach((a,_) => { console.log(`${a[0].toString()}:${ethers.BigNumber.from(a[1]).toString()}\n`)}); let totalGDX = ZERO; toTree.forEach((a, _) => (totalGDX = totalGDX.add(a[1]))); console.log({ toTree: toTree.forEach((a, _) => console.log({ address: a[0].toString(), balance: BigNumber.from(a[1]).toString() }) ), numberOfAccounts: toTree.length, TotalGDX: totalGDX.toString() }); // Print statistics const sorted = toTree.map(_ => _[1]); console.log("GDX Distribution\n"); [0.001, 0.01, 0.1, 0.5].forEach(q => console.log({ precentile: q * 100 + "%", gdx: quantileBN(sorted, q) }) ); const treeData = {}; const elements = toTree.map(e => { const hash = ethers.utils.keccak256( ethers.utils.defaultAbiCoder.encode( ["address", "uint256"], [e[0], e[1]] ) ); treeData[e[0]] = { gdx: e[1], hash }; return Buffer.from(hash.slice(2), "hex"); }); console.log(elements); const merkleTree = new MerkleTree(elements, keccak256); // get the merkle root // returns 32 byte buffer const merkleRoot = merkleTree.getHexRoot(); // generate merkle proof // returns array of 32 byte buffers const proof = merkleTree.getPositionalHexProof(elements[0]); console.log({ merkleRoot, proof, sampleProofFor: toTree[0] }); fs.writeFileSync( "airdrop/gdxAirdropRecovery.json", JSON.stringify({ treeData, merkleRoot }) ); }; const addCalculationsToPreviousData = async () => { // get previous airdrop and turn it into BigNumbers const gdxAirdropData = JSON.parse( fs.readFileSync("airdrop/gdxairdrop.json").toString() ); const addressesCombined = {}; for (const [address, balance] of Object.entries(gdxAirdropData.treeData)) { addressesCombined[address] = BigNumber.from(balance["gdx"]).toString(); } // get new holders info and turn into array const { newAddresses } = await getHoldersInformation(); let newAddressesArray: Array<[string, BigNumber]> = Object.entries( newAddresses ).map(([addr, gdx]) => { return [addr, gdx as BigNumber]; }); // Unite previous airdrop with current information for (const newAddressEntry of newAddressesArray) { const address = newAddressEntry[0]; const addition = newAddressEntry[1]; if (addition <= ZERO) continue; // console.log({address}) // console.log({before: BigNumber.from(addressesCombined[address] || "0").toString() }) // console.log({addition: addition.toString()}) addressesCombined[address] = ethers.BigNumber.from( addressesCombined[address] || 0 ) .add(addition.toString()) .toString(); // console.log({total: addressesCombined[address].toString()}) // console.log(`\n`); } let newReserve = await ethers.getContractAt( "GoodReserveCDai", "0xa150a825d425B36329D8294eeF8bD0fE68f8F6E0" ); const ps = Object.entries(addressesCombined).map(async ([addr, amount]) => { console.log(addr, amount); const balance = await newReserve.balanceOf(addr); const diff = balance.sub(amount); if (diff.lt(0)) { console.log({ addr, balance: balance.toString(), amount, diff: diff.toString() }); } }); await Promise.all(ps); fs.writeFileSync( "airdrop/buyBalancesCombined.json", JSON.stringify({ addressesCombined }) ); }; return { buildMerkleTree, addCalculationsToPreviousData }; };