Skip to content

Instantly share code, notes, and snippets.

@thowar2
Forked from kevinzhangTO/bal_redistribution.ts
Created March 30, 2021 04:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thowar2/61d8dfc149526125c79621c31c279115 to your computer and use it in GitHub Desktop.
Save thowar2/61d8dfc149526125c79621c31c279115 to your computer and use it in GitHub Desktop.
Rough calculation of balancer reward redistribution
import { ethers } from "ethers";
import { parseUnits } from "ethers/lib/utils";
import fs from "fs";
import fetch from "node-fetch";
import path from "path";
const START_BLOCK = 11905930;
const END_BLOCK = 12111455;
const SNAPSHOT_BLOCK_DELTA = 240; // 240 blocks; Rougly 1 hr between snapshots
const ETHERSCAN_API_KEY = "";
const STAKING_POOLS = {
XSGD: {
address: "0x0a6d3eb98abb3754e54abee7da2ddaa892ecb980",
stakingToken: "0x1d55fb62451d36448b0f4fc4a0ff1b6e2ce9cef7",
},
EURS: {
address: "0x1Bd4F9e718442A1c9F14cc04a235119e3C0d2Cf2",
stakingToken: "0x20DeD7F6F8dbb6C1CC989fC923fB180142Ee0144",
},
CADC: {
address: "0xd1681390a5ac8a60d4392eb3c22ca662b2db68d7",
stakingToken: "0x47b6bfd8f2a85595bd5737ee1d620618e1e35323",
},
};
const getERC20TransferEvents = async (
erc20Address: string,
startBlock = 0,
endBlock = 999999999
) => {
const URL = `https://api.etherscan.io/api?module=account&action=tokentx&address=${erc20Address}&startblock=${startBlock}&endblock=${endBlock}&sort=asc&apikey=${ETHERSCAN_API_KEY}`;
const data = await fetch(URL).then((x) => x.json());
return data.result;
};
const calcBalDistribution = async ({
amountToDistribute,
stakingPool,
stakingToken,
stakingPoolName,
}) => {
const events = await getERC20TransferEvents(stakingPool, START_BLOCK);
const stakingPoolEvents = events.filter((x) => {
return (
(x.from.toLowerCase() === stakingPool.toLowerCase() ||
x.to.toLowerCase() === stakingPool.toLowerCase()) &&
x.contractAddress.toLowerCase() === stakingToken.toLowerCase()
);
});
// Accumulator
let balAccumulator = {};
// balances
let historicalBalances: any[] = [];
let curBlock = START_BLOCK;
do {
// Only get relevant events
const relevantEvents = stakingPoolEvents.filter((x) => {
const eventBlockNo = parseInt(x.blockNumber);
return (
eventBlockNo >= curBlock &&
eventBlockNo < curBlock + SNAPSHOT_BLOCK_DELTA
);
});
// Loop through accumulator gather balances
for (let i = 0; i < relevantEvents.length; i++) {
const curEvent = relevantEvents[i];
const from = curEvent.from.toLowerCase();
const to = curEvent.to.toLowerCase();
if (from === stakingPool.toLowerCase()) {
// Withdraw
balAccumulator[to] = (balAccumulator[to] || ethers.constants.Zero).sub(
ethers.BigNumber.from(curEvent.value)
);
} else if (to === stakingPool.toLowerCase()) {
// Deposit
balAccumulator[from] = (
balAccumulator[from] || ethers.constants.Zero
).add(ethers.BigNumber.from(curEvent.value));
}
}
// Get total deposits
const totalDeposits = Object.keys(balAccumulator).reduce((acc, x) => {
return acc.add(balAccumulator[x]);
}, ethers.constants.Zero);
// Balances in string
const balancesStr = Object.keys(balAccumulator).reduce((acc, x) => {
if (balAccumulator[x].gt(ethers.constants.Zero)) {
return { ...acc, [x]: balAccumulator[x].toString() };
}
return acc;
}, {});
// Save to balances
historicalBalances.push({
fromBlock: curBlock,
toBlock: curBlock + SNAPSHOT_BLOCK_DELTA,
totalDeposits: totalDeposits.toString(),
balances: JSON.parse(JSON.stringify(balancesStr)),
});
curBlock = curBlock + SNAPSHOT_BLOCK_DELTA;
} while (curBlock <= END_BLOCK);
// Have historical balances now
// Calculate _all_ deposits proportionally
const snapshotTotalDeposits = historicalBalances.reduce((acc, x) => {
return ethers.BigNumber.from(x.totalDeposits).add(acc);
}, ethers.constants.Zero);
// How much does each snapshot earn
for (let i = 0; i < historicalBalances.length; i++) {
const amount = amountToDistribute
.mul(ethers.BigNumber.from(historicalBalances[i].totalDeposits))
.div(snapshotTotalDeposits);
historicalBalances[i].totalBalClaimable = amount.toString();
const balClaimable = Object.keys(historicalBalances[i].balances).reduce(
(acc, x) => {
const userClaimable = amount
.mul(ethers.BigNumber.from(historicalBalances[i].balances[x]))
.div(ethers.BigNumber.from(historicalBalances[i].totalDeposits));
return {
...acc,
[x]: userClaimable.toString(),
};
},
{}
);
historicalBalances[i].balClaimable = JSON.parse(
JSON.stringify(balClaimable)
);
}
// Final output
let claims = {};
for (let i = 0; i < historicalBalances.length; i++) {
const claimableAddresses = Object.keys(historicalBalances[i].balClaimable);
for (let j = 0; j < claimableAddresses.length; j++) {
const curAddress = claimableAddresses[j];
claims[curAddress] = (claims[curAddress] || ethers.constants.Zero).add(
ethers.BigNumber.from(historicalBalances[i].balClaimable[curAddress])
);
}
}
// Make sure sum adds up to amount
const totalClaimable = Object.keys(claims).reduce((acc, x) => {
return acc.add(claims[x]);
}, ethers.constants.Zero);
if (totalClaimable.gt(amountToDistribute)) {
console.log(
stakingPoolName,
"totalClaimable",
totalClaimable.toString(),
"amountToDistribute",
amountToDistribute.toString()
);
console.error("Invalid calculations");
}
const claimStr = Object.keys(claims).reduce((acc, x) => {
return { ...acc, [x]: claims[x].toString() };
}, {});
fs.writeFileSync(
path.resolve(__dirname, `${stakingPoolName}_bal_distribution.json`),
JSON.stringify(claimStr, null, 4)
);
fs.writeFileSync(
path.resolve(__dirname, `${stakingPoolName}_historical.json`),
JSON.stringify(historicalBalances, null, 4)
);
};
const main = async () => {
await calcBalDistribution({
amountToDistribute: ethers.BigNumber.from("1766811000000000000000"),
stakingToken: STAKING_POOLS.XSGD.stakingToken,
stakingPool: STAKING_POOLS.XSGD.address,
stakingPoolName: "xsgd",
});
await calcBalDistribution({
amountToDistribute: ethers.BigNumber.from("1271867000000000000000"),
stakingToken: STAKING_POOLS.CADC.stakingToken,
stakingPool: STAKING_POOLS.CADC.address,
stakingPoolName: "cadc",
});
};
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment