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"
)
2021-07-22 12:53:18 +00:00
// AccumulateHardBorrowRewards calculates new rewards to distribute this block and updates the global indexes to reflect this.
// The provided rewardPeriod must be valid to avoid panics in calculating time durations.
func ( k Keeper ) AccumulateHardBorrowRewards ( ctx sdk . Context , rewardPeriod types . MultiRewardPeriod ) {
2021-05-04 14:47:21 +00:00
previousAccrualTime , found := k . GetPreviousHardBorrowRewardAccrualTime ( ctx , rewardPeriod . CollateralType )
if ! found {
2021-07-22 12:53:18 +00:00
previousAccrualTime = ctx . BlockTime ( )
2021-05-04 14:47:21 +00:00
}
2021-07-22 12:53:18 +00:00
indexes , found := k . GetHardBorrowRewardIndexes ( ctx , rewardPeriod . CollateralType )
if ! found {
indexes = types . RewardIndexes { }
2021-05-04 14:47:21 +00:00
}
2021-07-22 12:53:18 +00:00
acc := types . NewAccumulator ( previousAccrualTime , indexes )
totalSource := k . getHardBorrowTotalSourceShares ( ctx , rewardPeriod . CollateralType )
acc . Accumulate ( rewardPeriod , totalSource , ctx . BlockTime ( ) )
k . SetPreviousHardBorrowRewardAccrualTime ( ctx , rewardPeriod . CollateralType , acc . PreviousAccumulationTime )
if len ( acc . Indexes ) > 0 {
// the store panics when setting empty or nil indexes
k . SetHardBorrowRewardIndexes ( ctx , rewardPeriod . CollateralType , acc . Indexes )
2021-05-04 14:47:21 +00:00
}
2021-07-22 12:53:18 +00:00
}
2021-05-04 14:47:21 +00:00
2021-07-22 12:53:18 +00:00
// getHardBorrowTotalSourceShares fetches the sum of all source shares for a borrow reward.
// In the case of hard borrow, this is the total borrowed divided by the borrow interest factor.
// This give the "pre interest" value of the total borrowed.
func ( k Keeper ) getHardBorrowTotalSourceShares ( ctx sdk . Context , denom string ) sdk . Dec {
totalBorrowedCoins , found := k . hardKeeper . GetBorrowedCoins ( ctx )
2021-05-04 14:47:21 +00:00
if ! found {
2021-07-22 12:53:18 +00:00
// assume no coins have been borrowed
totalBorrowedCoins = sdk . NewCoins ( )
2021-05-04 14:47:21 +00:00
}
2021-07-22 12:53:18 +00:00
totalBorrowed := totalBorrowedCoins . AmountOf ( denom )
interestFactor , found := k . hardKeeper . GetBorrowInterestFactor ( ctx , denom )
2021-05-04 14:47:21 +00:00
if ! found {
2021-07-22 12:53:18 +00:00
// assume nothing has been borrowed so the factor starts at it's default value
interestFactor = sdk . OneDec ( )
2021-05-04 14:47:21 +00:00
}
2021-07-22 12:53:18 +00:00
// return borrowed/factor to get the "pre interest" value of the current total borrowed
return totalBorrowed . ToDec ( ) . Quo ( interestFactor )
2021-05-04 14:47:21 +00:00
}
// 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
2021-07-22 12:53:18 +00:00
// CalculateRewards computes how much rewards should have accrued to a reward source (eg a user's hard borrowed btc amount)
2021-06-21 21:05:17 +00:00
// between two index values.
//
2021-07-22 12:53:18 +00:00
// oldIndex is normally the index stored on a claim, newIndex the current global value, and sourceShares a hard borrowed/supplied amount.
2021-06-21 21:05:17 +00:00
//
2021-07-22 12:53:18 +00:00
// It returns an error if newIndexes does not contain all CollateralTypes from oldIndexes, or if any value of oldIndex.RewardFactor > newIndex.RewardFactor.
2021-06-21 21:05:17 +00:00
// 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.
2021-07-22 12:53:18 +00:00
func ( k Keeper ) CalculateRewards ( oldIndexes , newIndexes types . RewardIndexes , sourceShares sdk . Dec ) ( sdk . Coins , error ) {
2021-06-21 21:05:17 +00:00
// 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 ( )
}
2021-07-22 12:53:18 +00:00
rewardAmount , err := k . CalculateSingleReward ( oldFactor , newIndex . RewardFactor , sourceShares )
2021-06-21 21:05:17 +00:00
if err != nil {
return nil , err
}
reward = reward . Add (
sdk . NewCoin ( newIndex . CollateralType , rewardAmount ) ,
)
}
return reward , nil
}
2021-07-22 12:53:18 +00:00
// CalculateSingleReward computes how much rewards should have accrued to a reward source (eg a user's btcb-a cdp principal)
2021-06-21 21:05:17 +00:00
// between two index values.
//
2021-07-22 12:53:18 +00:00
// oldIndex is normally the index stored on a claim, newIndex the current global value, and sourceShares a cdp principal amount.
2021-06-21 21:05:17 +00:00
//
// 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.
2021-07-22 12:53:18 +00:00
func ( k Keeper ) CalculateSingleReward ( oldIndex , newIndex , sourceShares sdk . Dec ) ( sdk . Int , error ) {
2021-06-21 21:05:17 +00:00
increase := newIndex . Sub ( oldIndex )
if increase . IsNegative ( ) {
return sdk . Int { } , sdkerrors . Wrapf ( types . ErrDecreasingRewardFactor , "old: %v, new: %v" , oldIndex , newIndex )
}
2021-07-22 12:53:18 +00:00
reward := increase . Mul ( sourceShares ) . RoundInt ( )
2021-06-21 21:05:17 +00:00
return reward , nil
}