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 }