package keeper import ( "bytes" "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/staking/types" ) // GetDelegation returns a specific delegation. func (k Keeper) GetDelegation(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (delegation types.Delegation, found bool) { store := ctx.KVStore(k.storeKey) key := types.GetDelegationKey(delAddr, valAddr) value := store.Get(key) if value == nil { return delegation, false } delegation = types.MustUnmarshalDelegation(k.cdc, value) return delegation, true } // IterateAllDelegations iterates through all of the delegations. func (k Keeper) IterateAllDelegations(ctx sdk.Context, cb func(delegation types.Delegation) (stop bool)) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, types.DelegationKey) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Value()) if cb(delegation) { break } } } // GetAllDelegations returns all delegations used during genesis dump. func (k Keeper) GetAllDelegations(ctx sdk.Context) (delegations []types.Delegation) { k.IterateAllDelegations(ctx, func(delegation types.Delegation) bool { delegations = append(delegations, delegation) return false }) return delegations } // GetValidatorDelegations returns all delegations to a specific validator. // Useful for querier. func (k Keeper) GetValidatorDelegations(ctx sdk.Context, valAddr sdk.ValAddress) (delegations []types.Delegation) { //nolint:interfacer store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, types.DelegationKey) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Value()) if delegation.GetValidatorAddr().Equals(valAddr) { delegations = append(delegations, delegation) } } return delegations } // GetDelegatorDelegations returns a given amount of all the delegations from a // delegator. func (k Keeper) GetDelegatorDelegations(ctx sdk.Context, delegator sdk.AccAddress, maxRetrieve uint16) (delegations []types.Delegation) { delegations = make([]types.Delegation, maxRetrieve) store := ctx.KVStore(k.storeKey) delegatorPrefixKey := types.GetDelegationsKey(delegator) iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) defer iterator.Close() i := 0 for ; iterator.Valid() && i < int(maxRetrieve); iterator.Next() { delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Value()) delegations[i] = delegation i++ } return delegations[:i] // trim if the array length < maxRetrieve } // SetDelegation sets a delegation. func (k Keeper) SetDelegation(ctx sdk.Context, delegation types.Delegation) { delegatorAddress, err := sdk.AccAddressFromBech32(delegation.DelegatorAddress) if err != nil { panic(err) } store := ctx.KVStore(k.storeKey) b := types.MustMarshalDelegation(k.cdc, delegation) store.Set(types.GetDelegationKey(delegatorAddress, delegation.GetValidatorAddr()), b) } // RemoveDelegation removes a delegation func (k Keeper) RemoveDelegation(ctx sdk.Context, delegation types.Delegation) error { delegatorAddress, err := sdk.AccAddressFromBech32(delegation.DelegatorAddress) if err != nil { panic(err) } // TODO: Consider calling hooks outside of the store wrapper functions, it's unobvious. if err := k.BeforeDelegationRemoved(ctx, delegatorAddress, delegation.GetValidatorAddr()); err != nil { return err } store := ctx.KVStore(k.storeKey) store.Delete(types.GetDelegationKey(delegatorAddress, delegation.GetValidatorAddr())) return nil } // GetUnbondingDelegations returns a given amount of all the delegator unbonding-delegations. func (k Keeper) GetUnbondingDelegations(ctx sdk.Context, delegator sdk.AccAddress, maxRetrieve uint16) (unbondingDelegations []types.UnbondingDelegation) { unbondingDelegations = make([]types.UnbondingDelegation, maxRetrieve) store := ctx.KVStore(k.storeKey) delegatorPrefixKey := types.GetUBDsKey(delegator) iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) defer iterator.Close() i := 0 for ; iterator.Valid() && i < int(maxRetrieve); iterator.Next() { unbondingDelegation := types.MustUnmarshalUBD(k.cdc, iterator.Value()) unbondingDelegations[i] = unbondingDelegation i++ } return unbondingDelegations[:i] // trim if the array length < maxRetrieve } // GetUnbondingDelegation returns a unbonding delegation. func (k Keeper) GetUnbondingDelegation( ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, ) (ubd types.UnbondingDelegation, found bool) { store := ctx.KVStore(k.storeKey) key := types.GetUBDKey(delAddr, valAddr) value := store.Get(key) if value == nil { return ubd, false } ubd = types.MustUnmarshalUBD(k.cdc, value) return ubd, true } // GetUnbondingDelegationsFromValidator returns all unbonding delegations from a // particular validator. func (k Keeper) GetUnbondingDelegationsFromValidator(ctx sdk.Context, valAddr sdk.ValAddress) (ubds []types.UnbondingDelegation) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, types.GetUBDsByValIndexKey(valAddr)) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { key := types.GetUBDKeyFromValIndexKey(iterator.Key()) value := store.Get(key) ubd := types.MustUnmarshalUBD(k.cdc, value) ubds = append(ubds, ubd) } return ubds } // IterateUnbondingDelegations iterates through all of the unbonding delegations. func (k Keeper) IterateUnbondingDelegations(ctx sdk.Context, fn func(index int64, ubd types.UnbondingDelegation) (stop bool)) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, types.UnbondingDelegationKey) defer iterator.Close() for i := int64(0); iterator.Valid(); iterator.Next() { ubd := types.MustUnmarshalUBD(k.cdc, iterator.Value()) if stop := fn(i, ubd); stop { break } i++ } } // GetDelegatorUnbonding returns the total amount a delegator has unbonding. func (k Keeper) GetDelegatorUnbonding(ctx sdk.Context, delegator sdk.AccAddress) sdk.Int { unbonding := sdk.ZeroInt() k.IterateDelegatorUnbondingDelegations(ctx, delegator, func(ubd types.UnbondingDelegation) bool { for _, entry := range ubd.Entries { unbonding = unbonding.Add(entry.Balance) } return false }) return unbonding } // IterateDelegatorUnbondingDelegations iterates through a delegator's unbonding delegations. func (k Keeper) IterateDelegatorUnbondingDelegations(ctx sdk.Context, delegator sdk.AccAddress, cb func(ubd types.UnbondingDelegation) (stop bool)) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, types.GetUBDsKey(delegator)) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { ubd := types.MustUnmarshalUBD(k.cdc, iterator.Value()) if cb(ubd) { break } } } // GetDelegatorBonded returs the total amount a delegator has bonded. func (k Keeper) GetDelegatorBonded(ctx sdk.Context, delegator sdk.AccAddress) sdk.Int { bonded := sdk.ZeroDec() k.IterateDelegatorDelegations(ctx, delegator, func(delegation types.Delegation) bool { validatorAddr, err := sdk.ValAddressFromBech32(delegation.ValidatorAddress) if err != nil { panic(err) // shouldn't happen } validator, found := k.GetValidator(ctx, validatorAddr) if found { shares := delegation.Shares tokens := validator.TokensFromSharesTruncated(shares) bonded = bonded.Add(tokens) } return false }) return bonded.RoundInt() } // IterateDelegatorDelegations iterates through one delegator's delegations. func (k Keeper) IterateDelegatorDelegations(ctx sdk.Context, delegator sdk.AccAddress, cb func(delegation types.Delegation) (stop bool)) { store := ctx.KVStore(k.storeKey) delegatorPrefixKey := types.GetDelegationsKey(delegator) iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Value()) if cb(delegation) { break } } } // IterateDelegatorRedelegations iterates through one delegator's redelegations. func (k Keeper) IterateDelegatorRedelegations(ctx sdk.Context, delegator sdk.AccAddress, cb func(red types.Redelegation) (stop bool)) { store := ctx.KVStore(k.storeKey) delegatorPrefixKey := types.GetREDsKey(delegator) iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { red := types.MustUnmarshalRED(k.cdc, iterator.Value()) if cb(red) { break } } } // HasMaxUnbondingDelegationEntries - check if unbonding delegation has maximum number of entries. func (k Keeper) HasMaxUnbondingDelegationEntries(ctx sdk.Context, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) bool { ubd, found := k.GetUnbondingDelegation(ctx, delegatorAddr, validatorAddr) if !found { return false } return len(ubd.Entries) >= int(k.MaxEntries(ctx)) } // SetUnbondingDelegation sets the unbonding delegation and associated index. func (k Keeper) SetUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDelegation) { delegatorAddress, err := sdk.AccAddressFromBech32(ubd.DelegatorAddress) if err != nil { panic(err) } store := ctx.KVStore(k.storeKey) bz := types.MustMarshalUBD(k.cdc, ubd) addr, err := sdk.ValAddressFromBech32(ubd.ValidatorAddress) if err != nil { panic(err) } key := types.GetUBDKey(delegatorAddress, addr) store.Set(key, bz) store.Set(types.GetUBDByValIndexKey(delegatorAddress, addr), []byte{}) // index, store empty bytes } // RemoveUnbondingDelegation removes the unbonding delegation object and associated index. func (k Keeper) RemoveUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDelegation) { delegatorAddress, err := sdk.AccAddressFromBech32(ubd.DelegatorAddress) if err != nil { panic(err) } store := ctx.KVStore(k.storeKey) addr, err := sdk.ValAddressFromBech32(ubd.ValidatorAddress) if err != nil { panic(err) } key := types.GetUBDKey(delegatorAddress, addr) store.Delete(key) store.Delete(types.GetUBDByValIndexKey(delegatorAddress, addr)) } // SetUnbondingDelegationEntry adds an entry to the unbonding delegation at // the given addresses. It creates the unbonding delegation if it does not exist. func (k Keeper) SetUnbondingDelegationEntry( ctx sdk.Context, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress, creationHeight int64, minTime time.Time, balance sdk.Int, ) types.UnbondingDelegation { ubd, found := k.GetUnbondingDelegation(ctx, delegatorAddr, validatorAddr) if found { ubd.AddEntry(creationHeight, minTime, balance) } else { ubd = types.NewUnbondingDelegation(delegatorAddr, validatorAddr, creationHeight, minTime, balance) } k.SetUnbondingDelegation(ctx, ubd) return ubd } // unbonding delegation queue timeslice operations // GetUBDQueueTimeSlice gets a specific unbonding queue timeslice. A timeslice // is a slice of DVPairs corresponding to unbonding delegations that expire at a // certain time. func (k Keeper) GetUBDQueueTimeSlice(ctx sdk.Context, timestamp time.Time) (dvPairs []types.DVPair) { store := ctx.KVStore(k.storeKey) bz := store.Get(types.GetUnbondingDelegationTimeKey(timestamp)) if bz == nil { return []types.DVPair{} } pairs := types.DVPairs{} k.cdc.MustUnmarshal(bz, &pairs) return pairs.Pairs } // SetUBDQueueTimeSlice sets a specific unbonding queue timeslice. func (k Keeper) SetUBDQueueTimeSlice(ctx sdk.Context, timestamp time.Time, keys []types.DVPair) { store := ctx.KVStore(k.storeKey) bz := k.cdc.MustMarshal(&types.DVPairs{Pairs: keys}) store.Set(types.GetUnbondingDelegationTimeKey(timestamp), bz) } // InsertUBDQueue inserts an unbonding delegation to the appropriate timeslice // in the unbonding queue. func (k Keeper) InsertUBDQueue(ctx sdk.Context, ubd types.UnbondingDelegation, completionTime time.Time) { dvPair := types.DVPair{DelegatorAddress: ubd.DelegatorAddress, ValidatorAddress: ubd.ValidatorAddress} timeSlice := k.GetUBDQueueTimeSlice(ctx, completionTime) if len(timeSlice) == 0 { k.SetUBDQueueTimeSlice(ctx, completionTime, []types.DVPair{dvPair}) } else { timeSlice = append(timeSlice, dvPair) k.SetUBDQueueTimeSlice(ctx, completionTime, timeSlice) } } // UBDQueueIterator returns all the unbonding queue timeslices from time 0 until endTime. func (k Keeper) UBDQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { store := ctx.KVStore(k.storeKey) return store.Iterator(types.UnbondingQueueKey, sdk.InclusiveEndBytes(types.GetUnbondingDelegationTimeKey(endTime))) } // DequeueAllMatureUBDQueue returns a concatenated list of all the timeslices inclusively previous to // currTime, and deletes the timeslices from the queue. func (k Keeper) DequeueAllMatureUBDQueue(ctx sdk.Context, currTime time.Time) (matureUnbonds []types.DVPair) { store := ctx.KVStore(k.storeKey) // gets an iterator for all timeslices from time 0 until the current Blockheader time unbondingTimesliceIterator := k.UBDQueueIterator(ctx, currTime) defer unbondingTimesliceIterator.Close() for ; unbondingTimesliceIterator.Valid(); unbondingTimesliceIterator.Next() { timeslice := types.DVPairs{} value := unbondingTimesliceIterator.Value() k.cdc.MustUnmarshal(value, ×lice) matureUnbonds = append(matureUnbonds, timeslice.Pairs...) store.Delete(unbondingTimesliceIterator.Key()) } return matureUnbonds } // GetRedelegations returns a given amount of all the delegator redelegations. func (k Keeper) GetRedelegations(ctx sdk.Context, delegator sdk.AccAddress, maxRetrieve uint16) (redelegations []types.Redelegation) { redelegations = make([]types.Redelegation, maxRetrieve) store := ctx.KVStore(k.storeKey) delegatorPrefixKey := types.GetREDsKey(delegator) iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) defer iterator.Close() i := 0 for ; iterator.Valid() && i < int(maxRetrieve); iterator.Next() { redelegation := types.MustUnmarshalRED(k.cdc, iterator.Value()) redelegations[i] = redelegation i++ } return redelegations[:i] // trim if the array length < maxRetrieve } // GetRedelegation returns a redelegation. func (k Keeper) GetRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress) (red types.Redelegation, found bool) { store := ctx.KVStore(k.storeKey) key := types.GetREDKey(delAddr, valSrcAddr, valDstAddr) value := store.Get(key) if value == nil { return red, false } red = types.MustUnmarshalRED(k.cdc, value) return red, true } // GetRedelegationsFromSrcValidator returns all redelegations from a particular // validator. func (k Keeper) GetRedelegationsFromSrcValidator(ctx sdk.Context, valAddr sdk.ValAddress) (reds []types.Redelegation) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, types.GetREDsFromValSrcIndexKey(valAddr)) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { key := types.GetREDKeyFromValSrcIndexKey(iterator.Key()) value := store.Get(key) red := types.MustUnmarshalRED(k.cdc, value) reds = append(reds, red) } return reds } // HasReceivingRedelegation checks if validator is receiving a redelegation. func (k Keeper) HasReceivingRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, valDstAddr sdk.ValAddress) bool { store := ctx.KVStore(k.storeKey) prefix := types.GetREDsByDelToValDstIndexKey(delAddr, valDstAddr) iterator := sdk.KVStorePrefixIterator(store, prefix) defer iterator.Close() return iterator.Valid() } // HasMaxRedelegationEntries checks if redelegation has maximum number of entries. func (k Keeper) HasMaxRedelegationEntries(ctx sdk.Context, delegatorAddr sdk.AccAddress, validatorSrcAddr, validatorDstAddr sdk.ValAddress) bool { red, found := k.GetRedelegation(ctx, delegatorAddr, validatorSrcAddr, validatorDstAddr) if !found { return false } return len(red.Entries) >= int(k.MaxEntries(ctx)) } // SetRedelegation set a redelegation and associated index. func (k Keeper) SetRedelegation(ctx sdk.Context, red types.Redelegation) { delegatorAddress, err := sdk.AccAddressFromBech32(red.DelegatorAddress) if err != nil { panic(err) } store := ctx.KVStore(k.storeKey) bz := types.MustMarshalRED(k.cdc, red) valSrcAddr, err := sdk.ValAddressFromBech32(red.ValidatorSrcAddress) if err != nil { panic(err) } valDestAddr, err := sdk.ValAddressFromBech32(red.ValidatorDstAddress) if err != nil { panic(err) } key := types.GetREDKey(delegatorAddress, valSrcAddr, valDestAddr) store.Set(key, bz) store.Set(types.GetREDByValSrcIndexKey(delegatorAddress, valSrcAddr, valDestAddr), []byte{}) store.Set(types.GetREDByValDstIndexKey(delegatorAddress, valSrcAddr, valDestAddr), []byte{}) } // SetRedelegationEntry adds an entry to the unbonding delegation at the given // addresses. It creates the unbonding delegation if it does not exist. func (k Keeper) SetRedelegationEntry(ctx sdk.Context, delegatorAddr sdk.AccAddress, validatorSrcAddr, validatorDstAddr sdk.ValAddress, creationHeight int64, minTime time.Time, balance sdk.Int, sharesSrc, sharesDst sdk.Dec) types.Redelegation { red, found := k.GetRedelegation(ctx, delegatorAddr, validatorSrcAddr, validatorDstAddr) if found { red.AddEntry(creationHeight, minTime, balance, sharesDst) } else { red = types.NewRedelegation(delegatorAddr, validatorSrcAddr, validatorDstAddr, creationHeight, minTime, balance, sharesDst) } k.SetRedelegation(ctx, red) return red } // IterateRedelegations iterates through all redelegations. func (k Keeper) IterateRedelegations(ctx sdk.Context, fn func(index int64, red types.Redelegation) (stop bool)) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, types.RedelegationKey) defer iterator.Close() for i := int64(0); iterator.Valid(); iterator.Next() { red := types.MustUnmarshalRED(k.cdc, iterator.Value()) if stop := fn(i, red); stop { break } i++ } } // RemoveRedelegation removes a redelegation object and associated index. func (k Keeper) RemoveRedelegation(ctx sdk.Context, red types.Redelegation) { delegatorAddress, err := sdk.AccAddressFromBech32(red.DelegatorAddress) if err != nil { panic(err) } store := ctx.KVStore(k.storeKey) valSrcAddr, err := sdk.ValAddressFromBech32(red.ValidatorSrcAddress) if err != nil { panic(err) } valDestAddr, err := sdk.ValAddressFromBech32(red.ValidatorDstAddress) if err != nil { panic(err) } redKey := types.GetREDKey(delegatorAddress, valSrcAddr, valDestAddr) store.Delete(redKey) store.Delete(types.GetREDByValSrcIndexKey(delegatorAddress, valSrcAddr, valDestAddr)) store.Delete(types.GetREDByValDstIndexKey(delegatorAddress, valSrcAddr, valDestAddr)) } // redelegation queue timeslice operations // GetRedelegationQueueTimeSlice gets a specific redelegation queue timeslice. A // timeslice is a slice of DVVTriplets corresponding to redelegations that // expire at a certain time. func (k Keeper) GetRedelegationQueueTimeSlice(ctx sdk.Context, timestamp time.Time) (dvvTriplets []types.DVVTriplet) { store := ctx.KVStore(k.storeKey) bz := store.Get(types.GetRedelegationTimeKey(timestamp)) if bz == nil { return []types.DVVTriplet{} } triplets := types.DVVTriplets{} k.cdc.MustUnmarshal(bz, &triplets) return triplets.Triplets } // SetRedelegationQueueTimeSlice sets a specific redelegation queue timeslice. func (k Keeper) SetRedelegationQueueTimeSlice(ctx sdk.Context, timestamp time.Time, keys []types.DVVTriplet) { store := ctx.KVStore(k.storeKey) bz := k.cdc.MustMarshal(&types.DVVTriplets{Triplets: keys}) store.Set(types.GetRedelegationTimeKey(timestamp), bz) } // InsertRedelegationQueue insert an redelegation delegation to the appropriate // timeslice in the redelegation queue. func (k Keeper) InsertRedelegationQueue(ctx sdk.Context, red types.Redelegation, completionTime time.Time) { timeSlice := k.GetRedelegationQueueTimeSlice(ctx, completionTime) dvvTriplet := types.DVVTriplet{ DelegatorAddress: red.DelegatorAddress, ValidatorSrcAddress: red.ValidatorSrcAddress, ValidatorDstAddress: red.ValidatorDstAddress} if len(timeSlice) == 0 { k.SetRedelegationQueueTimeSlice(ctx, completionTime, []types.DVVTriplet{dvvTriplet}) } else { timeSlice = append(timeSlice, dvvTriplet) k.SetRedelegationQueueTimeSlice(ctx, completionTime, timeSlice) } } // RedelegationQueueIterator returns all the redelegation queue timeslices from // time 0 until endTime. func (k Keeper) RedelegationQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { store := ctx.KVStore(k.storeKey) return store.Iterator(types.RedelegationQueueKey, sdk.InclusiveEndBytes(types.GetRedelegationTimeKey(endTime))) } // DequeueAllMatureRedelegationQueue returns a concatenated list of all the // timeslices inclusively previous to currTime, and deletes the timeslices from // the queue. func (k Keeper) DequeueAllMatureRedelegationQueue(ctx sdk.Context, currTime time.Time) (matureRedelegations []types.DVVTriplet) { store := ctx.KVStore(k.storeKey) // gets an iterator for all timeslices from time 0 until the current Blockheader time redelegationTimesliceIterator := k.RedelegationQueueIterator(ctx, ctx.BlockHeader().Time) defer redelegationTimesliceIterator.Close() for ; redelegationTimesliceIterator.Valid(); redelegationTimesliceIterator.Next() { timeslice := types.DVVTriplets{} value := redelegationTimesliceIterator.Value() k.cdc.MustUnmarshal(value, ×lice) matureRedelegations = append(matureRedelegations, timeslice.Triplets...) store.Delete(redelegationTimesliceIterator.Key()) } return matureRedelegations } // Delegate performs a delegation, set/update everything necessary within the store. // tokenSrc indicates the bond status of the incoming funds. func (k Keeper) Delegate( ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.Int, tokenSrc types.BondStatus, validator types.Validator, subtractAccount bool, ) (newShares sdk.Dec, err error) { // In some situations, the exchange rate becomes invalid, e.g. if // Validator loses all tokens due to slashing. In this case, // make all future delegations invalid. if validator.InvalidExRate() { return sdk.ZeroDec(), types.ErrDelegatorShareExRateInvalid } // Get or create the delegation object delegation, found := k.GetDelegation(ctx, delAddr, validator.GetOperator()) if !found { delegation = types.NewDelegation(delAddr, validator.GetOperator(), sdk.ZeroDec()) } // call the appropriate hook if present if found { err = k.BeforeDelegationSharesModified(ctx, delAddr, validator.GetOperator()) } else { err = k.BeforeDelegationCreated(ctx, delAddr, validator.GetOperator()) } if err != nil { return sdk.ZeroDec(), err } delegatorAddress, err := sdk.AccAddressFromBech32(delegation.DelegatorAddress) if err != nil { panic(err) } // if subtractAccount is true then we are // performing a delegation and not a redelegation, thus the source tokens are // all non bonded if subtractAccount { if tokenSrc == types.Bonded { panic("delegation token source cannot be bonded") } var sendName string switch { case validator.IsBonded(): sendName = types.BondedPoolName case validator.IsUnbonding(), validator.IsUnbonded(): sendName = types.NotBondedPoolName default: panic("invalid validator status") } coins := sdk.NewCoins(sdk.NewCoin(k.BondDenom(ctx), bondAmt)) if err := k.bankKeeper.DelegateCoinsFromAccountToModule(ctx, delegatorAddress, sendName, coins); err != nil { return sdk.Dec{}, err } } else { // potentially transfer tokens between pools, if switch { case tokenSrc == types.Bonded && validator.IsBonded(): // do nothing case (tokenSrc == types.Unbonded || tokenSrc == types.Unbonding) && !validator.IsBonded(): // do nothing case (tokenSrc == types.Unbonded || tokenSrc == types.Unbonding) && validator.IsBonded(): // transfer pools k.notBondedTokensToBonded(ctx, bondAmt) case tokenSrc == types.Bonded && !validator.IsBonded(): // transfer pools k.bondedTokensToNotBonded(ctx, bondAmt) default: panic("unknown token source bond status") } } _, newShares = k.AddValidatorTokensAndShares(ctx, validator, bondAmt) // Update delegation delegation.Shares = delegation.Shares.Add(newShares) k.SetDelegation(ctx, delegation) // Call the after-modification hook if err := k.AfterDelegationModified(ctx, delegatorAddress, delegation.GetValidatorAddr()); err != nil { return newShares, err } return newShares, nil } // Unbond unbonds a particular delegation and perform associated store operations. func (k Keeper) Unbond( ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, shares sdk.Dec, ) (amount sdk.Int, err error) { // check if a delegation object exists in the store delegation, found := k.GetDelegation(ctx, delAddr, valAddr) if !found { return amount, types.ErrNoDelegatorForAddress } // call the before-delegation-modified hook if err := k.BeforeDelegationSharesModified(ctx, delAddr, valAddr); err != nil { return amount, err } // ensure that we have enough shares to remove if delegation.Shares.LT(shares) { return amount, sdkerrors.Wrap(types.ErrNotEnoughDelegationShares, delegation.Shares.String()) } // get validator validator, found := k.GetValidator(ctx, valAddr) if !found { return amount, types.ErrNoValidatorFound } // subtract shares from delegation delegation.Shares = delegation.Shares.Sub(shares) delegatorAddress, err := sdk.AccAddressFromBech32(delegation.DelegatorAddress) if err != nil { return amount, err } isValidatorOperator := delegatorAddress.Equals(validator.GetOperator()) // If the delegation is the operator of the validator and undelegating will decrease the validator's // self-delegation below their minimum, we jail the validator. if isValidatorOperator && !validator.Jailed && validator.TokensFromShares(delegation.Shares).TruncateInt().LT(validator.MinSelfDelegation) { k.jailValidator(ctx, validator) validator = k.mustGetValidator(ctx, validator.GetOperator()) } if delegation.Shares.IsZero() { err = k.RemoveDelegation(ctx, delegation) } else { k.SetDelegation(ctx, delegation) // call the after delegation modification hook err = k.AfterDelegationModified(ctx, delegatorAddress, delegation.GetValidatorAddr()) } if err != nil { return amount, err } // remove the shares and coins from the validator // NOTE that the amount is later (in keeper.Delegation) moved between staking module pools validator, amount = k.RemoveValidatorTokensAndShares(ctx, validator, shares) if validator.DelegatorShares.IsZero() && validator.IsUnbonded() { // if not unbonded, we must instead remove validator in EndBlocker once it finishes its unbonding period k.RemoveValidator(ctx, validator.GetOperator()) } return amount, nil } // getBeginInfo returns the completion time and height of a redelegation, along // with a boolean signaling if the redelegation is complete based on the source // validator. func (k Keeper) getBeginInfo( ctx sdk.Context, valSrcAddr sdk.ValAddress, ) (completionTime time.Time, height int64, completeNow bool) { validator, found := k.GetValidator(ctx, valSrcAddr) // TODO: When would the validator not be found? switch { case !found || validator.IsBonded(): // the longest wait - just unbonding period from now completionTime = ctx.BlockHeader().Time.Add(k.UnbondingTime(ctx)) height = ctx.BlockHeight() return completionTime, height, false case validator.IsUnbonded(): return completionTime, height, true case validator.IsUnbonding(): return validator.UnbondingTime, validator.UnbondingHeight, false default: panic(fmt.Sprintf("unknown validator status: %s", validator.Status)) } } // Undelegate unbonds an amount of delegator shares from a given validator. It // will verify that the unbonding entries between the delegator and validator // are not exceeded and unbond the staked tokens (based on shares) by creating // an unbonding object and inserting it into the unbonding queue which will be // processed during the staking EndBlocker. func (k Keeper) Undelegate( ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec, ) (time.Time, error) { validator, found := k.GetValidator(ctx, valAddr) if !found { return time.Time{}, types.ErrNoDelegatorForAddress } if k.HasMaxUnbondingDelegationEntries(ctx, delAddr, valAddr) { return time.Time{}, types.ErrMaxUnbondingDelegationEntries } returnAmount, err := k.Unbond(ctx, delAddr, valAddr, sharesAmount) if err != nil { return time.Time{}, err } // transfer the validator tokens to the not bonded pool if validator.IsBonded() { k.bondedTokensToNotBonded(ctx, returnAmount) } completionTime := ctx.BlockHeader().Time.Add(k.UnbondingTime(ctx)) ubd := k.SetUnbondingDelegationEntry(ctx, delAddr, valAddr, ctx.BlockHeight(), completionTime, returnAmount) k.InsertUBDQueue(ctx, ubd, completionTime) return completionTime, nil } // CompleteUnbonding completes the unbonding of all mature entries in the // retrieved unbonding delegation object and returns the total unbonding balance // or an error upon failure. func (k Keeper) CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) { ubd, found := k.GetUnbondingDelegation(ctx, delAddr, valAddr) if !found { return nil, types.ErrNoUnbondingDelegation } bondDenom := k.GetParams(ctx).BondDenom balances := sdk.NewCoins() ctxTime := ctx.BlockHeader().Time delegatorAddress, err := sdk.AccAddressFromBech32(ubd.DelegatorAddress) if err != nil { return nil, err } // loop through all the entries and complete unbonding mature entries for i := 0; i < len(ubd.Entries); i++ { entry := ubd.Entries[i] if entry.IsMature(ctxTime) { ubd.RemoveEntry(int64(i)) i-- // track undelegation only when remaining or truncated shares are non-zero if !entry.Balance.IsZero() { amt := sdk.NewCoin(bondDenom, entry.Balance) if err := k.bankKeeper.UndelegateCoinsFromModuleToAccount( ctx, types.NotBondedPoolName, delegatorAddress, sdk.NewCoins(amt), ); err != nil { return nil, err } balances = balances.Add(amt) } } } // set the unbonding delegation or remove it if there are no more entries if len(ubd.Entries) == 0 { k.RemoveUnbondingDelegation(ctx, ubd) } else { k.SetUnbondingDelegation(ctx, ubd) } return balances, nil } // BeginRedelegation begins unbonding / redelegation and creates a redelegation // record. func (k Keeper) BeginRedelegation( ctx sdk.Context, delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, sharesAmount sdk.Dec, ) (completionTime time.Time, err error) { if bytes.Equal(valSrcAddr, valDstAddr) { return time.Time{}, types.ErrSelfRedelegation } dstValidator, found := k.GetValidator(ctx, valDstAddr) if !found { return time.Time{}, types.ErrBadRedelegationDst } srcValidator, found := k.GetValidator(ctx, valSrcAddr) if !found { return time.Time{}, types.ErrBadRedelegationDst } // check if this is a transitive redelegation if k.HasReceivingRedelegation(ctx, delAddr, valSrcAddr) { return time.Time{}, types.ErrTransitiveRedelegation } if k.HasMaxRedelegationEntries(ctx, delAddr, valSrcAddr, valDstAddr) { return time.Time{}, types.ErrMaxRedelegationEntries } returnAmount, err := k.Unbond(ctx, delAddr, valSrcAddr, sharesAmount) if err != nil { return time.Time{}, err } if returnAmount.IsZero() { return time.Time{}, types.ErrTinyRedelegationAmount } sharesCreated, err := k.Delegate(ctx, delAddr, returnAmount, srcValidator.GetStatus(), dstValidator, false) if err != nil { return time.Time{}, err } // create the unbonding delegation completionTime, height, completeNow := k.getBeginInfo(ctx, valSrcAddr) if completeNow { // no need to create the redelegation object return completionTime, nil } red := k.SetRedelegationEntry( ctx, delAddr, valSrcAddr, valDstAddr, height, completionTime, returnAmount, sharesAmount, sharesCreated, ) k.InsertRedelegationQueue(ctx, red, completionTime) return completionTime, nil } // CompleteRedelegation completes the redelegations of all mature entries in the // retrieved redelegation object and returns the total redelegation (initial) // balance or an error upon failure. func (k Keeper) CompleteRedelegation( ctx sdk.Context, delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, ) (sdk.Coins, error) { red, found := k.GetRedelegation(ctx, delAddr, valSrcAddr, valDstAddr) if !found { return nil, types.ErrNoRedelegation } bondDenom := k.GetParams(ctx).BondDenom balances := sdk.NewCoins() ctxTime := ctx.BlockHeader().Time // loop through all the entries and complete mature redelegation entries for i := 0; i < len(red.Entries); i++ { entry := red.Entries[i] if entry.IsMature(ctxTime) { red.RemoveEntry(int64(i)) i-- if !entry.InitialBalance.IsZero() { balances = balances.Add(sdk.NewCoin(bondDenom, entry.InitialBalance)) } } } // set the redelegation or remove it if there are no more entries if len(red.Entries) == 0 { k.RemoveRedelegation(ctx, red) } else { k.SetRedelegation(ctx, red) } return balances, nil } // ValidateUnbondAmount validates that a given unbond or redelegation amount is // valied based on upon the converted shares. If the amount is valid, the total // amount of respective shares is returned, otherwise an error is returned. func (k Keeper) ValidateUnbondAmount( ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt sdk.Int, ) (shares sdk.Dec, err error) { validator, found := k.GetValidator(ctx, valAddr) if !found { return shares, types.ErrNoValidatorFound } del, found := k.GetDelegation(ctx, delAddr, valAddr) if !found { return shares, types.ErrNoDelegation } shares, err = validator.SharesFromTokens(amt) if err != nil { return shares, err } sharesTruncated, err := validator.SharesFromTokensTruncated(amt) if err != nil { return shares, err } delShares := del.GetShares() if sharesTruncated.GT(delShares) { return shares, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid shares amount") } // Cap the shares at the delegation's shares. Shares being greater could occur // due to rounding, however we don't want to truncate the shares or take the // minimum because we want to allow for the full withdraw of shares from a // delegation. if shares.GT(delShares) { shares = delShares } return shares, nil }