101 lines
5.0 KiB
TypeScript
101 lines
5.0 KiB
TypeScript
import Web3 from "web3";
|
|
import BigNumber from "bignumber.js";
|
|
import { AbiItem } from "web3-utils";
|
|
import { EthAddress, IStaker } from "./@types/staker";
|
|
|
|
import * as dotenv from "dotenv";
|
|
import { governanceAddress, governanceRewardsProposalBlock, hackBlock, oldStakingAddress, multicallContractAddress } from "./config";
|
|
|
|
import GovernanceAbi from "../abi/GovernanceAbi.json";
|
|
import StakingAbi from "../abi/StakingABI.json";
|
|
import MulticallABI from "../abi/MultiCallABI.json";
|
|
|
|
dotenv.config();
|
|
|
|
const web3 = new Web3(process.env.MAINNET_RPC_URL as string);
|
|
|
|
function getGovernanceFunctionSelector(functionName: string): string {
|
|
const lockFunctionAbi = GovernanceAbi.find((item) => item.name == functionName);
|
|
if (!lockFunctionAbi) throw new Error(`Cannot find function ${functionName} in Governance contract ABI`);
|
|
|
|
const selector = web3.eth.abi.encodeFunctionSignature(lockFunctionAbi as AbiItem);
|
|
|
|
return selector;
|
|
}
|
|
|
|
async function getAddressesStakersWithProssibleRewards(): Promise<Array<EthAddress>> {
|
|
const governanceContract = new web3.eth.Contract(GovernanceAbi as AbiItem[], governanceAddress);
|
|
|
|
/* Don't need to fetch all "lock" or "lockAndApproval" transactions from Governance contract,
|
|
* because user rewards start updating only when RewardUpdateSuccessful events is emitted.
|
|
*/
|
|
const rewardsUpdateEvents = await governanceContract.getPastEvents("RewardUpdateSuccessful", {
|
|
fromBlock: governanceRewardsProposalBlock,
|
|
});
|
|
|
|
const governanceStakers = rewardsUpdateEvents.map((event) => event.returnValues.account as string);
|
|
|
|
return [...new Set(governanceStakers)];
|
|
}
|
|
|
|
export async function getStakersWithRewardsBeforeHack(): Promise<Array<IStaker>> {
|
|
const stakingContract = new web3.eth.Contract(StakingAbi as AbiItem[], oldStakingAddress);
|
|
const stakersAddresses = await getAddressesStakersWithProssibleRewards();
|
|
|
|
const multiCallQuery: Array<[string, object]> = stakersAddresses.map((stakerAddress) =>
|
|
stakingContract.methods.checkReward(stakerAddress).encodeABI()
|
|
);
|
|
const stakersWithRewardsBeforeHackRawData = await useMultiCall(oldStakingAddress, multiCallQuery, hackBlock - 1);
|
|
const stakersWithRewardsBeforeHack: Array<IStaker> = stakersWithRewardsBeforeHackRawData.map((reward, index) => ({
|
|
address: stakersAddresses[index],
|
|
rewardBalance: BigNumber(reward),
|
|
}));
|
|
|
|
// let stakersWithRewardsBeforeHack: Array<IStaker> = [];
|
|
// for (const address of stakersAddresses) {
|
|
// // Check stakers rewards on previous block before hack
|
|
// const rewardsBeforeHack = await stakingContract.methods.checkReward(address).call({}, hackBlock - 1);
|
|
// // Discard stakers with rewards less than 1 TORN, because accruing each reward requires paying gas
|
|
// if (BigNumber(rewardsBeforeHack).div(1e18).isGreaterThan(1))
|
|
// stakersWithRewardsBeforeHack.push({ address, rewardBalance: BigNumber(rewardsBeforeHack) });
|
|
// }
|
|
|
|
return stakersWithRewardsBeforeHack.filter((staker) => staker.rewardBalance.div(1e18).isGreaterThan(1));
|
|
}
|
|
|
|
export async function getStakersWithdrawedAfterHack(): Promise<Array<EthAddress>> {
|
|
const stakingContract = new web3.eth.Contract(StakingAbi as AbiItem[], oldStakingAddress);
|
|
|
|
const withdrawRewardEvents = await stakingContract.getPastEvents("RewardsClaimed", { fromBlock: hackBlock });
|
|
const stakersWithdrawedAfterHack = withdrawRewardEvents.map((event) => event.returnValues.account as string);
|
|
|
|
return stakersWithdrawedAfterHack;
|
|
}
|
|
|
|
async function useMultiCall(contractAddress: EthAddress, queryArray: Array<Object>, callBlock?: number): Promise<any[]> {
|
|
const multiCallContract = new web3.eth.Contract(MulticallABI as AbiItem[], multicallContractAddress);
|
|
const multicallQueryArray = queryArray.map((query) => [contractAddress, query]);
|
|
const { returnData } = await multiCallContract.methods.aggregate(multicallQueryArray).call({}, callBlock);
|
|
return returnData;
|
|
}
|
|
|
|
export async function getStakersLockedBalancesSum(block: number): Promise<BigNumber> {
|
|
const governanceContract = new web3.eth.Contract(GovernanceAbi as AbiItem[], governanceAddress);
|
|
|
|
// All stakers who had more than 1 TORN in rewards at the time of the hack
|
|
const stakersWithRewardsBeforeHack = await getStakersWithRewardsBeforeHack();
|
|
// Stakers who withdrew rewards from the time of hack until the balance of the old Staking contract was nullified
|
|
const stakersWithdrawedAfterHack = await getStakersWithdrawedAfterHack();
|
|
|
|
// It makes no sense to restore awards to those who already withdrew them from the old Staking contract
|
|
const stakersToRestoreRewards = stakersWithRewardsBeforeHack.filter((staker) => !stakersWithdrawedAfterHack.includes(staker.address));
|
|
|
|
const lockedBalances: Array<string> = await useMultiCall(
|
|
governanceAddress,
|
|
stakersToRestoreRewards.map((staker) => governanceContract.methods.lockedBalance(staker.address).encodeABI()),
|
|
block
|
|
);
|
|
|
|
return lockedBalances.reduce((acc, cur) => acc.plus(BigNumber(cur)), BigNumber(0));
|
|
}
|