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 }