Created
March 26, 2021 21:29
-
-
Save kevinzhangTO/b0ecd529de86412c9f9d983c660a2a7d to your computer and use it in GitHub Desktop.
Rough calculation of balancer reward redistribution
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 { 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