github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/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 8 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/distribution/types" 9 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/staking/exported" 10 ) 11 12 // initialize starting info for a new delegation 13 func (k Keeper) initializeDelegation(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress) { 14 // period has already been incremented - we want to store the period ended by this delegation action 15 previousPeriod := k.GetValidatorCurrentRewards(ctx, val).Period - 1 16 17 // increment reference count for the period we're going to track 18 k.incrementReferenceCount(ctx, val, previousPeriod) 19 20 validator := k.stakingKeeper.Validator(ctx, val) 21 delegation := k.stakingKeeper.Delegation(ctx, del, val) 22 23 // calculate delegation stake in tokens 24 // we don't store directly, so multiply delegation shares * (tokens per share) 25 // note: necessary to truncate so we don't allow withdrawing more rewards than owed 26 stake := validator.TokensFromSharesTruncated(delegation.GetShares()) 27 k.SetDelegatorStartingInfo(ctx, val, del, types.NewDelegatorStartingInfo(previousPeriod, stake, uint64(ctx.BlockHeight()))) 28 } 29 30 // calculate the rewards accrued by a delegation between two periods 31 func (k Keeper) calculateDelegationRewardsBetween(ctx sdk.Context, val exported.ValidatorI, 32 startingPeriod, endingPeriod uint64, stake sdk.Dec) (rewards sdk.DecCoins) { 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 return 53 } 54 55 // calculate the total rewards accrued by a delegation 56 func (k Keeper) calculateDelegationRewards(ctx sdk.Context, val exported.ValidatorI, del exported.DelegationI, endingPeriod uint64) (rewards sdk.DecCoins) { 57 // fetch starting info for delegation 58 startingInfo := k.GetDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr()) 59 60 if startingInfo.Height == uint64(ctx.BlockHeight()) { 61 // started this height, no rewards yet 62 return 63 } 64 65 startingPeriod := startingInfo.PreviousPeriod 66 stake := startingInfo.Stake 67 68 // Iterate through slashes and withdraw with calculated staking for 69 // distribution periods. These period offsets are dependent on *when* slashes 70 // happen - namely, in BeginBlock, after rewards are allocated... 71 // Slashes which happened in the first block would have been before this 72 // delegation existed, UNLESS they were slashes of a redelegation to this 73 // validator which was itself slashed (from a fault committed by the 74 // redelegation source validator) earlier in the same BeginBlock. 75 startingHeight := startingInfo.Height 76 // Slashes this block happened after reward allocation, but we have to account 77 // for them for the stake sanity check below. 78 endingHeight := uint64(ctx.BlockHeight()) 79 if endingHeight > startingHeight { 80 k.IterateValidatorSlashEventsBetween(ctx, del.GetValidatorAddr(), startingHeight, endingHeight, 81 func(height uint64, event types.ValidatorSlashEvent) (stop bool) { 82 endingPeriod := event.ValidatorPeriod 83 if endingPeriod > startingPeriod { 84 rewards = rewards.Add(k.calculateDelegationRewardsBetween(ctx, val, startingPeriod, endingPeriod, stake)...) 85 86 // Note: It is necessary to truncate so we don't allow withdrawing 87 // more rewards than owed. 88 stake = stake.MulTruncate(sdk.OneDec().Sub(event.Fraction)) 89 startingPeriod = endingPeriod 90 } 91 return false 92 }, 93 ) 94 } 95 96 // A total stake sanity check; Recalculated final stake should be less than or 97 // equal to current stake here. We cannot use Equals because stake is truncated 98 // when multiplied by slash fractions (see above). We could only use equals if 99 // we had arbitrary-precision rationals. 100 currentStake := val.TokensFromShares(del.GetShares()) 101 102 if stake.GT(currentStake) { 103 // Account for rounding inconsistencies between: 104 // 105 // currentStake: calculated as in staking with a single computation 106 // stake: calculated as an accumulation of stake 107 // calculations across validator's distribution periods 108 // 109 // These inconsistencies are due to differing order of operations which 110 // will inevitably have different accumulated rounding and may lead to 111 // the smallest decimal place being one greater in stake than 112 // currentStake. When we calculated slashing by period, even if we 113 // round down for each slash fraction, it's possible due to how much is 114 // being rounded that we slash less when slashing by period instead of 115 // for when we slash without periods. In other words, the single slash, 116 // and the slashing by period could both be rounding down but the 117 // slashing by period is simply rounding down less, thus making stake > 118 // currentStake 119 // 120 // A small amount of this error is tolerated and corrected for, 121 // however any greater amount should be considered a breach in expected 122 // behaviour. 123 marginOfErr := sdk.SmallestDec().MulInt64(3) 124 if stake.LTE(currentStake.Add(marginOfErr)) { 125 stake = currentStake 126 } else { 127 panic(fmt.Sprintf("calculated final stake for delegator %s greater than current stake"+ 128 "\n\tfinal stake:\t%s"+ 129 "\n\tcurrent stake:\t%s", 130 del.GetDelegatorAddr(), stake, currentStake)) 131 } 132 } 133 134 // calculate rewards for final period 135 rewards = rewards.Add(k.calculateDelegationRewardsBetween(ctx, val, startingPeriod, endingPeriod, stake)...) 136 return rewards 137 } 138 139 func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val exported.ValidatorI, del exported.DelegationI) (sdk.Coins, error) { 140 // check existence of delegator starting info 141 if !k.HasDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr()) { 142 return nil, types.ErrEmptyDelegationDistInfo 143 } 144 145 // end current period and calculate rewards 146 endingPeriod := k.incrementValidatorPeriod(ctx, val) 147 rewardsRaw := k.calculateDelegationRewards(ctx, val, del, endingPeriod) 148 outstanding := k.GetValidatorOutstandingRewards(ctx, del.GetValidatorAddr()) 149 150 // defensive edge case may happen on the very final digits 151 // of the decCoins due to operation order of the distribution mechanism. 152 rewards := rewardsRaw.Intersect(outstanding) 153 if !rewards.IsEqual(rewardsRaw) { 154 logger := k.Logger(ctx) 155 logger.Info(fmt.Sprintf("missing rewards rounding error, delegator %v"+ 156 "withdrawing rewards from validator %v, should have received %v, got %v", 157 val.GetOperator(), del.GetDelegatorAddr(), rewardsRaw, rewards)) 158 } 159 160 // truncate coins, return remainder to community pool 161 coins, remainder := rewards.TruncateDecimal() 162 163 // add coins to user account 164 if !coins.IsZero() { 165 withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, del.GetDelegatorAddr()) 166 err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, withdrawAddr, coins) 167 if err != nil { 168 return nil, err 169 } 170 } 171 172 // update the outstanding rewards and the community pool only if the 173 // transaction was successful 174 k.SetValidatorOutstandingRewards(ctx, del.GetValidatorAddr(), outstanding.Sub(rewards)) 175 feePool := k.GetFeePool(ctx) 176 feePool.CommunityPool = feePool.CommunityPool.Add(remainder...) 177 k.SetFeePool(ctx, feePool) 178 179 // decrement reference count of starting period 180 startingInfo := k.GetDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr()) 181 startingPeriod := startingInfo.PreviousPeriod 182 k.decrementReferenceCount(ctx, del.GetValidatorAddr(), startingPeriod) 183 184 // remove delegator starting info 185 k.DeleteDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr()) 186 187 return coins, nil 188 }