github.com/cosmos/cosmos-sdk@v0.50.10/x/gov/keeper/msg_server.go (about)

     1  package keeper
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  
     8  	"cosmossdk.io/errors"
     9  	"cosmossdk.io/math"
    10  
    11  	sdk "github.com/cosmos/cosmos-sdk/types"
    12  	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
    13  	govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
    14  	v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
    15  	"github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
    16  )
    17  
    18  type msgServer struct {
    19  	*Keeper
    20  }
    21  
    22  // NewMsgServerImpl returns an implementation of the gov MsgServer interface
    23  // for the provided Keeper.
    24  func NewMsgServerImpl(keeper *Keeper) v1.MsgServer {
    25  	return &msgServer{Keeper: keeper}
    26  }
    27  
    28  var _ v1.MsgServer = msgServer{}
    29  
    30  // SubmitProposal implements the MsgServer.SubmitProposal method.
    31  func (k msgServer) SubmitProposal(goCtx context.Context, msg *v1.MsgSubmitProposal) (*v1.MsgSubmitProposalResponse, error) {
    32  	if msg.Title == "" {
    33  		return nil, errors.Wrap(sdkerrors.ErrInvalidRequest, "proposal title cannot be empty")
    34  	}
    35  	if msg.Summary == "" {
    36  		return nil, errors.Wrap(sdkerrors.ErrInvalidRequest, "proposal summary cannot be empty")
    37  	}
    38  
    39  	proposer, err := k.authKeeper.AddressCodec().StringToBytes(msg.GetProposer())
    40  	if err != nil {
    41  		return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid proposer address: %s", err)
    42  	}
    43  
    44  	// check that either metadata or Msgs length is non nil.
    45  	if len(msg.Messages) == 0 && len(msg.Metadata) == 0 {
    46  		return nil, errors.Wrap(govtypes.ErrNoProposalMsgs, "either metadata or Msgs length must be non-nil")
    47  	}
    48  
    49  	// verify that if present, the metadata title and summary equals the proposal title and summary
    50  	if len(msg.Metadata) != 0 {
    51  		proposalMetadata := govtypes.ProposalMetadata{}
    52  		if err := json.Unmarshal([]byte(msg.Metadata), &proposalMetadata); err == nil {
    53  			if proposalMetadata.Title != msg.Title {
    54  				return nil, errors.Wrapf(govtypes.ErrInvalidProposalContent, "metadata title '%s' must equal proposal title '%s'", proposalMetadata.Title, msg.Title)
    55  			}
    56  
    57  			if proposalMetadata.Summary != msg.Summary {
    58  				return nil, errors.Wrapf(govtypes.ErrInvalidProposalContent, "metadata summary '%s' must equal proposal summary '%s'", proposalMetadata.Summary, msg.Summary)
    59  			}
    60  		}
    61  
    62  		// if we can't unmarshal the metadata, this means the client didn't use the recommended metadata format
    63  		// nothing can be done here, and this is still a valid case, so we ignore the error
    64  	}
    65  
    66  	proposalMsgs, err := msg.GetMsgs()
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	ctx := sdk.UnwrapSDKContext(goCtx)
    72  	initialDeposit := msg.GetInitialDeposit()
    73  
    74  	params, err := k.Params.Get(ctx)
    75  	if err != nil {
    76  		return nil, fmt.Errorf("failed to get governance parameters: %w", err)
    77  	}
    78  
    79  	if err := k.validateInitialDeposit(ctx, params, initialDeposit, msg.Expedited); err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	if err := k.validateDepositDenom(ctx, params, initialDeposit); err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	proposal, err := k.Keeper.SubmitProposal(ctx, proposalMsgs, msg.Metadata, msg.Title, msg.Summary, proposer, msg.Expedited)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	bytes, err := proposal.Marshal()
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	// ref: https://github.com/cosmos/cosmos-sdk/issues/9683
    98  	ctx.GasMeter().ConsumeGas(
    99  		3*ctx.KVGasConfig().WriteCostPerByte*uint64(len(bytes)),
   100  		"submit proposal",
   101  	)
   102  
   103  	votingStarted, err := k.Keeper.AddDeposit(ctx, proposal.Id, proposer, msg.GetInitialDeposit())
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	if votingStarted {
   109  		ctx.EventManager().EmitEvent(
   110  			sdk.NewEvent(govtypes.EventTypeSubmitProposal,
   111  				sdk.NewAttribute(govtypes.AttributeKeyVotingPeriodStart, fmt.Sprintf("%d", proposal.Id)),
   112  			),
   113  		)
   114  	}
   115  
   116  	return &v1.MsgSubmitProposalResponse{
   117  		ProposalId: proposal.Id,
   118  	}, nil
   119  }
   120  
   121  // CancelProposal implements the MsgServer.CancelProposal method.
   122  func (k msgServer) CancelProposal(goCtx context.Context, msg *v1.MsgCancelProposal) (*v1.MsgCancelProposalResponse, error) {
   123  	_, err := k.authKeeper.AddressCodec().StringToBytes(msg.Proposer)
   124  	if err != nil {
   125  		return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid proposer address: %s", err)
   126  	}
   127  
   128  	ctx := sdk.UnwrapSDKContext(goCtx)
   129  	if err := k.Keeper.CancelProposal(ctx, msg.ProposalId, msg.Proposer); err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	ctx.EventManager().EmitEvent(
   134  		sdk.NewEvent(
   135  			govtypes.EventTypeCancelProposal,
   136  			sdk.NewAttribute(sdk.AttributeKeySender, msg.Proposer),
   137  			sdk.NewAttribute(govtypes.AttributeKeyProposalID, fmt.Sprint(msg.ProposalId)),
   138  		),
   139  	)
   140  
   141  	return &v1.MsgCancelProposalResponse{
   142  		ProposalId:     msg.ProposalId,
   143  		CanceledTime:   ctx.BlockTime(),
   144  		CanceledHeight: uint64(ctx.BlockHeight()),
   145  	}, nil
   146  }
   147  
   148  // ExecLegacyContent implements the MsgServer.ExecLegacyContent method.
   149  func (k msgServer) ExecLegacyContent(goCtx context.Context, msg *v1.MsgExecLegacyContent) (*v1.MsgExecLegacyContentResponse, error) {
   150  	ctx := sdk.UnwrapSDKContext(goCtx)
   151  
   152  	govAcct := k.GetGovernanceAccount(ctx).GetAddress().String()
   153  	if govAcct != msg.Authority {
   154  		return nil, errors.Wrapf(govtypes.ErrInvalidSigner, "expected %s got %s", govAcct, msg.Authority)
   155  	}
   156  
   157  	content, err := v1.LegacyContentFromMessage(msg)
   158  	if err != nil {
   159  		return nil, errors.Wrapf(govtypes.ErrInvalidProposalContent, "%+v", err)
   160  	}
   161  
   162  	// Ensure that the content has a respective handler
   163  	if !k.Keeper.legacyRouter.HasRoute(content.ProposalRoute()) {
   164  		return nil, errors.Wrap(govtypes.ErrNoProposalHandlerExists, content.ProposalRoute())
   165  	}
   166  
   167  	handler := k.Keeper.legacyRouter.GetRoute(content.ProposalRoute())
   168  	if err := handler(ctx, content); err != nil {
   169  		return nil, errors.Wrapf(govtypes.ErrInvalidProposalContent, "failed to run legacy handler %s, %+v", content.ProposalRoute(), err)
   170  	}
   171  
   172  	return &v1.MsgExecLegacyContentResponse{}, nil
   173  }
   174  
   175  // Vote implements the MsgServer.Vote method.
   176  func (k msgServer) Vote(goCtx context.Context, msg *v1.MsgVote) (*v1.MsgVoteResponse, error) {
   177  	accAddr, err := k.authKeeper.AddressCodec().StringToBytes(msg.Voter)
   178  	if err != nil {
   179  		return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid voter address: %s", err)
   180  	}
   181  
   182  	if !v1.ValidVoteOption(msg.Option) {
   183  		return nil, errors.Wrap(govtypes.ErrInvalidVote, msg.Option.String())
   184  	}
   185  
   186  	ctx := sdk.UnwrapSDKContext(goCtx)
   187  	err = k.Keeper.AddVote(ctx, msg.ProposalId, accAddr, v1.NewNonSplitVoteOption(msg.Option), msg.Metadata)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  
   192  	return &v1.MsgVoteResponse{}, nil
   193  }
   194  
   195  // VoteWeighted implements the MsgServer.VoteWeighted method.
   196  func (k msgServer) VoteWeighted(goCtx context.Context, msg *v1.MsgVoteWeighted) (*v1.MsgVoteWeightedResponse, error) {
   197  	accAddr, accErr := k.authKeeper.AddressCodec().StringToBytes(msg.Voter)
   198  	if accErr != nil {
   199  		return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid voter address: %s", accErr)
   200  	}
   201  
   202  	if len(msg.Options) == 0 {
   203  		return nil, errors.Wrap(sdkerrors.ErrInvalidRequest, v1.WeightedVoteOptions(msg.Options).String())
   204  	}
   205  
   206  	totalWeight := math.LegacyNewDec(0)
   207  	usedOptions := make(map[v1.VoteOption]bool)
   208  	for _, option := range msg.Options {
   209  		if !option.IsValid() {
   210  			return nil, errors.Wrap(govtypes.ErrInvalidVote, option.String())
   211  		}
   212  		weight, err := math.LegacyNewDecFromStr(option.Weight)
   213  		if err != nil {
   214  			return nil, errors.Wrapf(govtypes.ErrInvalidVote, "invalid weight: %s", err)
   215  		}
   216  		totalWeight = totalWeight.Add(weight)
   217  		if usedOptions[option.Option] {
   218  			return nil, errors.Wrap(govtypes.ErrInvalidVote, "duplicated vote option")
   219  		}
   220  		usedOptions[option.Option] = true
   221  	}
   222  
   223  	if totalWeight.GT(math.LegacyNewDec(1)) {
   224  		return nil, errors.Wrap(govtypes.ErrInvalidVote, "total weight overflow 1.00")
   225  	}
   226  
   227  	if totalWeight.LT(math.LegacyNewDec(1)) {
   228  		return nil, errors.Wrap(govtypes.ErrInvalidVote, "total weight lower than 1.00")
   229  	}
   230  
   231  	ctx := sdk.UnwrapSDKContext(goCtx)
   232  	err := k.Keeper.AddVote(ctx, msg.ProposalId, accAddr, msg.Options, msg.Metadata)
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  
   237  	return &v1.MsgVoteWeightedResponse{}, nil
   238  }
   239  
   240  // Deposit implements the MsgServer.Deposit method.
   241  func (k msgServer) Deposit(goCtx context.Context, msg *v1.MsgDeposit) (*v1.MsgDepositResponse, error) {
   242  	accAddr, err := k.authKeeper.AddressCodec().StringToBytes(msg.Depositor)
   243  	if err != nil {
   244  		return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid depositor address: %s", err)
   245  	}
   246  
   247  	if err := validateDeposit(msg.Amount); err != nil {
   248  		return nil, err
   249  	}
   250  
   251  	ctx := sdk.UnwrapSDKContext(goCtx)
   252  	votingStarted, err := k.Keeper.AddDeposit(ctx, msg.ProposalId, accAddr, msg.Amount)
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  
   257  	if votingStarted {
   258  		ctx.EventManager().EmitEvent(
   259  			sdk.NewEvent(
   260  				govtypes.EventTypeProposalDeposit,
   261  				sdk.NewAttribute(govtypes.AttributeKeyVotingPeriodStart, fmt.Sprintf("%d", msg.ProposalId)),
   262  			),
   263  		)
   264  	}
   265  
   266  	return &v1.MsgDepositResponse{}, nil
   267  }
   268  
   269  // UpdateParams implements the MsgServer.UpdateParams method.
   270  func (k msgServer) UpdateParams(goCtx context.Context, msg *v1.MsgUpdateParams) (*v1.MsgUpdateParamsResponse, error) {
   271  	if k.authority != msg.Authority {
   272  		return nil, errors.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", k.authority, msg.Authority)
   273  	}
   274  
   275  	if err := msg.Params.ValidateBasic(); err != nil {
   276  		return nil, err
   277  	}
   278  
   279  	ctx := sdk.UnwrapSDKContext(goCtx)
   280  	if err := k.Params.Set(ctx, msg.Params); err != nil {
   281  		return nil, err
   282  	}
   283  
   284  	return &v1.MsgUpdateParamsResponse{}, nil
   285  }
   286  
   287  type legacyMsgServer struct {
   288  	govAcct string
   289  	server  v1.MsgServer
   290  }
   291  
   292  // NewLegacyMsgServerImpl returns an implementation of the v1beta1 legacy MsgServer interface. It wraps around
   293  // the current MsgServer
   294  func NewLegacyMsgServerImpl(govAcct string, v1Server v1.MsgServer) v1beta1.MsgServer {
   295  	return &legacyMsgServer{govAcct: govAcct, server: v1Server}
   296  }
   297  
   298  var _ v1beta1.MsgServer = legacyMsgServer{}
   299  
   300  func (k legacyMsgServer) SubmitProposal(goCtx context.Context, msg *v1beta1.MsgSubmitProposal) (*v1beta1.MsgSubmitProposalResponse, error) {
   301  	content := msg.GetContent()
   302  	if content == nil {
   303  		return nil, errors.Wrap(govtypes.ErrInvalidProposalContent, "missing content")
   304  	}
   305  	if !v1beta1.IsValidProposalType(content.ProposalType()) {
   306  		return nil, errors.Wrap(govtypes.ErrInvalidProposalType, content.ProposalType())
   307  	}
   308  	if err := content.ValidateBasic(); err != nil {
   309  		return nil, err
   310  	}
   311  
   312  	contentMsg, err := v1.NewLegacyContent(msg.GetContent(), k.govAcct)
   313  	if err != nil {
   314  		return nil, fmt.Errorf("error converting legacy content into proposal message: %w", err)
   315  	}
   316  
   317  	proposal, err := v1.NewMsgSubmitProposal(
   318  		[]sdk.Msg{contentMsg},
   319  		msg.InitialDeposit,
   320  		msg.Proposer,
   321  		"",
   322  		msg.GetContent().GetTitle(),
   323  		msg.GetContent().GetDescription(),
   324  		false, // legacy proposals cannot be expedited
   325  	)
   326  	if err != nil {
   327  		return nil, err
   328  	}
   329  
   330  	resp, err := k.server.SubmitProposal(goCtx, proposal)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  
   335  	return &v1beta1.MsgSubmitProposalResponse{ProposalId: resp.ProposalId}, nil
   336  }
   337  
   338  func (k legacyMsgServer) Vote(goCtx context.Context, msg *v1beta1.MsgVote) (*v1beta1.MsgVoteResponse, error) {
   339  	_, err := k.server.Vote(goCtx, &v1.MsgVote{
   340  		ProposalId: msg.ProposalId,
   341  		Voter:      msg.Voter,
   342  		Option:     v1.VoteOption(msg.Option),
   343  	})
   344  	if err != nil {
   345  		return nil, err
   346  	}
   347  	return &v1beta1.MsgVoteResponse{}, nil
   348  }
   349  
   350  func (k legacyMsgServer) VoteWeighted(goCtx context.Context, msg *v1beta1.MsgVoteWeighted) (*v1beta1.MsgVoteWeightedResponse, error) {
   351  	opts := make([]*v1.WeightedVoteOption, len(msg.Options))
   352  	for idx, opt := range msg.Options {
   353  		opts[idx] = &v1.WeightedVoteOption{
   354  			Option: v1.VoteOption(opt.Option),
   355  			Weight: opt.Weight.String(),
   356  		}
   357  	}
   358  
   359  	_, err := k.server.VoteWeighted(goCtx, &v1.MsgVoteWeighted{
   360  		ProposalId: msg.ProposalId,
   361  		Voter:      msg.Voter,
   362  		Options:    opts,
   363  	})
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  	return &v1beta1.MsgVoteWeightedResponse{}, nil
   368  }
   369  
   370  func (k legacyMsgServer) Deposit(goCtx context.Context, msg *v1beta1.MsgDeposit) (*v1beta1.MsgDepositResponse, error) {
   371  	_, err := k.server.Deposit(goCtx, &v1.MsgDeposit{
   372  		ProposalId: msg.ProposalId,
   373  		Depositor:  msg.Depositor,
   374  		Amount:     msg.Amount,
   375  	})
   376  	if err != nil {
   377  		return nil, err
   378  	}
   379  	return &v1beta1.MsgDepositResponse{}, nil
   380  }
   381  
   382  // validateDeposit validates the deposit amount, do not use for initial deposit.
   383  func validateDeposit(amount sdk.Coins) error {
   384  	if !amount.IsValid() || !amount.IsAllPositive() {
   385  		return sdkerrors.ErrInvalidCoins.Wrap(amount.String())
   386  	}
   387  
   388  	return nil
   389  }