2021-05-04 14:47:21 +00:00
package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
2021-06-21 21:05:17 +00:00
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
2021-05-04 14:47:21 +00:00
hardtypes "github.com/kava-labs/kava/x/hard/types"
"github.com/kava-labs/kava/x/incentive/types"
)
// AccumulateHardBorrowRewards updates the rewards accumulated for the input reward period
func ( k Keeper ) AccumulateHardBorrowRewards ( ctx sdk . Context , rewardPeriod types . MultiRewardPeriod ) error {
previousAccrualTime , found := k . GetPreviousHardBorrowRewardAccrualTime ( ctx , rewardPeriod . CollateralType )
if ! found {
k . SetPreviousHardBorrowRewardAccrualTime ( 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 . SetPreviousHardBorrowRewardAccrualTime ( ctx , rewardPeriod . CollateralType , ctx . BlockTime ( ) )
return nil
}
totalBorrowedCoins , foundTotalBorrowedCoins := k . hardKeeper . GetBorrowedCoins ( ctx )
if ! foundTotalBorrowedCoins {
k . SetPreviousHardBorrowRewardAccrualTime ( ctx , rewardPeriod . CollateralType , ctx . BlockTime ( ) )
return nil
}
totalBorrowed := totalBorrowedCoins . AmountOf ( rewardPeriod . CollateralType ) . ToDec ( )
if totalBorrowed . IsZero ( ) {
k . SetPreviousHardBorrowRewardAccrualTime ( ctx , rewardPeriod . CollateralType , ctx . BlockTime ( ) )
return nil
}
previousRewardIndexes , found := k . GetHardBorrowRewardIndexes ( ctx , rewardPeriod . CollateralType )
if ! found {
for _ , rewardCoin := range rewardPeriod . RewardsPerSecond {
rewardIndex := types . NewRewardIndex ( rewardCoin . Denom , sdk . ZeroDec ( ) )
previousRewardIndexes = append ( previousRewardIndexes , rewardIndex )
}
k . SetHardBorrowRewardIndexes ( ctx , rewardPeriod . CollateralType , previousRewardIndexes )
}
hardFactor , found := k . hardKeeper . GetBorrowInterestFactor ( ctx , rewardPeriod . CollateralType )
if ! found {
k . SetPreviousHardBorrowRewardAccrualTime ( 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 ( totalBorrowed )
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 . SetHardBorrowRewardIndexes ( ctx , rewardPeriod . CollateralType , newRewardIndexes )
k . SetPreviousHardBorrowRewardAccrualTime ( ctx , rewardPeriod . CollateralType , ctx . BlockTime ( ) )
return nil
}
// InitializeHardBorrowReward initializes the borrow-side of a hard liquidity provider claim
// by creating the claim and setting the borrow reward factor index
func ( k Keeper ) InitializeHardBorrowReward ( ctx sdk . Context , borrow hardtypes . Borrow ) {
claim , found := k . GetHardLiquidityProviderClaim ( ctx , borrow . Borrower )
if ! found {
2021-07-07 16:50:14 +00:00
claim = types . NewHardLiquidityProviderClaim ( borrow . Borrower , sdk . Coins { } , nil , nil )
2021-05-04 14:47:21 +00:00
}
var borrowRewardIndexes types . MultiRewardIndexes
for _ , coin := range borrow . Amount {
2021-06-21 21:05:17 +00:00
globalRewardIndexes , found := k . GetHardBorrowRewardIndexes ( ctx , coin . Denom )
if ! found {
globalRewardIndexes = types . RewardIndexes { }
2021-05-04 14:47:21 +00:00
}
2021-06-21 21:05:17 +00:00
borrowRewardIndexes = borrowRewardIndexes . With ( coin . Denom , globalRewardIndexes )
2021-05-04 14:47:21 +00:00
}
claim . BorrowRewardIndexes = borrowRewardIndexes
k . SetHardLiquidityProviderClaim ( ctx , claim )
}
// SynchronizeHardBorrowReward updates the claim object by adding any accumulated rewards
// and updating the reward index value
func ( k Keeper ) SynchronizeHardBorrowReward ( ctx sdk . Context , borrow hardtypes . Borrow ) {
claim , found := k . GetHardLiquidityProviderClaim ( ctx , borrow . Borrower )
if ! found {
return
}
for _ , coin := range borrow . Amount {
2021-06-21 21:05:17 +00:00
globalRewardIndexes , found := k . GetHardBorrowRewardIndexes ( ctx , coin . Denom )
if ! found {
// The global factor is only not found if
// - the borrowed 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 . BorrowRewardIndexes . 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 borrowed 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 . BorrowRewardIndexes = claim . BorrowRewardIndexes . With ( coin . Denom , globalRewardIndexes )
2021-05-04 14:47:21 +00:00
}
k . SetHardLiquidityProviderClaim ( ctx , claim )
}
// UpdateHardBorrowIndexDenoms adds any new borrow denoms to the claim's borrow reward index
func ( k Keeper ) UpdateHardBorrowIndexDenoms ( ctx sdk . Context , borrow hardtypes . Borrow ) {
claim , found := k . GetHardLiquidityProviderClaim ( ctx , borrow . Borrower )
if ! found {
2021-07-07 16:50:14 +00:00
claim = types . NewHardLiquidityProviderClaim ( borrow . Borrower , sdk . Coins { } , nil , nil )
2021-05-04 14:47:21 +00:00
}
borrowDenoms := getDenoms ( borrow . Amount )
borrowRewardIndexDenoms := claim . BorrowRewardIndexes . GetCollateralTypes ( )
borrowRewardIndexes := claim . BorrowRewardIndexes
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 borrow denom
2021-06-21 21:05:17 +00:00
uniqueBorrowDenoms := setDifference ( borrowDenoms , borrowRewardIndexDenoms )
2021-05-04 14:47:21 +00:00
for _ , denom := range uniqueBorrowDenoms {
2021-06-21 21:05:17 +00:00
globalBorrowRewardIndexes , found := k . GetHardBorrowRewardIndexes ( ctx , denom )
if ! found {
globalBorrowRewardIndexes = types . RewardIndexes { }
2021-05-04 14:47:21 +00:00
}
2021-06-21 21:05:17 +00:00
borrowRewardIndexes = borrowRewardIndexes . With ( denom , globalBorrowRewardIndexes )
2021-05-04 14:47:21 +00:00
}
// Delete multi-reward index from claim if the collateral type is no longer borrowed
2021-06-21 21:05:17 +00:00
uniqueBorrowRewardDenoms := setDifference ( borrowRewardIndexDenoms , borrowDenoms )
2021-05-04 14:47:21 +00:00
for _ , denom := range uniqueBorrowRewardDenoms {
borrowRewardIndexes = borrowRewardIndexes . RemoveRewardIndex ( denom )
}
claim . BorrowRewardIndexes = borrowRewardIndexes
k . SetHardLiquidityProviderClaim ( ctx , claim )
}
2021-06-21 21:05:17 +00:00
// CalculateRewards computes how much rewards should have accrued to a source (eg a user's hard borrowed btc amount)
// between two index values.
//
// oldIndex is normally the index stored on a claim, newIndex the current global value, and rewardSource a hard borrowed/supplied amount.
//
// Returns an error if newIndexes does not contain all CollateralTypes from oldIndexes, or if any value of oldIndex.RewardFactor > newIndex.RewardFactor.
// This should never happen, as it would mean that a global reward index has decreased in value, or that a global reward index has been deleted from state.
func ( k Keeper ) CalculateRewards ( oldIndexes , newIndexes types . RewardIndexes , rewardSource sdk . Dec ) ( sdk . Coins , error ) {
// check for missing CollateralType's
for _ , oldIndex := range oldIndexes {
if newIndex , found := newIndexes . Get ( oldIndex . CollateralType ) ; ! found {
return nil , sdkerrors . Wrapf ( types . ErrDecreasingRewardFactor , "old: %v, new: %v" , oldIndex , newIndex )
}
}
var reward sdk . Coins
for _ , newIndex := range newIndexes {
oldFactor , found := oldIndexes . Get ( newIndex . CollateralType )
if ! found {
oldFactor = sdk . ZeroDec ( )
}
rewardAmount , err := k . CalculateSingleReward ( oldFactor , newIndex . RewardFactor , rewardSource )
if err != nil {
return nil , err
}
reward = reward . Add (
sdk . NewCoin ( newIndex . CollateralType , rewardAmount ) ,
)
}
return reward , nil
}
// CalculateSingleReward computes how much rewards should have accrued to a source (eg a user's btcb-a cdp principal)
// between two index values.
//
// oldIndex is normally the index stored on a claim, newIndex the current global value, and rewardSource a cdp principal amount.
//
// Returns an error if oldIndex > newIndex. This should never happen, as it would mean that a global reward index has decreased in value,
// or that a global reward index has been deleted from state.
func ( k Keeper ) CalculateSingleReward ( oldIndex , newIndex , rewardSource sdk . Dec ) ( sdk . Int , error ) {
increase := newIndex . Sub ( oldIndex )
if increase . IsNegative ( ) {
return sdk . Int { } , sdkerrors . Wrapf ( types . ErrDecreasingRewardFactor , "old: %v, new: %v" , oldIndex , newIndex )
}
reward := increase . Mul ( rewardSource ) . RoundInt ( )
return reward , nil
}