2021-05-04 14:47:21 +00:00
package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
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
// AccumulateHardSupplyRewards 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 ) AccumulateHardSupplyRewards ( ctx sdk . Context , rewardPeriod types . MultiRewardPeriod ) {
2021-05-04 14:47:21 +00:00
previousAccrualTime , found := k . GetPreviousHardSupplyRewardAccrualTime ( 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 . GetHardSupplyRewardIndexes ( 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 . getHardSupplyTotalSourceShares ( ctx , rewardPeriod . CollateralType )
acc . Accumulate ( rewardPeriod , totalSource , ctx . BlockTime ( ) )
k . SetPreviousHardSupplyRewardAccrualTime ( ctx , rewardPeriod . CollateralType , acc . PreviousAccumulationTime )
if len ( acc . Indexes ) > 0 {
// the store panics when setting empty or nil indexes
k . SetHardSupplyRewardIndexes ( 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
// getHardSupplyTotalSourceShares fetches the sum of all source shares for a supply reward.
// In the case of hard supply, this is the total supplied divided by the supply interest factor.
2021-07-26 19:07:24 +00:00
// This gives the "pre interest" value of the total supplied.
2021-07-22 12:53:18 +00:00
func ( k Keeper ) getHardSupplyTotalSourceShares ( ctx sdk . Context , denom string ) sdk . Dec {
totalSuppliedCoins , found := k . hardKeeper . GetSuppliedCoins ( ctx )
2021-05-04 14:47:21 +00:00
if ! found {
2021-07-22 12:53:18 +00:00
// assume no coins have been supplied
totalSuppliedCoins = sdk . NewCoins ( )
2021-05-04 14:47:21 +00:00
}
2021-07-22 12:53:18 +00:00
totalSupplied := totalSuppliedCoins . AmountOf ( denom )
interestFactor , found := k . hardKeeper . GetSupplyInterestFactor ( 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 supplied/factor to get the "pre interest" value of the current total supplied
return totalSupplied . ToDec ( ) . Quo ( interestFactor )
2021-05-04 14:47:21 +00:00
}
// 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
}
2021-07-26 19:07:24 +00:00
// Source shares for hard deposits is their normalized deposit amount
normalizedDeposit , err := deposit . NormalizedDeposit ( )
if err != nil {
panic ( fmt . Sprintf ( "during deposit reward sync, could not get normalized deposit for %s: %s" , deposit . Depositor , err . Error ( ) ) )
}
2021-05-04 14:47:21 +00:00
2021-07-26 19:07:24 +00:00
for _ , normedDeposit := range normalizedDeposit {
claim = k . synchronizeSingleHardSupplyReward ( ctx , claim , normedDeposit . Denom , normedDeposit . Amount )
2021-05-04 14:47:21 +00:00
}
k . SetHardLiquidityProviderClaim ( ctx , claim )
}
2021-07-26 19:07:24 +00:00
// synchronizeSingleHardSupplyReward synchronizes a single rewarded supply denom in a hard claim.
// It returns the claim without setting in the store.
// The public methods for accessing and modifying claims are preferred over this one. Direct modification of claims is easy to get wrong.
func ( k Keeper ) synchronizeSingleHardSupplyReward ( ctx sdk . Context , claim types . HardLiquidityProviderClaim , denom string , sourceShares sdk . Dec ) types . HardLiquidityProviderClaim {
globalRewardIndexes , found := k . GetHardSupplyRewardIndexes ( ctx , 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.
return claim
}
userRewardIndexes , found := claim . SupplyRewardIndexes . Get ( 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 { }
}
newRewards , err := k . CalculateRewards ( userRewardIndexes , globalRewardIndexes , sourceShares )
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 ) )
}
claim . Reward = claim . Reward . Add ( newRewards ... )
claim . SupplyRewardIndexes = claim . SupplyRewardIndexes . With ( denom , globalRewardIndexes )
return claim
}
2021-05-04 14:47:21 +00:00
// 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 )
}
}
// 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
}
// 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
}