github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/distribution/keeper/delegation.go (about) 1 package keeper 2 3 import ( 4 "fmt" 5 6 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 7 "github.com/fibonacci-chain/fbc/x/distribution/types" 8 stakingexported "github.com/fibonacci-chain/fbc/x/staking/exported" 9 ) 10 11 // initialize starting info for a new delegation 12 func (k Keeper) initializeDelegation(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress) { 13 if !k.CheckDistributionProposalValid(ctx) { 14 return 15 } 16 17 logger := k.Logger(ctx) 18 // period has already been incremented - we want to store the period ended by this delegation action 19 previousPeriod := k.GetValidatorCurrentRewards(ctx, val).Period - 1 20 21 // increment reference count for the period we're going to track 22 k.incrementReferenceCount(ctx, val, previousPeriod) 23 delegation := k.stakingKeeper.Delegator(ctx, del) 24 25 k.SetDelegatorStartingInfo(ctx, val, del, types.NewDelegatorStartingInfo(previousPeriod, delegation.GetLastAddedShares(), uint64(ctx.BlockHeight()))) 26 logger.Debug("initializeDelegation", "ValAddress", val, "Delegator", del, "Shares", delegation.GetLastAddedShares()) 27 } 28 29 // calculate the rewards accrued by a delegation between two periods 30 func (k Keeper) calculateDelegationRewardsBetween(ctx sdk.Context, val stakingexported.ValidatorI, 31 startingPeriod, endingPeriod uint64, stake sdk.Dec) (rewards sdk.DecCoins) { 32 logger := k.Logger(ctx) 33 // sanity check 34 if startingPeriod > endingPeriod { 35 panic("startingPeriod cannot be greater than endingPeriod") 36 } 37 38 // sanity check 39 if stake.IsNegative() { 40 panic("stake should not be negative") 41 } 42 43 // return staking * (ending - starting) 44 starting := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), startingPeriod) 45 ending := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), endingPeriod) 46 difference := ending.CumulativeRewardRatio.Sub(starting.CumulativeRewardRatio) 47 if difference.IsAnyNegative() { 48 panic("negative rewards should not be possible") 49 } 50 // note: necessary to truncate so we don't allow withdrawing more rewards than owed 51 rewards = difference.MulDecTruncate(stake) 52 logger.Debug("calculateDelegationRewardsBetween", "Validator", val.GetOperator(), 53 "Start", starting.CumulativeRewardRatio, "End", ending.CumulativeRewardRatio, "Stake", stake, 54 "Difference", difference, "Rewards", rewards) 55 return 56 } 57 58 // calculate the total rewards accrued by a delegation 59 func (k Keeper) calculateDelegationRewards(ctx sdk.Context, val stakingexported.ValidatorI, delAddr sdk.AccAddress, endingPeriod uint64) (rewards sdk.DecCoins) { 60 logger := k.Logger(ctx) 61 del := k.stakingKeeper.Delegator(ctx, delAddr) 62 63 // fetch starting info for delegation 64 startingInfo := k.GetDelegatorStartingInfo(ctx, val.GetOperator(), del.GetDelegatorAddress()) 65 66 if startingInfo.Height == uint64(ctx.BlockHeight()) { 67 // started this height, no rewards yet 68 logger.Debug(fmt.Sprintf("calculateDelegationRewards end, error, val:%s, del:%s, height:%d", 69 val.GetOperator().String(), delAddr.String(), startingInfo.Height)) 70 return 71 } 72 73 startingPeriod := startingInfo.PreviousPeriod 74 stake := startingInfo.Stake 75 if stake.GT(del.GetLastAddedShares()) { 76 panic(fmt.Sprintf("calculated final stake for delegator %s greater than current stake"+ 77 "\n\tfinal stake:\t%s"+ 78 "\n\tcurrent stake:\t%s", 79 del.GetDelegatorAddress(), stake, del.GetLastAddedShares())) 80 } 81 82 // calculate rewards for final period 83 rewards = rewards.Add(k.calculateDelegationRewardsBetween(ctx, val, startingPeriod, endingPeriod, stake)...) 84 85 logger.Debug("calculateDelegationRewards", "Validator", val.GetOperator(), 86 "Delegator", delAddr, "Start", startingPeriod, "End", endingPeriod, "Stake", stake, "Rewards", rewards) 87 88 return rewards 89 } 90 91 // withdraw rewards according to the specified validator by delegator 92 func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val stakingexported.ValidatorI, delAddress sdk.AccAddress) (sdk.Coins, error) { 93 if !k.CheckDistributionProposalValid(ctx) { 94 return nil, types.ErrCodeNotSupportWithdrawDelegationRewards() 95 } 96 97 logger := k.Logger(ctx) 98 99 // check existence of delegator starting info 100 if !k.HasDelegatorStartingInfo(ctx, val.GetOperator(), delAddress) { 101 del := k.stakingKeeper.Delegator(ctx, delAddress) 102 if del.GetLastAddedShares().IsZero() { 103 return nil, types.ErrCodeZeroDelegationShares() 104 } 105 k.initExistedDelegationStartInfo(ctx, val, del) 106 } 107 108 // end current period and calculate rewards 109 endingPeriod := k.incrementValidatorPeriod(ctx, val) 110 rewardsRaw := k.calculateDelegationRewards(ctx, val, delAddress, endingPeriod) 111 outstanding := k.GetValidatorOutstandingRewards(ctx, val.GetOperator()) 112 113 // defensive edge case may happen on the very final digits 114 // of the decCoins due to operation order of the distribution mechanism. 115 rewards := rewardsRaw.Intersect(outstanding) 116 if !rewards.IsEqual(rewardsRaw) { 117 logger.Info(fmt.Sprintf("missing rewards rounding error, delegator %v"+ 118 "withdrawing rewards from validator %v, should have received %v, got %v", 119 val.GetOperator(), delAddress, rewardsRaw, rewards)) 120 } 121 122 // truncate coins, return remainder to community pool 123 coins, remainder := rewards.TruncateWithPrec(k.GetRewardTruncatePrecision(ctx)) 124 125 // add coins to user account 126 if !coins.IsZero() { 127 withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, delAddress) 128 err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, withdrawAddr, coins) 129 logger.Debug("SendCoinsFromModuleToAccount", "From", types.ModuleName, 130 "To", withdrawAddr, "Coins", coins) 131 if err != nil { 132 return nil, err 133 } 134 } 135 136 // update the outstanding rewards and the community pool only if the 137 // transaction was successful 138 k.SetValidatorOutstandingRewards(ctx, val.GetOperator(), outstanding.Sub(rewards)) 139 feePool := k.GetFeePool(ctx) 140 feePool.CommunityPool = feePool.CommunityPool.Add(remainder...) 141 k.SetFeePool(ctx, feePool) 142 143 // decrement reference count of starting period 144 startingInfo := k.GetDelegatorStartingInfo(ctx, val.GetOperator(), delAddress) 145 startingPeriod := startingInfo.PreviousPeriod 146 k.decrementReferenceCount(ctx, val.GetOperator(), startingPeriod) 147 148 // remove delegator starting info 149 k.DeleteDelegatorStartingInfo(ctx, val.GetOperator(), delAddress) 150 151 logger.Debug("withdrawDelegationRewards", "Validator", val.GetOperator(), "Delegator", delAddress, 152 "Stake", startingInfo.Stake, "StartingPeriod", startingPeriod, "EndingPeriod", endingPeriod, 153 "RewardsRaw", rewardsRaw, "Rewards", rewards, "Coins", coins, "Remainder", remainder) 154 return coins, nil 155 } 156 157 // initExistedDelegationStartInfo If the delegator existed but no start info, it add shares before distribution proposal, and need to set a new start info 158 func (k Keeper) initExistedDelegationStartInfo(ctx sdk.Context, val stakingexported.ValidatorI, del stakingexported.DelegatorI) { 159 if !k.CheckDistributionProposalValid(ctx) { 160 return 161 } 162 163 logger := k.Logger(ctx) 164 //set previous validator period 0 165 previousPeriod := uint64(0) 166 // increment reference count for the period we're going to track 167 k.incrementReferenceCount(ctx, val.GetOperator(), previousPeriod) 168 169 k.SetDelegatorStartingInfo(ctx, val.GetOperator(), del.GetDelegatorAddress(), 170 types.NewDelegatorStartingInfo(previousPeriod, del.GetLastAddedShares(), 0)) 171 172 logger.Debug("initExistedDelegationStartInfo", "Validator", val.GetOperator(), 173 "Delegator", del.GetDelegatorAddress(), "Shares", del.GetLastAddedShares()) 174 return 175 }