mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-27 00:35:18 +00:00
69512d508b
* fix: check for any status other than bonded * Add harvest staking tests (#680) * refactor config initialization * refactor delegator rewards test, add tests * fix share to token calculation, add unbonding test * remove dead link Co-authored-by: Ruaridh <rhuairahrighairidh@users.noreply.github.com>
176 lines
6.3 KiB
Go
176 lines
6.3 KiB
Go
package keeper
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
stakingexported "github.com/cosmos/cosmos-sdk/x/staking/exported"
|
|
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
|
|
|
"github.com/kava-labs/kava/x/harvest/types"
|
|
)
|
|
|
|
// ApplyDepositRewards iterates over lp and gov deposits and updates the amount of rewards for each depositor
|
|
func (k Keeper) ApplyDepositRewards(ctx sdk.Context) {
|
|
previousBlockTime, found := k.GetPreviousBlockTime(ctx)
|
|
if !found {
|
|
previousBlockTime = ctx.BlockTime()
|
|
k.SetPreviousBlockTime(ctx, previousBlockTime)
|
|
return
|
|
}
|
|
params := k.GetParams(ctx)
|
|
if !params.Active {
|
|
return
|
|
}
|
|
timeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousBlockTime.Unix())
|
|
|
|
for _, lps := range params.LiquidityProviderSchedules {
|
|
if !lps.Active {
|
|
continue
|
|
}
|
|
if lps.End.Before(ctx.BlockTime()) {
|
|
continue
|
|
}
|
|
if lps.Start.After(ctx.BlockTime()) {
|
|
continue
|
|
}
|
|
totalDeposited := k.GetTotalDeposited(ctx, types.LP, lps.DepositDenom)
|
|
if totalDeposited.IsZero() {
|
|
continue
|
|
}
|
|
rewardsToDistribute := lps.RewardsPerSecond.Amount.Mul(timeElapsed)
|
|
if rewardsToDistribute.IsZero() {
|
|
continue
|
|
}
|
|
rewardsDistributed := sdk.ZeroInt()
|
|
k.IterateDepositsByTypeAndDenom(ctx, types.LP, lps.DepositDenom, func(dep types.Deposit) (stop bool) {
|
|
rewardsShare := sdk.NewDecFromInt(dep.Amount.Amount).Quo(sdk.NewDecFromInt(totalDeposited))
|
|
if rewardsShare.IsZero() {
|
|
return false
|
|
}
|
|
rewardsEarned := rewardsShare.Mul(sdk.NewDecFromInt(rewardsToDistribute)).RoundInt()
|
|
if rewardsEarned.IsZero() {
|
|
return false
|
|
}
|
|
k.AddToClaim(ctx, dep.Depositor, dep.Amount.Denom, dep.Type, sdk.NewCoin(lps.RewardsPerSecond.Denom, rewardsEarned))
|
|
rewardsDistributed = rewardsDistributed.Add(rewardsEarned)
|
|
return false
|
|
})
|
|
ctx.EventManager().EmitEvent(
|
|
sdk.NewEvent(
|
|
types.EventTypeHarvestLPDistribution,
|
|
sdk.NewAttribute(types.AttributeKeyBlockHeight, fmt.Sprintf("%d", ctx.BlockHeight())),
|
|
sdk.NewAttribute(types.AttributeKeyRewardsDistribution, rewardsDistributed.String()),
|
|
sdk.NewAttribute(types.AttributeKeyDepositDenom, lps.DepositDenom),
|
|
),
|
|
)
|
|
}
|
|
k.SetPreviousBlockTime(ctx, ctx.BlockTime())
|
|
}
|
|
|
|
// ShouldDistributeValidatorRewards returns true if enough time has elapsed such that rewards should be distributed to delegators
|
|
func (k Keeper) ShouldDistributeValidatorRewards(ctx sdk.Context, denom string) bool {
|
|
previousDistributionTime, found := k.GetPreviousDelegatorDistribution(ctx, denom)
|
|
if !found {
|
|
k.SetPreviousDelegationDistribution(ctx, ctx.BlockTime(), denom)
|
|
return false
|
|
}
|
|
params := k.GetParams(ctx)
|
|
if !params.Active {
|
|
return false
|
|
}
|
|
for _, dds := range params.DelegatorDistributionSchedules {
|
|
if denom != dds.DistributionSchedule.DepositDenom {
|
|
continue
|
|
}
|
|
if dds.DistributionSchedule.End.Before(ctx.BlockTime()) {
|
|
continue
|
|
}
|
|
timeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousDistributionTime.Unix())
|
|
if timeElapsed.GTE(sdk.NewInt(int64(dds.DistributionFrequency.Seconds()))) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ApplyDelegationRewards iterates over each delegation object in the staking store and applies rewards according to the input delegation distribution schedule
|
|
func (k Keeper) ApplyDelegationRewards(ctx sdk.Context, denom string) {
|
|
dds, found := k.GetDelegatorSchedule(ctx, denom)
|
|
if !found {
|
|
return
|
|
}
|
|
if !dds.DistributionSchedule.Active {
|
|
return
|
|
}
|
|
if dds.DistributionSchedule.Start.After(ctx.BlockTime()) {
|
|
return
|
|
}
|
|
bondMacc := k.stakingKeeper.GetBondedPool(ctx)
|
|
bondedCoinAmount := bondMacc.GetCoins().AmountOf(dds.DistributionSchedule.DepositDenom)
|
|
if bondedCoinAmount.IsZero() {
|
|
return
|
|
}
|
|
previousDistributionTime, found := k.GetPreviousDelegatorDistribution(ctx, dds.DistributionSchedule.DepositDenom)
|
|
if !found {
|
|
return
|
|
}
|
|
timeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousDistributionTime.Unix())
|
|
rewardsToDistribute := dds.DistributionSchedule.RewardsPerSecond.Amount.Mul(timeElapsed)
|
|
|
|
// create a map that has each validator address (sdk.ValAddress) as a key and the coversion factor for going from delegator shares to tokens for delegations to that validator.
|
|
// If a validator has never been slashed, the conversion factor will be 1.0, if they have been, it will be < 1.0
|
|
sharesToTokens := make(map[string]sdk.Dec)
|
|
k.stakingKeeper.IterateValidators(ctx, func(index int64, validator stakingexported.ValidatorI) (stop bool) {
|
|
if validator.GetTokens().IsZero() {
|
|
return false
|
|
}
|
|
// don't include a validator if it's unbonded or unbonding- ie delegators don't accumulate rewards when delegated to an unbonded/slashed validator
|
|
if validator.GetStatus() != sdk.Bonded {
|
|
return false
|
|
}
|
|
sharesToTokens[validator.GetOperator().String()] = sdk.NewDecFromInt(validator.GetTokens()).Quo(validator.GetDelegatorShares())
|
|
return false
|
|
})
|
|
|
|
rewardsDistributed := sdk.ZeroInt()
|
|
|
|
k.stakingKeeper.IterateAllDelegations(ctx, func(delegation stakingtypes.Delegation) (stop bool) {
|
|
conversionFactor, ok := sharesToTokens[delegation.ValidatorAddress.String()]
|
|
if ok {
|
|
delegationTokens := conversionFactor.Mul(delegation.Shares)
|
|
delegationShare := delegationTokens.Quo(sdk.NewDecFromInt(bondedCoinAmount))
|
|
rewardsEarned := delegationShare.Mul(sdk.NewDecFromInt(rewardsToDistribute)).RoundInt()
|
|
if rewardsEarned.IsZero() {
|
|
return false
|
|
}
|
|
k.AddToClaim(
|
|
ctx, delegation.DelegatorAddress, dds.DistributionSchedule.DepositDenom,
|
|
types.Stake, sdk.NewCoin(dds.DistributionSchedule.RewardsPerSecond.Denom, rewardsEarned))
|
|
rewardsDistributed = rewardsDistributed.Add(rewardsEarned)
|
|
}
|
|
return false
|
|
})
|
|
|
|
ctx.EventManager().EmitEvent(
|
|
sdk.NewEvent(
|
|
types.EventTypeHarvestDelegatorDistribution,
|
|
sdk.NewAttribute(types.AttributeKeyBlockHeight, fmt.Sprintf("%d", ctx.BlockHeight())),
|
|
sdk.NewAttribute(types.AttributeKeyRewardsDistribution, rewardsDistributed.String()),
|
|
sdk.NewAttribute(types.AttributeKeyDepositDenom, denom),
|
|
),
|
|
)
|
|
|
|
}
|
|
|
|
// AddToClaim adds the input amount to an existing claim or creates a new one
|
|
func (k Keeper) AddToClaim(ctx sdk.Context, owner sdk.AccAddress, depositDenom string, depositType types.DepositType, amountToAdd sdk.Coin) {
|
|
claim, found := k.GetClaim(ctx, owner, depositDenom, depositType)
|
|
if !found {
|
|
claim = types.NewClaim(owner, depositDenom, amountToAdd, depositType)
|
|
} else {
|
|
claim.Amount = claim.Amount.Add(amountToAdd)
|
|
}
|
|
k.SetClaim(ctx, claim)
|
|
}
|