Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ValSet-Pref] Added Proto Definitions and spec #2745

Merged
merged 7 commits into from
Sep 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions proto/osmosis/validator-preference/v1beta1/query.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
syntax = "proto3";
package osmosis.validatorpreference.v1beta1;

import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "osmosis/validator-preference/v1beta1/state.proto";

option go_package = "github.com/osmosis-labs/osmosis/v12/x/validator-preference/client/queryproto";
option (gogoproto.goproto_getters_all) = false;

// Query defines the gRPC querier service.
service Query {
// Returns the list of ValidatorPreferences for the user.
rpc UserValidatorPreferences(QueryUserValidatorPreferences)
returns (QueryUserValidatorPreferenceResponse) {
option (google.api.http).get =
"/osmosis/validator-preference/v1beta1/{user}";
}
}

// Request type for UserValidatorPreferences.
message QueryUserValidatorPreferences {
// user account address
string user = 1;
}

// Response type the QueryUserValidatorPreferences query request
message QueryUserValidatorPreferenceResponse {
stackman27 marked this conversation as resolved.
Show resolved Hide resolved
repeated ValidatorPreference preferences = 1 [ (gogoproto.nullable) = false ];
}
37 changes: 37 additions & 0 deletions proto/osmosis/validator-preference/v1beta1/state.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
syntax = "proto3";
package osmosis.validatorpreference.v1beta1;

import "gogoproto/gogo.proto";
import "google/api/annotations.proto";

option go_package = "github.com/osmosis-labs/osmosis/v12/x/validator-preference/types";
option (gogoproto.goproto_getters_all) = false;

// ValidatorPreference defines the message structure for
// CreateValidatorSetPreference. It allows a user to set {val_addr, weight} in
// state. If a user does not have a validator set preference list set, and has
// staked, make their preference list default to their current staking
// distribution.
message ValidatorPreference {
// val_oper_address holds the validator address the user wants to delegate
// funds to.
string val_oper_address = 1
[ (gogoproto.moretags) = "yaml:\"val_oper_address\"" ];
// weight is decimal between 0 and 1, and they all sum to 1.
string weight = 2 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
}

// ValidatorSetPreferences defines a delegator's validator set preference.
// It contains a list of (validator, percent_allocation) pairs.
// The percent allocation are arranged in decimal notation from 0 to 1 and must
// add up to 1.
message ValidatorSetPreferences {
// preference holds {valAddr, weight} for the user who created it.
repeated ValidatorPreference preferences = 2 [
(gogoproto.moretags) = "yaml:\"preferences\"",
(gogoproto.nullable) = false
];
}
90 changes: 90 additions & 0 deletions proto/osmosis/validator-preference/v1beta1/tx.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
syntax = "proto3";
package osmosis.validatorpreference.v1beta1;

import "gogoproto/gogo.proto";
import "cosmos/base/v1beta1/coin.proto";
import "osmosis/validator-preference/v1beta1/state.proto";

option go_package = "github.com/osmosis-labs/osmosis/v12/x/validator-preference/types";

// Msg defines the validator-preference modules's gRPC message service.
service Msg {
// SetValidatorSetPreference creates a set of validator preference.
// This message will process both create + update request.
rpc SetValidatorSetPreference(MsgSetValidatorSetPreference)
returns (MsgSetValidatorSetPreferenceResponse);

// DelegateToValidatorSet gets the owner, coins and delegates to a
// validator-set.
rpc DelegateToValidatorSet(MsgDelegateToValidatorSet)
returns (MsgDelegateToValidatorSetResponse);

// UndelegateFromValidatorSet gets the owner and coins and undelegates from
// validator-set. The unbonding logic will follow the `Undelegate` logic from
// the sdk.
rpc UndelegateFromValidatorSet(MsgUndelegateFromValidatorSet)
returns (MsgUndelegateFromValidatorSetResponse);

// WithdrawDelegationRewards allows users to claim rewards from the
// validator-set.
rpc WithdrawDelegationRewards(MsgWithdrawDelegationRewards)
returns (MsgWithdrawDelegationRewardsResponse);
}

// MsgCreateValidatorSetPreference is a list that holds validator-set.
message MsgSetValidatorSetPreference {
// 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
];
}

message MsgSetValidatorSetPreferenceResponse {}

// MsgDelegateToValidatorSet allows users to delegate to an existing
// validator-set
message MsgDelegateToValidatorSet {
// delegator is the user who is trying to delegate.
string delegator = 1 [ (gogoproto.moretags) = "yaml:\"delegator\"" ];

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

message MsgDelegateToValidatorSetResponse {}

message MsgUndelegateFromValidatorSet {
// delegator is the user who is trying to undelegate.
string delegator = 1 [ (gogoproto.moretags) = "yaml:\"delegator\"" ];

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

message MsgUndelegateFromValidatorSetResponse {}

// MsgWithdrawDelegationRewards allows user to claim staking rewards from the
// validator set.
message MsgWithdrawDelegationRewards {
// delegator is the user who is trying to claim staking rewards.
string delegator = 1 [ (gogoproto.moretags) = "yaml:\"delegator\"" ];
}

message MsgWithdrawDelegationRewardsResponse {}
14 changes: 13 additions & 1 deletion tests/localosmosis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,19 @@ After a while (~15 minutes), this will create a file called `state_export.json`
cp $HOME/state_export.json $HOME/osmosis/tests/LocalOsmosis/state_export/
```

2. Initialize LocalOsmosis:
6. Ensure you have docker and docker-compose installed:

```sh
# Docker
sudo apt-get remove docker docker-engine docker.io
sudo apt-get update
sudo apt install docker.io -y

# Docker compose
sudo apt install docker-compose -y
```

7. Build the `local:osmosis` docker image:

```bash
make localnet-state-export-init
Expand Down
155 changes: 155 additions & 0 deletions x/validator-preference/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# 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.
stackman27 marked this conversation as resolved.
Show resolved Hide resolved

## 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.
stackman27 marked this conversation as resolved.
Show resolved Hide resolved
- 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 `MsgStakeToValidatorSet` divides the amount based on validator weight distribution.
For example: Stake 100osmo with validator-set {ValA -> 0.5, ValB -> 0.3, ValC -> 0.2}
our delegate logic will attempt to delegate (100 * 0.5) 50osmo for ValA , (100 * 0.3) 30osmo from ValB and (100 * 0.2) 20osmo from ValC.

UnStaking Calculation
stackman27 marked this conversation as resolved.
Show resolved Hide resolved

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

## Messages

### CreateValidatorSetPreference

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.

```go
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.

### UpdateValidatorSetPreference

Updates a validator-set of `{valAddr, Weight}` given the delegator address
and existing preferences. The weights calculations follow the same rule as `CreateValidatorSetPreference`.
If a user changes their preferences list, the unstaking logic will run from the old set and
restaking to a new set is going to happen behind the scenes.
stackman27 marked this conversation as resolved.
Show resolved Hide resolved

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

**State Modifications:**

- Follows the same rule as `CreateValidatorSetPreference` for weights checks.
- Update the `KVStore` value for the specific owner address key.
- Run the undelegate logic and restake the tokens with updated weights.

### StakeToValidatorSet

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!

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

**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](https://github.com/cosmos/cosmos-sdk/blob/main/x/staking/keeper/delegation.go#L614) method from the cosmos-sdk to handle delegation.

### UnStakeFromValidatorSet

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!

The given amount will be divided based on the weights distributed to the validators.

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

**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](https://github.com/cosmos/cosmos-sdk/blob/main/x/staking/keeper/delegation.go#L614) method from the cosmos-sdk to handle delegation.

### WithdrawDelegationRewards

Allows the user to claim rewards based from the existing validator-set. The user can claim rewards from all the validators at once.

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

## 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.
- twapmodule/validatorsetpreference.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)
Loading