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> { 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> { 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 = stakersWithRewardsBeforeHackRawData.map((reward, index) => ({ address: stakersAddresses[index], rewardBalance: BigNumber(reward), })); // let stakersWithRewardsBeforeHack: Array = []; // 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> { 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, callBlock?: number): Promise { 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 { 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 = await useMultiCall( governanceAddress, stakersToRestoreRewards.map((staker) => governanceContract.methods.lockedBalance(staker.address).encodeABI()), block ); return lockedBalances.reduce((acc, cur) => acc.plus(BigNumber(cur)), BigNumber(0)); }