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  }