From d47588c087b32154d97c2792d050f7202df81c25 Mon Sep 17 00:00:00 2001 From: 0xHansLee Date: Fri, 25 Oct 2024 17:46:09 -0700 Subject: [PATCH] fix(evmstaking): fix double spent issue of claimed reward --- client/x/evmstaking/keeper/withdraw.go | 277 ++++++++++++------------- 1 file changed, 131 insertions(+), 146 deletions(-) diff --git a/client/x/evmstaking/keeper/withdraw.go b/client/x/evmstaking/keeper/withdraw.go index b83ffe9b..608e4718 100644 --- a/client/x/evmstaking/keeper/withdraw.go +++ b/client/x/evmstaking/keeper/withdraw.go @@ -21,13 +21,6 @@ import ( "github.com/piplabs/story/lib/log" ) -type RewardWithdrawal struct { - DelegatorAddress string - ValidatorAddress string - WithdrawalEntry types.Withdrawal - RemainedBalance uint64 -} - func (k Keeper) ProcessUnbondingWithdrawals(ctx context.Context, unbondedEntries []stypes.UnbondedEntry) error { log.Debug(ctx, "Processing mature unbonding delegations", "count", len(unbondedEntries)) @@ -77,22 +70,9 @@ func (k Keeper) ProcessUnbondingWithdrawals(ctx context.Context, unbondedEntries func (k Keeper) ProcessRewardWithdrawals(ctx context.Context) error { log.Debug(ctx, "Processing reward withdrawals") - rewardWithdrawals, err := k.ExpectedRewardWithdrawals(ctx) - if err != nil { - return errors.Wrap(err, "get expected reward withdrawals") - } - - if err := k.EnqueueEligibleRewardWithdrawal(ctx, rewardWithdrawals); err != nil { - return errors.Wrap(err, "enqueue eligible reward withdrawals") - } - - return nil -} - -func (k Keeper) ExpectedRewardWithdrawals(ctx context.Context) ([]RewardWithdrawal, error) { validatorSweepIndex, err := k.GetValidatorSweepIndex(ctx) if err != nil { - return nil, err + return errors.Wrap(err, "get validator sweep index") } nextValIndex, nextValDelIndex := validatorSweepIndex.NextValIndex, validatorSweepIndex.NextValDelIndex @@ -100,7 +80,7 @@ func (k Keeper) ExpectedRewardWithdrawals(ctx context.Context) ([]RewardWithdraw // Get all validators first, and then do a circular sweep validatorSet, err := (k.stakingKeeper.(*skeeper.Keeper)).GetAllValidators(ctx) if err != nil { - return nil, errors.Wrap(err, "get all validators") + return errors.Wrap(err, "get all validators") } if nextValIndex >= uint64(len(validatorSet)) { @@ -116,21 +96,18 @@ func (k Keeper) ExpectedRewardWithdrawals(ctx context.Context) ([]RewardWithdraw } // Iterate all validators from `nextValidatorIndex` to find out eligible partial withdrawals. - var ( - swept uint32 - withdrawals []RewardWithdrawal - ) + var swept uint32 // Get sweep limit per block. sweepBound, err := k.MaxSweepPerBlock(ctx) if err != nil { - return nil, err + return errors.Wrap(err, "get max sweep per block") } - // Get minimal partial withdrawal amount. - minPartialWithdrawalAmount, err := k.MinPartialWithdrawalAmount(ctx) + // Get minimal reward withdrawal amount. + minRewardWithdrawalAmount, err := k.MinPartialWithdrawalAmount(ctx) if err != nil { - return nil, err + return errors.Wrap(err, "get minimum partial withdrawal amount") } // Sweep and get eligible partial withdrawals. @@ -143,86 +120,38 @@ func (k Keeper) ExpectedRewardWithdrawals(ctx context.Context) ([]RewardWithdraw continue } - // Get validator's address. - valBz, err := k.stakingKeeper.ValidatorAddressCodec().StringToBytes(validatorSet[nextValIndex].GetOperator()) - if err != nil { - return nil, errors.Wrap(err, "validator address from bech32") - } - valAddr := sdk.ValAddress(valBz) - valAccAddr := sdk.AccAddress(valAddr) - - // Get validator commissions. - valCommission, err := k.distributionKeeper.GetValidatorAccumulatedCommission(ctx, valAddr) + valAddr, err := k.stakingKeeper.ValidatorAddressCodec().StringToBytes(validatorSet[nextValIndex].GetOperator()) if err != nil { - return nil, err + return errors.Wrap(err, "convert validator address from string to bytes") } // Get all delegators of the validator. - delegators, err := (k.stakingKeeper.(*skeeper.Keeper)).GetValidatorDelegations(ctx, valAddr) + delegations, err := (k.stakingKeeper.(*skeeper.Keeper)).GetValidatorDelegations(ctx, sdk.ValAddress(valAddr)) if err != nil { - return nil, errors.Wrap(err, "get validator delegations") + return errors.Wrap(err, "get validator delegations") } - if nextValDelIndex >= uint64(len(delegators)) { + if nextValDelIndex >= uint64(len(delegations)) { nextValIndex = (nextValIndex + 1) % uint64(len(validatorSet)) nextValDelIndex = 0 continue } - nextDelegators := delegators[nextValDelIndex:] + nextDelegations := delegations[nextValDelIndex:] var shouldStopPrematurely bool // Check if the sweep should stop prematurely as the current delegator loop exceeds the sweep bound while sweeping. remainingSweep := sweepBound - swept - if uint32(len(nextDelegators)) > remainingSweep { - nextDelegators = nextDelegators[:remainingSweep] + if uint32(len(nextDelegations)) > remainingSweep { + nextDelegations = nextDelegations[:remainingSweep] shouldStopPrematurely = true } // Iterate on the validator's delegator rewards in the range [nextValDelIndex, len(delegators)]. - for i := range nextDelegators { - // Get end current period and calculate rewards. - endingPeriod, err := k.distributionKeeper.IncrementValidatorPeriod(ctx, validatorSet[nextValIndex]) - if err != nil { - return nil, err - } - - delRewards, err := k.distributionKeeper.CalculateDelegationRewards(ctx, validatorSet[nextValIndex], nextDelegators[i], endingPeriod) - if err != nil { - return nil, err - } - - if nextDelegators[i].DelegatorAddress == valAccAddr.String() { - delRewards = delRewards.Add(valCommission.Commission...) - } - - addr, err := sdk.AccAddressFromBech32(nextDelegators[i].DelegatorAddress) - if err != nil { - return nil, errors.Wrap(err, "convert acc address from bech32 address") - } - - delRewardsTruncated, _ := delRewards.TruncateDecimal() - rewardInDistrKeeper := delRewardsTruncated.AmountOf(sdk.DefaultBondDenom).Uint64() - rewardInBankKeeper := k.bankKeeper.GetBalance(ctx, addr, sdk.DefaultBondDenom).Amount.Uint64() - totalReward := rewardInDistrKeeper + rewardInBankKeeper - - if totalReward >= minPartialWithdrawalAmount { - delEvmAddr, err := k.DelegatorRewardAddress.Get(ctx, nextDelegators[i].DelegatorAddress) - if err != nil { - return nil, errors.Wrap(err, "map delegator pubkey to evm reward address") - } - - withdrawals = append(withdrawals, RewardWithdrawal{ - DelegatorAddress: nextDelegators[i].DelegatorAddress, - ValidatorAddress: valAddr.String(), - WithdrawalEntry: types.NewWithdrawal( - uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()), - delEvmAddr, - rewardInDistrKeeper, - ), - RemainedBalance: rewardInBankKeeper, - }) + for _, delegation := range nextDelegations { + if err := k.ProcessEligibleRewardWithdrawal(ctx, delegation, validatorSet[nextValIndex], minRewardWithdrawalAmount); err != nil { + return errors.Wrap(err, "process eligible reward withdrawal") } nextValDelIndex++ @@ -239,92 +168,148 @@ func (k Keeper) ExpectedRewardWithdrawals(ctx context.Context) ([]RewardWithdraw nextValDelIndex = 0 // Increase the total swept amount. - swept += uint32(len(nextDelegators)) + swept += uint32(len(nextDelegations)) } // Update the validator sweep index. if err := k.SetValidatorSweepIndex(ctx, types.NewValidatorSweepIndex(nextValIndex, nextValDelIndex)); err != nil { - return nil, err + return errors.Wrap(err, "set validator sweep index") } log.Debug( ctx, "Finish validator sweep for partial withdrawals", "next_validator_index", nextValIndex, "next_validator_delegation_index", nextValDelIndex, - "partial_withdrawals", len(withdrawals), ) - return withdrawals, nil + return nil } -func (k Keeper) EnqueueEligibleRewardWithdrawal(ctx context.Context, rewardWithdrawals []RewardWithdrawal) error { - for i := range rewardWithdrawals { - valAddr, err := sdk.ValAddressFromBech32(rewardWithdrawals[i].ValidatorAddress) - if err != nil { - return errors.Wrap(err, "validator address from bech32") - } +// ProcessEligibleRewardWithdrawal processes the reward withdrawal of delegation. +func (k Keeper) ProcessEligibleRewardWithdrawal(ctx context.Context, delegation stypes.Delegation, validator stypes.Validator, minRewardWithdrawalAmount uint64) error { + // Get validator's address. + valBz, err := k.stakingKeeper.ValidatorAddressCodec().StringToBytes(validator.GetOperator()) + if err != nil { + return errors.Wrap(err, "validator address from bech32") + } + valAddr := sdk.ValAddress(valBz) + valAccAddr := sdk.AccAddress(valAddr) + + // Get end current period and calculate rewards. + endingPeriod, err := k.distributionKeeper.IncrementValidatorPeriod(ctx, validator) + if err != nil { + return err + } - valAccAddr := sdk.AccAddress(valAddr).String() + delRewards, err := k.distributionKeeper.CalculateDelegationRewards(ctx, validator, delegation, endingPeriod) + if err != nil { + return err + } - // Withdraw delegation rewards. - delAddr := sdk.MustAccAddressFromBech32(rewardWithdrawals[i].DelegatorAddress) - delRewards, err := k.distributionKeeper.WithdrawDelegationRewards(ctx, delAddr, valAddr) + // if it is self-delegation, add commission + if delegation.DelegatorAddress == valAccAddr.String() { + // Get validator commissions. + valCommission, err := k.distributionKeeper.GetValidatorAccumulatedCommission(ctx, valAddr) if err != nil { return err } - // Withdraw commission if it is a self delegation. - if rewardWithdrawals[i].DelegatorAddress == valAccAddr { - commissionRewards, err := k.distributionKeeper.WithdrawValidatorCommission(ctx, valAddr) - if errors.Is(err, dtypes.ErrNoValidatorCommission) { - log.Debug( - ctx, "No validator commission", - "validator_addr", rewardWithdrawals[i].ValidatorAddress, - "validator_account_addr", valAccAddr, - ) - } else if err != nil { - return err - } else { - delRewards = delRewards.Add(commissionRewards...) - } - } + delRewards = delRewards.Add(valCommission.Commission...) + } - if rewardWithdrawals[i].RemainedBalance > 0 { - rewardCoins := sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(int64(rewardWithdrawals[i].RemainedBalance))) - delRewards = delRewards.Add(rewardCoins) - } + addr, err := sdk.AccAddressFromBech32(delegation.DelegatorAddress) + if err != nil { + return errors.Wrap(err, "convert acc address from bech32 address") + } - delRewardUint64 := delRewards.AmountOf(sdk.DefaultBondDenom).Uint64() - log.Debug( - ctx, "Withdraw delegator rewards", - "validator_addr", rewardWithdrawals[i].ValidatorAddress, - "validator_account_addr", valAccAddr, - "delegator_addr", rewardWithdrawals[i].DelegatorAddress, - "amount_calculate", rewardWithdrawals[i].WithdrawalEntry.Amount, - "amount_deposited", rewardWithdrawals[i].RemainedBalance, - "amount_total_withdrawal", delRewardUint64, - ) + delRewardsTruncated, _ := delRewards.TruncateDecimal() + unclaimedReward := delRewardsTruncated.AmountOf(sdk.DefaultBondDenom).Uint64() + claimedReward := k.bankKeeper.SpendableCoin(ctx, addr, sdk.DefaultBondDenom).Amount.Uint64() + totalReward := unclaimedReward + claimedReward - // Burn tokens from the delegator - err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, delAddr, types.ModuleName, delRewards) - if err != nil { - return err - } - err = k.bankKeeper.BurnCoins(ctx, types.ModuleName, delRewards) - if err != nil { - return err + // if total reward is greater than or equal to min reward withdrawal amount, enqueue the reward withdrawal + if totalReward >= minRewardWithdrawalAmount { + if err := k.EnqueueRewardWithdrawal(ctx, delegation.DelegatorAddress, valAddr.String(), claimedReward); err != nil { + return errors.Wrap(err, "enqueue reward withdrawal") } + } - // Enqueue to the global withdrawal queue. - if err := k.AddRewardWithdrawalToQueue(ctx, types.NewWithdrawal( - uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()), - rewardWithdrawals[i].WithdrawalEntry.ExecutionAddress, - delRewardUint64, - )); err != nil { + return nil +} + +// EnqueueRewardWithdrawal enqueues the reward withdrawal to mint reward IP token on EL side. +func (k Keeper) EnqueueRewardWithdrawal(ctx context.Context, delAddrBech32, valAddrBech32 string, claimedReward uint64) error { + valAddr, err := sdk.ValAddressFromBech32(valAddrBech32) + if err != nil { + return errors.Wrap(err, "validator address from bech32") + } + + valAccAddr := sdk.AccAddress(valAddr).String() + + // Withdraw delegation rewards. + delAddr := sdk.MustAccAddressFromBech32(delAddrBech32) + delRewards, err := k.distributionKeeper.WithdrawDelegationRewards(ctx, delAddr, valAddr) + if err != nil { + return err + } + + // Withdraw commission if it is a self delegation. + if delAddrBech32 == valAccAddr { + commissionRewards, err := k.distributionKeeper.WithdrawValidatorCommission(ctx, valAddr) + if errors.Is(err, dtypes.ErrNoValidatorCommission) { + log.Debug( + ctx, "No validator commission", + "validator_addr", valAddrBech32, + "validator_account_addr", valAccAddr, + ) + } else if err != nil { return err + } else { + delRewards = delRewards.Add(commissionRewards...) } } + if claimedReward > 0 { + rewardCoins := sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(int64(claimedReward))) + delRewards = delRewards.Add(rewardCoins) + } + + delRewardUint64 := delRewards.AmountOf(sdk.DefaultBondDenom).Uint64() + + withdrawalEVMAddr, err := k.DelegatorRewardAddress.Get(ctx, delAddrBech32) + if err != nil { + return errors.Wrap(err, "map delegator pubkey to evm reward address") + } + + log.Debug( + ctx, "Withdraw delegator rewards", + "validator_addr", valAddrBech32, + "validator_account_addr", valAccAddr, + "delegator_addr", delAddrBech32, + "amount_calculate", withdrawalEVMAddr, + "amount_claimed", claimedReward, + "amount_total_withdrawal", delRewardUint64, + ) + + // Burn tokens from the delegator + err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, delAddr, types.ModuleName, delRewards) + if err != nil { + return err + } + err = k.bankKeeper.BurnCoins(ctx, types.ModuleName, delRewards) + if err != nil { + return err + } + + // Enqueue to the global withdrawal queue. + if err := k.AddRewardWithdrawalToQueue(ctx, types.NewWithdrawal( + uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()), + withdrawalEVMAddr, + delRewardUint64, + )); err != nil { + return errors.Wrap(err, "add reward withdrawal to queue") + } + return nil }