2021-05-04 14:47:21 +00:00
package keeper
import (
"fmt"
"math"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
hardtypes "github.com/kava-labs/kava/x/hard/types"
"github.com/kava-labs/kava/x/incentive/types"
)
// AccumulateHardSupplyRewards updates the rewards accumulated for the input reward period
func ( k Keeper ) AccumulateHardSupplyRewards ( ctx sdk . Context , rewardPeriod types . MultiRewardPeriod ) error {
previousAccrualTime , found := k . GetPreviousHardSupplyRewardAccrualTime ( ctx , rewardPeriod . CollateralType )
if ! found {
k . SetPreviousHardSupplyRewardAccrualTime ( ctx , rewardPeriod . CollateralType , ctx . BlockTime ( ) )
return nil
}
timeElapsed := CalculateTimeElapsed ( rewardPeriod . Start , rewardPeriod . End , ctx . BlockTime ( ) , previousAccrualTime )
if timeElapsed . IsZero ( ) {
return nil
}
if rewardPeriod . RewardsPerSecond . IsZero ( ) {
k . SetPreviousHardSupplyRewardAccrualTime ( ctx , rewardPeriod . CollateralType , ctx . BlockTime ( ) )
return nil
}
totalSuppliedCoins , foundTotalSuppliedCoins := k . hardKeeper . GetSuppliedCoins ( ctx )
if ! foundTotalSuppliedCoins {
k . SetPreviousHardSupplyRewardAccrualTime ( ctx , rewardPeriod . CollateralType , ctx . BlockTime ( ) )
return nil
}
totalSupplied := totalSuppliedCoins . AmountOf ( rewardPeriod . CollateralType ) . ToDec ( )
if totalSupplied . IsZero ( ) {
k . SetPreviousHardSupplyRewardAccrualTime ( ctx , rewardPeriod . CollateralType , ctx . BlockTime ( ) )
return nil
}
previousRewardIndexes , found := k . GetHardSupplyRewardIndexes ( ctx , rewardPeriod . CollateralType )
if ! found {
for _ , rewardCoin := range rewardPeriod . RewardsPerSecond {
rewardIndex := types . NewRewardIndex ( rewardCoin . Denom , sdk . ZeroDec ( ) )
previousRewardIndexes = append ( previousRewardIndexes , rewardIndex )
}
k . SetHardSupplyRewardIndexes ( ctx , rewardPeriod . CollateralType , previousRewardIndexes )
}
hardFactor , found := k . hardKeeper . GetSupplyInterestFactor ( ctx , rewardPeriod . CollateralType )
if ! found {
k . SetPreviousHardSupplyRewardAccrualTime ( ctx , rewardPeriod . CollateralType , ctx . BlockTime ( ) )
return nil
}
newRewardIndexes := previousRewardIndexes
for _ , rewardCoin := range rewardPeriod . RewardsPerSecond {
newRewards := rewardCoin . Amount . ToDec ( ) . Mul ( timeElapsed . ToDec ( ) )
previousRewardIndex , found := previousRewardIndexes . GetRewardIndex ( rewardCoin . Denom )
if ! found {
previousRewardIndex = types . NewRewardIndex ( rewardCoin . Denom , sdk . ZeroDec ( ) )
}
// Calculate new reward factor and update reward index
rewardFactor := newRewards . Mul ( hardFactor ) . Quo ( totalSupplied )
newRewardFactorValue := previousRewardIndex . RewardFactor . Add ( rewardFactor )
newRewardIndex := types . NewRewardIndex ( rewardCoin . Denom , newRewardFactorValue )
i , found := newRewardIndexes . GetFactorIndex ( rewardCoin . Denom )
if found {
newRewardIndexes [ i ] = newRewardIndex
} else {
newRewardIndexes = append ( newRewardIndexes , newRewardIndex )
}
}
k . SetHardSupplyRewardIndexes ( ctx , rewardPeriod . CollateralType , newRewardIndexes )
k . SetPreviousHardSupplyRewardAccrualTime ( ctx , rewardPeriod . CollateralType , ctx . BlockTime ( ) )
return nil
}
// InitializeHardSupplyReward initializes the supply-side of a hard liquidity provider claim
// by creating the claim and setting the supply reward factor index
func ( k Keeper ) InitializeHardSupplyReward ( ctx sdk . Context , deposit hardtypes . Deposit ) {
claim , found := k . GetHardLiquidityProviderClaim ( ctx , deposit . Depositor )
if ! found {
2021-07-07 16:50:14 +00:00
claim = types . NewHardLiquidityProviderClaim ( deposit . Depositor , sdk . Coins { } , nil , nil )
2021-05-04 14:47:21 +00:00
}
2021-06-21 21:05:17 +00:00
var supplyRewardIndexes types . MultiRewardIndexes
for _ , coin := range deposit . Amount {
globalRewardIndexes , found := k . GetHardSupplyRewardIndexes ( ctx , coin . Denom )
if ! found {
globalRewardIndexes = types . RewardIndexes { }
}
supplyRewardIndexes = supplyRewardIndexes . With ( coin . Denom , globalRewardIndexes )
}
2021-05-04 14:47:21 +00:00
claim . SupplyRewardIndexes = supplyRewardIndexes
k . SetHardLiquidityProviderClaim ( ctx , claim )
}
// SynchronizeHardSupplyReward updates the claim object by adding any accumulated rewards
// and updating the reward index value
func ( k Keeper ) SynchronizeHardSupplyReward ( ctx sdk . Context , deposit hardtypes . Deposit ) {
claim , found := k . GetHardLiquidityProviderClaim ( ctx , deposit . Depositor )
if ! found {
return
}
for _ , coin := range deposit . Amount {
2021-06-21 21:05:17 +00:00
globalRewardIndexes , found := k . GetHardSupplyRewardIndexes ( ctx , coin . Denom )
if ! found {
// The global factor is only not found if
// - the supply denom has not started accumulating rewards yet (either there is no reward specified in params, or the reward start time hasn't been hit)
// - OR it was wrongly deleted from state (factors should never be removed while unsynced claims exist)
// If not found we could either skip this sync, or assume the global factor is zero.
// Skipping will avoid storing unnecessary factors in the claim for non rewarded denoms.
// And in the event a global factor is wrongly deleted, it will avoid this function panicking when calculating rewards.
2021-05-04 14:47:21 +00:00
continue
}
2021-06-21 21:05:17 +00:00
userRewardIndexes , found := claim . SupplyRewardIndexes . Get ( coin . Denom )
if ! found {
// Normally the reward indexes should always be found.
// But if a denom was not rewarded then becomes rewarded (ie a reward period is added to params), then the indexes will be missing from claims for that supplied denom.
// So given the reward period was just added, assume the starting value for any global reward indexes, which is an empty slice.
userRewardIndexes = types . RewardIndexes { }
2021-05-04 14:47:21 +00:00
}
2021-06-21 21:05:17 +00:00
newRewards , err := k . CalculateRewards ( userRewardIndexes , globalRewardIndexes , coin . Amount . ToDec ( ) )
if err != nil {
// Global reward factors should never decrease, as it would lead to a negative update to claim.Rewards.
// This panics if a global reward factor decreases or disappears between the old and new indexes.
panic ( fmt . Sprintf ( "corrupted global reward indexes found: %v" , err ) )
2021-05-04 14:47:21 +00:00
}
2021-06-21 21:05:17 +00:00
claim . Reward = claim . Reward . Add ( newRewards ... )
claim . SupplyRewardIndexes = claim . SupplyRewardIndexes . With ( coin . Denom , globalRewardIndexes )
2021-05-04 14:47:21 +00:00
}
k . SetHardLiquidityProviderClaim ( ctx , claim )
}
// UpdateHardSupplyIndexDenoms adds any new deposit denoms to the claim's supply reward index
func ( k Keeper ) UpdateHardSupplyIndexDenoms ( ctx sdk . Context , deposit hardtypes . Deposit ) {
claim , found := k . GetHardLiquidityProviderClaim ( ctx , deposit . Depositor )
if ! found {
2021-07-07 16:50:14 +00:00
claim = types . NewHardLiquidityProviderClaim ( deposit . Depositor , sdk . Coins { } , nil , nil )
2021-05-04 14:47:21 +00:00
}
depositDenoms := getDenoms ( deposit . Amount )
supplyRewardIndexDenoms := claim . SupplyRewardIndexes . GetCollateralTypes ( )
supplyRewardIndexes := claim . SupplyRewardIndexes
2021-06-21 21:05:17 +00:00
2021-05-04 14:47:21 +00:00
// Create a new multi-reward index in the claim for every new deposit denom
2021-06-21 21:05:17 +00:00
uniqueDepositDenoms := setDifference ( depositDenoms , supplyRewardIndexDenoms )
2021-05-04 14:47:21 +00:00
for _ , denom := range uniqueDepositDenoms {
2021-06-21 21:05:17 +00:00
globalSupplyRewardIndexes , found := k . GetHardSupplyRewardIndexes ( ctx , denom )
if ! found {
globalSupplyRewardIndexes = types . RewardIndexes { }
2021-05-04 14:47:21 +00:00
}
2021-06-21 21:05:17 +00:00
supplyRewardIndexes = supplyRewardIndexes . With ( denom , globalSupplyRewardIndexes )
2021-05-04 14:47:21 +00:00
}
// Delete multi-reward index from claim if the collateral type is no longer deposited
2021-06-21 21:05:17 +00:00
uniqueSupplyRewardDenoms := setDifference ( supplyRewardIndexDenoms , depositDenoms )
2021-05-04 14:47:21 +00:00
for _ , denom := range uniqueSupplyRewardDenoms {
supplyRewardIndexes = supplyRewardIndexes . RemoveRewardIndex ( denom )
}
claim . SupplyRewardIndexes = supplyRewardIndexes
k . SetHardLiquidityProviderClaim ( ctx , claim )
}
// SynchronizeHardLiquidityProviderClaim adds any accumulated rewards
func ( k Keeper ) SynchronizeHardLiquidityProviderClaim ( ctx sdk . Context , owner sdk . AccAddress ) {
// Synchronize any hard liquidity supply-side rewards
deposit , foundDeposit := k . hardKeeper . GetDeposit ( ctx , owner )
if foundDeposit {
k . SynchronizeHardSupplyReward ( ctx , deposit )
}
// Synchronize any hard liquidity borrow-side rewards
borrow , foundBorrow := k . hardKeeper . GetBorrow ( ctx , owner )
if foundBorrow {
k . SynchronizeHardBorrowReward ( ctx , borrow )
}
}
// ZeroHardLiquidityProviderClaim zeroes out the claim object's rewards and returns the updated claim object
func ( k Keeper ) ZeroHardLiquidityProviderClaim ( ctx sdk . Context , claim types . HardLiquidityProviderClaim ) types . HardLiquidityProviderClaim {
claim . Reward = sdk . NewCoins ( )
k . SetHardLiquidityProviderClaim ( ctx , claim )
return claim
}
// SimulateHardSynchronization calculates a user's outstanding hard rewards by simulating reward synchronization
func ( k Keeper ) SimulateHardSynchronization ( ctx sdk . Context , claim types . HardLiquidityProviderClaim ) types . HardLiquidityProviderClaim {
// 1. Simulate Hard supply-side rewards
for _ , ri := range claim . SupplyRewardIndexes {
globalRewardIndexes , foundGlobalRewardIndexes := k . GetHardSupplyRewardIndexes ( ctx , ri . CollateralType )
if ! foundGlobalRewardIndexes {
continue
}
userRewardIndexes , foundUserRewardIndexes := claim . SupplyRewardIndexes . GetRewardIndex ( ri . CollateralType )
if ! foundUserRewardIndexes {
continue
}
userRewardIndexIndex , foundUserRewardIndexIndex := claim . SupplyRewardIndexes . GetRewardIndexIndex ( ri . CollateralType )
if ! foundUserRewardIndexIndex {
continue
}
for _ , globalRewardIndex := range globalRewardIndexes {
userRewardIndex , foundUserRewardIndex := userRewardIndexes . RewardIndexes . GetRewardIndex ( globalRewardIndex . CollateralType )
if ! foundUserRewardIndex {
userRewardIndex = types . NewRewardIndex ( globalRewardIndex . CollateralType , sdk . ZeroDec ( ) )
userRewardIndexes . RewardIndexes = append ( userRewardIndexes . RewardIndexes , userRewardIndex )
claim . SupplyRewardIndexes [ userRewardIndexIndex ] . RewardIndexes = append ( claim . SupplyRewardIndexes [ userRewardIndexIndex ] . RewardIndexes , userRewardIndex )
}
globalRewardFactor := globalRewardIndex . RewardFactor
userRewardFactor := userRewardIndex . RewardFactor
rewardsAccumulatedFactor := globalRewardFactor . Sub ( userRewardFactor )
if rewardsAccumulatedFactor . IsZero ( ) {
continue
}
deposit , found := k . hardKeeper . GetDeposit ( ctx , claim . GetOwner ( ) )
if ! found {
continue
}
newRewardsAmount := rewardsAccumulatedFactor . Mul ( deposit . Amount . AmountOf ( ri . CollateralType ) . ToDec ( ) ) . RoundInt ( )
if newRewardsAmount . IsZero ( ) || newRewardsAmount . IsNegative ( ) {
continue
}
factorIndex , foundFactorIndex := userRewardIndexes . RewardIndexes . GetFactorIndex ( globalRewardIndex . CollateralType )
if ! foundFactorIndex {
continue
}
claim . SupplyRewardIndexes [ userRewardIndexIndex ] . RewardIndexes [ factorIndex ] . RewardFactor = globalRewardIndex . RewardFactor
newRewardsCoin := sdk . NewCoin ( userRewardIndex . CollateralType , newRewardsAmount )
claim . Reward = claim . Reward . Add ( newRewardsCoin )
}
}
// 2. Simulate Hard borrow-side rewards
for _ , ri := range claim . BorrowRewardIndexes {
globalRewardIndexes , foundGlobalRewardIndexes := k . GetHardBorrowRewardIndexes ( ctx , ri . CollateralType )
if ! foundGlobalRewardIndexes {
continue
}
userRewardIndexes , foundUserRewardIndexes := claim . BorrowRewardIndexes . GetRewardIndex ( ri . CollateralType )
if ! foundUserRewardIndexes {
continue
}
userRewardIndexIndex , foundUserRewardIndexIndex := claim . BorrowRewardIndexes . GetRewardIndexIndex ( ri . CollateralType )
if ! foundUserRewardIndexIndex {
continue
}
for _ , globalRewardIndex := range globalRewardIndexes {
userRewardIndex , foundUserRewardIndex := userRewardIndexes . RewardIndexes . GetRewardIndex ( globalRewardIndex . CollateralType )
if ! foundUserRewardIndex {
userRewardIndex = types . NewRewardIndex ( globalRewardIndex . CollateralType , sdk . ZeroDec ( ) )
userRewardIndexes . RewardIndexes = append ( userRewardIndexes . RewardIndexes , userRewardIndex )
claim . BorrowRewardIndexes [ userRewardIndexIndex ] . RewardIndexes = append ( claim . BorrowRewardIndexes [ userRewardIndexIndex ] . RewardIndexes , userRewardIndex )
}
globalRewardFactor := globalRewardIndex . RewardFactor
userRewardFactor := userRewardIndex . RewardFactor
rewardsAccumulatedFactor := globalRewardFactor . Sub ( userRewardFactor )
if rewardsAccumulatedFactor . IsZero ( ) {
continue
}
borrow , found := k . hardKeeper . GetBorrow ( ctx , claim . GetOwner ( ) )
if ! found {
continue
}
newRewardsAmount := rewardsAccumulatedFactor . Mul ( borrow . Amount . AmountOf ( ri . CollateralType ) . ToDec ( ) ) . RoundInt ( )
if newRewardsAmount . IsZero ( ) || newRewardsAmount . IsNegative ( ) {
continue
}
factorIndex , foundFactorIndex := userRewardIndexes . RewardIndexes . GetFactorIndex ( globalRewardIndex . CollateralType )
if ! foundFactorIndex {
continue
}
claim . BorrowRewardIndexes [ userRewardIndexIndex ] . RewardIndexes [ factorIndex ] . RewardFactor = globalRewardIndex . RewardFactor
newRewardsCoin := sdk . NewCoin ( userRewardIndex . CollateralType , newRewardsAmount )
claim . Reward = claim . Reward . Add ( newRewardsCoin )
}
}
return claim
}
// CalculateTimeElapsed calculates the number of reward-eligible seconds that have passed since the previous
// time rewards were accrued, taking into account the end time of the reward period
func CalculateTimeElapsed ( start , end , blockTime time . Time , previousAccrualTime time . Time ) sdk . Int {
if ( end . Before ( blockTime ) &&
( end . Before ( previousAccrualTime ) || end . Equal ( previousAccrualTime ) ) ) ||
( start . After ( blockTime ) ) ||
( start . Equal ( blockTime ) ) {
return sdk . ZeroInt ( )
}
if start . After ( previousAccrualTime ) && start . Before ( blockTime ) {
previousAccrualTime = start
}
if end . Before ( blockTime ) {
return sdk . MaxInt ( sdk . ZeroInt ( ) , sdk . NewInt ( int64 ( math . RoundToEven (
end . Sub ( previousAccrualTime ) . Seconds ( ) ,
) ) ) )
}
return sdk . MaxInt ( sdk . ZeroInt ( ) , sdk . NewInt ( int64 ( math . RoundToEven (
blockTime . Sub ( previousAccrualTime ) . Seconds ( ) ,
) ) ) )
}
// Set setDifference: A - B
func setDifference ( a , b [ ] string ) ( diff [ ] string ) {
m := make ( map [ string ] bool )
for _ , item := range b {
m [ item ] = true
}
for _ , item := range a {
if _ , ok := m [ item ] ; ! ok {
diff = append ( diff , item )
}
}
return
}
func getDenoms ( coins sdk . Coins ) [ ] string {
denoms := [ ] string { }
for _ , coin := range coins {
denoms = append ( denoms , coin . Denom )
}
return denoms
}