2020-09-21 21:08:43 +00:00
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"
2020-09-25 15:07:20 +00:00
"github.com/kava-labs/kava/x/harvest/types"
2020-09-21 21:08:43 +00:00
)
// 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
}
2020-09-30 18:44:56 +00:00
if lps . Start . After ( ctx . BlockTime ( ) ) {
continue
}
2020-09-21 21:08:43 +00:00
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
}
2020-09-30 18:44:56 +00:00
if dds . DistributionSchedule . End . Before ( ctx . BlockTime ( ) ) {
continue
}
2020-09-21 21:08:43 +00:00
timeElapsed := sdk . NewInt ( ctx . BlockTime ( ) . Unix ( ) - previousDistributionTime . Unix ( ) )
2020-09-30 18:44:56 +00:00
if timeElapsed . GTE ( sdk . NewInt ( int64 ( dds . DistributionFrequency . Seconds ( ) ) ) ) {
2020-09-21 21:08:43 +00:00
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
}
2020-09-30 18:44:56 +00:00
if dds . DistributionSchedule . Start . After ( ctx . BlockTime ( ) ) {
return
}
2020-09-21 21:08:43 +00:00
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 - ie delegators don't accumulate rewards when delegated to an unbonded validator
if validator . GetStatus ( ) == sdk . Unbonded {
return false
}
sharesToTokens [ validator . GetOperator ( ) . String ( ) ] = ( validator . GetDelegatorShares ( ) ) . Quo ( sdk . NewDecFromInt ( validator . GetTokens ( ) ) )
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 )
}