keeper

package
v17.1.0-20230810231521... Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 10, 2023 License: Apache-2.0 Imports: 15 Imported by: 0

README

validator-set Preference

Abstract

Validator-Set preference is a new module which gives users and contracts a better UX for staking to a set of validators. For example: a one click button that delegates to multiple validators. Then the user can set (or realistically a frontend provides) a list of recommended defaults (Ex: active governors, relayers, core stack contributors etc). Currently this can be done on-chain with frontends, but having a preference list stored locally eases frontend code burden.

Design

How does this module work?

  • Allow a user to set a list of {val-addr, weight} in the state, called their validator-set preference.
  • Allow a user to update a list of {val-addr, weight} in the state, then do the following;
    • Unstake the existing tokens (run the same unbond logic as cosmos-sdk staking).
    • Update the validator distribution weights.
    • Stake the tokens based on the new weights.
    • Redelegate their current delegation to the currently configured set.
  • Give users a single message to delegate {X} tokens, according to their validator-set preference distribution.
  • Give users a single message to undelegate {X} tokens, according to their validator-set preference distribution.
  • Give users a single message to claim rewards from everyone on their preference list.
  • If the delegator has not set a validator-set preference list then the validator set, then it defaults to their current validator set.
  • If a user has no preference list and has not staked, then these messages / queries return errors.

Calculations

Staking Calculation

  • The user provides an amount to delegate and our MsgDelegateToValidatorSet divides the amount based on validator weight distribution. For example: Stake 100fury with validator-set {ValA -> 0.5, ValB -> 0.3, ValC -> 0.2} our delegate logic will attempt to delegate (100 * 0.5) 50fury for ValA , (100 * 0.3) 30fury from ValB and (100 * 0.2) 20fury from ValC.

UnStaking Calculation

  • The user provides an amount to undelegate and our MsgUnDelegateToValidatorSet divides the amount based on validator weight distribution.
  • Here, the user can either undelegate the entire amount or partial amount
    • Entire amount unstaking: UnStake 100fury from validator-set {ValA -> 0.5, ValB -> 0.3, ValC -> 0.2}, our undelegate logic will attempt to undelegate 50fury from ValA , 30fury from ValB, 20fury from ValC
    • Partial amount unstaking: UnStake 27fury from validator-set {ValA -> 0.5, ValB -> 0.3, ValC -> 0.2}, our undelegate logic will attempt to undelegate (27 * 0.5) 13.5furys from ValA, (27 * 0.3), 8.1fury from ValB, and (50 * 0.2) 5.4smo from ValC where 13.5fury + 8.1fury + 5.4fury = 27fury
    • The user will then have 73fury remaining with unchanged weights {ValA -> 0.5, ValB -> 0.3, ValC -> 0.2},

Messages

SetValidatorSetPreference

Creates a validator-set of {valAddr, Weight} given the delegator address. and preferences. The weights are in decimal format from 0 to 1 and must add up to 1.

    string delegator = 1 [ (gogoproto.moretags) = "yaml:\"delegator\"" ];
    repeated ValidatorPreference preferences = 2 [
      (gogoproto.moretags) = "yaml:\"preferences\"",
      (gogoproto.nullable) = false
    ];

State Modifications:

  • Safety Checks
    • check if the user already has a validator-set created.
    • check if the validator exist and is valid.
    • check if the validator-set add up to 1.
  • Add owner address to the KVStore, where a state of validator-set is stored.
MsgDelegateToValidatorSet

Gets the existing validator-set of the delegator and delegates the given amount. The given amount will be divided based on the weights distributed to the validators. The weights will be unchanged. If the user does not have an existing validator set use delegators' current staking position.

    string delegator = 1 [ (gogoproto.moretags) = "yaml:\"delegator\"" ];
    // the amount of tokens the user is trying to delegate.
    // For ex: delegate 10fury with validator-set {ValA -> 0.5, ValB -> 0.3, ValC
    // -> 0.2} our staking logic would attempt to delegate 5fury to A , 3fury to
    // B, 2fury to C.
    cosmos.base.v1beta1.Coin coin = 2 [
      (gogoproto.nullable) = false,
      (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coin"
    ];

State Modifications:

  • Check if the user has a validator-set and if so, get the users validator-set from KVStore.
  • Safety Checks
    • check if the user has enough funds to delegate.
    • check overflow/underflow since Delegate method takes sdk.Int as tokenAmount.
  • use the Delegate method from the cosmos-sdk to handle delegation.
MsgUndelegateFromValidatorSet

Gets the existing validator-set of the delegator and undelegate the given amount. The amount to undelegate will will be divided based on the weights distributed to the validators. The weights will be unchanged! If the user does not have an existing validator set use delegators' current staking position. The given amount will be divided based on the weights distributed to the validators.

    string delegator = 1 [ (gogoproto.moretags) = "yaml:\"delegator\"" ];
    // the amount the user wants to undelegate
    // For ex: Undelegate 10fury with validator-set {ValA -> 0.5, ValB -> 0.3,
    // ValC
    // -> 0.2} our undelegate logic would attempt to undelegate 5fury from A ,
    // 3fury from B, 2fury from C
    cosmos.base.v1beta1.Coin coin = 3 [
      (gogoproto.nullable) = false,
      (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coin"
    ];

State Modifications:

  • Check if the user has a validator-set and if so, get the users validator-set from KVStore.
  • The unbonding logic will be follow the UnDelegate logic from the cosmos-sdk.
  • Safety Checks
    • check that the amount of funds to undelegate is <= to the funds the user has in the address.
    • UnDelegate method takes sdk.Dec as tokenAmount, so check if overflow/underflow case is relevant.
  • use the UnDelegate method from the cosmos-sdk to handle delegation.
MsgWithdrawDelegationRewards

Allows the user to claim rewards based from the existing validator-set. The user can claim rewards from all the validators at once. If the user does not have an existing validator set use delegators' current staking position.

    string delegator = 1 [ (gogoproto.moretags) = "yaml:\"delegator\"" ];
MsgRedelegateValidatorSet

The redelegation command allows delegators to instantly switch validators. Once the unbonding period has passed, the redelegation is automatically completed in the EndBlocker. If the user does not have an existing validator set use delegators' current staking position.

  // delegator is the user who is trying to create a validator-set.
  string delegator = 1 [ (gogoproto.moretags) = "yaml:\"delegator\"" ];

  // list of {valAddr, weight} to delegate to
  repeated ValidatorPreference preferences = 2 [
    (gogoproto.moretags) = "yaml:\"preferences\"",
    (gogoproto.nullable) = false
  ];

Redelegate algorithm logic pseudocode

Existing ValSet 20furys {ValA-> 0.5, ValB-> 0.3, ValC-> 0.2} [ValA-> 10fury, ValB-> 6fury, ValC-> 4fury] New ValSet 20furys {ValD-> 0.2, ValE-> 0.2, ValF-> 0.6} [ValD-> 4fury, ValE-> 4fury, ValF-> 12fury]

  • // Rearranging the existingValSet and newValSet to to add extra validator padding

    • existing_valset_updated = [ValA: 10, ValB: 6, ValC: 4, ValD: 0, ValE: 0, ValF: 0]
    • new_valset_updated = [ValD: 4, ValE: 4, ValF: 12, ValA: 0, ValB: 0, ValC: 0]

    // calculate the difference between two sets

    • diff_arr = [ValA: 10, ValB: 6, ValC: 4, ValD: -4, ValE: -4, ValF: -12]

      // Algorithm starts here

  • for _, validator in diff_arr:

    • if validator.amount > 0:
      • for idx, targetDiffVal := range diff_arr // this will gives us target validator
        • if targetDiffVal.Amount < 0 && targetDiffVal.valAddr != validator.Address source_validator = validator.Address target_validator = targetDiffVal.valAddr

          // checks if there are any more redelegation possible if target_validator.amount.Equal(0) { break }

          // reDelegationAmt to is the amount to redelegate, which is the min of diffAmount and target_validator reDelegationAmt = FindMin(abs(target_validator.amount), validator.amount) sdk.BeginRedelegation(ctx, delegator, source_validator, target_validator, reDelegationAmt)

          // Update the current diffAmount by subtracting it with the reDelegationAmount validator.amount = validator.amount - reDelegationAmt // Find target_validator through idx in diffValSet and set that to (target_validatorAmount - reDelegationAmount) diff_arr[idx].amount = target_validator.amount + reDelegationAmt

  • Result

    1. diff_arr = [ValA: 0, ValB: 0, ValC: 0, ValD: 0, ValE: 0, ValF: 0]
    2. [ValA: 0, ValB: 0, ValC: 0, ValD: 4, ValE: 4, ValF: 12] // final result

Redelegation Constraints

  1. ValA -> ValB redelegate upto 7 times in 21 day period
  2. ValA -> ValB (redelegate) ValB -> ValC (redelegate) CONSECUTIVE REDELEGATION DOES NOT WORK
  3. Once you redelegate from ValA -> ValB, you will not be able to redelegate from ValB to another validator for the next 21 days.
  • the validator on the receiving end of redelegation will be on a 21-day redelegation lock
  1. Cannot redelegate to same validator

Code Layout

The Code Layout is very similar to TWAP module.

  • client/* - Implementation of GRPC and CLI queries
  • types/* - Implement ValidatorSetPreference, GenesisState. Define the interface and setup keys.
  • valpref-module/module.go - SDK AppModule interface implementation.
  • api.go - Public API, that other users / modules can/should depend on
  • listeners.go - Defines hooks & calls to logic.go, for triggering actions on
  • keeper.go - generic SDK boilerplate (defining a wrapper for store keys + params)
  • msg_server.go - handle messages request from client and process responses.
  • store.go - Managing logic for getting and setting things to underlying stores (KVStore)

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewMsgServerImpl

func NewMsgServerImpl(keeper *Keeper) types.MsgServer

NewMsgServerImpl returns an implementation of the MsgServer interface for the provided Keeper.

Types

type Keeper

type Keeper struct {
	// contains filtered or unexported fields
}

func NewKeeper

func NewKeeper(storeKey sdk.StoreKey,
	paramSpace paramtypes.Subspace,
	stakingKeeper types.StakingInterface,
	distirbutionKeeper types.DistributionKeeper,
	lockupKeeper types.LockupKeeper,
) Keeper

func (Keeper) CheckUndelegateTotalAmount

func (k Keeper) CheckUndelegateTotalAmount(tokenAmt sdk.Dec, existingSet []types.ValidatorPreference) error

CheckUndelegateTotalAmount checks if the tokenAmount equals the total amount calculated from valset weights.

func (Keeper) DelegateToValidatorSet

func (k Keeper) DelegateToValidatorSet(ctx sdk.Context, delegatorAddr string, coin sdk.Coin) error

DelegateToValidatorSet delegates to a delegators existing validator-set. If the valset does not exist, it delegates to existing staking position. For ex: delegate 10fury with validator-set {ValA -> 0.5, ValB -> 0.3, ValC -> 0.2} our delegate logic would attempt to delegate 5fury to A , 2fury to B, 3fury to C nolint: staticcheck

func (Keeper) ForceUnlockBondedOsmo

func (k Keeper) ForceUnlockBondedOsmo(ctx sdk.Context, lockID uint64, delegatorAddr string) (sdk.Coin, error)

ForceUnlockBondedOsmo allows breaking of a bonded lockup (by ID) of fury, of length <= 2 weeks. We want to later have fury incentives get auto-staked, we want people w/ no staking positions to get their fury auto-locked. This function takes all that fury and stakes according to your current validator set preference. (Note: Noting that there is an implicit valset preference if you've already staked) CONTRACT: This method should **never** be used alone.

func (Keeper) GetDelegationPreferences

func (k Keeper) GetDelegationPreferences(ctx sdk.Context, delegator string) (types.ValidatorSetPreferences, error)

GetDelegationPreferences checks if valset position exists, if it does return that else return existing delegation that's not valset.

func (Keeper) GetExistingStakingDelegations

func (k Keeper) GetExistingStakingDelegations(ctx sdk.Context, delAddr sdk.AccAddress) ([]types.ValidatorPreference, error)

GetExistingStakingDelegations returns the existing delegation that's not valset. This function also formats the output into ValidatorSetPreference struct {valAddr, weight}. The weight is calculated based on (valDelegation / totalDelegations) for each validator. This method erros when given address does not have any existing delegations.

func (Keeper) GetValSetStruct

func (k Keeper) GetValSetStruct(validator types.ValidatorPreference, amountFromShares sdk.Dec) (valStruct valSet, valStructZeroAmt valSet)

GetValSetStruct initializes valSet struct with valAddr, weight and amount. It also creates an extra struct with zero amount, that can be appended to newValSet that will be created. We do this to make sure the struct array length is the same to calculate their difference.

func (Keeper) GetValidatorInfo

func (k Keeper) GetValidatorInfo(ctx sdk.Context, existingValAddr string) (sdk.ValAddress, stakingtypes.Validator, error)

func (Keeper) GetValidatorSetPreference

func (k Keeper) GetValidatorSetPreference(ctx sdk.Context, delegator string) (types.ValidatorSetPreferences, bool)

GetValidatorSetPreference returns the existing valset position for a delegator.

func (Keeper) IsPreferenceValid

func (k Keeper) IsPreferenceValid(ctx sdk.Context, preferences []types.ValidatorPreference) ([]types.ValidatorPreference, error)

IsPreferenceValid loops through the validator preferences and checks its existence and validity.

func (Keeper) IsValidatorSetEqual

func (k Keeper) IsValidatorSetEqual(newPreferences, existingPreferences []types.ValidatorPreference) bool

IsValidatorSetEqual returns true if the two preferences are equal.

func (Keeper) Logger

func (k Keeper) Logger(ctx sdk.Context) log.Logger

func (Keeper) PreformRedelegation

func (k Keeper) PreformRedelegation(ctx sdk.Context, delegator sdk.AccAddress, existingSet []types.ValidatorPreference, newSet []types.ValidatorPreference) error

The redelegation command allows delegators to instantly switch validators. Once the unbonding period has passed, the redelegation is automatically completed in the EndBlocker. A redelegation object is created every time a redelegation occurs. To prevent "redelegation hopping" where delegatorA can redelegate between many validators over small period of time, redelegations may not occur under the following situation: 1. delegatorA attempts to redelegate to the same validator

  • valA --redelegate--> valB
  • valB --redelegate--> valB (ERROR: Self redelegation is not allowed)

2. delegatorA attempts to redelegate to an immature redelegation validator

  • valA --redelegate--> valB
  • valB --redelegate--> valA (ERROR: Redelegation to ValB is already in progress)

3. delegatorA attempts to redelegate while unbonding is in progress

  • unbond (10fury) from valA
  • valA --redelegate--> valB (ERROR: new redelegation while unbonding is in progress)

func (Keeper) SetValidatorSetPreference

func (k Keeper) SetValidatorSetPreference(ctx sdk.Context, delegator string, preferences []types.ValidatorPreference) (types.ValidatorSetPreferences, error)

SetValidatorSetPreference creates or updates delegators validator set. Errors when the given preference is the same as the existing preference in state.

func (Keeper) SetValidatorSetPreferences

func (k Keeper) SetValidatorSetPreferences(ctx sdk.Context, delegator string, validators types.ValidatorSetPreferences)

SetValidatorSetPreferences sets a new valset position for a delegator in modules state.

func (Keeper) UndelegateFromValidatorSet

func (k Keeper) UndelegateFromValidatorSet(ctx sdk.Context, delegatorAddr string, coin sdk.Coin) error

UndelegateFromValidatorSet undelegates {coin} amount from the validator set. If the valset does not exist, it undelegates from existing staking position. For ex: userA has staked 10tokens with weight {Val->0.5, ValB->0.3, ValC->0.2} undelegate 6fury with validator-set {ValA -> 0.5, ValB -> 0.3, ValC -> 0.2} our undelegate logic would attempt to undelegate 3fury from A, 1.8fury from B, 1.2fury from C nolint: staticcheck

func (Keeper) WithdrawDelegationRewards

func (k Keeper) WithdrawDelegationRewards(ctx sdk.Context, delegatorAddr string) error

WithdrawDelegationRewards withdraws all the delegation rewards from the validator in the val-set. If the valset does not exist, it withdraws from existing staking position. Delegation reward is collected by the validator and in doing so, they can charge commission to the delegators. Rewards are calculated per period, and is updated each time validator delegation changes. For ex: when a delegator receives new delgation the rewards can be calculated by taking (total rewards before new delegation - the total current rewards).

Directories

Path Synopsis
cli
queryproto
Package queryproto is a reverse proxy.
Package queryproto is a reverse proxy.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL