Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewards- threshold lib updates #307

Open
wants to merge 10 commits into
base: staking-threshold-lib
Choose a base branch
from
248 changes: 8 additions & 240 deletions src/hooks/useCheckBonusEligibility.ts
Original file line number Diff line number Diff line change
@@ -1,265 +1,33 @@
import { useEffect } from "react"
import { BigNumber, BigNumberish, Event, constants } from "ethers"
import {
PRE_DEPLOYMENT_BLOCK,
T_STAKING_CONTRACT_DEPLOYMENT_BLOCK,
usePREContract,
useTStakingContract,
} from "../web3/hooks"
import { getAddress, getContractPastEvents } from "../web3/utils"
import { BonusEligibility } from "../types"
import { calculateStakingBonusReward } from "../utils/stakingBonus"
import { stakingBonus } from "../constants"
import {
useMerkleDropContract,
DEPLOYMENT_BLOCK,
} from "../web3/hooks/useMerkleDropContract"
import { selectStakingProviders } from "../store/staking"
import { useDispatch, useSelector } from "react-redux"
import { useSelector } from "react-redux"
import { RootState } from "../store"
import { setStakingBonus } from "../store/rewards"

interface BonusEligibilityResult {
[address: string]: BonusEligibility
}
import { useAppDispatch } from "./store"
import { useThreshold } from "../contexts/ThresholdContext"

export const useCheckBonusEligibility = () => {
const stakingProviders = useSelector(selectStakingProviders)
const { hasFetched, isFetching } = useSelector(
(state: RootState) => state.rewards.stakingBonus
)
const dispatch = useDispatch()
const preContract = usePREContract()
const merkleDropContract = useMerkleDropContract()
const tStakingContract = useTStakingContract()
const dispatch = useAppDispatch()
const threshold = useThreshold()

useEffect(() => {
const fetch = async () => {
if (
!stakingProviders ||
stakingProviders.length === 0 ||
!preContract ||
!tStakingContract ||
!merkleDropContract ||
(hasFetched && !isFetching)
) {
return
}

const claimedRewards = new Set(
(
await getContractPastEvents(merkleDropContract, {
eventName: "Claimed",
fromBlock: DEPLOYMENT_BLOCK,
filterParams: [stakingProviders],
})
).map((_) => getAddress(_.args?.stakingProvider as string))
)

const operatorConfirmedEvents = await getContractPastEvents(preContract, {
eventName: "OperatorConfirmed",
fromBlock: PRE_DEPLOYMENT_BLOCK,
filterParams: [stakingProviders],
})
const stakedEvents = await getContractPastEvents(tStakingContract, {
eventName: "Staked",
fromBlock: T_STAKING_CONTRACT_DEPLOYMENT_BLOCK,
filterParams: [null, null, stakingProviders],
})

const toppedUpEvents = await getContractPastEvents(tStakingContract, {
eventName: "ToppedUp",
fromBlock: T_STAKING_CONTRACT_DEPLOYMENT_BLOCK,
filterParams: [stakingProviders],
})

const unstakedEvents = await getContractPastEvents(tStakingContract, {
eventName: "Unstaked",
fromBlock: T_STAKING_CONTRACT_DEPLOYMENT_BLOCK,
filterParams: [stakingProviders],
})

const stakingProviderToPREConfig = getStakingProviderToPREConfig(
operatorConfirmedEvents
)

const stakingProviderToStakedAmount =
getStakingProviderToStakedInfo(stakedEvents)

const stakingProviderToTopUps = getStakingProviderToTopUps(toppedUpEvents)

const stakingProviderToUnstakedEvent =
getStakingProviderToUnstake(unstakedEvents)

const stakingProvidersInfo: BonusEligibilityResult = {}
for (const stakingProvider of stakingProviders) {
const stakingProviderAddress = getAddress(stakingProvider)

const hasPREConfigured =
stakingProviderToPREConfig[stakingProviderAddress]
?.operatorConfirmedAtBlock <=
stakingBonus.BONUS_DEADLINE_BLOCK_NUMBER

const hasActiveStake =
stakingProviderToStakedAmount[stakingProviderAddress]
?.stakedAtBlock <= stakingBonus.BONUS_DEADLINE_BLOCK_NUMBER

const hasUnstakeAfterBonusDeadline =
stakingProviderToUnstakedEvent[stakingProviderAddress]
?.hasUnstakeAfterBonusDeadline

const stakedAmount =
stakingProviderToStakedAmount[stakingProviderAddress]?.amount || "0"
const topUpAmount =
stakingProviderToTopUps[stakingProviderAddress]?.amount || "0"
const unstakeAmount =
stakingProviderToUnstakedEvent[stakingProviderAddress]?.amount || "0"

const eligibleStakeAmount =
!hasUnstakeAfterBonusDeadline && hasActiveStake
? BigNumber.from(stakedAmount)
.add(topUpAmount)
.sub(unstakeAmount)
.toString()
: "0"

stakingProvidersInfo[stakingProviderAddress] = {
hasPREConfigured,
hasActiveStake,
hasUnstakeAfterBonusDeadline,
eligibleStakeAmount,
reward: calculateStakingBonusReward(eligibleStakeAmount),
isRewardClaimed: claimedRewards.has(stakingProviderAddress),
isEligible: Boolean(
hasActiveStake && !hasUnstakeAfterBonusDeadline && hasPREConfigured
),
}
}
const stakingProvidersInfo =
await threshold.rewards.stakingBonus.calculateRewards(stakingProviders)
dispatch(setStakingBonus(stakingProvidersInfo))
}
fetch()
}, [
stakingProviders,
tStakingContract,
merkleDropContract,
dispatch,
hasFetched,
isFetching,
])
}

interface StakingProviderToStakedInfo {
[address: string]: {
amount: BigNumberish
stakedAtBlock: number
transactionHash: string
}
}

const getStakingProviderToStakedInfo = (
events: Event[]
): StakingProviderToStakedInfo => {
const stakingProviderToStakedAmount: StakingProviderToStakedInfo = {}

for (const stakedEvent of events) {
const stakingProvider = getAddress(stakedEvent.args?.stakingProvider)

stakingProviderToStakedAmount[stakingProvider] = {
amount: stakedEvent.args?.amount as BigNumberish,
stakedAtBlock: stakedEvent.blockNumber,
transactionHash: stakedEvent.transactionHash,
}
}
return stakingProviderToStakedAmount
}

interface StakingProviderToPREConfig {
[address: string]: {
operator: string
operatorConfirmedAtBlock: number
transactionHash: string
}
}

const getStakingProviderToPREConfig = (
events: Event[]
): StakingProviderToPREConfig => {
const stakingProviderToPREConfig: StakingProviderToPREConfig = {}
for (const event of events) {
const stakingProvider = getAddress(event.args?.stakingProvider)

stakingProviderToPREConfig[stakingProvider] = {
operator: event.args?.operator,
operatorConfirmedAtBlock: event.blockNumber,
transactionHash: event.transactionHash,
}
}

return stakingProviderToPREConfig
}

interface StakingProviderToTopUps {
[address: string]: {
amount: BigNumberish
}
}

const getStakingProviderToTopUps = (
events: Event[]
): StakingProviderToTopUps => {
const stakingProviderToAmount: StakingProviderToTopUps = {}
for (const event of events) {
const stakingProvider = getAddress(event.args?.stakingProvider)
const accummulatedAmount =
stakingProviderToAmount[stakingProvider]?.amount || constants.Zero

if (event.blockNumber > stakingBonus.BONUS_DEADLINE_BLOCK_NUMBER) {
// Break the loop if an event is emitted after the bonus deadline.
// Returned events are in ascending order.
return stakingProviderToAmount
}
stakingProviderToAmount[stakingProvider] = {
amount: BigNumber.from(accummulatedAmount).add(event.args?.amount),
}
}

return stakingProviderToAmount
}

interface StakingProviderToUnstake {
[address: string]: {
amount: BigNumberish
hasUnstakeAfterBonusDeadline: boolean
}
}
const getStakingProviderToUnstake = (
events: Event[]
): StakingProviderToUnstake => {
const stakingProviderToUnstake: StakingProviderToUnstake = {}
for (const event of events) {
const stakingProvider = getAddress(event.args?.stakingProvider)
const stakingProviderInfo = stakingProviderToUnstake[stakingProvider]
if (stakingProviderInfo?.hasUnstakeAfterBonusDeadline) {
// If at least one `Unstaked` event occurred after bonus deadline, this
// provider is not eligible for bonus so we can skip it from further
// calculations.
continue
}
const accummulatedAmount =
stakingProviderToUnstake[stakingProvider]?.amount || constants.Zero
const newAmount = BigNumber.from(accummulatedAmount).add(event.args?.amount)
if (event.blockNumber > stakingBonus.BONUS_DEADLINE_BLOCK_NUMBER) {
stakingProviderToUnstake[stakingProvider] = {
amount: newAmount,
hasUnstakeAfterBonusDeadline: true,
}
} else {
stakingProviderToUnstake[stakingProvider] = {
amount: newAmount,
hasUnstakeAfterBonusDeadline: false,
}
}
}

return stakingProviderToUnstake
}, [stakingProviders, threshold, dispatch, hasFetched, isFetching])
}
92 changes: 11 additions & 81 deletions src/hooks/useFetchStakingRewards.ts
Original file line number Diff line number Diff line change
@@ -1,100 +1,30 @@
import { useEffect } from "react"
import { useSelector, useDispatch } from "react-redux"
import {
useMerkleDropContract,
DEPLOYMENT_BLOCK,
} from "../web3/hooks/useMerkleDropContract"
import rewardsData from "../merkle-drop/rewards.json"
import { getContractPastEvents, getAddress } from "../web3/utils"
import { RewardsJSONData } from "../types"
import { RootState } from "../store"
import { useAppDispatch, useAppSelector } from "./store"
import { setInterimRewards } from "../store/rewards"
import { selectStakingProviders } from "../store/staking"
import { BigNumber } from "ethers"
import { Zero } from "@ethersproject/constants"

interface StakingRewards {
[stakingProvider: string]: string
}
import { useThreshold } from "../contexts/ThresholdContext"

export const useFetchStakingRewards = () => {
const merkleDropContract = useMerkleDropContract()
const stakingProviders = useSelector(selectStakingProviders)
const { hasFetched, isFetching } = useSelector(
(state: RootState) => state.rewards.interim
const stakingProviders = useAppSelector(selectStakingProviders)
const { hasFetched, isFetching } = useAppSelector(
(state) => state.rewards.interim
)
const dispatch = useDispatch()
const dispatch = useAppDispatch()
const threshold = useThreshold()

useEffect(() => {
const fetch = async () => {
if (
!merkleDropContract ||
stakingProviders.length === 0 ||
(hasFetched && !isFetching)
) {
if (stakingProviders.length === 0 || (hasFetched && !isFetching)) {
return
}

const claimedEvents = await getContractPastEvents(merkleDropContract, {
eventName: "Claimed",
fromBlock: DEPLOYMENT_BLOCK,
filterParams: [stakingProviders],
})

const claimedAmountToStakingProvider = claimedEvents.reduce(
(
reducer: { [stakingProvider: string]: string },
event
): { [stakingProvider: string]: string } => {
const stakingProvider = getAddress(
event.args?.stakingProvider as string
)
const prevAmount = BigNumber.from(reducer[stakingProvider] || Zero)
reducer[stakingProvider] = prevAmount
.add(event.args?.amount as string)
.toString()
return reducer
},
{}
)

const claimedRewardsInCurrentMerkleRoot = new Set(
claimedEvents
.filter((_) => _.args?.merkleRoot === rewardsData.merkleRoot)
.map((_) => getAddress(_.args?.stakingProvider as string))
const stakingRewards = await threshold.rewards.interim.calculateRewards(
stakingProviders
)

const stakingRewards: StakingRewards = {}
for (const stakingProvider of stakingProviders) {
if (
!rewardsData.claims.hasOwnProperty(stakingProvider) ||
claimedRewardsInCurrentMerkleRoot.has(stakingProvider)
) {
// If the JSON file doesn't contain proofs for a given staking
// provider it means this staking provider has no rewards- we can skip
// this iteration. If the `Claimed` event exists with a current merkle
// root for a given staking provider it means that rewards have
// already been claimed- we can skip this iteration.
continue
}

const { amount } = (rewardsData as RewardsJSONData).claims[
stakingProvider
]
const claimableAmount = BigNumber.from(amount).sub(
claimedAmountToStakingProvider[stakingProvider] || Zero
)

if (claimableAmount.lte(Zero)) {
continue
}

stakingRewards[stakingProvider] = claimableAmount.toString()
}

dispatch(setInterimRewards(stakingRewards))
}

fetch()
}, [stakingProviders, merkleDropContract, hasFetched, isFetching, dispatch])
}, [stakingProviders, hasFetched, isFetching, dispatch, threshold])
}
Loading