github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/farm/keeper/calc.go (about)

     1  package keeper
     2  
     3  import (
     4  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
     5  	"github.com/fibonacci-chain/fbc/x/farm/types"
     6  )
     7  
     8  // CalculateAmountYieldedBetween is used for calculating how many tokens haven been yielded from
     9  // startBlockHeight to endBlockHeight. And return the amount.
    10  func (k Keeper) CalculateAmountYieldedBetween(ctx sdk.Context, pool types.FarmPool) (types.FarmPool, sdk.SysCoins) {
    11  	currentPeriod := k.GetPoolCurrentRewards(ctx, pool.Name)
    12  	endBlockHeight := ctx.BlockHeight()
    13  
    14  	totalYieldedTokens := sdk.SysCoins{}
    15  	for i := 0; i < len(pool.YieldedTokenInfos); i++ {
    16  		startBlockHeightToYield := pool.YieldedTokenInfos[i].StartBlockHeightToYield
    17  		var startBlockHeight int64
    18  		if currentPeriod.StartBlockHeight <= startBlockHeightToYield {
    19  			startBlockHeight = startBlockHeightToYield
    20  		} else {
    21  			startBlockHeight = currentPeriod.StartBlockHeight
    22  		}
    23  
    24  		// no tokens to yield
    25  		if startBlockHeightToYield == 0 || startBlockHeight >= endBlockHeight {
    26  			continue
    27  		}
    28  
    29  		yieldedTokens := sdk.SysCoins{}
    30  		// calculate how many tokens to be yielded between startBlockHeight and endBlockHeight
    31  		blockInterval := sdk.NewDec(endBlockHeight - startBlockHeight)
    32  		amount := blockInterval.MulTruncate(pool.YieldedTokenInfos[i].AmountYieldedPerBlock)
    33  		remaining := pool.YieldedTokenInfos[i].RemainingAmount
    34  		if amount.LT(remaining.Amount) {
    35  			pool.YieldedTokenInfos[i].RemainingAmount.Amount = remaining.Amount.Sub(amount)
    36  			yieldedTokens = sdk.NewDecCoinsFromDec(remaining.Denom, amount)
    37  		} else {
    38  			pool.YieldedTokenInfos[i] = types.NewYieldedTokenInfo(
    39  				sdk.NewDecCoin(remaining.Denom, sdk.ZeroInt()), 0, sdk.ZeroDec(),
    40  			)
    41  			yieldedTokens = sdk.NewDecCoinsFromDec(remaining.Denom, remaining.Amount)
    42  		}
    43  		pool.TotalAccumulatedRewards = pool.TotalAccumulatedRewards.Add2(yieldedTokens)
    44  		totalYieldedTokens = totalYieldedTokens.Add2(yieldedTokens)
    45  	}
    46  	return pool, totalYieldedTokens
    47  }
    48  
    49  func (k Keeper) WithdrawRewards(
    50  	ctx sdk.Context, poolName string, totalValueLocked sdk.SysCoin, yieldedTokens sdk.SysCoins, addr sdk.AccAddress,
    51  ) (sdk.SysCoins, sdk.Error) {
    52  	// 0. check existence of lock info
    53  	lockInfo, found := k.GetLockInfo(ctx, addr, poolName)
    54  	if !found {
    55  		return nil, types.ErrNoLockInfoFound(addr.String(), poolName)
    56  	}
    57  
    58  	// 1. end current period and calculate rewards
    59  	endingPeriod := k.IncrementPoolPeriod(ctx, poolName, totalValueLocked, yieldedTokens)
    60  	rewards := k.calculateRewards(ctx, poolName, addr, endingPeriod, lockInfo)
    61  
    62  	// 2. transfer rewards to user account
    63  	if !rewards.IsZero() {
    64  		err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.YieldFarmingAccount, addr, rewards)
    65  		if err != nil {
    66  			return nil, err
    67  		}
    68  	}
    69  
    70  	// 3. decrement reference count of lock info
    71  	k.decrementReferenceCount(ctx, poolName, lockInfo.ReferencePeriod)
    72  
    73  	return rewards, nil
    74  }
    75  
    76  // IncrementPoolPeriod increments pool period, returning the period just ended
    77  func (k Keeper) IncrementPoolPeriod(
    78  	ctx sdk.Context, poolName string, totalValueLocked sdk.SysCoin, yieldedTokens sdk.SysCoins,
    79  ) uint64 {
    80  	// 1. fetch current period rewards
    81  	rewards := k.GetPoolCurrentRewards(ctx, poolName)
    82  	// 2. calculate current reward ratio
    83  	rewards.Rewards = rewards.Rewards.Add2(yieldedTokens)
    84  	var currentRatio sdk.SysCoins
    85  	if totalValueLocked.IsZero() {
    86  		currentRatio = sdk.SysCoins{}
    87  	} else {
    88  		currentRatio = rewards.Rewards.QuoDecTruncate(totalValueLocked.Amount)
    89  	}
    90  
    91  	// 3.1 get the previous pool historical rewards
    92  	historical := k.GetPoolHistoricalRewards(ctx, poolName, rewards.Period-1).CumulativeRewardRatio
    93  	// 3.2 decrement reference count
    94  	k.decrementReferenceCount(ctx, poolName, rewards.Period-1)
    95  	// 3.3 create new pool historical rewards with reference count of 1, then set it into store
    96  	newHistoricalRewards := types.NewPoolHistoricalRewards(historical.Add2(currentRatio), 1)
    97  	k.SetPoolHistoricalRewards(ctx, poolName, rewards.Period, newHistoricalRewards)
    98  
    99  	// 4. set new current rewards into store, incrementing period by 1
   100  	newCurRewards := types.NewPoolCurrentRewards(ctx.BlockHeight(), rewards.Period+1, sdk.SysCoins{})
   101  	k.SetPoolCurrentRewards(ctx, poolName, newCurRewards)
   102  
   103  	return rewards.Period
   104  }
   105  
   106  // incrementReferenceCount increments the reference count for a historical rewards value
   107  func (k Keeper) incrementReferenceCount(ctx sdk.Context, poolName string, period uint64) {
   108  	historical := k.GetPoolHistoricalRewards(ctx, poolName, period)
   109  	if historical.ReferenceCount > 2 {
   110  		panic("reference count should never exceed 2")
   111  	}
   112  	historical.ReferenceCount++
   113  	k.SetPoolHistoricalRewards(ctx, poolName, period, historical)
   114  }
   115  
   116  // decrementReferenceCount decrements the reference count for a historical rewards value,
   117  // and delete if zero references remain.
   118  func (k Keeper) decrementReferenceCount(ctx sdk.Context, poolName string, period uint64) {
   119  	historical := k.GetPoolHistoricalRewards(ctx, poolName, period)
   120  	if historical.ReferenceCount == 0 {
   121  		panic("cannot set negative reference count")
   122  	}
   123  	historical.ReferenceCount--
   124  	if historical.ReferenceCount == 0 {
   125  		k.DeletePoolHistoricalReward(ctx, poolName, period)
   126  	} else {
   127  		k.SetPoolHistoricalRewards(ctx, poolName, period, historical)
   128  	}
   129  }
   130  
   131  func (k Keeper) calculateRewards(
   132  	ctx sdk.Context, poolName string, addr sdk.AccAddress, endingPeriod uint64, lockInfo types.LockInfo,
   133  ) (rewards sdk.SysCoins) {
   134  	if lockInfo.StartBlockHeight == ctx.BlockHeight() {
   135  		// started this height, no rewards yet
   136  		return
   137  	}
   138  
   139  	startingPeriod := lockInfo.ReferencePeriod
   140  	// calculate rewards for final period
   141  	return k.calculateLockRewardsBetween(ctx, poolName, startingPeriod, endingPeriod, lockInfo.Amount)
   142  }
   143  
   144  // calculateLockRewardsBetween calculate the rewards accrued by a pool between two periods
   145  func (k Keeper) calculateLockRewardsBetween(ctx sdk.Context, poolName string, startingPeriod, endingPeriod uint64,
   146  	amount sdk.SysCoin) (rewards sdk.SysCoins) {
   147  
   148  	// sanity check
   149  	if startingPeriod > endingPeriod {
   150  		panic("startingPeriod cannot be greater than endingPeriod")
   151  	}
   152  
   153  	if amount.Amount.LT(sdk.ZeroDec()) {
   154  		panic("amount should not be negative")
   155  	}
   156  
   157  	// return amount * (ending - starting)
   158  	starting := k.GetPoolHistoricalRewards(ctx, poolName, startingPeriod)
   159  	ending := k.GetPoolHistoricalRewards(ctx, poolName, endingPeriod)
   160  	difference := ending.CumulativeRewardRatio.Sub(starting.CumulativeRewardRatio)
   161  	rewards = difference.MulDecTruncate(amount.Amount)
   162  	return
   163  }
   164  
   165  // UpdateLockInfo updates lock info for the modified lock info
   166  func (k Keeper) UpdateLockInfo(ctx sdk.Context, addr sdk.AccAddress, poolName string, changedAmount sdk.Dec) {
   167  	// period has already been incremented - we want to store the period ended by this lock action
   168  	previousPeriod := k.GetPoolCurrentRewards(ctx, poolName).Period - 1
   169  
   170  	// get lock info, then set it into store
   171  	lockInfo, found := k.GetLockInfo(ctx, addr, poolName)
   172  	if !found {
   173  		panic("the lock info can't be found")
   174  	}
   175  	lockInfo.StartBlockHeight = ctx.BlockHeight()
   176  	lockInfo.ReferencePeriod = previousPeriod
   177  	lockInfo.Amount.Amount = lockInfo.Amount.Amount.Add(changedAmount)
   178  	if lockInfo.Amount.IsZero() {
   179  		k.DeleteLockInfo(ctx, lockInfo.Owner, lockInfo.PoolName)
   180  		k.DeleteAddressInFarmPool(ctx, lockInfo.PoolName, lockInfo.Owner)
   181  	} else {
   182  		// increment reference count for the period we're going to track
   183  		k.incrementReferenceCount(ctx, poolName, previousPeriod)
   184  
   185  		// set the updated lock info
   186  		k.SetLockInfo(ctx, lockInfo)
   187  		k.SetAddressInFarmPool(ctx, lockInfo.PoolName, lockInfo.Owner)
   188  	}
   189  }