0g-chain/x/liquid/keeper/staking.go
2024-08-03 15:03:23 +08:00

111 lines
4.5 KiB
Go

package keeper
import (
errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/0glabs/0g-chain/x/liquid/types"
)
// TransferDelegation moves some delegation shares between addresses, while keeping the same validator.
//
// Internally shares are unbonded, tokens moved then bonded again. This limits only vested tokens from being transferred.
// The sending delegation must not have any active redelegations.
// A validator cannot reduce self delegated shares below its min self delegation.
// Attempting to transfer zero shares will error.
func (k Keeper) TransferDelegation(ctx sdk.Context, valAddr sdk.ValAddress, fromDelegator, toDelegator sdk.AccAddress, shares sdk.Dec) (sdk.Dec, error) {
// Redelegations link a delegation to it's previous validator so slashes are propagated to the new validator.
// If the delegation is transferred to a new owner, the redelegation object must be updated.
// For expediency all transfers with redelegations are blocked.
if k.stakingKeeper.HasReceivingRedelegation(ctx, fromDelegator, valAddr) {
return sdk.Dec{}, types.ErrRedelegationsNotCompleted
}
if shares.IsNil() || shares.LT(sdk.ZeroDec()) {
return sdk.Dec{}, errorsmod.Wrap(types.ErrUntransferableShares, "nil or negative shares")
}
if shares.Equal(sdk.ZeroDec()) {
// Block 0 transfers to reduce edge cases.
return sdk.Dec{}, errorsmod.Wrap(types.ErrUntransferableShares, "zero shares")
}
fromDelegation, found := k.stakingKeeper.GetDelegation(ctx, fromDelegator, valAddr)
if !found {
return sdk.Dec{}, types.ErrNoDelegatorForAddress
}
validator, found := k.stakingKeeper.GetValidator(ctx, valAddr)
if !found {
return sdk.Dec{}, types.ErrNoValidatorFound
}
// Prevent validators from reducing their self delegation below the min.
isValidatorOperator := fromDelegator.Equals(valAddr)
if isValidatorOperator {
if isBelowMinSelfDelegation(validator, fromDelegation.Shares.Sub(shares)) {
return sdk.Dec{}, types.ErrSelfDelegationBelowMinimum
}
}
returnAmount, err := k.fastUndelegate(ctx, valAddr, fromDelegator, shares)
if err != nil {
return sdk.Dec{}, err
}
returnCoins := sdk.NewCoins(sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), returnAmount))
if err := k.bankKeeper.SendCoins(ctx, fromDelegator, toDelegator, returnCoins); err != nil {
return sdk.Dec{}, err
}
receivedShares, err := k.delegateFromAccount(ctx, valAddr, toDelegator, returnAmount)
if err != nil {
return sdk.Dec{}, err
}
return receivedShares, nil
}
// isBelowMinSelfDelegation check if the supplied shares, converted to tokens, are under the validator's min_self_delegation.
func isBelowMinSelfDelegation(validator stakingtypes.ValidatorI, shares sdk.Dec) bool {
return validator.TokensFromShares(shares).TruncateInt().LT(validator.GetMinSelfDelegation())
}
// fastUndelegate undelegates shares from a validator skipping the unbonding period and not creating any unbonding delegations.
func (k Keeper) fastUndelegate(ctx sdk.Context, valAddr sdk.ValAddress, delegator sdk.AccAddress, shares sdk.Dec) (sdkmath.Int, error) {
validator, found := k.stakingKeeper.GetValidator(ctx, valAddr)
if !found {
return sdkmath.Int{}, types.ErrNoDelegatorForAddress
}
returnAmount, err := k.stakingKeeper.Unbond(ctx, delegator, valAddr, shares)
if err != nil {
return sdkmath.Int{}, err
}
returnCoins := sdk.NewCoins(sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), returnAmount))
// transfer the validator tokens to the not bonded pool
if validator.IsBonded() {
if err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, stakingtypes.BondedPoolName, stakingtypes.NotBondedPoolName, returnCoins); err != nil {
panic(err)
}
}
if err := k.bankKeeper.UndelegateCoinsFromModuleToAccount(ctx, stakingtypes.NotBondedPoolName, delegator, returnCoins); err != nil {
return sdkmath.Int{}, err
}
return returnAmount, nil
}
// delegateFromAccount delegates to a validator from an account (vs redelegating from an existing delegation)
func (k Keeper) delegateFromAccount(ctx sdk.Context, valAddr sdk.ValAddress, delegator sdk.AccAddress, amount sdkmath.Int) (sdk.Dec, error) {
validator, found := k.stakingKeeper.GetValidator(ctx, valAddr)
if !found {
return sdk.Dec{}, types.ErrNoValidatorFound
}
// source tokens are from an account, so subtractAccount true and tokenSrc unbonded
newShares, err := k.stakingKeeper.Delegate(ctx, delegator, amount, stakingtypes.Unbonded, validator, true)
if err != nil {
return sdk.Dec{}, err
}
return newShares, nil
}