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  }