mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-04-04 15:55:23 +00:00 
			
		
		
		
	Incentive Refactor: split large files (#905)
* group reward code by type * split out usdx reward tests into own file * split out delegator reward tests into own file * split supply borrow reward tests into own files * sync order of test functions in files
This commit is contained in:
		
							parent
							
								
									78b194ce0f
								
							
						
					
					
						commit
						42c0b187f4
					
				| @ -1,914 +0,0 @@ | ||||
| package keeper | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"math" | ||||
| 	"time" | ||||
| 
 | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 
 | ||||
| 	cdptypes "github.com/kava-labs/kava/x/cdp/types" | ||||
| 	hardtypes "github.com/kava-labs/kava/x/hard/types" | ||||
| 	"github.com/kava-labs/kava/x/incentive/types" | ||||
| ) | ||||
| 
 | ||||
| // AccumulateUSDXMintingRewards updates the rewards accumulated for the input reward period
 | ||||
| func (k Keeper) AccumulateUSDXMintingRewards(ctx sdk.Context, rewardPeriod types.RewardPeriod) error { | ||||
| 	previousAccrualTime, found := k.GetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType) | ||||
| 	if !found { | ||||
| 		k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) | ||||
| 		return nil | ||||
| 	} | ||||
| 	timeElapsed := CalculateTimeElapsed(rewardPeriod.Start, rewardPeriod.End, ctx.BlockTime(), previousAccrualTime) | ||||
| 	if timeElapsed.IsZero() { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if rewardPeriod.RewardsPerSecond.Amount.IsZero() { | ||||
| 		k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) | ||||
| 		return nil | ||||
| 	} | ||||
| 	totalPrincipal := k.cdpKeeper.GetTotalPrincipal(ctx, rewardPeriod.CollateralType, types.PrincipalDenom).ToDec() | ||||
| 	if totalPrincipal.IsZero() { | ||||
| 		k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) | ||||
| 		return nil | ||||
| 	} | ||||
| 	newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount) | ||||
| 	cdpFactor, found := k.cdpKeeper.GetInterestFactor(ctx, rewardPeriod.CollateralType) | ||||
| 	if !found { | ||||
| 		k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) | ||||
| 		return nil | ||||
| 	} | ||||
| 	rewardFactor := newRewards.ToDec().Mul(cdpFactor).Quo(totalPrincipal) | ||||
| 
 | ||||
| 	previousRewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, rewardPeriod.CollateralType) | ||||
| 	if !found { | ||||
| 		previousRewardFactor = sdk.ZeroDec() | ||||
| 	} | ||||
| 	newRewardFactor := previousRewardFactor.Add(rewardFactor) | ||||
| 	k.SetUSDXMintingRewardFactor(ctx, rewardPeriod.CollateralType, newRewardFactor) | ||||
| 	k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // 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 | ||||
| } | ||||
| 
 | ||||
| // 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 | ||||
| } | ||||
| 
 | ||||
| // InitializeUSDXMintingClaim creates or updates a claim such that no new rewards are accrued, but any existing rewards are not lost.
 | ||||
| // this function should be called after a cdp is created. If a user previously had a cdp, then closed it, they shouldn't
 | ||||
| // accrue rewards during the period the cdp was closed. By setting the reward factor to the current global reward factor,
 | ||||
| // any unclaimed rewards are preserved, but no new rewards are added.
 | ||||
| func (k Keeper) InitializeUSDXMintingClaim(ctx sdk.Context, cdp cdptypes.CDP) { | ||||
| 	_, found := k.GetUSDXMintingRewardPeriod(ctx, cdp.Type) | ||||
| 	if !found { | ||||
| 		// this collateral type is not incentivized, do nothing
 | ||||
| 		return | ||||
| 	} | ||||
| 	rewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, cdp.Type) | ||||
| 	if !found { | ||||
| 		rewardFactor = sdk.ZeroDec() | ||||
| 	} | ||||
| 	claim, found := k.GetUSDXMintingClaim(ctx, cdp.Owner) | ||||
| 	if !found { // this is the owner's first usdx minting reward claim
 | ||||
| 		claim = types.NewUSDXMintingClaim(cdp.Owner, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()), types.RewardIndexes{types.NewRewardIndex(cdp.Type, rewardFactor)}) | ||||
| 		k.SetUSDXMintingClaim(ctx, claim) | ||||
| 		return | ||||
| 	} | ||||
| 	// the owner has an existing usdx minting reward claim
 | ||||
| 	index, hasRewardIndex := claim.HasRewardIndex(cdp.Type) | ||||
| 	if !hasRewardIndex { // this is the owner's first usdx minting reward for this collateral type
 | ||||
| 		claim.RewardIndexes = append(claim.RewardIndexes, types.NewRewardIndex(cdp.Type, rewardFactor)) | ||||
| 	} else { // the owner has a previous usdx minting reward for this collateral type
 | ||||
| 		claim.RewardIndexes[index] = types.NewRewardIndex(cdp.Type, rewardFactor) | ||||
| 	} | ||||
| 	k.SetUSDXMintingClaim(ctx, claim) | ||||
| } | ||||
| 
 | ||||
| // SynchronizeUSDXMintingReward updates the claim object by adding any accumulated rewards and updating the reward index value.
 | ||||
| // this should be called before a cdp is modified, immediately after the 'SynchronizeInterest' method is called in the cdp module
 | ||||
| func (k Keeper) SynchronizeUSDXMintingReward(ctx sdk.Context, cdp cdptypes.CDP) { | ||||
| 	_, found := k.GetUSDXMintingRewardPeriod(ctx, cdp.Type) | ||||
| 	if !found { | ||||
| 		// this collateral type is not incentivized, do nothing
 | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	globalRewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, cdp.Type) | ||||
| 	if !found { | ||||
| 		globalRewardFactor = sdk.ZeroDec() | ||||
| 	} | ||||
| 	claim, found := k.GetUSDXMintingClaim(ctx, cdp.Owner) | ||||
| 	if !found { | ||||
| 		claim = types.NewUSDXMintingClaim(cdp.Owner, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()), types.RewardIndexes{types.NewRewardIndex(cdp.Type, globalRewardFactor)}) | ||||
| 		k.SetUSDXMintingClaim(ctx, claim) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// the owner has an existing usdx minting reward claim
 | ||||
| 	index, hasRewardIndex := claim.HasRewardIndex(cdp.Type) | ||||
| 	if !hasRewardIndex { // this is the owner's first usdx minting reward for this collateral type
 | ||||
| 		claim.RewardIndexes = append(claim.RewardIndexes, types.NewRewardIndex(cdp.Type, globalRewardFactor)) | ||||
| 		k.SetUSDXMintingClaim(ctx, claim) | ||||
| 		return | ||||
| 	} | ||||
| 	userRewardFactor := claim.RewardIndexes[index].RewardFactor | ||||
| 	rewardsAccumulatedFactor := globalRewardFactor.Sub(userRewardFactor) | ||||
| 	if rewardsAccumulatedFactor.IsZero() { | ||||
| 		return | ||||
| 	} | ||||
| 	claim.RewardIndexes[index].RewardFactor = globalRewardFactor | ||||
| 	newRewardsAmount := rewardsAccumulatedFactor.Mul(cdp.GetTotalPrincipal().Amount.ToDec()).RoundInt() | ||||
| 	if newRewardsAmount.IsZero() { | ||||
| 		k.SetUSDXMintingClaim(ctx, claim) | ||||
| 		return | ||||
| 	} | ||||
| 	newRewardsCoin := sdk.NewCoin(types.USDXMintingRewardDenom, newRewardsAmount) | ||||
| 	claim.Reward = claim.Reward.Add(newRewardsCoin) | ||||
| 	k.SetUSDXMintingClaim(ctx, claim) | ||||
| } | ||||
| 
 | ||||
| // 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) { | ||||
| 	var supplyRewardIndexes types.MultiRewardIndexes | ||||
| 	for _, coin := range deposit.Amount { | ||||
| 		globalRewardIndexes, foundGlobalRewardIndexes := k.GetHardSupplyRewardIndexes(ctx, coin.Denom) | ||||
| 		var multiRewardIndex types.MultiRewardIndex | ||||
| 		if foundGlobalRewardIndexes { | ||||
| 			multiRewardIndex = types.NewMultiRewardIndex(coin.Denom, globalRewardIndexes) | ||||
| 		} else { | ||||
| 			multiRewardIndex = types.NewMultiRewardIndex(coin.Denom, types.RewardIndexes{}) | ||||
| 		} | ||||
| 		supplyRewardIndexes = append(supplyRewardIndexes, multiRewardIndex) | ||||
| 	} | ||||
| 
 | ||||
| 	claim, found := k.GetHardLiquidityProviderClaim(ctx, deposit.Depositor) | ||||
| 	if !found { | ||||
| 		// Instantiate claim object
 | ||||
| 		claim = types.NewHardLiquidityProviderClaim(deposit.Depositor, sdk.Coins{}, nil, nil, nil) | ||||
| 	} | ||||
| 
 | ||||
| 	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 { | ||||
| 		globalRewardIndexes, foundGlobalRewardIndexes := k.GetHardSupplyRewardIndexes(ctx, coin.Denom) | ||||
| 		if !foundGlobalRewardIndexes { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		userMultiRewardIndex, foundUserMultiRewardIndex := claim.SupplyRewardIndexes.GetRewardIndex(coin.Denom) | ||||
| 		if !foundUserMultiRewardIndex { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		userRewardIndexIndex, foundUserRewardIndexIndex := claim.SupplyRewardIndexes.GetRewardIndexIndex(coin.Denom) | ||||
| 		if !foundUserRewardIndexIndex { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		for _, globalRewardIndex := range globalRewardIndexes { | ||||
| 			userRewardIndex, foundUserRewardIndex := userMultiRewardIndex.RewardIndexes.GetRewardIndex(globalRewardIndex.CollateralType) | ||||
| 			if !foundUserRewardIndex { | ||||
| 				// User deposited this coin type before it had rewards. When new rewards are added, legacy depositors
 | ||||
| 				// should immediately begin earning rewards. Enable users to do so by updating their claim with the global
 | ||||
| 				// reward index denom and start their reward factor at 0.0
 | ||||
| 				userRewardIndex = types.NewRewardIndex(globalRewardIndex.CollateralType, sdk.ZeroDec()) | ||||
| 				userMultiRewardIndex.RewardIndexes = append(userMultiRewardIndex.RewardIndexes, userRewardIndex) | ||||
| 				claim.SupplyRewardIndexes[userRewardIndexIndex] = userMultiRewardIndex | ||||
| 			} | ||||
| 
 | ||||
| 			globalRewardFactor := globalRewardIndex.RewardFactor | ||||
| 			userRewardFactor := userRewardIndex.RewardFactor | ||||
| 			rewardsAccumulatedFactor := globalRewardFactor.Sub(userRewardFactor) | ||||
| 			if rewardsAccumulatedFactor.IsNegative() { | ||||
| 				panic(fmt.Sprintf("reward accumulation factor cannot be negative: %s", rewardsAccumulatedFactor)) | ||||
| 			} | ||||
| 
 | ||||
| 			newRewardsAmount := rewardsAccumulatedFactor.Mul(deposit.Amount.AmountOf(coin.Denom).ToDec()).RoundInt() | ||||
| 
 | ||||
| 			factorIndex, foundFactorIndex := userMultiRewardIndex.RewardIndexes.GetFactorIndex(globalRewardIndex.CollateralType) | ||||
| 			if !foundFactorIndex { // should never trigger, as we basically do this check at the start of this loop
 | ||||
| 				continue | ||||
| 			} | ||||
| 			claim.SupplyRewardIndexes[userRewardIndexIndex].RewardIndexes[factorIndex].RewardFactor = globalRewardIndex.RewardFactor | ||||
| 
 | ||||
| 			newRewardsCoin := sdk.NewCoin(userRewardIndex.CollateralType, newRewardsAmount) | ||||
| 			claim.Reward = claim.Reward.Add(newRewardsCoin) | ||||
| 		} | ||||
| 	} | ||||
| 	k.SetHardLiquidityProviderClaim(ctx, claim) | ||||
| } | ||||
| 
 | ||||
| // 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 { | ||||
| 		claim = types.NewHardLiquidityProviderClaim(borrow.Borrower, sdk.Coins{}, nil, nil, nil) | ||||
| 	} | ||||
| 
 | ||||
| 	var borrowRewardIndexes types.MultiRewardIndexes | ||||
| 	for _, coin := range borrow.Amount { | ||||
| 		globalRewardIndexes, foundGlobalRewardIndexes := k.GetHardBorrowRewardIndexes(ctx, coin.Denom) | ||||
| 		var multiRewardIndex types.MultiRewardIndex | ||||
| 		if foundGlobalRewardIndexes { | ||||
| 			multiRewardIndex = types.NewMultiRewardIndex(coin.Denom, globalRewardIndexes) | ||||
| 		} else { | ||||
| 			multiRewardIndex = types.NewMultiRewardIndex(coin.Denom, types.RewardIndexes{}) | ||||
| 		} | ||||
| 		borrowRewardIndexes = append(borrowRewardIndexes, multiRewardIndex) | ||||
| 	} | ||||
| 
 | ||||
| 	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 { | ||||
| 		globalRewardIndexes, foundGlobalRewardIndexes := k.GetHardBorrowRewardIndexes(ctx, coin.Denom) | ||||
| 		if !foundGlobalRewardIndexes { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		userMultiRewardIndex, foundUserMultiRewardIndex := claim.BorrowRewardIndexes.GetRewardIndex(coin.Denom) | ||||
| 		if !foundUserMultiRewardIndex { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		userRewardIndexIndex, foundUserRewardIndexIndex := claim.BorrowRewardIndexes.GetRewardIndexIndex(coin.Denom) | ||||
| 		if !foundUserRewardIndexIndex { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		for _, globalRewardIndex := range globalRewardIndexes { | ||||
| 			userRewardIndex, foundUserRewardIndex := userMultiRewardIndex.RewardIndexes.GetRewardIndex(globalRewardIndex.CollateralType) | ||||
| 			if !foundUserRewardIndex { | ||||
| 				// User borrowed this coin type before it had rewards. When new rewards are added, legacy borrowers
 | ||||
| 				// should immediately begin earning rewards. Enable users to do so by updating their claim with the global
 | ||||
| 				// reward index denom and start their reward factor at 0.0
 | ||||
| 				userRewardIndex = types.NewRewardIndex(globalRewardIndex.CollateralType, sdk.ZeroDec()) | ||||
| 				userMultiRewardIndex.RewardIndexes = append(userMultiRewardIndex.RewardIndexes, userRewardIndex) | ||||
| 				claim.BorrowRewardIndexes[userRewardIndexIndex] = userMultiRewardIndex | ||||
| 			} | ||||
| 
 | ||||
| 			globalRewardFactor := globalRewardIndex.RewardFactor | ||||
| 			userRewardFactor := userRewardIndex.RewardFactor | ||||
| 			rewardsAccumulatedFactor := globalRewardFactor.Sub(userRewardFactor) | ||||
| 			if rewardsAccumulatedFactor.IsNegative() { | ||||
| 				panic(fmt.Sprintf("reward accumulation factor cannot be negative: %s", rewardsAccumulatedFactor)) | ||||
| 			} | ||||
| 
 | ||||
| 			newRewardsAmount := rewardsAccumulatedFactor.Mul(borrow.Amount.AmountOf(coin.Denom).ToDec()).RoundInt() | ||||
| 
 | ||||
| 			factorIndex, foundFactorIndex := userMultiRewardIndex.RewardIndexes.GetFactorIndex(globalRewardIndex.CollateralType) | ||||
| 			if !foundFactorIndex { // should never trigger
 | ||||
| 				continue | ||||
| 			} | ||||
| 			claim.BorrowRewardIndexes[userRewardIndexIndex].RewardIndexes[factorIndex].RewardFactor = globalRewardIndex.RewardFactor | ||||
| 			newRewardsCoin := sdk.NewCoin(userRewardIndex.CollateralType, newRewardsAmount) | ||||
| 			claim.Reward = claim.Reward.Add(newRewardsCoin) | ||||
| 		} | ||||
| 	} | ||||
| 	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 { | ||||
| 		claim = types.NewHardLiquidityProviderClaim(deposit.Depositor, sdk.Coins{}, nil, nil, nil) | ||||
| 	} | ||||
| 
 | ||||
| 	depositDenoms := getDenoms(deposit.Amount) | ||||
| 	supplyRewardIndexDenoms := claim.SupplyRewardIndexes.GetCollateralTypes() | ||||
| 
 | ||||
| 	uniqueDepositDenoms := setDifference(depositDenoms, supplyRewardIndexDenoms) | ||||
| 	uniqueSupplyRewardDenoms := setDifference(supplyRewardIndexDenoms, depositDenoms) | ||||
| 
 | ||||
| 	supplyRewardIndexes := claim.SupplyRewardIndexes | ||||
| 	// Create a new multi-reward index in the claim for every new deposit denom
 | ||||
| 	for _, denom := range uniqueDepositDenoms { | ||||
| 		_, foundUserRewardIndexes := claim.SupplyRewardIndexes.GetRewardIndex(denom) | ||||
| 		if !foundUserRewardIndexes { | ||||
| 			globalSupplyRewardIndexes, foundGlobalSupplyRewardIndexes := k.GetHardSupplyRewardIndexes(ctx, denom) | ||||
| 			var multiRewardIndex types.MultiRewardIndex | ||||
| 			if foundGlobalSupplyRewardIndexes { | ||||
| 				multiRewardIndex = types.NewMultiRewardIndex(denom, globalSupplyRewardIndexes) | ||||
| 			} else { | ||||
| 				multiRewardIndex = types.NewMultiRewardIndex(denom, types.RewardIndexes{}) | ||||
| 			} | ||||
| 			supplyRewardIndexes = append(supplyRewardIndexes, multiRewardIndex) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Delete multi-reward index from claim if the collateral type is no longer deposited
 | ||||
| 	for _, denom := range uniqueSupplyRewardDenoms { | ||||
| 		supplyRewardIndexes = supplyRewardIndexes.RemoveRewardIndex(denom) | ||||
| 	} | ||||
| 
 | ||||
| 	claim.SupplyRewardIndexes = supplyRewardIndexes | ||||
| 	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 { | ||||
| 		claim = types.NewHardLiquidityProviderClaim(borrow.Borrower, sdk.Coins{}, nil, nil, nil) | ||||
| 	} | ||||
| 
 | ||||
| 	borrowDenoms := getDenoms(borrow.Amount) | ||||
| 	borrowRewardIndexDenoms := claim.BorrowRewardIndexes.GetCollateralTypes() | ||||
| 
 | ||||
| 	uniqueBorrowDenoms := setDifference(borrowDenoms, borrowRewardIndexDenoms) | ||||
| 	uniqueBorrowRewardDenoms := setDifference(borrowRewardIndexDenoms, borrowDenoms) | ||||
| 
 | ||||
| 	borrowRewardIndexes := claim.BorrowRewardIndexes | ||||
| 	// Create a new multi-reward index in the claim for every new borrow denom
 | ||||
| 	for _, denom := range uniqueBorrowDenoms { | ||||
| 		_, foundUserRewardIndexes := claim.BorrowRewardIndexes.GetRewardIndex(denom) | ||||
| 		if !foundUserRewardIndexes { | ||||
| 			globalBorrowRewardIndexes, foundGlobalBorrowRewardIndexes := k.GetHardBorrowRewardIndexes(ctx, denom) | ||||
| 			var multiRewardIndex types.MultiRewardIndex | ||||
| 			if foundGlobalBorrowRewardIndexes { | ||||
| 				multiRewardIndex = types.NewMultiRewardIndex(denom, globalBorrowRewardIndexes) | ||||
| 			} else { | ||||
| 				multiRewardIndex = types.NewMultiRewardIndex(denom, types.RewardIndexes{}) | ||||
| 			} | ||||
| 			borrowRewardIndexes = append(borrowRewardIndexes, multiRewardIndex) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Delete multi-reward index from claim if the collateral type is no longer borrowed
 | ||||
| 	for _, denom := range uniqueBorrowRewardDenoms { | ||||
| 		borrowRewardIndexes = borrowRewardIndexes.RemoveRewardIndex(denom) | ||||
| 	} | ||||
| 
 | ||||
| 	claim.BorrowRewardIndexes = borrowRewardIndexes | ||||
| 	k.SetHardLiquidityProviderClaim(ctx, claim) | ||||
| } | ||||
| 
 | ||||
| // SynchronizeHardDelegatorRewards updates the claim object by adding any accumulated rewards, and setting the reward indexes to the global values.
 | ||||
| // valAddr and shouldIncludeValidator are used to ignore or include delegations to a particular validator when summing up the total delegation.
 | ||||
| // Normally only delegations to Bonded validators are included in the total. This is needed as staking hooks are sometimes called on the wrong side of a validator's state update (from this module's perspective).
 | ||||
| func (k Keeper) SynchronizeHardDelegatorRewards(ctx sdk.Context, delegator sdk.AccAddress, valAddr sdk.ValAddress, shouldIncludeValidator bool) { | ||||
| 	claim, found := k.GetHardLiquidityProviderClaim(ctx, delegator) | ||||
| 	if !found { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	delagatorFactor, found := k.GetHardDelegatorRewardFactor(ctx, types.BondDenom) | ||||
| 	if !found { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	delegatorIndex, hasDelegatorRewardIndex := claim.HasDelegatorRewardIndex(types.BondDenom) | ||||
| 	if !hasDelegatorRewardIndex { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	userRewardFactor := claim.DelegatorRewardIndexes[delegatorIndex].RewardFactor | ||||
| 	rewardsAccumulatedFactor := delagatorFactor.Sub(userRewardFactor) | ||||
| 	if rewardsAccumulatedFactor.IsNegative() { | ||||
| 		panic(fmt.Sprintf("reward accumulation factor cannot be negative: %s", rewardsAccumulatedFactor)) | ||||
| 	} | ||||
| 	claim.DelegatorRewardIndexes[delegatorIndex].RewardFactor = delagatorFactor | ||||
| 
 | ||||
| 	totalDelegated := sdk.ZeroDec() | ||||
| 
 | ||||
| 	delegations := k.stakingKeeper.GetDelegatorDelegations(ctx, delegator, 200) | ||||
| 	for _, delegation := range delegations { | ||||
| 		validator, found := k.stakingKeeper.GetValidator(ctx, delegation.GetValidatorAddr()) | ||||
| 		if !found { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if valAddr == nil { | ||||
| 			// Delegators don't accumulate rewards if their validator is unbonded
 | ||||
| 			if validator.GetStatus() != sdk.Bonded { | ||||
| 				continue | ||||
| 			} | ||||
| 		} else { | ||||
| 			if !shouldIncludeValidator && validator.OperatorAddress.Equals(valAddr) { | ||||
| 				// ignore tokens delegated to the validator
 | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if validator.GetTokens().IsZero() { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		delegatedTokens := validator.TokensFromShares(delegation.GetShares()) | ||||
| 		if delegatedTokens.IsNegative() { | ||||
| 			continue | ||||
| 		} | ||||
| 		totalDelegated = totalDelegated.Add(delegatedTokens) | ||||
| 	} | ||||
| 	rewardsEarned := rewardsAccumulatedFactor.Mul(totalDelegated).RoundInt() | ||||
| 
 | ||||
| 	// Add rewards to delegator's hard claim
 | ||||
| 	newRewardsCoin := sdk.NewCoin(types.HardLiquidityRewardDenom, rewardsEarned) | ||||
| 	claim.Reward = claim.Reward.Add(newRewardsCoin) | ||||
| 	k.SetHardLiquidityProviderClaim(ctx, claim) | ||||
| } | ||||
| 
 | ||||
| // AccumulateHardDelegatorRewards updates the rewards accumulated for the input reward period
 | ||||
| func (k Keeper) AccumulateHardDelegatorRewards(ctx sdk.Context, rewardPeriod types.RewardPeriod) error { | ||||
| 	previousAccrualTime, found := k.GetPreviousHardDelegatorRewardAccrualTime(ctx, rewardPeriod.CollateralType) | ||||
| 	if !found { | ||||
| 		k.SetPreviousHardDelegatorRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) | ||||
| 		return nil | ||||
| 	} | ||||
| 	timeElapsed := CalculateTimeElapsed(rewardPeriod.Start, rewardPeriod.End, ctx.BlockTime(), previousAccrualTime) | ||||
| 	if timeElapsed.IsZero() { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if rewardPeriod.RewardsPerSecond.Amount.IsZero() { | ||||
| 		k.SetPreviousHardDelegatorRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	totalBonded := k.stakingKeeper.TotalBondedTokens(ctx).ToDec() | ||||
| 	if totalBonded.IsZero() { | ||||
| 		k.SetPreviousHardDelegatorRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount) | ||||
| 	rewardFactor := newRewards.ToDec().Quo(totalBonded) | ||||
| 
 | ||||
| 	previousRewardFactor, found := k.GetHardDelegatorRewardFactor(ctx, rewardPeriod.CollateralType) | ||||
| 	if !found { | ||||
| 		previousRewardFactor = sdk.ZeroDec() | ||||
| 	} | ||||
| 	newRewardFactor := previousRewardFactor.Add(rewardFactor) | ||||
| 	k.SetHardDelegatorRewardFactor(ctx, rewardPeriod.CollateralType, newRewardFactor) | ||||
| 	k.SetPreviousHardDelegatorRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // InitializeHardDelegatorReward initializes the delegator reward index of a hard claim
 | ||||
| func (k Keeper) InitializeHardDelegatorReward(ctx sdk.Context, delegator sdk.AccAddress) { | ||||
| 	delegatorFactor, foundDelegatorFactor := k.GetHardDelegatorRewardFactor(ctx, types.BondDenom) | ||||
| 	if !foundDelegatorFactor { // Should always be found...
 | ||||
| 		delegatorFactor = sdk.ZeroDec() | ||||
| 	} | ||||
| 
 | ||||
| 	delegatorRewardIndexes := types.NewRewardIndex(types.BondDenom, delegatorFactor) | ||||
| 
 | ||||
| 	claim, found := k.GetHardLiquidityProviderClaim(ctx, delegator) | ||||
| 	if !found { | ||||
| 		// Instantiate claim object
 | ||||
| 		claim = types.NewHardLiquidityProviderClaim(delegator, sdk.Coins{}, nil, nil, nil) | ||||
| 	} else { | ||||
| 		k.SynchronizeHardDelegatorRewards(ctx, delegator, nil, false) | ||||
| 		claim, _ = k.GetHardLiquidityProviderClaim(ctx, delegator) | ||||
| 	} | ||||
| 
 | ||||
| 	claim.DelegatorRewardIndexes = types.RewardIndexes{delegatorRewardIndexes} | ||||
| 	k.SetHardLiquidityProviderClaim(ctx, claim) | ||||
| } | ||||
| 
 | ||||
| // ZeroUSDXMintingClaim zeroes out the claim object's rewards and returns the updated claim object
 | ||||
| func (k Keeper) ZeroUSDXMintingClaim(ctx sdk.Context, claim types.USDXMintingClaim) types.USDXMintingClaim { | ||||
| 	claim.Reward = sdk.NewCoin(claim.Reward.Denom, sdk.ZeroInt()) | ||||
| 	k.SetUSDXMintingClaim(ctx, claim) | ||||
| 	return claim | ||||
| } | ||||
| 
 | ||||
| // SynchronizeUSDXMintingClaim updates the claim object by adding any rewards that have accumulated.
 | ||||
| // Returns the updated claim object
 | ||||
| func (k Keeper) SynchronizeUSDXMintingClaim(ctx sdk.Context, claim types.USDXMintingClaim) (types.USDXMintingClaim, error) { | ||||
| 	for _, ri := range claim.RewardIndexes { | ||||
| 		cdp, found := k.cdpKeeper.GetCdpByOwnerAndCollateralType(ctx, claim.Owner, ri.CollateralType) | ||||
| 		if !found { | ||||
| 			// if the cdp for this collateral type has been closed, no updates are needed
 | ||||
| 			continue | ||||
| 		} | ||||
| 		claim = k.synchronizeRewardAndReturnClaim(ctx, cdp) | ||||
| 	} | ||||
| 	return claim, nil | ||||
| } | ||||
| 
 | ||||
| // this function assumes a claim already exists, so don't call it if that's not the case
 | ||||
| func (k Keeper) synchronizeRewardAndReturnClaim(ctx sdk.Context, cdp cdptypes.CDP) types.USDXMintingClaim { | ||||
| 	k.SynchronizeUSDXMintingReward(ctx, cdp) | ||||
| 	claim, _ := k.GetUSDXMintingClaim(ctx, cdp.Owner) | ||||
| 	return 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) | ||||
| 	} | ||||
| 
 | ||||
| 	// Synchronize any hard delegator rewards
 | ||||
| 	k.SynchronizeHardDelegatorRewards(ctx, owner, nil, false) | ||||
| } | ||||
| 
 | ||||
| // 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 | ||||
| } | ||||
| 
 | ||||
| // 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(), | ||||
| 	)))) | ||||
| } | ||||
| 
 | ||||
| // 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) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// 3. Simulate Hard delegator rewards
 | ||||
| 	delagatorFactor, found := k.GetHardDelegatorRewardFactor(ctx, types.BondDenom) | ||||
| 	if !found { | ||||
| 		return claim | ||||
| 	} | ||||
| 
 | ||||
| 	delegatorIndex, hasDelegatorRewardIndex := claim.HasDelegatorRewardIndex(types.BondDenom) | ||||
| 	if !hasDelegatorRewardIndex { | ||||
| 		return claim | ||||
| 	} | ||||
| 
 | ||||
| 	userRewardFactor := claim.DelegatorRewardIndexes[delegatorIndex].RewardFactor | ||||
| 	rewardsAccumulatedFactor := delagatorFactor.Sub(userRewardFactor) | ||||
| 	if rewardsAccumulatedFactor.IsZero() { | ||||
| 		return claim | ||||
| 	} | ||||
| 	claim.DelegatorRewardIndexes[delegatorIndex].RewardFactor = delagatorFactor | ||||
| 
 | ||||
| 	totalDelegated := sdk.ZeroDec() | ||||
| 
 | ||||
| 	delegations := k.stakingKeeper.GetDelegatorDelegations(ctx, claim.GetOwner(), 200) | ||||
| 	for _, delegation := range delegations { | ||||
| 		validator, found := k.stakingKeeper.GetValidator(ctx, delegation.GetValidatorAddr()) | ||||
| 		if !found { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// Delegators don't accumulate rewards if their validator is unbonded/slashed
 | ||||
| 		if validator.GetStatus() != sdk.Bonded { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if validator.GetTokens().IsZero() { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		delegatedTokens := validator.TokensFromShares(delegation.GetShares()) | ||||
| 		if delegatedTokens.IsZero() || delegatedTokens.IsNegative() { | ||||
| 			continue | ||||
| 		} | ||||
| 		totalDelegated = totalDelegated.Add(delegatedTokens) | ||||
| 	} | ||||
| 
 | ||||
| 	rewardsEarned := rewardsAccumulatedFactor.Mul(totalDelegated).RoundInt() | ||||
| 	if rewardsEarned.IsZero() || rewardsEarned.IsNegative() { | ||||
| 		return claim | ||||
| 	} | ||||
| 
 | ||||
| 	// Add rewards to delegator's hard claim
 | ||||
| 	newRewardsCoin := sdk.NewCoin(types.HardLiquidityRewardDenom, rewardsEarned) | ||||
| 	claim.Reward = claim.Reward.Add(newRewardsCoin) | ||||
| 
 | ||||
| 	return claim | ||||
| } | ||||
| 
 | ||||
| // SimulateUSDXMintingSynchronization calculates a user's outstanding USDX minting rewards by simulating reward synchronization
 | ||||
| func (k Keeper) SimulateUSDXMintingSynchronization(ctx sdk.Context, claim types.USDXMintingClaim) types.USDXMintingClaim { | ||||
| 	for _, ri := range claim.RewardIndexes { | ||||
| 		_, found := k.GetUSDXMintingRewardPeriod(ctx, ri.CollateralType) | ||||
| 		if !found { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		globalRewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, ri.CollateralType) | ||||
| 		if !found { | ||||
| 			globalRewardFactor = sdk.ZeroDec() | ||||
| 		} | ||||
| 
 | ||||
| 		// the owner has an existing usdx minting reward claim
 | ||||
| 		index, hasRewardIndex := claim.HasRewardIndex(ri.CollateralType) | ||||
| 		if !hasRewardIndex { // this is the owner's first usdx minting reward for this collateral type
 | ||||
| 			claim.RewardIndexes = append(claim.RewardIndexes, types.NewRewardIndex(ri.CollateralType, globalRewardFactor)) | ||||
| 		} | ||||
| 		userRewardFactor := claim.RewardIndexes[index].RewardFactor | ||||
| 		rewardsAccumulatedFactor := globalRewardFactor.Sub(userRewardFactor) | ||||
| 		if rewardsAccumulatedFactor.IsZero() { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		claim.RewardIndexes[index].RewardFactor = globalRewardFactor | ||||
| 
 | ||||
| 		cdp, found := k.cdpKeeper.GetCdpByOwnerAndCollateralType(ctx, claim.GetOwner(), ri.CollateralType) | ||||
| 		if !found { | ||||
| 			continue | ||||
| 		} | ||||
| 		newRewardsAmount := rewardsAccumulatedFactor.Mul(cdp.GetTotalPrincipal().Amount.ToDec()).RoundInt() | ||||
| 		if newRewardsAmount.IsZero() { | ||||
| 			continue | ||||
| 		} | ||||
| 		newRewardsCoin := sdk.NewCoin(types.USDXMintingRewardDenom, 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 | ||||
| } | ||||
							
								
								
									
										194
									
								
								x/incentive/keeper/rewards_borrow.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								x/incentive/keeper/rewards_borrow.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,194 @@ | ||||
| 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" | ||||
| ) | ||||
| 
 | ||||
| // 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 { | ||||
| 		claim = types.NewHardLiquidityProviderClaim(borrow.Borrower, sdk.Coins{}, nil, nil, nil) | ||||
| 	} | ||||
| 
 | ||||
| 	var borrowRewardIndexes types.MultiRewardIndexes | ||||
| 	for _, coin := range borrow.Amount { | ||||
| 		globalRewardIndexes, foundGlobalRewardIndexes := k.GetHardBorrowRewardIndexes(ctx, coin.Denom) | ||||
| 		var multiRewardIndex types.MultiRewardIndex | ||||
| 		if foundGlobalRewardIndexes { | ||||
| 			multiRewardIndex = types.NewMultiRewardIndex(coin.Denom, globalRewardIndexes) | ||||
| 		} else { | ||||
| 			multiRewardIndex = types.NewMultiRewardIndex(coin.Denom, types.RewardIndexes{}) | ||||
| 		} | ||||
| 		borrowRewardIndexes = append(borrowRewardIndexes, multiRewardIndex) | ||||
| 	} | ||||
| 
 | ||||
| 	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 { | ||||
| 		globalRewardIndexes, foundGlobalRewardIndexes := k.GetHardBorrowRewardIndexes(ctx, coin.Denom) | ||||
| 		if !foundGlobalRewardIndexes { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		userMultiRewardIndex, foundUserMultiRewardIndex := claim.BorrowRewardIndexes.GetRewardIndex(coin.Denom) | ||||
| 		if !foundUserMultiRewardIndex { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		userRewardIndexIndex, foundUserRewardIndexIndex := claim.BorrowRewardIndexes.GetRewardIndexIndex(coin.Denom) | ||||
| 		if !foundUserRewardIndexIndex { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		for _, globalRewardIndex := range globalRewardIndexes { | ||||
| 			userRewardIndex, foundUserRewardIndex := userMultiRewardIndex.RewardIndexes.GetRewardIndex(globalRewardIndex.CollateralType) | ||||
| 			if !foundUserRewardIndex { | ||||
| 				// User borrowed this coin type before it had rewards. When new rewards are added, legacy borrowers
 | ||||
| 				// should immediately begin earning rewards. Enable users to do so by updating their claim with the global
 | ||||
| 				// reward index denom and start their reward factor at 0.0
 | ||||
| 				userRewardIndex = types.NewRewardIndex(globalRewardIndex.CollateralType, sdk.ZeroDec()) | ||||
| 				userMultiRewardIndex.RewardIndexes = append(userMultiRewardIndex.RewardIndexes, userRewardIndex) | ||||
| 				claim.BorrowRewardIndexes[userRewardIndexIndex] = userMultiRewardIndex | ||||
| 			} | ||||
| 
 | ||||
| 			globalRewardFactor := globalRewardIndex.RewardFactor | ||||
| 			userRewardFactor := userRewardIndex.RewardFactor | ||||
| 			rewardsAccumulatedFactor := globalRewardFactor.Sub(userRewardFactor) | ||||
| 			if rewardsAccumulatedFactor.IsNegative() { | ||||
| 				panic(fmt.Sprintf("reward accumulation factor cannot be negative: %s", rewardsAccumulatedFactor)) | ||||
| 			} | ||||
| 
 | ||||
| 			newRewardsAmount := rewardsAccumulatedFactor.Mul(borrow.Amount.AmountOf(coin.Denom).ToDec()).RoundInt() | ||||
| 
 | ||||
| 			factorIndex, foundFactorIndex := userMultiRewardIndex.RewardIndexes.GetFactorIndex(globalRewardIndex.CollateralType) | ||||
| 			if !foundFactorIndex { // should never trigger
 | ||||
| 				continue | ||||
| 			} | ||||
| 			claim.BorrowRewardIndexes[userRewardIndexIndex].RewardIndexes[factorIndex].RewardFactor = globalRewardIndex.RewardFactor | ||||
| 			newRewardsCoin := sdk.NewCoin(userRewardIndex.CollateralType, newRewardsAmount) | ||||
| 			claim.Reward = claim.Reward.Add(newRewardsCoin) | ||||
| 		} | ||||
| 	} | ||||
| 	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 { | ||||
| 		claim = types.NewHardLiquidityProviderClaim(borrow.Borrower, sdk.Coins{}, nil, nil, nil) | ||||
| 	} | ||||
| 
 | ||||
| 	borrowDenoms := getDenoms(borrow.Amount) | ||||
| 	borrowRewardIndexDenoms := claim.BorrowRewardIndexes.GetCollateralTypes() | ||||
| 
 | ||||
| 	uniqueBorrowDenoms := setDifference(borrowDenoms, borrowRewardIndexDenoms) | ||||
| 	uniqueBorrowRewardDenoms := setDifference(borrowRewardIndexDenoms, borrowDenoms) | ||||
| 
 | ||||
| 	borrowRewardIndexes := claim.BorrowRewardIndexes | ||||
| 	// Create a new multi-reward index in the claim for every new borrow denom
 | ||||
| 	for _, denom := range uniqueBorrowDenoms { | ||||
| 		_, foundUserRewardIndexes := claim.BorrowRewardIndexes.GetRewardIndex(denom) | ||||
| 		if !foundUserRewardIndexes { | ||||
| 			globalBorrowRewardIndexes, foundGlobalBorrowRewardIndexes := k.GetHardBorrowRewardIndexes(ctx, denom) | ||||
| 			var multiRewardIndex types.MultiRewardIndex | ||||
| 			if foundGlobalBorrowRewardIndexes { | ||||
| 				multiRewardIndex = types.NewMultiRewardIndex(denom, globalBorrowRewardIndexes) | ||||
| 			} else { | ||||
| 				multiRewardIndex = types.NewMultiRewardIndex(denom, types.RewardIndexes{}) | ||||
| 			} | ||||
| 			borrowRewardIndexes = append(borrowRewardIndexes, multiRewardIndex) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Delete multi-reward index from claim if the collateral type is no longer borrowed
 | ||||
| 	for _, denom := range uniqueBorrowRewardDenoms { | ||||
| 		borrowRewardIndexes = borrowRewardIndexes.RemoveRewardIndex(denom) | ||||
| 	} | ||||
| 
 | ||||
| 	claim.BorrowRewardIndexes = borrowRewardIndexes | ||||
| 	k.SetHardLiquidityProviderClaim(ctx, claim) | ||||
| } | ||||
							
								
								
									
										1160
									
								
								x/incentive/keeper/rewards_borrow_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1160
									
								
								x/incentive/keeper/rewards_borrow_test.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										131
									
								
								x/incentive/keeper/rewards_delegator.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								x/incentive/keeper/rewards_delegator.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,131 @@ | ||||
| package keeper | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 
 | ||||
| 	"github.com/kava-labs/kava/x/incentive/types" | ||||
| ) | ||||
| 
 | ||||
| // AccumulateHardDelegatorRewards updates the rewards accumulated for the input reward period
 | ||||
| func (k Keeper) AccumulateHardDelegatorRewards(ctx sdk.Context, rewardPeriod types.RewardPeriod) error { | ||||
| 	previousAccrualTime, found := k.GetPreviousHardDelegatorRewardAccrualTime(ctx, rewardPeriod.CollateralType) | ||||
| 	if !found { | ||||
| 		k.SetPreviousHardDelegatorRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) | ||||
| 		return nil | ||||
| 	} | ||||
| 	timeElapsed := CalculateTimeElapsed(rewardPeriod.Start, rewardPeriod.End, ctx.BlockTime(), previousAccrualTime) | ||||
| 	if timeElapsed.IsZero() { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if rewardPeriod.RewardsPerSecond.Amount.IsZero() { | ||||
| 		k.SetPreviousHardDelegatorRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	totalBonded := k.stakingKeeper.TotalBondedTokens(ctx).ToDec() | ||||
| 	if totalBonded.IsZero() { | ||||
| 		k.SetPreviousHardDelegatorRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount) | ||||
| 	rewardFactor := newRewards.ToDec().Quo(totalBonded) | ||||
| 
 | ||||
| 	previousRewardFactor, found := k.GetHardDelegatorRewardFactor(ctx, rewardPeriod.CollateralType) | ||||
| 	if !found { | ||||
| 		previousRewardFactor = sdk.ZeroDec() | ||||
| 	} | ||||
| 	newRewardFactor := previousRewardFactor.Add(rewardFactor) | ||||
| 	k.SetHardDelegatorRewardFactor(ctx, rewardPeriod.CollateralType, newRewardFactor) | ||||
| 	k.SetPreviousHardDelegatorRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // InitializeHardDelegatorReward initializes the delegator reward index of a hard claim
 | ||||
| func (k Keeper) InitializeHardDelegatorReward(ctx sdk.Context, delegator sdk.AccAddress) { | ||||
| 	delegatorFactor, foundDelegatorFactor := k.GetHardDelegatorRewardFactor(ctx, types.BondDenom) | ||||
| 	if !foundDelegatorFactor { // Should always be found...
 | ||||
| 		delegatorFactor = sdk.ZeroDec() | ||||
| 	} | ||||
| 
 | ||||
| 	delegatorRewardIndexes := types.NewRewardIndex(types.BondDenom, delegatorFactor) | ||||
| 
 | ||||
| 	claim, found := k.GetHardLiquidityProviderClaim(ctx, delegator) | ||||
| 	if !found { | ||||
| 		// Instantiate claim object
 | ||||
| 		claim = types.NewHardLiquidityProviderClaim(delegator, sdk.Coins{}, nil, nil, nil) | ||||
| 	} else { | ||||
| 		k.SynchronizeHardDelegatorRewards(ctx, delegator, nil, false) | ||||
| 		claim, _ = k.GetHardLiquidityProviderClaim(ctx, delegator) | ||||
| 	} | ||||
| 
 | ||||
| 	claim.DelegatorRewardIndexes = types.RewardIndexes{delegatorRewardIndexes} | ||||
| 	k.SetHardLiquidityProviderClaim(ctx, claim) | ||||
| } | ||||
| 
 | ||||
| // SynchronizeHardDelegatorRewards updates the claim object by adding any accumulated rewards, and setting the reward indexes to the global values.
 | ||||
| // valAddr and shouldIncludeValidator are used to ignore or include delegations to a particular validator when summing up the total delegation.
 | ||||
| // Normally only delegations to Bonded validators are included in the total. This is needed as staking hooks are sometimes called on the wrong side of a validator's state update (from this module's perspective).
 | ||||
| func (k Keeper) SynchronizeHardDelegatorRewards(ctx sdk.Context, delegator sdk.AccAddress, valAddr sdk.ValAddress, shouldIncludeValidator bool) { | ||||
| 	claim, found := k.GetHardLiquidityProviderClaim(ctx, delegator) | ||||
| 	if !found { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	delagatorFactor, found := k.GetHardDelegatorRewardFactor(ctx, types.BondDenom) | ||||
| 	if !found { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	delegatorIndex, hasDelegatorRewardIndex := claim.HasDelegatorRewardIndex(types.BondDenom) | ||||
| 	if !hasDelegatorRewardIndex { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	userRewardFactor := claim.DelegatorRewardIndexes[delegatorIndex].RewardFactor | ||||
| 	rewardsAccumulatedFactor := delagatorFactor.Sub(userRewardFactor) | ||||
| 	if rewardsAccumulatedFactor.IsNegative() { | ||||
| 		panic(fmt.Sprintf("reward accumulation factor cannot be negative: %s", rewardsAccumulatedFactor)) | ||||
| 	} | ||||
| 	claim.DelegatorRewardIndexes[delegatorIndex].RewardFactor = delagatorFactor | ||||
| 
 | ||||
| 	totalDelegated := sdk.ZeroDec() | ||||
| 
 | ||||
| 	delegations := k.stakingKeeper.GetDelegatorDelegations(ctx, delegator, 200) | ||||
| 	for _, delegation := range delegations { | ||||
| 		validator, found := k.stakingKeeper.GetValidator(ctx, delegation.GetValidatorAddr()) | ||||
| 		if !found { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if valAddr == nil { | ||||
| 			// Delegators don't accumulate rewards if their validator is unbonded
 | ||||
| 			if validator.GetStatus() != sdk.Bonded { | ||||
| 				continue | ||||
| 			} | ||||
| 		} else { | ||||
| 			if !shouldIncludeValidator && validator.OperatorAddress.Equals(valAddr) { | ||||
| 				// ignore tokens delegated to the validator
 | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if validator.GetTokens().IsZero() { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		delegatedTokens := validator.TokensFromShares(delegation.GetShares()) | ||||
| 		if delegatedTokens.IsNegative() { | ||||
| 			continue | ||||
| 		} | ||||
| 		totalDelegated = totalDelegated.Add(delegatedTokens) | ||||
| 	} | ||||
| 	rewardsEarned := rewardsAccumulatedFactor.Mul(totalDelegated).RoundInt() | ||||
| 
 | ||||
| 	// Add rewards to delegator's hard claim
 | ||||
| 	newRewardsCoin := sdk.NewCoin(types.HardLiquidityRewardDenom, rewardsEarned) | ||||
| 	claim.Reward = claim.Reward.Add(newRewardsCoin) | ||||
| 	k.SetHardLiquidityProviderClaim(ctx, claim) | ||||
| } | ||||
							
								
								
									
										731
									
								
								x/incentive/keeper/rewards_delegator_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										731
									
								
								x/incentive/keeper/rewards_delegator_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,731 @@ | ||||
| package keeper_test | ||||
| 
 | ||||
| import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 	"github.com/cosmos/cosmos-sdk/x/staking" | ||||
| 	abci "github.com/tendermint/tendermint/abci/types" | ||||
| 	"github.com/tendermint/tendermint/crypto/ed25519" | ||||
| 
 | ||||
| 	"github.com/kava-labs/kava/x/hard" | ||||
| 	hardtypes "github.com/kava-labs/kava/x/hard/types" | ||||
| 	"github.com/kava-labs/kava/x/incentive/types" | ||||
| ) | ||||
| 
 | ||||
| func (suite *KeeperTestSuite) TestAccumulateHardDelegatorRewards() { | ||||
| 	type args struct { | ||||
| 		delegation           sdk.Coin | ||||
| 		rewardsPerSecond     sdk.Coin | ||||
| 		initialTime          time.Time | ||||
| 		timeElapsed          int | ||||
| 		expectedRewardFactor sdk.Dec | ||||
| 	} | ||||
| 	type test struct { | ||||
| 		name string | ||||
| 		args args | ||||
| 	} | ||||
| 	testCases := []test{ | ||||
| 		{ | ||||
| 			"7 seconds", | ||||
| 			args{ | ||||
| 				delegation:           c("ukava", 1_000_000), | ||||
| 				rewardsPerSecond:     c("hard", 122354), | ||||
| 				initialTime:          time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), | ||||
| 				timeElapsed:          7, | ||||
| 				expectedRewardFactor: d("0.428239000000000000"), | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"1 day", | ||||
| 			args{ | ||||
| 				delegation:           c("ukava", 1_000_000), | ||||
| 				rewardsPerSecond:     c("hard", 122354), | ||||
| 				initialTime:          time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), | ||||
| 				timeElapsed:          86400, | ||||
| 				expectedRewardFactor: d("5285.692800000000000000"), | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"0 seconds", | ||||
| 			args{ | ||||
| 				delegation:           c("ukava", 1_000_000), | ||||
| 				rewardsPerSecond:     c("hard", 122354), | ||||
| 				initialTime:          time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), | ||||
| 				timeElapsed:          0, | ||||
| 				expectedRewardFactor: d("0.0"), | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tc := range testCases { | ||||
| 		suite.Run(tc.name, func() { | ||||
| 			suite.SetupWithGenState() | ||||
| 			suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) | ||||
| 
 | ||||
| 			// Mint coins to hard module account
 | ||||
| 			supplyKeeper := suite.app.GetSupplyKeeper() | ||||
| 			hardMaccCoins := sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(200000000))) | ||||
| 			supplyKeeper.MintCoins(suite.ctx, hardtypes.ModuleAccountName, hardMaccCoins) | ||||
| 
 | ||||
| 			// Set up incentive state
 | ||||
| 			params := types.NewParams( | ||||
| 				types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, | ||||
| 				types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond))}, | ||||
| 				types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond))}, | ||||
| 				types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, | ||||
| 				types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, | ||||
| 				tc.args.initialTime.Add(time.Hour*24*365*5), | ||||
| 			) | ||||
| 			suite.keeper.SetParams(suite.ctx, params) | ||||
| 			suite.keeper.SetPreviousHardDelegatorRewardAccrualTime(suite.ctx, tc.args.delegation.Denom, tc.args.initialTime) | ||||
| 			suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, tc.args.delegation.Denom, sdk.ZeroDec()) | ||||
| 
 | ||||
| 			// Set up hard state (interest factor for the relevant denom)
 | ||||
| 			suite.hardKeeper.SetPreviousAccrualTime(suite.ctx, tc.args.delegation.Denom, tc.args.initialTime) | ||||
| 
 | ||||
| 			err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], tc.args.delegation) | ||||
| 			suite.Require().NoError(err) | ||||
| 			err = suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[0], tc.args.delegation) | ||||
| 			suite.Require().NoError(err) | ||||
| 
 | ||||
| 			staking.EndBlocker(suite.ctx, suite.stakingKeeper) | ||||
| 
 | ||||
| 			// Set up chain context at future time
 | ||||
| 			runAtTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed)) | ||||
| 			runCtx := suite.ctx.WithBlockTime(runAtTime) | ||||
| 
 | ||||
| 			// Run Hard begin blocker in order to update the denom's index factor
 | ||||
| 			hard.BeginBlocker(runCtx, suite.hardKeeper) | ||||
| 
 | ||||
| 			rewardPeriod, found := suite.keeper.GetHardDelegatorRewardPeriod(runCtx, tc.args.delegation.Denom) | ||||
| 			suite.Require().True(found) | ||||
| 			err = suite.keeper.AccumulateHardDelegatorRewards(runCtx, rewardPeriod) | ||||
| 			suite.Require().NoError(err) | ||||
| 
 | ||||
| 			rewardFactor, found := suite.keeper.GetHardDelegatorRewardFactor(runCtx, tc.args.delegation.Denom) | ||||
| 			suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (suite *KeeperTestSuite) TestSynchronizeHardDelegatorReward() { | ||||
| 	type args struct { | ||||
| 		delegation           sdk.Coin | ||||
| 		rewardsPerSecond     sdk.Coin | ||||
| 		initialTime          time.Time | ||||
| 		blockTimes           []int | ||||
| 		expectedRewardFactor sdk.Dec | ||||
| 		expectedRewards      sdk.Coins | ||||
| 	} | ||||
| 	type test struct { | ||||
| 		name string | ||||
| 		args args | ||||
| 	} | ||||
| 
 | ||||
| 	testCases := []test{ | ||||
| 		{ | ||||
| 			"10 blocks", | ||||
| 			args{ | ||||
| 				delegation:           c("ukava", 1_000_000), | ||||
| 				rewardsPerSecond:     c("hard", 122354), | ||||
| 				initialTime:          time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), | ||||
| 				blockTimes:           []int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, | ||||
| 				expectedRewardFactor: d("6.117700000000000000"), | ||||
| 				expectedRewards:      cs(c("hard", 6117700)), | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"10 blocks - long block time", | ||||
| 			args{ | ||||
| 				delegation:           c("ukava", 1_000_000), | ||||
| 				rewardsPerSecond:     c("hard", 122354), | ||||
| 				initialTime:          time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), | ||||
| 				blockTimes:           []int{86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400}, | ||||
| 				expectedRewardFactor: d("52856.928000000000000000"), | ||||
| 				expectedRewards:      cs(c("hard", 52856928000)), | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"delegator reward index updated when reward is zero", | ||||
| 			args{ | ||||
| 				delegation:           c("ukava", 1), | ||||
| 				rewardsPerSecond:     c("hard", 1), | ||||
| 				initialTime:          time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), | ||||
| 				blockTimes:           []int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, | ||||
| 				expectedRewardFactor: d("0.000099999900000100"), | ||||
| 				expectedRewards:      nil, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tc := range testCases { | ||||
| 		suite.Run(tc.name, func() { | ||||
| 			suite.SetupWithGenState() | ||||
| 			suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) | ||||
| 
 | ||||
| 			// Mint coins to hard module account
 | ||||
| 			supplyKeeper := suite.app.GetSupplyKeeper() | ||||
| 			hardMaccCoins := sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(200000000))) | ||||
| 			supplyKeeper.MintCoins(suite.ctx, hardtypes.ModuleAccountName, hardMaccCoins) | ||||
| 
 | ||||
| 			// setup incentive state
 | ||||
| 			params := types.NewParams( | ||||
| 				types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, | ||||
| 				types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond))}, | ||||
| 				types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond))}, | ||||
| 				types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, | ||||
| 				types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, | ||||
| 				tc.args.initialTime.Add(time.Hour*24*365*5), | ||||
| 			) | ||||
| 			suite.keeper.SetParams(suite.ctx, params) | ||||
| 			suite.keeper.SetPreviousHardDelegatorRewardAccrualTime(suite.ctx, tc.args.delegation.Denom, tc.args.initialTime) | ||||
| 			suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, tc.args.delegation.Denom, sdk.ZeroDec()) | ||||
| 
 | ||||
| 			// Set up hard state (interest factor for the relevant denom)
 | ||||
| 			suite.hardKeeper.SetPreviousAccrualTime(suite.ctx, tc.args.delegation.Denom, tc.args.initialTime) | ||||
| 
 | ||||
| 			// Create validator account
 | ||||
| 			staking.BeginBlocker(suite.ctx, suite.stakingKeeper) | ||||
| 			selfDelegationCoins := c("ukava", 1_000_000) | ||||
| 			err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], selfDelegationCoins) | ||||
| 			suite.Require().NoError(err) | ||||
| 			staking.EndBlocker(suite.ctx, suite.stakingKeeper) | ||||
| 
 | ||||
| 			// Delegator delegates
 | ||||
| 			err = suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[0], tc.args.delegation) | ||||
| 			suite.Require().NoError(err) | ||||
| 
 | ||||
| 			// Check that validator account has been created and delegation was successful
 | ||||
| 			valAcc, found := suite.stakingKeeper.GetValidator(suite.ctx, suite.validatorAddrs[0]) | ||||
| 			suite.True(found) | ||||
| 			suite.Require().Equal(valAcc.Status, sdk.Bonded) | ||||
| 			suite.Require().Equal(valAcc.Tokens, tc.args.delegation.Amount.Add(selfDelegationCoins.Amount)) | ||||
| 
 | ||||
| 			// Check that Staking hooks initialized a HardLiquidityProviderClaim
 | ||||
| 			claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0]) | ||||
| 			suite.Require().True(found) | ||||
| 			suite.Require().Equal(sdk.ZeroDec(), claim.DelegatorRewardIndexes[0].RewardFactor) | ||||
| 
 | ||||
| 			// Run accumulator at several intervals
 | ||||
| 			var timeElapsed int | ||||
| 			previousBlockTime := suite.ctx.BlockTime() | ||||
| 			for _, t := range tc.args.blockTimes { | ||||
| 				timeElapsed += t | ||||
| 				updatedBlockTime := previousBlockTime.Add(time.Duration(int(time.Second) * t)) | ||||
| 				previousBlockTime = updatedBlockTime | ||||
| 				blockCtx := suite.ctx.WithBlockTime(updatedBlockTime) | ||||
| 
 | ||||
| 				// Run Hard begin blocker for each block ctx to update denom's interest factor
 | ||||
| 				hard.BeginBlocker(blockCtx, suite.hardKeeper) | ||||
| 
 | ||||
| 				rewardPeriod, found := suite.keeper.GetHardDelegatorRewardPeriod(blockCtx, tc.args.delegation.Denom) | ||||
| 				suite.Require().True(found) | ||||
| 
 | ||||
| 				err := suite.keeper.AccumulateHardDelegatorRewards(blockCtx, rewardPeriod) | ||||
| 				suite.Require().NoError(err) | ||||
| 			} | ||||
| 			updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed)) | ||||
| 			suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) | ||||
| 
 | ||||
| 			// After we've accumulated, run synchronize
 | ||||
| 			suite.Require().NotPanics(func() { | ||||
| 				suite.keeper.SynchronizeHardDelegatorRewards(suite.ctx, suite.addrs[0], nil, false) | ||||
| 			}) | ||||
| 
 | ||||
| 			// Check that reward factor and claim have been updated as expected
 | ||||
| 			rewardFactor, found := suite.keeper.GetHardDelegatorRewardFactor(suite.ctx, tc.args.delegation.Denom) | ||||
| 			suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor) | ||||
| 
 | ||||
| 			claim, found = suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0]) | ||||
| 			suite.Require().True(found) | ||||
| 			suite.Require().Equal(tc.args.expectedRewardFactor, claim.DelegatorRewardIndexes[0].RewardFactor) | ||||
| 			suite.Require().Equal(tc.args.expectedRewards, claim.Reward) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (suite *KeeperTestSuite) TestSimulateHardDelegatorRewardSynchronization() { | ||||
| 	type args struct { | ||||
| 		delegation            sdk.Coin | ||||
| 		rewardsPerSecond      sdk.Coins | ||||
| 		initialTime           time.Time | ||||
| 		blockTimes            []int | ||||
| 		expectedRewardIndexes types.RewardIndexes | ||||
| 		expectedRewards       sdk.Coins | ||||
| 	} | ||||
| 	type test struct { | ||||
| 		name string | ||||
| 		args args | ||||
| 	} | ||||
| 
 | ||||
| 	testCases := []test{ | ||||
| 		{ | ||||
| 			"10 blocks", | ||||
| 			args{ | ||||
| 				delegation:            c("ukava", 1_000_000), | ||||
| 				rewardsPerSecond:      cs(c("hard", 122354)), | ||||
| 				initialTime:           time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), | ||||
| 				blockTimes:            []int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, | ||||
| 				expectedRewardIndexes: types.RewardIndexes{types.NewRewardIndex("ukava", d("6.117700000000000000"))}, // Here the reward index stores data differently than inside a MultiRewardIndex
 | ||||
| 				expectedRewards:       cs(c("hard", 6117700)), | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"10 blocks - long block time", | ||||
| 			args{ | ||||
| 				delegation:            c("ukava", 1_000_000), | ||||
| 				rewardsPerSecond:      cs(c("hard", 122354)), | ||||
| 				initialTime:           time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), | ||||
| 				blockTimes:            []int{86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400}, | ||||
| 				expectedRewardIndexes: types.RewardIndexes{types.NewRewardIndex("ukava", d("52856.928000000000000000"))}, | ||||
| 				expectedRewards:       cs(c("hard", 52856928000)), | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, tc := range testCases { | ||||
| 		suite.Run(tc.name, func() { | ||||
| 			suite.SetupWithGenState() | ||||
| 			suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) | ||||
| 
 | ||||
| 			// Mint coins to hard module account
 | ||||
| 			supplyKeeper := suite.app.GetSupplyKeeper() | ||||
| 			hardMaccCoins := sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(200000000))) | ||||
| 			supplyKeeper.MintCoins(suite.ctx, hardtypes.ModuleAccountName, hardMaccCoins) | ||||
| 
 | ||||
| 			// setup incentive state
 | ||||
| 			params := types.NewParams( | ||||
| 				types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond[0])}, | ||||
| 				types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, | ||||
| 				types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, | ||||
| 				types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond[0])}, | ||||
| 				types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, | ||||
| 				tc.args.initialTime.Add(time.Hour*24*365*5), | ||||
| 			) | ||||
| 			suite.keeper.SetParams(suite.ctx, params) | ||||
| 			suite.keeper.SetPreviousHardDelegatorRewardAccrualTime(suite.ctx, tc.args.delegation.Denom, tc.args.initialTime) | ||||
| 			suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, tc.args.delegation.Denom, sdk.ZeroDec()) | ||||
| 
 | ||||
| 			// Set up hard state (interest factor for the relevant denom)
 | ||||
| 			suite.hardKeeper.SetPreviousAccrualTime(suite.ctx, tc.args.delegation.Denom, tc.args.initialTime) | ||||
| 
 | ||||
| 			// Delegator delegates
 | ||||
| 			err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], tc.args.delegation) | ||||
| 			suite.Require().NoError(err) | ||||
| 			err = suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[0], tc.args.delegation) | ||||
| 			suite.Require().NoError(err) | ||||
| 
 | ||||
| 			staking.EndBlocker(suite.ctx, suite.stakingKeeper) | ||||
| 
 | ||||
| 			// Check that Staking hooks initialized a HardLiquidityProviderClaim
 | ||||
| 			claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0]) | ||||
| 			suite.Require().True(found) | ||||
| 			suite.Require().Equal(sdk.ZeroDec(), claim.DelegatorRewardIndexes[0].RewardFactor) | ||||
| 
 | ||||
| 			// Run accumulator at several intervals
 | ||||
| 			var timeElapsed int | ||||
| 			previousBlockTime := suite.ctx.BlockTime() | ||||
| 			for _, t := range tc.args.blockTimes { | ||||
| 				timeElapsed += t | ||||
| 				updatedBlockTime := previousBlockTime.Add(time.Duration(int(time.Second) * t)) | ||||
| 				previousBlockTime = updatedBlockTime | ||||
| 				blockCtx := suite.ctx.WithBlockTime(updatedBlockTime) | ||||
| 
 | ||||
| 				// Run Hard begin blocker for each block ctx to update denom's interest factor
 | ||||
| 				hard.BeginBlocker(blockCtx, suite.hardKeeper) | ||||
| 
 | ||||
| 				// Accumulate hard delegator rewards
 | ||||
| 				rewardPeriod, found := suite.keeper.GetHardDelegatorRewardPeriod(blockCtx, tc.args.delegation.Denom) | ||||
| 				suite.Require().True(found) | ||||
| 				err := suite.keeper.AccumulateHardDelegatorRewards(blockCtx, rewardPeriod) | ||||
| 				suite.Require().NoError(err) | ||||
| 			} | ||||
| 			updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed)) | ||||
| 			suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) | ||||
| 
 | ||||
| 			// Check that the synced claim held in memory has properly simulated syncing
 | ||||
| 			syncedClaim := suite.keeper.SimulateHardSynchronization(suite.ctx, claim) | ||||
| 			for _, expectedRewardIndex := range tc.args.expectedRewardIndexes { | ||||
| 				// Check that the user's claim's reward index matches the expected reward index
 | ||||
| 				rewardIndex, found := syncedClaim.DelegatorRewardIndexes.GetRewardIndex(expectedRewardIndex.CollateralType) | ||||
| 				suite.Require().True(found) | ||||
| 				suite.Require().Equal(expectedRewardIndex, rewardIndex) | ||||
| 
 | ||||
| 				// Check that the user's claim holds the expected amount of reward coins
 | ||||
| 				suite.Require().Equal( | ||||
| 					tc.args.expectedRewards.AmountOf(expectedRewardIndex.CollateralType), | ||||
| 					syncedClaim.Reward.AmountOf(expectedRewardIndex.CollateralType), | ||||
| 				) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (suite *KeeperTestSuite) deliverMsgCreateValidator(ctx sdk.Context, address sdk.ValAddress, selfDelegation sdk.Coin) error { | ||||
| 	msg := staking.NewMsgCreateValidator( | ||||
| 		address, | ||||
| 		ed25519.GenPrivKey().PubKey(), | ||||
| 		selfDelegation, | ||||
| 		staking.Description{}, | ||||
| 		staking.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), | ||||
| 		sdk.NewInt(1_000_000), | ||||
| 	) | ||||
| 	handleStakingMsg := staking.NewHandler(suite.stakingKeeper) | ||||
| 	_, err := handleStakingMsg(ctx, msg) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (suite *KeeperTestSuite) deliverMsgDelegate(ctx sdk.Context, delegator sdk.AccAddress, validator sdk.ValAddress, amount sdk.Coin) error { | ||||
| 	msg := staking.NewMsgDelegate( | ||||
| 		delegator, | ||||
| 		validator, | ||||
| 		amount, | ||||
| 	) | ||||
| 	handleStakingMsg := staking.NewHandler(suite.stakingKeeper) | ||||
| 	_, err := handleStakingMsg(ctx, msg) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (suite *KeeperTestSuite) deliverMsgRedelegate(ctx sdk.Context, delegator sdk.AccAddress, sourceValidator, destinationValidator sdk.ValAddress, amount sdk.Coin) error { | ||||
| 	msg := staking.NewMsgBeginRedelegate( | ||||
| 		delegator, | ||||
| 		sourceValidator, | ||||
| 		destinationValidator, | ||||
| 		amount, | ||||
| 	) | ||||
| 	handleStakingMsg := staking.NewHandler(suite.stakingKeeper) | ||||
| 	_, err := handleStakingMsg(ctx, msg) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // given a user has a delegation to a bonded validator, when the validator starts unbonding, the user does not accumulate rewards
 | ||||
| func (suite *KeeperTestSuite) TestUnbondingValidatorSyncsClaim() { | ||||
| 	suite.SetupWithGenState() | ||||
| 	initialTime := time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC) | ||||
| 	suite.ctx = suite.ctx.WithBlockTime(initialTime) | ||||
| 	blockDuration := 10 * time.Second | ||||
| 
 | ||||
| 	// Setup incentive state
 | ||||
| 	rewardsPerSecond := c("hard", 122354) | ||||
| 	bondDenom := "ukava" | ||||
| 	params := types.NewParams( | ||||
| 		nil, | ||||
| 		nil, | ||||
| 		nil, | ||||
| 		types.RewardPeriods{ | ||||
| 			types.NewRewardPeriod(true, bondDenom, initialTime.Add(-1*oneYear), initialTime.Add(4*oneYear), rewardsPerSecond), | ||||
| 		}, | ||||
| 		types.DefaultMultipliers, | ||||
| 		initialTime.Add(5*oneYear), | ||||
| 	) | ||||
| 	suite.keeper.SetParams(suite.ctx, params) | ||||
| 	suite.keeper.SetPreviousHardDelegatorRewardAccrualTime(suite.ctx, bondDenom, initialTime) | ||||
| 	suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, bondDenom, sdk.ZeroDec()) | ||||
| 
 | ||||
| 	// Reduce the size of the validator set
 | ||||
| 	stakingParams := suite.app.GetStakingKeeper().GetParams(suite.ctx) | ||||
| 	stakingParams.MaxValidators = 2 | ||||
| 	suite.app.GetStakingKeeper().SetParams(suite.ctx, stakingParams) | ||||
| 
 | ||||
| 	// Create 3 validators
 | ||||
| 	err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], c(bondDenom, 10_000_000)) | ||||
| 	suite.Require().NoError(err) | ||||
| 	err = suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[1], c(bondDenom, 5_000_000)) | ||||
| 	suite.Require().NoError(err) | ||||
| 	err = suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[2], c(bondDenom, 1_000_000)) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	// End the block so top validators become bonded
 | ||||
| 	_ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{}) | ||||
| 
 | ||||
| 	suite.ctx = suite.ctx.WithBlockTime(initialTime.Add(1 * blockDuration)) | ||||
| 	_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{}) // height and time in header are ignored by module begin blockers
 | ||||
| 
 | ||||
| 	// Delegate to a bonded validator from the test user. This will initialize their incentive claim.
 | ||||
| 	err = suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[1], c(bondDenom, 1_000_000)) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	// Start a new block to accumulate some delegation rewards for the user.
 | ||||
| 	_ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{}) | ||||
| 	suite.ctx = suite.ctx.WithBlockTime(initialTime.Add(2 * blockDuration)) | ||||
| 	_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{}) // height and time in header are ignored by module begin blockers
 | ||||
| 
 | ||||
| 	// Delegate to the unbonded validator to push it into the bonded validator set, pushing out the user's delegated validator
 | ||||
| 	err = suite.deliverMsgDelegate(suite.ctx, suite.addrs[2], suite.validatorAddrs[2], c(bondDenom, 8_000_000)) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	// End the block to start unbonding the user's validator
 | ||||
| 	_ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{}) | ||||
| 	// but don't start the next block as it will accumulate delegator rewards and we won't be able to tell if the user's reward was synced.
 | ||||
| 
 | ||||
| 	// Check that the user's claim has been synced. ie rewards added, index updated
 | ||||
| 	claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0]) | ||||
| 	suite.Require().True(found) | ||||
| 
 | ||||
| 	globalIndex, found := suite.keeper.GetHardDelegatorRewardFactor(suite.ctx, bondDenom) | ||||
| 	suite.Require().True(found) | ||||
| 	claimIndex, found := claim.DelegatorRewardIndexes.GetRewardIndex(bondDenom) | ||||
| 	suite.Require().True(found) | ||||
| 	suite.Require().Equal(globalIndex, claimIndex.RewardFactor) | ||||
| 
 | ||||
| 	suite.Require().Equal( | ||||
| 		cs(c(rewardsPerSecond.Denom, 76471)), | ||||
| 		claim.Reward, | ||||
| 	) | ||||
| 
 | ||||
| 	// Run another block and check the claim is not accumulating more rewards
 | ||||
| 	suite.ctx = suite.ctx.WithBlockTime(initialTime.Add(3 * blockDuration)) | ||||
| 	_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{}) | ||||
| 
 | ||||
| 	suite.keeper.SynchronizeHardDelegatorRewards(suite.ctx, suite.addrs[0], nil, false) | ||||
| 
 | ||||
| 	// rewards are the same as before
 | ||||
| 	laterClaim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0]) | ||||
| 	suite.Require().True(found) | ||||
| 	suite.Require().Equal(claim.Reward, laterClaim.Reward) | ||||
| 
 | ||||
| 	// claim index has been updated to latest global value
 | ||||
| 	laterClaimIndex, found := laterClaim.DelegatorRewardIndexes.GetRewardIndex(bondDenom) | ||||
| 	suite.Require().True(found) | ||||
| 	globalIndex, found = suite.keeper.GetHardDelegatorRewardFactor(suite.ctx, bondDenom) | ||||
| 	suite.Require().True(found) | ||||
| 	suite.Require().Equal(globalIndex, laterClaimIndex.RewardFactor) | ||||
| } | ||||
| 
 | ||||
| // given a user has a delegation to an unbonded validator, when the validator becomes bonded, the user starts accumulating rewards
 | ||||
| func (suite *KeeperTestSuite) TestBondingValidatorSyncsClaim() { | ||||
| 	suite.SetupWithGenState() | ||||
| 	initialTime := time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC) | ||||
| 	suite.ctx = suite.ctx.WithBlockTime(initialTime) | ||||
| 	blockDuration := 10 * time.Second | ||||
| 
 | ||||
| 	// Setup incentive state
 | ||||
| 	rewardsPerSecond := c("hard", 122354) | ||||
| 	bondDenom := "ukava" | ||||
| 	params := types.NewParams( | ||||
| 		nil, | ||||
| 		nil, | ||||
| 		nil, | ||||
| 		types.RewardPeriods{ | ||||
| 			types.NewRewardPeriod(true, bondDenom, initialTime.Add(-1*oneYear), initialTime.Add(4*oneYear), rewardsPerSecond), | ||||
| 		}, | ||||
| 		types.DefaultMultipliers, | ||||
| 		initialTime.Add(5*oneYear), | ||||
| 	) | ||||
| 	suite.keeper.SetParams(suite.ctx, params) | ||||
| 	suite.keeper.SetPreviousHardDelegatorRewardAccrualTime(suite.ctx, bondDenom, initialTime) | ||||
| 	suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, bondDenom, sdk.ZeroDec()) | ||||
| 
 | ||||
| 	// Reduce the size of the validator set
 | ||||
| 	stakingParams := suite.app.GetStakingKeeper().GetParams(suite.ctx) | ||||
| 	stakingParams.MaxValidators = 2 | ||||
| 	suite.app.GetStakingKeeper().SetParams(suite.ctx, stakingParams) | ||||
| 
 | ||||
| 	// Create 3 validators
 | ||||
| 	err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], c(bondDenom, 10_000_000)) | ||||
| 	suite.Require().NoError(err) | ||||
| 	err = suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[1], c(bondDenom, 5_000_000)) | ||||
| 	suite.Require().NoError(err) | ||||
| 	err = suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[2], c(bondDenom, 1_000_000)) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	// End the block so top validators become bonded
 | ||||
| 	_ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{}) | ||||
| 
 | ||||
| 	suite.ctx = suite.ctx.WithBlockTime(initialTime.Add(1 * blockDuration)) | ||||
| 	_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{}) // height and time in header are ignored by module begin blockers
 | ||||
| 
 | ||||
| 	// Delegate to an unbonded validator from the test user. This will initialize their incentive claim.
 | ||||
| 	err = suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[2], c(bondDenom, 1_000_000)) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	// Start a new block to accumulate some delegation rewards globally.
 | ||||
| 	_ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{}) | ||||
| 	suite.ctx = suite.ctx.WithBlockTime(initialTime.Add(2 * blockDuration)) | ||||
| 	_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{}) | ||||
| 
 | ||||
| 	// Delegate to the user's unbonded validator to push it into the bonded validator set
 | ||||
| 	err = suite.deliverMsgDelegate(suite.ctx, suite.addrs[2], suite.validatorAddrs[2], c(bondDenom, 4_000_000)) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	// End the block to bond the user's validator
 | ||||
| 	_ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{}) | ||||
| 	// but don't start the next block as it will accumulate delegator rewards and we won't be able to tell if the user's reward was synced.
 | ||||
| 
 | ||||
| 	// Check that the user's claim has been synced. ie rewards added, index updated
 | ||||
| 	claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0]) | ||||
| 	suite.Require().True(found) | ||||
| 
 | ||||
| 	globalIndex, found := suite.keeper.GetHardDelegatorRewardFactor(suite.ctx, bondDenom) | ||||
| 	suite.Require().True(found) | ||||
| 	claimIndex, found := claim.DelegatorRewardIndexes.GetRewardIndex(bondDenom) | ||||
| 	suite.Require().True(found) | ||||
| 	suite.Require().Equal(globalIndex, claimIndex.RewardFactor) | ||||
| 
 | ||||
| 	suite.Require().Equal( | ||||
| 		sdk.Coins(nil), | ||||
| 		claim.Reward, | ||||
| 	) | ||||
| 
 | ||||
| 	// Run another block and check the claim is accumulating more rewards
 | ||||
| 	suite.ctx = suite.ctx.WithBlockTime(initialTime.Add(3 * blockDuration)) | ||||
| 	_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{}) | ||||
| 
 | ||||
| 	suite.keeper.SynchronizeHardDelegatorRewards(suite.ctx, suite.addrs[0], nil, false) | ||||
| 
 | ||||
| 	// rewards are greater than before
 | ||||
| 	laterClaim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0]) | ||||
| 	suite.Require().True(found) | ||||
| 	suite.Require().True(laterClaim.Reward.IsAllGT(claim.Reward)) | ||||
| 
 | ||||
| 	// claim index has been updated to latest global value
 | ||||
| 	laterClaimIndex, found := laterClaim.DelegatorRewardIndexes.GetRewardIndex(bondDenom) | ||||
| 	suite.Require().True(found) | ||||
| 	globalIndex, found = suite.keeper.GetHardDelegatorRewardFactor(suite.ctx, bondDenom) | ||||
| 	suite.Require().True(found) | ||||
| 	suite.Require().Equal(globalIndex, laterClaimIndex.RewardFactor) | ||||
| } | ||||
| 
 | ||||
| // If a validator is slashed delegators should have their claims synced
 | ||||
| func (suite *KeeperTestSuite) TestSlashingValidatorSyncsClaim() { | ||||
| 	suite.SetupWithGenState() | ||||
| 	initialTime := time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC) | ||||
| 	suite.ctx = suite.ctx.WithBlockTime(initialTime) | ||||
| 	blockDuration := 10 * time.Second | ||||
| 
 | ||||
| 	// Setup incentive state
 | ||||
| 	rewardsPerSecond := c("hard", 122354) | ||||
| 	bondDenom := "ukava" | ||||
| 	params := types.NewParams( | ||||
| 		nil, | ||||
| 		nil, | ||||
| 		nil, | ||||
| 		types.RewardPeriods{ | ||||
| 			types.NewRewardPeriod(true, bondDenom, initialTime.Add(-1*oneYear), initialTime.Add(4*oneYear), rewardsPerSecond), | ||||
| 		}, | ||||
| 		types.DefaultMultipliers, | ||||
| 		initialTime.Add(5*oneYear), | ||||
| 	) | ||||
| 	suite.keeper.SetParams(suite.ctx, params) | ||||
| 	suite.keeper.SetPreviousHardDelegatorRewardAccrualTime(suite.ctx, bondDenom, initialTime.Add(-1*blockDuration)) | ||||
| 	suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, bondDenom, sdk.ZeroDec()) | ||||
| 
 | ||||
| 	// Reduce the size of the validator set
 | ||||
| 	stakingParams := suite.app.GetStakingKeeper().GetParams(suite.ctx) | ||||
| 	stakingParams.MaxValidators = 2 | ||||
| 	suite.app.GetStakingKeeper().SetParams(suite.ctx, stakingParams) | ||||
| 
 | ||||
| 	// Create 2 validators
 | ||||
| 	err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], c(bondDenom, 10_000_000)) | ||||
| 	suite.Require().NoError(err) | ||||
| 	err = suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[1], c(bondDenom, 10_000_000)) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	// End the block so validators become bonded
 | ||||
| 	_ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{}) | ||||
| 
 | ||||
| 	suite.ctx = suite.ctx.WithBlockTime(initialTime.Add(1 * blockDuration)) | ||||
| 	_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{}) // height and time in header are ignored by module begin blockers
 | ||||
| 
 | ||||
| 	// Delegate to a bonded validator from the test user. This will initialize their incentive claim.
 | ||||
| 	err = suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[1], c(bondDenom, 1_000_000)) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	// Check that claim has been created with synced reward index but no reward coins
 | ||||
| 	initialClaim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0]) | ||||
| 	suite.True(found) | ||||
| 	initialGlobalIndex, found := suite.keeper.GetHardDelegatorRewardFactor(suite.ctx, bondDenom) | ||||
| 	suite.True(found) | ||||
| 	initialClaimIndex, found := initialClaim.DelegatorRewardIndexes.GetRewardIndex(bondDenom) | ||||
| 	suite.True(found) | ||||
| 	suite.Require().Equal(initialGlobalIndex, initialClaimIndex.RewardFactor) | ||||
| 	suite.True(initialClaim.Reward.Empty()) // Initial claim should not have any rewards
 | ||||
| 
 | ||||
| 	// Start a new block to accumulate some delegation rewards for the user.
 | ||||
| 	_ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{}) | ||||
| 	suite.ctx = suite.ctx.WithBlockTime(initialTime.Add(2 * blockDuration)) | ||||
| 	_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{}) // height and time in header are ignored by module begin blockers
 | ||||
| 
 | ||||
| 	// Fetch validator and slash them
 | ||||
| 	stakingKeeper := suite.app.GetStakingKeeper() | ||||
| 	validator, found := stakingKeeper.GetValidator(suite.ctx, suite.validatorAddrs[1]) | ||||
| 	suite.Require().True(found) | ||||
| 	suite.Require().True(validator.GetTokens().IsPositive()) | ||||
| 	fraction := sdk.NewDecWithPrec(5, 1) | ||||
| 	stakingKeeper.Slash(suite.ctx, validator.ConsAddress(), suite.ctx.BlockHeight(), 10, fraction) | ||||
| 
 | ||||
| 	// Check that the user's claim has been synced. ie rewards added, index updated
 | ||||
| 	claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0]) | ||||
| 	suite.Require().True(found) | ||||
| 	globalIndex, found := suite.keeper.GetHardDelegatorRewardFactor(suite.ctx, bondDenom) | ||||
| 	suite.Require().True(found) | ||||
| 	claimIndex, found := claim.DelegatorRewardIndexes.GetRewardIndex(bondDenom) | ||||
| 	suite.Require().True(found) | ||||
| 	suite.Require().Equal(globalIndex, claimIndex.RewardFactor) | ||||
| 
 | ||||
| 	// Check that rewards were added
 | ||||
| 	suite.Require().Equal( | ||||
| 		cs(c(rewardsPerSecond.Denom, 58264)), | ||||
| 		claim.Reward, | ||||
| 	) | ||||
| 
 | ||||
| 	// Check that reward factor increased from initial value
 | ||||
| 	suite.True(claimIndex.RewardFactor.GT(initialClaimIndex.RewardFactor)) | ||||
| } | ||||
| 
 | ||||
| // Given a delegation to a bonded validator, when a user redelegates everything to another (bonded) validator, the user's claim is synced
 | ||||
| func (suite *KeeperTestSuite) TestRedelegationSyncsClaim() { | ||||
| 	suite.SetupWithGenState() | ||||
| 	initialTime := time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC) | ||||
| 	suite.ctx = suite.ctx.WithBlockTime(initialTime) | ||||
| 	blockDuration := 10 * time.Second | ||||
| 
 | ||||
| 	// Setup incentive state
 | ||||
| 	rewardsPerSecond := c("hard", 122354) | ||||
| 	bondDenom := "ukava" | ||||
| 	params := types.NewParams( | ||||
| 		nil, | ||||
| 		nil, | ||||
| 		nil, | ||||
| 		types.RewardPeriods{ | ||||
| 			types.NewRewardPeriod(true, bondDenom, initialTime.Add(-1*oneYear), initialTime.Add(4*oneYear), rewardsPerSecond), | ||||
| 		}, | ||||
| 		types.DefaultMultipliers, | ||||
| 		initialTime.Add(5*oneYear), | ||||
| 	) | ||||
| 	suite.keeper.SetParams(suite.ctx, params) | ||||
| 	suite.keeper.SetPreviousHardDelegatorRewardAccrualTime(suite.ctx, bondDenom, initialTime) | ||||
| 	suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, bondDenom, sdk.ZeroDec()) | ||||
| 
 | ||||
| 	// Create 2 validators
 | ||||
| 	err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], c(bondDenom, 10_000_000)) | ||||
| 	suite.Require().NoError(err) | ||||
| 	err = suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[1], c(bondDenom, 5_000_000)) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	// Delegatefrom the test user. This will initialize their incentive claim.
 | ||||
| 	err = suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[0], c(bondDenom, 1_000_000)) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	// Start a new block to accumulate some delegation rewards globally.
 | ||||
| 	_ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{}) | ||||
| 	suite.ctx = suite.ctx.WithBlockTime(initialTime.Add(1 * blockDuration)) | ||||
| 	_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{}) // height and time in header are ignored by module begin blockers
 | ||||
| 
 | ||||
| 	// Redelegate the user's delegation between the two validators. This should trigger hooks that sync the user's claim.
 | ||||
| 	err = suite.deliverMsgRedelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[0], suite.validatorAddrs[1], c(bondDenom, 1_000_000)) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	// Check that the user's claim has been synced. ie rewards added, index updated
 | ||||
| 	claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0]) | ||||
| 	suite.Require().True(found) | ||||
| 
 | ||||
| 	globalIndex, found := suite.keeper.GetHardDelegatorRewardFactor(suite.ctx, bondDenom) | ||||
| 	suite.Require().True(found) | ||||
| 	claimIndex, found := claim.DelegatorRewardIndexes.GetRewardIndex(bondDenom) | ||||
| 	suite.Require().True(found) | ||||
| 	suite.Require().Equal(globalIndex, claimIndex.RewardFactor) | ||||
| 	suite.Require().Equal( | ||||
| 		cs(c(rewardsPerSecond.Denom, 76471)), | ||||
| 		claim.Reward, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										427
									
								
								x/incentive/keeper/rewards_supply.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										427
									
								
								x/incentive/keeper/rewards_supply.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,427 @@ | ||||
| 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) { | ||||
| 	var supplyRewardIndexes types.MultiRewardIndexes | ||||
| 	for _, coin := range deposit.Amount { | ||||
| 		globalRewardIndexes, foundGlobalRewardIndexes := k.GetHardSupplyRewardIndexes(ctx, coin.Denom) | ||||
| 		var multiRewardIndex types.MultiRewardIndex | ||||
| 		if foundGlobalRewardIndexes { | ||||
| 			multiRewardIndex = types.NewMultiRewardIndex(coin.Denom, globalRewardIndexes) | ||||
| 		} else { | ||||
| 			multiRewardIndex = types.NewMultiRewardIndex(coin.Denom, types.RewardIndexes{}) | ||||
| 		} | ||||
| 		supplyRewardIndexes = append(supplyRewardIndexes, multiRewardIndex) | ||||
| 	} | ||||
| 
 | ||||
| 	claim, found := k.GetHardLiquidityProviderClaim(ctx, deposit.Depositor) | ||||
| 	if !found { | ||||
| 		// Instantiate claim object
 | ||||
| 		claim = types.NewHardLiquidityProviderClaim(deposit.Depositor, sdk.Coins{}, nil, nil, nil) | ||||
| 	} | ||||
| 
 | ||||
| 	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 { | ||||
| 		globalRewardIndexes, foundGlobalRewardIndexes := k.GetHardSupplyRewardIndexes(ctx, coin.Denom) | ||||
| 		if !foundGlobalRewardIndexes { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		userMultiRewardIndex, foundUserMultiRewardIndex := claim.SupplyRewardIndexes.GetRewardIndex(coin.Denom) | ||||
| 		if !foundUserMultiRewardIndex { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		userRewardIndexIndex, foundUserRewardIndexIndex := claim.SupplyRewardIndexes.GetRewardIndexIndex(coin.Denom) | ||||
| 		if !foundUserRewardIndexIndex { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		for _, globalRewardIndex := range globalRewardIndexes { | ||||
| 			userRewardIndex, foundUserRewardIndex := userMultiRewardIndex.RewardIndexes.GetRewardIndex(globalRewardIndex.CollateralType) | ||||
| 			if !foundUserRewardIndex { | ||||
| 				// User deposited this coin type before it had rewards. When new rewards are added, legacy depositors
 | ||||
| 				// should immediately begin earning rewards. Enable users to do so by updating their claim with the global
 | ||||
| 				// reward index denom and start their reward factor at 0.0
 | ||||
| 				userRewardIndex = types.NewRewardIndex(globalRewardIndex.CollateralType, sdk.ZeroDec()) | ||||
| 				userMultiRewardIndex.RewardIndexes = append(userMultiRewardIndex.RewardIndexes, userRewardIndex) | ||||
| 				claim.SupplyRewardIndexes[userRewardIndexIndex] = userMultiRewardIndex | ||||
| 			} | ||||
| 
 | ||||
| 			globalRewardFactor := globalRewardIndex.RewardFactor | ||||
| 			userRewardFactor := userRewardIndex.RewardFactor | ||||
| 			rewardsAccumulatedFactor := globalRewardFactor.Sub(userRewardFactor) | ||||
| 			if rewardsAccumulatedFactor.IsNegative() { | ||||
| 				panic(fmt.Sprintf("reward accumulation factor cannot be negative: %s", rewardsAccumulatedFactor)) | ||||
| 			} | ||||
| 
 | ||||
| 			newRewardsAmount := rewardsAccumulatedFactor.Mul(deposit.Amount.AmountOf(coin.Denom).ToDec()).RoundInt() | ||||
| 
 | ||||
| 			factorIndex, foundFactorIndex := userMultiRewardIndex.RewardIndexes.GetFactorIndex(globalRewardIndex.CollateralType) | ||||
| 			if !foundFactorIndex { // should never trigger, as we basically do this check at the start of this loop
 | ||||
| 				continue | ||||
| 			} | ||||
| 			claim.SupplyRewardIndexes[userRewardIndexIndex].RewardIndexes[factorIndex].RewardFactor = globalRewardIndex.RewardFactor | ||||
| 
 | ||||
| 			newRewardsCoin := sdk.NewCoin(userRewardIndex.CollateralType, newRewardsAmount) | ||||
| 			claim.Reward = claim.Reward.Add(newRewardsCoin) | ||||
| 		} | ||||
| 	} | ||||
| 	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 { | ||||
| 		claim = types.NewHardLiquidityProviderClaim(deposit.Depositor, sdk.Coins{}, nil, nil, nil) | ||||
| 	} | ||||
| 
 | ||||
| 	depositDenoms := getDenoms(deposit.Amount) | ||||
| 	supplyRewardIndexDenoms := claim.SupplyRewardIndexes.GetCollateralTypes() | ||||
| 
 | ||||
| 	uniqueDepositDenoms := setDifference(depositDenoms, supplyRewardIndexDenoms) | ||||
| 	uniqueSupplyRewardDenoms := setDifference(supplyRewardIndexDenoms, depositDenoms) | ||||
| 
 | ||||
| 	supplyRewardIndexes := claim.SupplyRewardIndexes | ||||
| 	// Create a new multi-reward index in the claim for every new deposit denom
 | ||||
| 	for _, denom := range uniqueDepositDenoms { | ||||
| 		_, foundUserRewardIndexes := claim.SupplyRewardIndexes.GetRewardIndex(denom) | ||||
| 		if !foundUserRewardIndexes { | ||||
| 			globalSupplyRewardIndexes, foundGlobalSupplyRewardIndexes := k.GetHardSupplyRewardIndexes(ctx, denom) | ||||
| 			var multiRewardIndex types.MultiRewardIndex | ||||
| 			if foundGlobalSupplyRewardIndexes { | ||||
| 				multiRewardIndex = types.NewMultiRewardIndex(denom, globalSupplyRewardIndexes) | ||||
| 			} else { | ||||
| 				multiRewardIndex = types.NewMultiRewardIndex(denom, types.RewardIndexes{}) | ||||
| 			} | ||||
| 			supplyRewardIndexes = append(supplyRewardIndexes, multiRewardIndex) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Delete multi-reward index from claim if the collateral type is no longer deposited
 | ||||
| 	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) | ||||
| 	} | ||||
| 
 | ||||
| 	// Synchronize any hard delegator rewards
 | ||||
| 	k.SynchronizeHardDelegatorRewards(ctx, owner, nil, false) | ||||
| } | ||||
| 
 | ||||
| // 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) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// 3. Simulate Hard delegator rewards
 | ||||
| 	delagatorFactor, found := k.GetHardDelegatorRewardFactor(ctx, types.BondDenom) | ||||
| 	if !found { | ||||
| 		return claim | ||||
| 	} | ||||
| 
 | ||||
| 	delegatorIndex, hasDelegatorRewardIndex := claim.HasDelegatorRewardIndex(types.BondDenom) | ||||
| 	if !hasDelegatorRewardIndex { | ||||
| 		return claim | ||||
| 	} | ||||
| 
 | ||||
| 	userRewardFactor := claim.DelegatorRewardIndexes[delegatorIndex].RewardFactor | ||||
| 	rewardsAccumulatedFactor := delagatorFactor.Sub(userRewardFactor) | ||||
| 	if rewardsAccumulatedFactor.IsZero() { | ||||
| 		return claim | ||||
| 	} | ||||
| 	claim.DelegatorRewardIndexes[delegatorIndex].RewardFactor = delagatorFactor | ||||
| 
 | ||||
| 	totalDelegated := sdk.ZeroDec() | ||||
| 
 | ||||
| 	delegations := k.stakingKeeper.GetDelegatorDelegations(ctx, claim.GetOwner(), 200) | ||||
| 	for _, delegation := range delegations { | ||||
| 		validator, found := k.stakingKeeper.GetValidator(ctx, delegation.GetValidatorAddr()) | ||||
| 		if !found { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// Delegators don't accumulate rewards if their validator is unbonded/slashed
 | ||||
| 		if validator.GetStatus() != sdk.Bonded { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if validator.GetTokens().IsZero() { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		delegatedTokens := validator.TokensFromShares(delegation.GetShares()) | ||||
| 		if delegatedTokens.IsZero() || delegatedTokens.IsNegative() { | ||||
| 			continue | ||||
| 		} | ||||
| 		totalDelegated = totalDelegated.Add(delegatedTokens) | ||||
| 	} | ||||
| 
 | ||||
| 	rewardsEarned := rewardsAccumulatedFactor.Mul(totalDelegated).RoundInt() | ||||
| 	if rewardsEarned.IsZero() || rewardsEarned.IsNegative() { | ||||
| 		return claim | ||||
| 	} | ||||
| 
 | ||||
| 	// Add rewards to delegator's hard claim
 | ||||
| 	newRewardsCoin := sdk.NewCoin(types.HardLiquidityRewardDenom, rewardsEarned) | ||||
| 	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 | ||||
| } | ||||
							
								
								
									
										1131
									
								
								x/incentive/keeper/rewards_supply_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1131
									
								
								x/incentive/keeper/rewards_supply_test.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										188
									
								
								x/incentive/keeper/rewards_usdx.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								x/incentive/keeper/rewards_usdx.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,188 @@ | ||||
| package keeper | ||||
| 
 | ||||
| import ( | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 
 | ||||
| 	cdptypes "github.com/kava-labs/kava/x/cdp/types" | ||||
| 	"github.com/kava-labs/kava/x/incentive/types" | ||||
| ) | ||||
| 
 | ||||
| // AccumulateUSDXMintingRewards updates the rewards accumulated for the input reward period
 | ||||
| func (k Keeper) AccumulateUSDXMintingRewards(ctx sdk.Context, rewardPeriod types.RewardPeriod) error { | ||||
| 	previousAccrualTime, found := k.GetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType) | ||||
| 	if !found { | ||||
| 		k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) | ||||
| 		return nil | ||||
| 	} | ||||
| 	timeElapsed := CalculateTimeElapsed(rewardPeriod.Start, rewardPeriod.End, ctx.BlockTime(), previousAccrualTime) | ||||
| 	if timeElapsed.IsZero() { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if rewardPeriod.RewardsPerSecond.Amount.IsZero() { | ||||
| 		k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) | ||||
| 		return nil | ||||
| 	} | ||||
| 	totalPrincipal := k.cdpKeeper.GetTotalPrincipal(ctx, rewardPeriod.CollateralType, types.PrincipalDenom).ToDec() | ||||
| 	if totalPrincipal.IsZero() { | ||||
| 		k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) | ||||
| 		return nil | ||||
| 	} | ||||
| 	newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount) | ||||
| 	cdpFactor, found := k.cdpKeeper.GetInterestFactor(ctx, rewardPeriod.CollateralType) | ||||
| 	if !found { | ||||
| 		k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) | ||||
| 		return nil | ||||
| 	} | ||||
| 	rewardFactor := newRewards.ToDec().Mul(cdpFactor).Quo(totalPrincipal) | ||||
| 
 | ||||
| 	previousRewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, rewardPeriod.CollateralType) | ||||
| 	if !found { | ||||
| 		previousRewardFactor = sdk.ZeroDec() | ||||
| 	} | ||||
| 	newRewardFactor := previousRewardFactor.Add(rewardFactor) | ||||
| 	k.SetUSDXMintingRewardFactor(ctx, rewardPeriod.CollateralType, newRewardFactor) | ||||
| 	k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // InitializeUSDXMintingClaim creates or updates a claim such that no new rewards are accrued, but any existing rewards are not lost.
 | ||||
| // this function should be called after a cdp is created. If a user previously had a cdp, then closed it, they shouldn't
 | ||||
| // accrue rewards during the period the cdp was closed. By setting the reward factor to the current global reward factor,
 | ||||
| // any unclaimed rewards are preserved, but no new rewards are added.
 | ||||
| func (k Keeper) InitializeUSDXMintingClaim(ctx sdk.Context, cdp cdptypes.CDP) { | ||||
| 	_, found := k.GetUSDXMintingRewardPeriod(ctx, cdp.Type) | ||||
| 	if !found { | ||||
| 		// this collateral type is not incentivized, do nothing
 | ||||
| 		return | ||||
| 	} | ||||
| 	rewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, cdp.Type) | ||||
| 	if !found { | ||||
| 		rewardFactor = sdk.ZeroDec() | ||||
| 	} | ||||
| 	claim, found := k.GetUSDXMintingClaim(ctx, cdp.Owner) | ||||
| 	if !found { // this is the owner's first usdx minting reward claim
 | ||||
| 		claim = types.NewUSDXMintingClaim(cdp.Owner, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()), types.RewardIndexes{types.NewRewardIndex(cdp.Type, rewardFactor)}) | ||||
| 		k.SetUSDXMintingClaim(ctx, claim) | ||||
| 		return | ||||
| 	} | ||||
| 	// the owner has an existing usdx minting reward claim
 | ||||
| 	index, hasRewardIndex := claim.HasRewardIndex(cdp.Type) | ||||
| 	if !hasRewardIndex { // this is the owner's first usdx minting reward for this collateral type
 | ||||
| 		claim.RewardIndexes = append(claim.RewardIndexes, types.NewRewardIndex(cdp.Type, rewardFactor)) | ||||
| 	} else { // the owner has a previous usdx minting reward for this collateral type
 | ||||
| 		claim.RewardIndexes[index] = types.NewRewardIndex(cdp.Type, rewardFactor) | ||||
| 	} | ||||
| 	k.SetUSDXMintingClaim(ctx, claim) | ||||
| } | ||||
| 
 | ||||
| // SynchronizeUSDXMintingReward updates the claim object by adding any accumulated rewards and updating the reward index value.
 | ||||
| // this should be called before a cdp is modified, immediately after the 'SynchronizeInterest' method is called in the cdp module
 | ||||
| func (k Keeper) SynchronizeUSDXMintingReward(ctx sdk.Context, cdp cdptypes.CDP) { | ||||
| 	_, found := k.GetUSDXMintingRewardPeriod(ctx, cdp.Type) | ||||
| 	if !found { | ||||
| 		// this collateral type is not incentivized, do nothing
 | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	globalRewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, cdp.Type) | ||||
| 	if !found { | ||||
| 		globalRewardFactor = sdk.ZeroDec() | ||||
| 	} | ||||
| 	claim, found := k.GetUSDXMintingClaim(ctx, cdp.Owner) | ||||
| 	if !found { | ||||
| 		claim = types.NewUSDXMintingClaim(cdp.Owner, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()), types.RewardIndexes{types.NewRewardIndex(cdp.Type, globalRewardFactor)}) | ||||
| 		k.SetUSDXMintingClaim(ctx, claim) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// the owner has an existing usdx minting reward claim
 | ||||
| 	index, hasRewardIndex := claim.HasRewardIndex(cdp.Type) | ||||
| 	if !hasRewardIndex { // this is the owner's first usdx minting reward for this collateral type
 | ||||
| 		claim.RewardIndexes = append(claim.RewardIndexes, types.NewRewardIndex(cdp.Type, globalRewardFactor)) | ||||
| 		k.SetUSDXMintingClaim(ctx, claim) | ||||
| 		return | ||||
| 	} | ||||
| 	userRewardFactor := claim.RewardIndexes[index].RewardFactor | ||||
| 	rewardsAccumulatedFactor := globalRewardFactor.Sub(userRewardFactor) | ||||
| 	if rewardsAccumulatedFactor.IsZero() { | ||||
| 		return | ||||
| 	} | ||||
| 	claim.RewardIndexes[index].RewardFactor = globalRewardFactor | ||||
| 	newRewardsAmount := rewardsAccumulatedFactor.Mul(cdp.GetTotalPrincipal().Amount.ToDec()).RoundInt() | ||||
| 	if newRewardsAmount.IsZero() { | ||||
| 		k.SetUSDXMintingClaim(ctx, claim) | ||||
| 		return | ||||
| 	} | ||||
| 	newRewardsCoin := sdk.NewCoin(types.USDXMintingRewardDenom, newRewardsAmount) | ||||
| 	claim.Reward = claim.Reward.Add(newRewardsCoin) | ||||
| 	k.SetUSDXMintingClaim(ctx, claim) | ||||
| } | ||||
| 
 | ||||
| // SimulateUSDXMintingSynchronization calculates a user's outstanding USDX minting rewards by simulating reward synchronization
 | ||||
| func (k Keeper) SimulateUSDXMintingSynchronization(ctx sdk.Context, claim types.USDXMintingClaim) types.USDXMintingClaim { | ||||
| 	for _, ri := range claim.RewardIndexes { | ||||
| 		_, found := k.GetUSDXMintingRewardPeriod(ctx, ri.CollateralType) | ||||
| 		if !found { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		globalRewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, ri.CollateralType) | ||||
| 		if !found { | ||||
| 			globalRewardFactor = sdk.ZeroDec() | ||||
| 		} | ||||
| 
 | ||||
| 		// the owner has an existing usdx minting reward claim
 | ||||
| 		index, hasRewardIndex := claim.HasRewardIndex(ri.CollateralType) | ||||
| 		if !hasRewardIndex { // this is the owner's first usdx minting reward for this collateral type
 | ||||
| 			claim.RewardIndexes = append(claim.RewardIndexes, types.NewRewardIndex(ri.CollateralType, globalRewardFactor)) | ||||
| 		} | ||||
| 		userRewardFactor := claim.RewardIndexes[index].RewardFactor | ||||
| 		rewardsAccumulatedFactor := globalRewardFactor.Sub(userRewardFactor) | ||||
| 		if rewardsAccumulatedFactor.IsZero() { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		claim.RewardIndexes[index].RewardFactor = globalRewardFactor | ||||
| 
 | ||||
| 		cdp, found := k.cdpKeeper.GetCdpByOwnerAndCollateralType(ctx, claim.GetOwner(), ri.CollateralType) | ||||
| 		if !found { | ||||
| 			continue | ||||
| 		} | ||||
| 		newRewardsAmount := rewardsAccumulatedFactor.Mul(cdp.GetTotalPrincipal().Amount.ToDec()).RoundInt() | ||||
| 		if newRewardsAmount.IsZero() { | ||||
| 			continue | ||||
| 		} | ||||
| 		newRewardsCoin := sdk.NewCoin(types.USDXMintingRewardDenom, newRewardsAmount) | ||||
| 		claim.Reward = claim.Reward.Add(newRewardsCoin) | ||||
| 	} | ||||
| 
 | ||||
| 	return claim | ||||
| } | ||||
| 
 | ||||
| // SynchronizeUSDXMintingClaim updates the claim object by adding any rewards that have accumulated.
 | ||||
| // Returns the updated claim object
 | ||||
| func (k Keeper) SynchronizeUSDXMintingClaim(ctx sdk.Context, claim types.USDXMintingClaim) (types.USDXMintingClaim, error) { | ||||
| 	for _, ri := range claim.RewardIndexes { | ||||
| 		cdp, found := k.cdpKeeper.GetCdpByOwnerAndCollateralType(ctx, claim.Owner, ri.CollateralType) | ||||
| 		if !found { | ||||
| 			// if the cdp for this collateral type has been closed, no updates are needed
 | ||||
| 			continue | ||||
| 		} | ||||
| 		claim = k.synchronizeRewardAndReturnClaim(ctx, cdp) | ||||
| 	} | ||||
| 	return claim, nil | ||||
| } | ||||
| 
 | ||||
| // this function assumes a claim already exists, so don't call it if that's not the case
 | ||||
| func (k Keeper) synchronizeRewardAndReturnClaim(ctx sdk.Context, cdp cdptypes.CDP) types.USDXMintingClaim { | ||||
| 	k.SynchronizeUSDXMintingReward(ctx, cdp) | ||||
| 	claim, _ := k.GetUSDXMintingClaim(ctx, cdp.Owner) | ||||
| 	return claim | ||||
| } | ||||
| 
 | ||||
| // ZeroUSDXMintingClaim zeroes out the claim object's rewards and returns the updated claim object
 | ||||
| func (k Keeper) ZeroUSDXMintingClaim(ctx sdk.Context, claim types.USDXMintingClaim) types.USDXMintingClaim { | ||||
| 	claim.Reward = sdk.NewCoin(claim.Reward.Denom, sdk.ZeroInt()) | ||||
| 	k.SetUSDXMintingClaim(ctx, claim) | ||||
| 	return claim | ||||
| } | ||||
							
								
								
									
										308
									
								
								x/incentive/keeper/rewards_usdx_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										308
									
								
								x/incentive/keeper/rewards_usdx_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,308 @@ | ||||
| package keeper_test | ||||
| 
 | ||||
| import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 
 | ||||
| 	cdptypes "github.com/kava-labs/kava/x/cdp/types" | ||||
| 	"github.com/kava-labs/kava/x/incentive/types" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	oneYear time.Duration = time.Hour * 24 * 365 | ||||
| ) | ||||
| 
 | ||||
| func (suite *KeeperTestSuite) TestAccumulateUSDXMintingRewards() { | ||||
| 	type args struct { | ||||
| 		ctype                 string | ||||
| 		rewardsPerSecond      sdk.Coin | ||||
| 		initialTime           time.Time | ||||
| 		initialTotalPrincipal sdk.Coin | ||||
| 		timeElapsed           int | ||||
| 		expectedRewardFactor  sdk.Dec | ||||
| 	} | ||||
| 	type test struct { | ||||
| 		name string | ||||
| 		args args | ||||
| 	} | ||||
| 	testCases := []test{ | ||||
| 		{ | ||||
| 			"7 seconds", | ||||
| 			args{ | ||||
| 				ctype:                 "bnb-a", | ||||
| 				rewardsPerSecond:      c("ukava", 122354), | ||||
| 				initialTime:           time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), | ||||
| 				initialTotalPrincipal: c("usdx", 1000000000000), | ||||
| 				timeElapsed:           7, | ||||
| 				expectedRewardFactor:  d("0.000000856478000000"), | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"1 day", | ||||
| 			args{ | ||||
| 				ctype:                 "bnb-a", | ||||
| 				rewardsPerSecond:      c("ukava", 122354), | ||||
| 				initialTime:           time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), | ||||
| 				initialTotalPrincipal: c("usdx", 1000000000000), | ||||
| 				timeElapsed:           86400, | ||||
| 				expectedRewardFactor:  d("0.0105713856"), | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"0 seconds", | ||||
| 			args{ | ||||
| 				ctype:                 "bnb-a", | ||||
| 				rewardsPerSecond:      c("ukava", 122354), | ||||
| 				initialTime:           time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), | ||||
| 				initialTotalPrincipal: c("usdx", 1000000000000), | ||||
| 				timeElapsed:           0, | ||||
| 				expectedRewardFactor:  d("0.0"), | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tc := range testCases { | ||||
| 		suite.Run(tc.name, func() { | ||||
| 			suite.SetupWithGenState() | ||||
| 			suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) | ||||
| 
 | ||||
| 			// setup cdp state
 | ||||
| 			cdpKeeper := suite.app.GetCDPKeeper() | ||||
| 			cdpKeeper.SetTotalPrincipal(suite.ctx, tc.args.ctype, cdptypes.DefaultStableDenom, tc.args.initialTotalPrincipal.Amount) | ||||
| 
 | ||||
| 			// setup incentive state
 | ||||
| 			params := types.NewParams( | ||||
| 				types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, | ||||
| 				types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond))}, | ||||
| 				types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond))}, | ||||
| 				types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, | ||||
| 				types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, | ||||
| 				tc.args.initialTime.Add(time.Hour*24*365*5), | ||||
| 			) | ||||
| 			suite.keeper.SetParams(suite.ctx, params) | ||||
| 			suite.keeper.SetPreviousUSDXMintingAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime) | ||||
| 			suite.keeper.SetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec()) | ||||
| 
 | ||||
| 			updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed)) | ||||
| 			suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) | ||||
| 			rewardPeriod, found := suite.keeper.GetUSDXMintingRewardPeriod(suite.ctx, tc.args.ctype) | ||||
| 			suite.Require().True(found) | ||||
| 			err := suite.keeper.AccumulateUSDXMintingRewards(suite.ctx, rewardPeriod) | ||||
| 			suite.Require().NoError(err) | ||||
| 
 | ||||
| 			rewardFactor, found := suite.keeper.GetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype) | ||||
| 			suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (suite *KeeperTestSuite) TestSynchronizeUSDXMintingReward() { | ||||
| 	type args struct { | ||||
| 		ctype                string | ||||
| 		rewardsPerSecond     sdk.Coin | ||||
| 		initialTime          time.Time | ||||
| 		initialCollateral    sdk.Coin | ||||
| 		initialPrincipal     sdk.Coin | ||||
| 		blockTimes           []int | ||||
| 		expectedRewardFactor sdk.Dec | ||||
| 		expectedRewards      sdk.Coin | ||||
| 	} | ||||
| 	type test struct { | ||||
| 		name string | ||||
| 		args args | ||||
| 	} | ||||
| 
 | ||||
| 	testCases := []test{ | ||||
| 		{ | ||||
| 			"10 blocks", | ||||
| 			args{ | ||||
| 				ctype:                "bnb-a", | ||||
| 				rewardsPerSecond:     c("ukava", 122354), | ||||
| 				initialTime:          time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), | ||||
| 				initialCollateral:    c("bnb", 1000000000000), | ||||
| 				initialPrincipal:     c("usdx", 10000000000), | ||||
| 				blockTimes:           []int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, | ||||
| 				expectedRewardFactor: d("0.001223540000000000"), | ||||
| 				expectedRewards:      c("ukava", 12235400), | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"10 blocks - long block time", | ||||
| 			args{ | ||||
| 				ctype:                "bnb-a", | ||||
| 				rewardsPerSecond:     c("ukava", 122354), | ||||
| 				initialTime:          time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), | ||||
| 				initialCollateral:    c("bnb", 1000000000000), | ||||
| 				initialPrincipal:     c("usdx", 10000000000), | ||||
| 				blockTimes:           []int{86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400}, | ||||
| 				expectedRewardFactor: d("10.57138560000000000"), | ||||
| 				expectedRewards:      c("ukava", 105713856000), | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tc := range testCases { | ||||
| 		suite.Run(tc.name, func() { | ||||
| 			suite.SetupWithGenState() | ||||
| 			suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) | ||||
| 
 | ||||
| 			// setup incentive state
 | ||||
| 			params := types.NewParams( | ||||
| 				types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, | ||||
| 				types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond))}, | ||||
| 				types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond))}, | ||||
| 				types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, | ||||
| 				types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, | ||||
| 				tc.args.initialTime.Add(time.Hour*24*365*5), | ||||
| 			) | ||||
| 			suite.keeper.SetParams(suite.ctx, params) | ||||
| 			suite.keeper.SetPreviousUSDXMintingAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime) | ||||
| 			suite.keeper.SetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec()) | ||||
| 
 | ||||
| 			// setup account state
 | ||||
| 			sk := suite.app.GetSupplyKeeper() | ||||
| 			sk.MintCoins(suite.ctx, cdptypes.ModuleName, sdk.NewCoins(tc.args.initialCollateral)) | ||||
| 			sk.SendCoinsFromModuleToAccount(suite.ctx, cdptypes.ModuleName, suite.addrs[0], sdk.NewCoins(tc.args.initialCollateral)) | ||||
| 
 | ||||
| 			// setup cdp state
 | ||||
| 			cdpKeeper := suite.app.GetCDPKeeper() | ||||
| 			err := cdpKeeper.AddCdp(suite.ctx, suite.addrs[0], tc.args.initialCollateral, tc.args.initialPrincipal, tc.args.ctype) | ||||
| 			suite.Require().NoError(err) | ||||
| 
 | ||||
| 			claim, found := suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0]) | ||||
| 			suite.Require().True(found) | ||||
| 			suite.Require().Equal(sdk.ZeroDec(), claim.RewardIndexes[0].RewardFactor) | ||||
| 
 | ||||
| 			var timeElapsed int | ||||
| 			previousBlockTime := suite.ctx.BlockTime() | ||||
| 			for _, t := range tc.args.blockTimes { | ||||
| 				timeElapsed += t | ||||
| 				updatedBlockTime := previousBlockTime.Add(time.Duration(int(time.Second) * t)) | ||||
| 				previousBlockTime = updatedBlockTime | ||||
| 				blockCtx := suite.ctx.WithBlockTime(updatedBlockTime) | ||||
| 				rewardPeriod, found := suite.keeper.GetUSDXMintingRewardPeriod(blockCtx, tc.args.ctype) | ||||
| 				suite.Require().True(found) | ||||
| 				err := suite.keeper.AccumulateUSDXMintingRewards(blockCtx, rewardPeriod) | ||||
| 				suite.Require().NoError(err) | ||||
| 			} | ||||
| 			updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed)) | ||||
| 			suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) | ||||
| 			cdp, found := cdpKeeper.GetCdpByOwnerAndCollateralType(suite.ctx, suite.addrs[0], tc.args.ctype) | ||||
| 			suite.Require().True(found) | ||||
| 			suite.Require().NotPanics(func() { | ||||
| 				suite.keeper.SynchronizeUSDXMintingReward(suite.ctx, cdp) | ||||
| 			}) | ||||
| 
 | ||||
| 			rewardFactor, found := suite.keeper.GetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype) | ||||
| 			suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor) | ||||
| 
 | ||||
| 			claim, found = suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0]) | ||||
| 			suite.Require().True(found) | ||||
| 			suite.Require().Equal(tc.args.expectedRewardFactor, claim.RewardIndexes[0].RewardFactor) | ||||
| 			suite.Require().Equal(tc.args.expectedRewards, claim.Reward) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (suite *KeeperTestSuite) TestSimulateUSDXMintingRewardSynchronization() { | ||||
| 	type args struct { | ||||
| 		ctype                string | ||||
| 		rewardsPerSecond     sdk.Coins | ||||
| 		initialTime          time.Time | ||||
| 		initialCollateral    sdk.Coin | ||||
| 		initialPrincipal     sdk.Coin | ||||
| 		blockTimes           []int | ||||
| 		expectedRewardFactor sdk.Dec | ||||
| 		expectedRewards      sdk.Coin | ||||
| 	} | ||||
| 	type test struct { | ||||
| 		name string | ||||
| 		args args | ||||
| 	} | ||||
| 
 | ||||
| 	testCases := []test{ | ||||
| 		{ | ||||
| 			"10 blocks", | ||||
| 			args{ | ||||
| 				ctype:                "bnb-a", | ||||
| 				rewardsPerSecond:     cs(c("ukava", 122354)), | ||||
| 				initialTime:          time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), | ||||
| 				initialCollateral:    c("bnb", 1000000000000), | ||||
| 				initialPrincipal:     c("usdx", 10000000000), | ||||
| 				blockTimes:           []int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, | ||||
| 				expectedRewardFactor: d("0.001223540000000000"), | ||||
| 				expectedRewards:      c("ukava", 12235400), | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"10 blocks - long block time", | ||||
| 			args{ | ||||
| 				ctype:                "bnb-a", | ||||
| 				rewardsPerSecond:     cs(c("ukava", 122354)), | ||||
| 				initialTime:          time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), | ||||
| 				initialCollateral:    c("bnb", 1000000000000), | ||||
| 				initialPrincipal:     c("usdx", 10000000000), | ||||
| 				blockTimes:           []int{86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400}, | ||||
| 				expectedRewardFactor: d("10.57138560000000000"), | ||||
| 				expectedRewards:      c("ukava", 105713856000), | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tc := range testCases { | ||||
| 		suite.Run(tc.name, func() { | ||||
| 			suite.SetupWithGenState() | ||||
| 			suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) | ||||
| 
 | ||||
| 			// setup incentive state
 | ||||
| 			params := types.NewParams( | ||||
| 				types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond[0])}, | ||||
| 				types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, | ||||
| 				types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, | ||||
| 				types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond[0])}, | ||||
| 				types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, | ||||
| 				tc.args.initialTime.Add(time.Hour*24*365*5), | ||||
| 			) | ||||
| 			suite.keeper.SetParams(suite.ctx, params) | ||||
| 			suite.keeper.SetParams(suite.ctx, params) | ||||
| 			suite.keeper.SetPreviousUSDXMintingAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime) | ||||
| 			suite.keeper.SetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec()) | ||||
| 
 | ||||
| 			// setup account state
 | ||||
| 			sk := suite.app.GetSupplyKeeper() | ||||
| 			sk.MintCoins(suite.ctx, cdptypes.ModuleName, sdk.NewCoins(tc.args.initialCollateral)) | ||||
| 			sk.SendCoinsFromModuleToAccount(suite.ctx, cdptypes.ModuleName, suite.addrs[0], sdk.NewCoins(tc.args.initialCollateral)) | ||||
| 
 | ||||
| 			// setup cdp state
 | ||||
| 			cdpKeeper := suite.app.GetCDPKeeper() | ||||
| 			err := cdpKeeper.AddCdp(suite.ctx, suite.addrs[0], tc.args.initialCollateral, tc.args.initialPrincipal, tc.args.ctype) | ||||
| 			suite.Require().NoError(err) | ||||
| 
 | ||||
| 			claim, found := suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0]) | ||||
| 			suite.Require().True(found) | ||||
| 			suite.Require().Equal(sdk.ZeroDec(), claim.RewardIndexes[0].RewardFactor) | ||||
| 
 | ||||
| 			var timeElapsed int | ||||
| 			previousBlockTime := suite.ctx.BlockTime() | ||||
| 			for _, t := range tc.args.blockTimes { | ||||
| 				timeElapsed += t | ||||
| 				updatedBlockTime := previousBlockTime.Add(time.Duration(int(time.Second) * t)) | ||||
| 				previousBlockTime = updatedBlockTime | ||||
| 				blockCtx := suite.ctx.WithBlockTime(updatedBlockTime) | ||||
| 				rewardPeriod, found := suite.keeper.GetUSDXMintingRewardPeriod(blockCtx, tc.args.ctype) | ||||
| 				suite.Require().True(found) | ||||
| 				err := suite.keeper.AccumulateUSDXMintingRewards(blockCtx, rewardPeriod) | ||||
| 				suite.Require().NoError(err) | ||||
| 			} | ||||
| 			updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed)) | ||||
| 			suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) | ||||
| 
 | ||||
| 			claim, found = suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0]) | ||||
| 			suite.Require().True(found) | ||||
| 			suite.Require().Equal(claim.RewardIndexes[0].RewardFactor, sdk.ZeroDec()) | ||||
| 			suite.Require().Equal(claim.Reward, sdk.NewCoin("ukava", sdk.ZeroInt())) | ||||
| 
 | ||||
| 			updatedClaim := suite.keeper.SimulateUSDXMintingSynchronization(suite.ctx, claim) | ||||
| 			suite.Require().Equal(tc.args.expectedRewardFactor, updatedClaim.RewardIndexes[0].RewardFactor) | ||||
| 			suite.Require().Equal(tc.args.expectedRewards, updatedClaim.Reward) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Ruaridh
						Ruaridh