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

feat(sponsorship): module scaffolding #994

Merged
merged 13 commits into from
Aug 5, 2024
11 changes: 8 additions & 3 deletions proto/dymensionxyz/dymension/sponsorship/events.proto
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,24 @@ message EventUpdateParams {
message EventVote {
string voter = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
Vote vote = 2 [ (gogoproto.nullable) = false ];
Distribution distribution = 3 [ (gogoproto.nullable) = false ];
}

message EventRevokeVote {
string voter = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
Distribution distribution = 2 [ (gogoproto.nullable) = false ];
}

message EventUpdateVotingPower {
message EventVotingPowerUpdate {
string voter = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
string new_voting_power = 2 [
string validator = 2 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
Distribution distribution = 3 [ (gogoproto.nullable) = false ];
bool vote_pruned = 4;
string new_voting_power = 5 [
mtsitrin marked this conversation as resolved.
Show resolved Hide resolved
(gogoproto.nullable) = false,
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int"
];
string old_voting_power = 3 [
string voting_power_diff = 6 [
(gogoproto.nullable) = false,
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int"
];
Expand Down
25 changes: 25 additions & 0 deletions proto/dymensionxyz/dymension/sponsorship/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ syntax = "proto3";
package dymensionxyz.dymension.sponsorship;

import "gogoproto/gogo.proto";
import "cosmos_proto/cosmos.proto";
import "dymensionxyz/dymension/sponsorship/sponsorship.proto";

option go_package = "github.com/dymensionxyz/dymension/v3/x/sponsorship/types";
Expand All @@ -10,4 +11,28 @@ option go_package = "github.com/dymensionxyz/dymension/v3/x/sponsorship/types";
message GenesisState {
// Params defines params for x/sponsorship module.
Params params = 1 [ (gogoproto.nullable) = false ];
// VoterInfos hold information about voters.
repeated VoterInfo voter_infos = 2 [ (gogoproto.nullable) = false ];
}

// VoterInfo hold information about the voter.
message VoterInfo {
// Voter is the bech32 encoded address of the user sending the vote.
string voter = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
// Vote represents the user's vote.
Vote vote = 2 [ (gogoproto.nullable) = false ];
// Validators is a breakdown of the user's voting power for different validators.
repeated ValidatorVotingPower validators = 3 [ (gogoproto.nullable) = false ];
}

// ValidatorVotingPower holds information about how much voting power the user
// gets from delegating to the given validator.
message ValidatorVotingPower {
// Validator is the bech32 encoded address of the validator.
string validator = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
// Power is a total voting power assigned to this validator.
string power = 2 [
(gogoproto.nullable) = false,
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int"
];
}
65 changes: 55 additions & 10 deletions proto/dymensionxyz/dymension/sponsorship/sponsorship.proto
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,60 @@ message Params {
];
}

// Distribution holds the distribution plan among gauges.
// Distribution holds the distribution plan among gauges. Distribution with the
// Merge operation forms an Abelian group:
// https://en.wikipedia.org/wiki/Abelian_group. Which helps to safely operate
// with it. That is, Distribution:
// 1. Is commutative: a + b = b + a
// 2. Is associative: a + (b + c) = (a + b) + c
// 3. Has the identity element: e + a = a + e = a
// 4. Has inverse elements: i + a = a + i = e
// where
// a, b, c, i, e : Distribution type,
// + : Merge operation (Merge method)
// i : inverse of a (Negate method),
// e : identity element (zero, NewDistribution method).
//
// Example 1:
// a : [100, 1] [50, 2] [0, 3] power 100
// b : [160, 1] [40, 2] [5, 3] power 200
// a + b : [260, 1] [90, 2] [5, 3] power 300
//
// Example 2:
// a : [100, 1] [50, 2] [0, 3] power 100
// b : [160, 1] power 200
// a + b : [260, 1] [50, 2] [0, 3] power 300
//
// Example 3:
// a : [100, 1] [50, 2] [0, 3] power 100
// b : [160, 4] power 200
// a + b : [100, 1] [50, 2] [0, 3] [160, 4] power 300
//
// Example 4:
// a : [210, 1] [180, 2] [210, 3] power 600
// -b : [-40, 1] [-10, 2] power -50
// a - b : [170, 1] [180, 2] [210, 3] power 550
//
// Example 5:
// a : [210, 1] [180, 2] [210, 3] power 600
// e : power 0
// a + e = a : [210, 1] [180, 2] [210, 3] power 600
//
// Example 6:
// a : [ 210, 1] [ 180, 2] [ 210, 3] power 600
// i = -a : [-210, 1] [-180, 2] [-210, 3] power -600
// a + i = e : power 0
//
// CONTRACT: Gauges are sorted by the gauge ID.
// CONTRACT: Gauges hold gauges only with non-zero power.
message Distribution {
// TotalVotingPower is the total voting power that the plan holds.
string total_voting_power = 1 [
// VotingPower is the total voting power that the distribution holds.
string voting_power = 1 [
(gogoproto.nullable) = false,
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int"
];
// Gauges is a breakdown of the users' votes for different gauges.
repeated Gauge gauges = 2;
// Gauges is a breakdown of the voting power for different gauges.
repeated Gauge gauges = 2 [ (gogoproto.nullable) = false ];
}

// Gauge represents a single gauge with its absolute power.
Expand All @@ -53,17 +98,17 @@ message Vote {
(gogoproto.nullable) = false,
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int"
];
// Weights is a breakdown of the user's vote for different gauges.
// Weights is a breakdown of the vote for different gauges.
repeated GaugeWeight weights = 2 [ (gogoproto.nullable) = false ];
}

// Weight is a weight distributed to the specified gauge.
// GaugeWeight is a weight distributed to the specified gauge.
message GaugeWeight {
// GaugeID is the ID of the gauge.
uint64 gauge_id = 1;
// Weight is a portion of the voting power that the user wants to allocate for
// the given gauge. The value must fall between Params.MinAllocationWeight and
// 100, inclusive.
// Weight is a portion of the voting power that is allocated for the given
// gauge. The value must fall between Params.MinAllocationWeight and 100,
// inclusive.
string weight = 2 [
(gogoproto.nullable) = false,
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int"
Expand Down
15 changes: 0 additions & 15 deletions proto/dymensionxyz/dymension/sponsorship/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ option go_package = "github.com/dymensionxyz/dymension/v3/x/sponsorship/types";
service Msg {
option (cosmos.msg.v1.service) = true;

// UpdateParams is used for updating module params.
rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse);

// Vote allows a user to cast their vote. The user specifies a desired weight
// breakdown among existing gauges.
rpc Vote(MsgVote) returns (MsgVoteResponse);
Expand All @@ -23,18 +20,6 @@ service Msg {
rpc RevokeVote(MsgRevokeVote) returns (MsgRevokeVoteResponse);
}

// MsgUpdateParams allows to update module params.
message MsgUpdateParams {
option (cosmos.msg.v1.signer) = "authority";

// Authority is the address that controls the module.
string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
// NewParams should be fully populated.
Params new_params = 2 [ (gogoproto.nullable) = false ];
}

message MsgUpdateParamsResponse {}

// MsgVote defines a message to cast a vote.
message MsgVote {
option (cosmos.msg.v1.signer) = "voter";
Expand Down
107 changes: 107 additions & 0 deletions x/sponsorship/client/cli/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package cli

import (
"context"
"fmt"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/spf13/cobra"

"github.com/dymensionxyz/dymension/v3/x/sponsorship/types"
)

// GetQueryCmd returns the cli query commands for this module.
func GetQueryCmd() *cobra.Command {
cmd := &cobra.Command{
Use: types.ModuleName,
Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName),
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}

cmd.AddCommand(
CmdQueryParams(),
CmdQueryDistribution(),
CmdQueryVote(),
)

return cmd
}

func CmdQueryParams() *cobra.Command {
cmd := &cobra.Command{
Use: "params",
Short: "Get module params",
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
queryClient := types.NewQueryClient(clientCtx)

res, err := queryClient.Params(
context.Background(),
&types.QueryParamsRequest{},
)
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}

func CmdQueryDistribution() *cobra.Command {
cmd := &cobra.Command{
Use: "distribution",
Short: "Get the current distribution plan",
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
queryClient := types.NewQueryClient(clientCtx)

res, err := queryClient.Distribution(
context.Background(),
&types.QueryDistributionRequest{},
)
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}

func CmdQueryVote() *cobra.Command {
cmd := &cobra.Command{
Use: "vote [voter-address]",
Short: "Get the vote by the voter address",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
queryClient := types.NewQueryClient(clientCtx)

res, err := queryClient.Vote(
context.Background(),
&types.QueryVoteRequest{Voter: args[0]},
)
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}
104 changes: 104 additions & 0 deletions x/sponsorship/client/cli/tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package cli

import (
"fmt"
"strconv"
"strings"

"cosmossdk.io/math"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/spf13/cobra"

"github.com/dymensionxyz/dymension/v3/x/sponsorship/types"
)

// GetTxCmd returns the transaction commands for this module.
func GetTxCmd() *cobra.Command {
cmd := &cobra.Command{
Use: types.ModuleName,
Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName),
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}

cmd.AddCommand(CmdVote())

return cmd
}

func CmdVote() *cobra.Command {
cmd := &cobra.Command{
Use: "vote [gauge-weights] --from <voter>",
Short: "Submit a vote for gauges",
Example: "dymd tx sponsorship vote gauge1=30,gauge2=40,abstain=30 --from my_validator",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

weights, err := ParseGaugeWeights(args[0])
if err != nil {
return fmt.Errorf("invalid gauge weights: %w", err)
}

msg := types.MsgVote{
Voter: clientCtx.GetFromAddress().String(),
Weights: weights,
}

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg)
},
}

flags.AddTxFlagsToCmd(cmd)

return cmd
}

func ParseGaugeWeights(inputWeights string) ([]types.GaugeWeight, error) {
if inputWeights == "" {
return nil, fmt.Errorf("input weights must not be empty")
}

var weights []types.GaugeWeight
pairs := strings.Split(inputWeights, ",")

for _, pair := range pairs {
idValue := strings.Split(pair, "=")
if len(idValue) != 2 {
return nil, fmt.Errorf("invalid gauge weight format: %s", pair)
}

gaugeID, err := strconv.ParseUint(idValue[0], 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid gauge ID '%s': %w", idValue[0], err)
}

weight, err := strconv.Atoi(idValue[1])
if err != nil {
return nil, fmt.Errorf("invalid gauge weight '%s': %w", idValue[1], err)
}

if weight < 0 || weight > 100 {
return nil, fmt.Errorf("weight must be between 0 and 100, got %d", weight)
}

weights = append(weights, types.GaugeWeight{
GaugeId: gaugeID,
Weight: math.NewInt(int64(weight)),
})
}

err := types.ValidateGaugeWeights(weights)
if err != nil {
return nil, fmt.Errorf("invalid gauge weights: %w", err)
}

return weights, nil
}
Loading
Loading