github.com/cosmos/cosmos-sdk@v0.50.10/x/group/keeper/msg_server.go (about) 1 package keeper 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/binary" 7 "encoding/json" 8 "fmt" 9 "strings" 10 11 errorsmod "cosmossdk.io/errors" 12 13 sdk "github.com/cosmos/cosmos-sdk/types" 14 sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 15 authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 16 govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" 17 "github.com/cosmos/cosmos-sdk/x/group" 18 "github.com/cosmos/cosmos-sdk/x/group/errors" 19 "github.com/cosmos/cosmos-sdk/x/group/internal/math" 20 "github.com/cosmos/cosmos-sdk/x/group/internal/orm" 21 ) 22 23 var _ group.MsgServer = Keeper{} 24 25 // TODO: Revisit this once we have proper gas fee framework. 26 // Tracking issues https://github.com/cosmos/cosmos-sdk/issues/9054, https://github.com/cosmos/cosmos-sdk/discussions/9072 27 const gasCostPerIteration = uint64(20) 28 29 func (k Keeper) CreateGroup(goCtx context.Context, msg *group.MsgCreateGroup) (*group.MsgCreateGroupResponse, error) { 30 if _, err := k.accKeeper.AddressCodec().StringToBytes(msg.Admin); err != nil { 31 return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid admin address: %s", msg.Admin) 32 } 33 34 if err := k.validateMembers(msg.Members); err != nil { 35 return nil, errorsmod.Wrap(err, "members") 36 } 37 38 if err := k.assertMetadataLength(msg.Metadata, "group metadata"); err != nil { 39 return nil, err 40 } 41 42 totalWeight := math.NewDecFromInt64(0) 43 for _, m := range msg.Members { 44 if err := k.assertMetadataLength(m.Metadata, "member metadata"); err != nil { 45 return nil, err 46 } 47 48 // Members of a group must have a positive weight. 49 // NOTE: group member with zero weight are only allowed when updating group members. 50 // If the member has a zero weight, it will be removed from the group. 51 weight, err := math.NewPositiveDecFromString(m.Weight) 52 if err != nil { 53 return nil, err 54 } 55 56 // Adding up members weights to compute group total weight. 57 totalWeight, err = totalWeight.Add(weight) 58 if err != nil { 59 return nil, err 60 } 61 } 62 63 // Create a new group in the groupTable. 64 ctx := sdk.UnwrapSDKContext(goCtx) 65 groupInfo := &group.GroupInfo{ 66 Id: k.groupTable.Sequence().PeekNextVal(ctx.KVStore(k.key)), 67 Admin: msg.Admin, 68 Metadata: msg.Metadata, 69 Version: 1, 70 TotalWeight: totalWeight.String(), 71 CreatedAt: ctx.BlockTime(), 72 } 73 groupID, err := k.groupTable.Create(ctx.KVStore(k.key), groupInfo) 74 if err != nil { 75 return nil, errorsmod.Wrap(err, "could not create group") 76 } 77 78 // Create new group members in the groupMemberTable. 79 for i, m := range msg.Members { 80 err := k.groupMemberTable.Create(ctx.KVStore(k.key), &group.GroupMember{ 81 GroupId: groupID, 82 Member: &group.Member{ 83 Address: m.Address, 84 Weight: m.Weight, 85 Metadata: m.Metadata, 86 AddedAt: ctx.BlockTime(), 87 }, 88 }) 89 if err != nil { 90 return nil, errorsmod.Wrapf(err, "could not store member %d", i) 91 } 92 } 93 94 if err := ctx.EventManager().EmitTypedEvent(&group.EventCreateGroup{GroupId: groupID}); err != nil { 95 return nil, err 96 } 97 98 return &group.MsgCreateGroupResponse{GroupId: groupID}, nil 99 } 100 101 func (k Keeper) UpdateGroupMembers(goCtx context.Context, msg *group.MsgUpdateGroupMembers) (*group.MsgUpdateGroupMembersResponse, error) { 102 if msg.GroupId == 0 { 103 return nil, errorsmod.Wrap(errors.ErrEmpty, "group id") 104 } 105 106 if len(msg.MemberUpdates) == 0 { 107 return nil, errorsmod.Wrap(errors.ErrEmpty, "member updates") 108 } 109 110 if err := k.validateMembers(msg.MemberUpdates); err != nil { 111 return nil, errorsmod.Wrap(err, "members") 112 } 113 114 ctx := sdk.UnwrapSDKContext(goCtx) 115 action := func(g *group.GroupInfo) error { 116 totalWeight, err := math.NewNonNegativeDecFromString(g.TotalWeight) 117 if err != nil { 118 return errorsmod.Wrap(err, "group total weight") 119 } 120 121 for _, member := range msg.MemberUpdates { 122 if err := k.assertMetadataLength(member.Metadata, "group member metadata"); err != nil { 123 return err 124 } 125 groupMember := group.GroupMember{ 126 GroupId: msg.GroupId, 127 Member: &group.Member{ 128 Address: member.Address, 129 Weight: member.Weight, 130 Metadata: member.Metadata, 131 }, 132 } 133 134 // Checking if the group member is already part of the group 135 var found bool 136 var prevGroupMember group.GroupMember 137 switch err := k.groupMemberTable.GetOne(ctx.KVStore(k.key), orm.PrimaryKey(&groupMember), &prevGroupMember); { 138 case err == nil: 139 found = true 140 case sdkerrors.ErrNotFound.Is(err): 141 found = false 142 default: 143 return errorsmod.Wrap(err, "get group member") 144 } 145 146 newMemberWeight, err := math.NewNonNegativeDecFromString(groupMember.Member.Weight) 147 if err != nil { 148 return err 149 } 150 151 // Handle delete for members with zero weight. 152 if newMemberWeight.IsZero() { 153 // We can't delete a group member that doesn't already exist. 154 if !found { 155 return errorsmod.Wrap(sdkerrors.ErrNotFound, "unknown member") 156 } 157 158 previousMemberWeight, err := math.NewPositiveDecFromString(prevGroupMember.Member.Weight) 159 if err != nil { 160 return err 161 } 162 163 // Subtract the weight of the group member to delete from the group total weight. 164 totalWeight, err = math.SubNonNegative(totalWeight, previousMemberWeight) 165 if err != nil { 166 return err 167 } 168 169 // Delete group member in the groupMemberTable. 170 if err := k.groupMemberTable.Delete(ctx.KVStore(k.key), &groupMember); err != nil { 171 return errorsmod.Wrap(err, "delete member") 172 } 173 continue 174 } 175 // If group member already exists, handle update 176 if found { 177 previousMemberWeight, err := math.NewPositiveDecFromString(prevGroupMember.Member.Weight) 178 if err != nil { 179 return err 180 } 181 // Subtract previous weight from the group total weight. 182 totalWeight, err = math.SubNonNegative(totalWeight, previousMemberWeight) 183 if err != nil { 184 return err 185 } 186 // Save updated group member in the groupMemberTable. 187 groupMember.Member.AddedAt = prevGroupMember.Member.AddedAt 188 if err := k.groupMemberTable.Update(ctx.KVStore(k.key), &groupMember); err != nil { 189 return errorsmod.Wrap(err, "add member") 190 } 191 } else { // else handle create. 192 groupMember.Member.AddedAt = ctx.BlockTime() 193 if err := k.groupMemberTable.Create(ctx.KVStore(k.key), &groupMember); err != nil { 194 return errorsmod.Wrap(err, "add member") 195 } 196 } 197 // In both cases (handle + update), we need to add the new member's weight to the group total weight. 198 totalWeight, err = totalWeight.Add(newMemberWeight) 199 if err != nil { 200 return err 201 } 202 } 203 // Update group in the groupTable. 204 g.TotalWeight = totalWeight.String() 205 g.Version++ 206 207 if err := k.validateDecisionPolicies(ctx, *g); err != nil { 208 return err 209 } 210 211 return k.groupTable.Update(ctx.KVStore(k.key), g.Id, g) 212 } 213 214 if err := k.doUpdateGroup(ctx, msg.GetGroupID(), msg.GetAdmin(), action, "members updated"); err != nil { 215 return nil, err 216 } 217 218 return &group.MsgUpdateGroupMembersResponse{}, nil 219 } 220 221 func (k Keeper) UpdateGroupAdmin(goCtx context.Context, msg *group.MsgUpdateGroupAdmin) (*group.MsgUpdateGroupAdminResponse, error) { 222 if msg.GroupId == 0 { 223 return nil, errorsmod.Wrap(errors.ErrEmpty, "group id") 224 } 225 226 if strings.EqualFold(msg.Admin, msg.NewAdmin) { 227 return nil, errorsmod.Wrap(errors.ErrInvalid, "new and old admin are the same") 228 } 229 230 if _, err := k.accKeeper.AddressCodec().StringToBytes(msg.Admin); err != nil { 231 return nil, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "admin address") 232 } 233 234 if _, err := k.accKeeper.AddressCodec().StringToBytes(msg.NewAdmin); err != nil { 235 return nil, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "new admin address") 236 } 237 238 ctx := sdk.UnwrapSDKContext(goCtx) 239 action := func(g *group.GroupInfo) error { 240 g.Admin = msg.NewAdmin 241 g.Version++ 242 243 return k.groupTable.Update(ctx.KVStore(k.key), g.Id, g) 244 } 245 246 if err := k.doUpdateGroup(ctx, msg.GetGroupID(), msg.GetAdmin(), action, "admin updated"); err != nil { 247 return nil, err 248 } 249 250 return &group.MsgUpdateGroupAdminResponse{}, nil 251 } 252 253 func (k Keeper) UpdateGroupMetadata(goCtx context.Context, msg *group.MsgUpdateGroupMetadata) (*group.MsgUpdateGroupMetadataResponse, error) { 254 if msg.GroupId == 0 { 255 return nil, errorsmod.Wrap(errors.ErrEmpty, "group id") 256 } 257 258 if err := k.assertMetadataLength(msg.Metadata, "group metadata"); err != nil { 259 return nil, err 260 } 261 262 if _, err := k.accKeeper.AddressCodec().StringToBytes(msg.Admin); err != nil { 263 return nil, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "admin address") 264 } 265 266 ctx := sdk.UnwrapSDKContext(goCtx) 267 action := func(g *group.GroupInfo) error { 268 g.Metadata = msg.Metadata 269 g.Version++ 270 return k.groupTable.Update(ctx.KVStore(k.key), g.Id, g) 271 } 272 273 if err := k.doUpdateGroup(ctx, msg.GetGroupID(), msg.GetAdmin(), action, "metadata updated"); err != nil { 274 return nil, err 275 } 276 277 return &group.MsgUpdateGroupMetadataResponse{}, nil 278 } 279 280 func (k Keeper) CreateGroupWithPolicy(ctx context.Context, msg *group.MsgCreateGroupWithPolicy) (*group.MsgCreateGroupWithPolicyResponse, error) { 281 // NOTE: admin, and group message validation is performed in the CreateGroup method 282 groupRes, err := k.CreateGroup(ctx, &group.MsgCreateGroup{ 283 Admin: msg.Admin, 284 Members: msg.Members, 285 Metadata: msg.GroupMetadata, 286 }) 287 if err != nil { 288 return nil, errorsmod.Wrap(err, "group response") 289 } 290 groupID := groupRes.GroupId 291 292 // NOTE: group policy message validation is performed in the CreateGroupPolicy method 293 groupPolicyRes, err := k.CreateGroupPolicy(ctx, &group.MsgCreateGroupPolicy{ 294 Admin: msg.Admin, 295 GroupId: groupID, 296 Metadata: msg.GroupPolicyMetadata, 297 DecisionPolicy: msg.DecisionPolicy, 298 }) 299 if err != nil { 300 return nil, errorsmod.Wrap(err, "group policy response") 301 } 302 303 if msg.GroupPolicyAsAdmin { 304 updateAdminReq := &group.MsgUpdateGroupAdmin{ 305 GroupId: groupID, 306 Admin: msg.Admin, 307 NewAdmin: groupPolicyRes.Address, 308 } 309 _, err = k.UpdateGroupAdmin(ctx, updateAdminReq) 310 if err != nil { 311 return nil, err 312 } 313 314 updatePolicyAddressReq := &group.MsgUpdateGroupPolicyAdmin{ 315 Admin: msg.Admin, 316 GroupPolicyAddress: groupPolicyRes.Address, 317 NewAdmin: groupPolicyRes.Address, 318 } 319 _, err = k.UpdateGroupPolicyAdmin(ctx, updatePolicyAddressReq) 320 if err != nil { 321 return nil, err 322 } 323 } 324 325 return &group.MsgCreateGroupWithPolicyResponse{GroupId: groupID, GroupPolicyAddress: groupPolicyRes.Address}, nil 326 } 327 328 func (k Keeper) CreateGroupPolicy(goCtx context.Context, msg *group.MsgCreateGroupPolicy) (*group.MsgCreateGroupPolicyResponse, error) { 329 if msg.GroupId == 0 { 330 return nil, errorsmod.Wrap(errors.ErrEmpty, "group id") 331 } 332 333 if err := k.assertMetadataLength(msg.GetMetadata(), "group policy metadata"); err != nil { 334 return nil, err 335 } 336 337 policy, err := msg.GetDecisionPolicy() 338 if err != nil { 339 return nil, errorsmod.Wrap(err, "request decision policy") 340 } 341 342 if err := policy.ValidateBasic(); err != nil { 343 return nil, errorsmod.Wrap(err, "decision policy") 344 } 345 346 reqGroupAdmin, err := k.accKeeper.AddressCodec().StringToBytes(msg.GetAdmin()) 347 if err != nil { 348 return nil, errorsmod.Wrap(err, "request admin") 349 } 350 351 ctx := sdk.UnwrapSDKContext(goCtx) 352 groupInfo, err := k.getGroupInfo(ctx, msg.GetGroupID()) 353 if err != nil { 354 return nil, err 355 } 356 357 groupAdmin, err := k.accKeeper.AddressCodec().StringToBytes(groupInfo.Admin) 358 if err != nil { 359 return nil, errorsmod.Wrap(err, "group admin") 360 } 361 362 // Only current group admin is authorized to create a group policy for this 363 if !bytes.Equal(groupAdmin, reqGroupAdmin) { 364 return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "not group admin") 365 } 366 367 if err := policy.Validate(groupInfo, k.config); err != nil { 368 return nil, err 369 } 370 371 // Generate account address of group policy. 372 var accountAddr sdk.AccAddress 373 // loop here in the rare case where a ADR-028-derived address creates a 374 // collision with an existing address. 375 for { 376 nextAccVal := k.groupPolicySeq.NextVal(ctx.KVStore(k.key)) 377 derivationKey := make([]byte, 8) 378 binary.BigEndian.PutUint64(derivationKey, nextAccVal) 379 380 ac, err := authtypes.NewModuleCredential(group.ModuleName, []byte{GroupPolicyTablePrefix}, derivationKey) 381 if err != nil { 382 return nil, err 383 } 384 accountAddr = sdk.AccAddress(ac.Address()) 385 if k.accKeeper.GetAccount(ctx, accountAddr) != nil { 386 // handle a rare collision, in which case we just go on to the 387 // next sequence value and derive a new address. 388 continue 389 } 390 391 // group policy accounts are unclaimable base accounts 392 account, err := authtypes.NewBaseAccountWithPubKey(ac) 393 if err != nil { 394 return nil, errorsmod.Wrap(err, "could not create group policy account") 395 } 396 397 acc := k.accKeeper.NewAccount(ctx, account) 398 k.accKeeper.SetAccount(ctx, acc) 399 400 break 401 } 402 403 groupPolicy, err := group.NewGroupPolicyInfo( 404 accountAddr, 405 msg.GetGroupID(), 406 reqGroupAdmin, 407 msg.GetMetadata(), 408 1, 409 policy, 410 ctx.BlockTime(), 411 ) 412 if err != nil { 413 return nil, err 414 } 415 416 if err := k.groupPolicyTable.Create(ctx.KVStore(k.key), &groupPolicy); err != nil { 417 return nil, errorsmod.Wrap(err, "could not create group policy") 418 } 419 420 if err := ctx.EventManager().EmitTypedEvent(&group.EventCreateGroupPolicy{Address: accountAddr.String()}); err != nil { 421 return nil, err 422 } 423 424 return &group.MsgCreateGroupPolicyResponse{Address: accountAddr.String()}, nil 425 } 426 427 func (k Keeper) UpdateGroupPolicyAdmin(goCtx context.Context, msg *group.MsgUpdateGroupPolicyAdmin) (*group.MsgUpdateGroupPolicyAdminResponse, error) { 428 if strings.EqualFold(msg.Admin, msg.NewAdmin) { 429 return nil, errorsmod.Wrap(errors.ErrInvalid, "new and old admin are same") 430 } 431 432 if _, err := k.accKeeper.AddressCodec().StringToBytes(msg.NewAdmin); err != nil { 433 return nil, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "new admin address") 434 } 435 436 ctx := sdk.UnwrapSDKContext(goCtx) 437 action := func(groupPolicy *group.GroupPolicyInfo) error { 438 groupPolicy.Admin = msg.NewAdmin 439 groupPolicy.Version++ 440 return k.groupPolicyTable.Update(ctx.KVStore(k.key), groupPolicy) 441 } 442 443 if err := k.doUpdateGroupPolicy(ctx, msg.GroupPolicyAddress, msg.Admin, action, "group policy admin updated"); err != nil { 444 return nil, err 445 } 446 447 return &group.MsgUpdateGroupPolicyAdminResponse{}, nil 448 } 449 450 func (k Keeper) UpdateGroupPolicyDecisionPolicy(goCtx context.Context, msg *group.MsgUpdateGroupPolicyDecisionPolicy) (*group.MsgUpdateGroupPolicyDecisionPolicyResponse, error) { 451 policy, err := msg.GetDecisionPolicy() 452 if err != nil { 453 return nil, errorsmod.Wrap(err, "decision policy") 454 } 455 456 if err := policy.ValidateBasic(); err != nil { 457 return nil, errorsmod.Wrap(err, "decision policy") 458 } 459 460 ctx := sdk.UnwrapSDKContext(goCtx) 461 action := func(groupPolicy *group.GroupPolicyInfo) error { 462 groupInfo, err := k.getGroupInfo(ctx, groupPolicy.GroupId) 463 if err != nil { 464 return err 465 } 466 467 err = policy.Validate(groupInfo, k.config) 468 if err != nil { 469 return err 470 } 471 472 err = groupPolicy.SetDecisionPolicy(policy) 473 if err != nil { 474 return err 475 } 476 477 groupPolicy.Version++ 478 return k.groupPolicyTable.Update(ctx.KVStore(k.key), groupPolicy) 479 } 480 481 if err = k.doUpdateGroupPolicy(ctx, msg.GroupPolicyAddress, msg.Admin, action, "group policy's decision policy updated"); err != nil { 482 return nil, err 483 } 484 485 return &group.MsgUpdateGroupPolicyDecisionPolicyResponse{}, nil 486 } 487 488 func (k Keeper) UpdateGroupPolicyMetadata(goCtx context.Context, msg *group.MsgUpdateGroupPolicyMetadata) (*group.MsgUpdateGroupPolicyMetadataResponse, error) { 489 ctx := sdk.UnwrapSDKContext(goCtx) 490 metadata := msg.GetMetadata() 491 492 action := func(groupPolicy *group.GroupPolicyInfo) error { 493 groupPolicy.Metadata = metadata 494 groupPolicy.Version++ 495 return k.groupPolicyTable.Update(ctx.KVStore(k.key), groupPolicy) 496 } 497 498 if err := k.assertMetadataLength(metadata, "group policy metadata"); err != nil { 499 return nil, err 500 } 501 502 err := k.doUpdateGroupPolicy(ctx, msg.GroupPolicyAddress, msg.Admin, action, "group policy metadata updated") 503 if err != nil { 504 return nil, err 505 } 506 507 return &group.MsgUpdateGroupPolicyMetadataResponse{}, nil 508 } 509 510 func (k Keeper) SubmitProposal(goCtx context.Context, msg *group.MsgSubmitProposal) (*group.MsgSubmitProposalResponse, error) { 511 if len(msg.Proposers) == 0 { 512 return nil, errorsmod.Wrap(errors.ErrEmpty, "proposers") 513 } 514 515 if err := k.validateProposers(msg.Proposers); err != nil { 516 return nil, err 517 } 518 519 groupPolicyAddr, err := k.accKeeper.AddressCodec().StringToBytes(msg.GroupPolicyAddress) 520 if err != nil { 521 return nil, errorsmod.Wrap(err, "request account address of group policy") 522 } 523 524 if err := k.assertMetadataLength(msg.Title, "proposal Title"); err != nil { 525 return nil, err 526 } 527 528 if err := k.assertSummaryLength(msg.Summary); err != nil { 529 return nil, err 530 } 531 532 if err := k.assertMetadataLength(msg.Metadata, "metadata"); err != nil { 533 return nil, err 534 } 535 536 // verify that if present, the metadata title and summary equals the proposal title and summary 537 if len(msg.Metadata) != 0 { 538 proposalMetadata := govtypes.ProposalMetadata{} 539 if err := json.Unmarshal([]byte(msg.Metadata), &proposalMetadata); err == nil { 540 if proposalMetadata.Title != msg.Title { 541 return nil, fmt.Errorf("metadata title '%s' must equal proposal title '%s'", proposalMetadata.Title, msg.Title) 542 } 543 544 if proposalMetadata.Summary != msg.Summary { 545 return nil, fmt.Errorf("metadata summary '%s' must equal proposal summary '%s'", proposalMetadata.Summary, msg.Summary) 546 } 547 } 548 549 // if we can't unmarshal the metadata, this means the client didn't use the recommended metadata format 550 // nothing can be done here, and this is still a valid case, so we ignore the error 551 } 552 553 msgs, err := msg.GetMsgs() 554 if err != nil { 555 return nil, errorsmod.Wrap(err, "request msgs") 556 } 557 558 if err := validateMsgs(msgs); err != nil { 559 return nil, err 560 } 561 562 ctx := sdk.UnwrapSDKContext(goCtx) 563 policyAcc, err := k.getGroupPolicyInfo(ctx, msg.GroupPolicyAddress) 564 if err != nil { 565 return nil, errorsmod.Wrapf(err, "load group policy: %s", msg.GroupPolicyAddress) 566 } 567 568 groupInfo, err := k.getGroupInfo(ctx, policyAcc.GroupId) 569 if err != nil { 570 return nil, errorsmod.Wrap(err, "get group by groupId of group policy") 571 } 572 573 // Only members of the group can submit a new proposal. 574 for _, proposer := range msg.Proposers { 575 if !k.groupMemberTable.Has(ctx.KVStore(k.key), orm.PrimaryKey(&group.GroupMember{GroupId: groupInfo.Id, Member: &group.Member{Address: proposer}})) { 576 return nil, errorsmod.Wrapf(errors.ErrUnauthorized, "not in group: %s", proposer) 577 } 578 } 579 580 // Check that if the messages require signers, they are all equal to the given account address of group policy. 581 if err := ensureMsgAuthZ(msgs, groupPolicyAddr, k.cdc); err != nil { 582 return nil, err 583 } 584 585 policy, err := policyAcc.GetDecisionPolicy() 586 if err != nil { 587 return nil, errorsmod.Wrap(err, "proposal group policy decision policy") 588 } 589 590 // Prevent proposal that cannot succeed. 591 if err = policy.Validate(groupInfo, k.config); err != nil { 592 return nil, err 593 } 594 595 m := &group.Proposal{ 596 Id: k.proposalTable.Sequence().PeekNextVal(ctx.KVStore(k.key)), 597 GroupPolicyAddress: msg.GroupPolicyAddress, 598 Metadata: msg.Metadata, 599 Proposers: msg.Proposers, 600 SubmitTime: ctx.BlockTime(), 601 GroupVersion: groupInfo.Version, 602 GroupPolicyVersion: policyAcc.Version, 603 Status: group.PROPOSAL_STATUS_SUBMITTED, 604 ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, 605 VotingPeriodEnd: ctx.BlockTime().Add(policy.GetVotingPeriod()), // The voting window begins as soon as the proposal is submitted. 606 FinalTallyResult: group.DefaultTallyResult(), 607 Title: msg.Title, 608 Summary: msg.Summary, 609 } 610 611 if err := m.SetMsgs(msgs); err != nil { 612 return nil, errorsmod.Wrap(err, "create proposal") 613 } 614 615 id, err := k.proposalTable.Create(ctx.KVStore(k.key), m) 616 if err != nil { 617 return nil, errorsmod.Wrap(err, "create proposal") 618 } 619 620 if err := ctx.EventManager().EmitTypedEvent(&group.EventSubmitProposal{ProposalId: id}); err != nil { 621 return nil, err 622 } 623 624 // Try to execute proposal immediately 625 if msg.Exec == group.Exec_EXEC_TRY { 626 // Consider proposers as Yes votes 627 for _, proposer := range msg.Proposers { 628 ctx.GasMeter().ConsumeGas(gasCostPerIteration, "vote on proposal") 629 _, err = k.Vote(ctx, &group.MsgVote{ 630 ProposalId: id, 631 Voter: proposer, 632 Option: group.VOTE_OPTION_YES, 633 }) 634 if err != nil { 635 return &group.MsgSubmitProposalResponse{ProposalId: id}, errorsmod.Wrapf(err, "the proposal was created but failed on vote for voter %s", proposer) 636 } 637 } 638 639 // Then try to execute the proposal 640 _, err = k.Exec(ctx, &group.MsgExec{ 641 ProposalId: id, 642 // We consider the first proposer as the MsgExecRequest signer 643 // but that could be revisited (eg using the group policy) 644 Executor: msg.Proposers[0], 645 }) 646 if err != nil { 647 return &group.MsgSubmitProposalResponse{ProposalId: id}, errorsmod.Wrap(err, "the proposal was created but failed on exec") 648 } 649 } 650 651 return &group.MsgSubmitProposalResponse{ProposalId: id}, nil 652 } 653 654 func (k Keeper) WithdrawProposal(goCtx context.Context, msg *group.MsgWithdrawProposal) (*group.MsgWithdrawProposalResponse, error) { 655 if msg.ProposalId == 0 { 656 return nil, errorsmod.Wrap(errors.ErrEmpty, "proposal id") 657 } 658 659 if _, err := k.accKeeper.AddressCodec().StringToBytes(msg.Address); err != nil { 660 return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid group policy admin / proposer address: %s", msg.Address) 661 } 662 663 ctx := sdk.UnwrapSDKContext(goCtx) 664 proposal, err := k.getProposal(ctx, msg.ProposalId) 665 if err != nil { 666 return nil, err 667 } 668 669 // Ensure the proposal can be withdrawn. 670 if proposal.Status != group.PROPOSAL_STATUS_SUBMITTED { 671 return nil, errorsmod.Wrapf(errors.ErrInvalid, "cannot withdraw a proposal with the status of %s", proposal.Status.String()) 672 } 673 674 var policyInfo group.GroupPolicyInfo 675 if policyInfo, err = k.getGroupPolicyInfo(ctx, proposal.GroupPolicyAddress); err != nil { 676 return nil, errorsmod.Wrap(err, "load group policy") 677 } 678 679 // check address is the group policy admin he is in proposers list.. 680 if msg.Address != policyInfo.Admin && !isProposer(proposal, msg.Address) { 681 return nil, errorsmod.Wrapf(errors.ErrUnauthorized, "given address is neither group policy admin nor in proposers: %s", msg.Address) 682 } 683 684 proposal.Status = group.PROPOSAL_STATUS_WITHDRAWN 685 if err := k.proposalTable.Update(ctx.KVStore(k.key), msg.ProposalId, &proposal); err != nil { 686 return nil, err 687 } 688 689 if err := ctx.EventManager().EmitTypedEvent(&group.EventWithdrawProposal{ProposalId: msg.ProposalId}); err != nil { 690 return nil, err 691 } 692 693 return &group.MsgWithdrawProposalResponse{}, nil 694 } 695 696 func (k Keeper) Vote(goCtx context.Context, msg *group.MsgVote) (*group.MsgVoteResponse, error) { 697 if msg.ProposalId == 0 { 698 return nil, errorsmod.Wrap(errors.ErrEmpty, "proposal id") 699 } 700 701 // verify vote options 702 if msg.Option == group.VOTE_OPTION_UNSPECIFIED { 703 return nil, errorsmod.Wrap(errors.ErrEmpty, "vote option") 704 } 705 706 if _, ok := group.VoteOption_name[int32(msg.Option)]; !ok { 707 return nil, errorsmod.Wrap(errors.ErrInvalid, "vote option") 708 } 709 710 if err := k.assertMetadataLength(msg.Metadata, "metadata"); err != nil { 711 return nil, err 712 } 713 714 if _, err := k.accKeeper.AddressCodec().StringToBytes(msg.Voter); err != nil { 715 return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid voter address: %s", msg.Voter) 716 } 717 718 ctx := sdk.UnwrapSDKContext(goCtx) 719 proposal, err := k.getProposal(ctx, msg.ProposalId) 720 if err != nil { 721 return nil, err 722 } 723 724 // Ensure that we can still accept votes for this proposal. 725 if proposal.Status != group.PROPOSAL_STATUS_SUBMITTED { 726 return nil, errorsmod.Wrap(errors.ErrInvalid, "proposal not open for voting") 727 } 728 729 if ctx.BlockTime().After(proposal.VotingPeriodEnd) { 730 return nil, errorsmod.Wrap(errors.ErrExpired, "voting period has ended already") 731 } 732 733 policyInfo, err := k.getGroupPolicyInfo(ctx, proposal.GroupPolicyAddress) 734 if err != nil { 735 return nil, errorsmod.Wrap(err, "load group policy") 736 } 737 738 groupInfo, err := k.getGroupInfo(ctx, policyInfo.GroupId) 739 if err != nil { 740 return nil, err 741 } 742 743 // Count and store votes. 744 voter := group.GroupMember{GroupId: groupInfo.Id, Member: &group.Member{Address: msg.Voter}} 745 if err := k.groupMemberTable.GetOne(ctx.KVStore(k.key), orm.PrimaryKey(&voter), &voter); err != nil { 746 return nil, errorsmod.Wrapf(err, "voter address: %s", msg.Voter) 747 } 748 newVote := group.Vote{ 749 ProposalId: msg.ProposalId, 750 Voter: msg.Voter, 751 Option: msg.Option, 752 Metadata: msg.Metadata, 753 SubmitTime: ctx.BlockTime(), 754 } 755 756 // The ORM will return an error if the vote already exists, 757 // making sure than a voter hasn't already voted. 758 if err := k.voteTable.Create(ctx.KVStore(k.key), &newVote); err != nil { 759 return nil, errorsmod.Wrap(err, "store vote") 760 } 761 762 if err := ctx.EventManager().EmitTypedEvent(&group.EventVote{ProposalId: msg.ProposalId}); err != nil { 763 return nil, err 764 } 765 766 // Try to execute proposal immediately 767 if msg.Exec == group.Exec_EXEC_TRY { 768 _, err = k.Exec(ctx, &group.MsgExec{ProposalId: msg.ProposalId, Executor: msg.Voter}) 769 if err != nil { 770 return nil, err 771 } 772 } 773 774 return &group.MsgVoteResponse{}, nil 775 } 776 777 // doTallyAndUpdate performs a tally, and, if the tally result is final, then: 778 // - updates the proposal's `Status` and `FinalTallyResult` fields, 779 // - prune all the votes. 780 func (k Keeper) doTallyAndUpdate(ctx sdk.Context, p *group.Proposal, groupInfo group.GroupInfo, policyInfo group.GroupPolicyInfo) error { 781 policy, err := policyInfo.GetDecisionPolicy() 782 if err != nil { 783 return err 784 } 785 786 tallyResult, err := k.Tally(ctx, *p, policyInfo.GroupId) 787 if err != nil { 788 return err 789 } 790 791 result, err := policy.Allow(tallyResult, groupInfo.TotalWeight) 792 if err != nil { 793 return errorsmod.Wrap(err, "policy allow") 794 } 795 796 // If the result was final (i.e. enough votes to pass) or if the voting 797 // period ended, then we consider the proposal as final. 798 if isFinal := result.Final || ctx.BlockTime().After(p.VotingPeriodEnd); isFinal { 799 if err := k.pruneVotes(ctx, p.Id); err != nil { 800 return err 801 } 802 p.FinalTallyResult = tallyResult 803 if result.Allow { 804 p.Status = group.PROPOSAL_STATUS_ACCEPTED 805 } else { 806 p.Status = group.PROPOSAL_STATUS_REJECTED 807 } 808 809 } 810 811 return nil 812 } 813 814 // Exec executes the messages from a proposal. 815 func (k Keeper) Exec(goCtx context.Context, msg *group.MsgExec) (*group.MsgExecResponse, error) { 816 if msg.ProposalId == 0 { 817 return nil, errorsmod.Wrap(errors.ErrEmpty, "proposal id") 818 } 819 820 ctx := sdk.UnwrapSDKContext(goCtx) 821 proposal, err := k.getProposal(ctx, msg.ProposalId) 822 if err != nil { 823 return nil, err 824 } 825 826 if proposal.Status != group.PROPOSAL_STATUS_SUBMITTED && proposal.Status != group.PROPOSAL_STATUS_ACCEPTED { 827 return nil, errorsmod.Wrapf(errors.ErrInvalid, "not possible to exec with proposal status %s", proposal.Status.String()) 828 } 829 830 policyInfo, err := k.getGroupPolicyInfo(ctx, proposal.GroupPolicyAddress) 831 if err != nil { 832 return nil, errorsmod.Wrap(err, "load group policy") 833 } 834 835 // If proposal is still in SUBMITTED phase, it means that the voting period 836 // didn't end yet, and tallying hasn't been done. In this case, we need to 837 // tally first. 838 if proposal.Status == group.PROPOSAL_STATUS_SUBMITTED { 839 groupInfo, err := k.getGroupInfo(ctx, policyInfo.GroupId) 840 if err != nil { 841 return nil, errorsmod.Wrap(err, "load group") 842 } 843 844 if err = k.doTallyAndUpdate(ctx, &proposal, groupInfo, policyInfo); err != nil { 845 return nil, err 846 } 847 } 848 849 // Execute proposal payload. 850 var logs string 851 if proposal.Status == group.PROPOSAL_STATUS_ACCEPTED && proposal.ExecutorResult != group.PROPOSAL_EXECUTOR_RESULT_SUCCESS { 852 // Caching context so that we don't update the store in case of failure. 853 cacheCtx, flush := ctx.CacheContext() 854 855 addr, err := k.accKeeper.AddressCodec().StringToBytes(policyInfo.Address) 856 if err != nil { 857 return nil, err 858 } 859 860 decisionPolicy := policyInfo.DecisionPolicy.GetCachedValue().(group.DecisionPolicy) 861 if results, err := k.doExecuteMsgs(cacheCtx, k.router, proposal, addr, decisionPolicy); err != nil { 862 proposal.ExecutorResult = group.PROPOSAL_EXECUTOR_RESULT_FAILURE 863 logs = fmt.Sprintf("proposal execution failed on proposal %d, because of error %s", proposal.Id, err.Error()) 864 k.Logger(ctx).Info("proposal execution failed", "cause", err, "proposalID", proposal.Id) 865 } else { 866 proposal.ExecutorResult = group.PROPOSAL_EXECUTOR_RESULT_SUCCESS 867 flush() 868 869 for _, res := range results { 870 // NOTE: The sdk msg handler creates a new EventManager, so events must be correctly propagated back to the current context 871 ctx.EventManager().EmitEvents(res.GetEvents()) 872 } 873 } 874 } 875 876 // Update proposal in proposalTable 877 // If proposal has successfully run, delete it from state. 878 if proposal.ExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS { 879 if err := k.pruneProposal(ctx, proposal.Id); err != nil { 880 return nil, err 881 } 882 883 // Emit event for proposal finalized with its result 884 if err := ctx.EventManager().EmitTypedEvent( 885 &group.EventProposalPruned{ 886 ProposalId: proposal.Id, 887 Status: proposal.Status, 888 TallyResult: &proposal.FinalTallyResult, 889 }); err != nil { 890 return nil, err 891 } 892 } else { 893 store := ctx.KVStore(k.key) 894 if err := k.proposalTable.Update(store, proposal.Id, &proposal); err != nil { 895 return nil, err 896 } 897 } 898 899 if err := ctx.EventManager().EmitTypedEvent(&group.EventExec{ 900 ProposalId: proposal.Id, 901 Logs: logs, 902 Result: proposal.ExecutorResult, 903 }); err != nil { 904 return nil, err 905 } 906 907 return &group.MsgExecResponse{ 908 Result: proposal.ExecutorResult, 909 }, nil 910 } 911 912 // LeaveGroup implements the MsgServer/LeaveGroup method. 913 func (k Keeper) LeaveGroup(goCtx context.Context, msg *group.MsgLeaveGroup) (*group.MsgLeaveGroupResponse, error) { 914 if msg.GroupId == 0 { 915 return nil, errorsmod.Wrap(errors.ErrEmpty, "group-id") 916 } 917 918 _, err := k.accKeeper.AddressCodec().StringToBytes(msg.Address) 919 if err != nil { 920 return nil, errorsmod.Wrap(err, "group member") 921 } 922 923 ctx := sdk.UnwrapSDKContext(goCtx) 924 groupInfo, err := k.getGroupInfo(ctx, msg.GroupId) 925 if err != nil { 926 return nil, errorsmod.Wrap(err, "group") 927 } 928 929 groupWeight, err := math.NewNonNegativeDecFromString(groupInfo.TotalWeight) 930 if err != nil { 931 return nil, err 932 } 933 934 gm, err := k.getGroupMember(ctx, &group.GroupMember{ 935 GroupId: msg.GroupId, 936 Member: &group.Member{Address: msg.Address}, 937 }) 938 if err != nil { 939 return nil, err 940 } 941 942 memberWeight, err := math.NewPositiveDecFromString(gm.Member.Weight) 943 if err != nil { 944 return nil, err 945 } 946 947 updatedWeight, err := math.SubNonNegative(groupWeight, memberWeight) 948 if err != nil { 949 return nil, err 950 } 951 952 // delete group member in the groupMemberTable. 953 if err := k.groupMemberTable.Delete(ctx.KVStore(k.key), gm); err != nil { 954 return nil, errorsmod.Wrap(err, "group member") 955 } 956 957 // update group weight 958 groupInfo.TotalWeight = updatedWeight.String() 959 groupInfo.Version++ 960 961 if err := k.validateDecisionPolicies(ctx, groupInfo); err != nil { 962 return nil, err 963 } 964 965 if err := k.groupTable.Update(ctx.KVStore(k.key), groupInfo.Id, &groupInfo); err != nil { 966 return nil, err 967 } 968 969 if err := ctx.EventManager().EmitTypedEvent(&group.EventLeaveGroup{ 970 GroupId: msg.GroupId, 971 Address: msg.Address, 972 }); err != nil { 973 return nil, err 974 } 975 976 return &group.MsgLeaveGroupResponse{}, nil 977 } 978 979 func (k Keeper) getGroupMember(ctx sdk.Context, member *group.GroupMember) (*group.GroupMember, error) { 980 var groupMember group.GroupMember 981 switch err := k.groupMemberTable.GetOne(ctx.KVStore(k.key), 982 orm.PrimaryKey(member), &groupMember); { 983 case err == nil: 984 break 985 case sdkerrors.ErrNotFound.Is(err): 986 return nil, sdkerrors.ErrNotFound.Wrapf("%s is not part of group %d", member.Member.Address, member.GroupId) 987 default: 988 return nil, err 989 } 990 991 return &groupMember, nil 992 } 993 994 type ( 995 actionFn func(m *group.GroupInfo) error 996 groupPolicyActionFn func(m *group.GroupPolicyInfo) error 997 ) 998 999 // doUpdateGroupPolicy first makes sure that the group policy admin initiated the group policy update, 1000 // before performing the group policy update and emitting an event. 1001 func (k Keeper) doUpdateGroupPolicy(ctx sdk.Context, reqGroupPolicy, reqAdmin string, action groupPolicyActionFn, note string) error { 1002 groupPolicyAddr, err := k.accKeeper.AddressCodec().StringToBytes(reqGroupPolicy) 1003 if err != nil { 1004 return errorsmod.Wrap(err, "group policy address") 1005 } 1006 1007 _, err = k.accKeeper.AddressCodec().StringToBytes(reqAdmin) 1008 if err != nil { 1009 return errorsmod.Wrap(err, "group policy admin") 1010 } 1011 1012 groupPolicyInfo, err := k.getGroupPolicyInfo(ctx, reqGroupPolicy) 1013 if err != nil { 1014 return errorsmod.Wrap(err, "load group policy") 1015 } 1016 1017 // Only current group policy admin is authorized to update a group policy. 1018 if reqAdmin != groupPolicyInfo.Admin { 1019 return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "not group policy admin") 1020 } 1021 1022 if err := action(&groupPolicyInfo); err != nil { 1023 return errorsmod.Wrap(err, note) 1024 } 1025 1026 if err = k.abortProposals(ctx, groupPolicyAddr); err != nil { 1027 return err 1028 } 1029 1030 if err = ctx.EventManager().EmitTypedEvent(&group.EventUpdateGroupPolicy{Address: groupPolicyInfo.Address}); err != nil { 1031 return err 1032 } 1033 1034 return nil 1035 } 1036 1037 // doUpdateGroup first makes sure that the group admin initiated the group update, 1038 // before performing the group update and emitting an event. 1039 func (k Keeper) doUpdateGroup(ctx sdk.Context, groupID uint64, reqGroupAdmin string, action actionFn, errNote string) error { 1040 groupInfo, err := k.getGroupInfo(ctx, groupID) 1041 if err != nil { 1042 return err 1043 } 1044 1045 if !strings.EqualFold(groupInfo.Admin, reqGroupAdmin) { 1046 return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "not group admin; got %s, expected %s", reqGroupAdmin, groupInfo.Admin) 1047 } 1048 1049 if err := action(&groupInfo); err != nil { 1050 return errorsmod.Wrap(err, errNote) 1051 } 1052 1053 if err := ctx.EventManager().EmitTypedEvent(&group.EventUpdateGroup{GroupId: groupID}); err != nil { 1054 return err 1055 } 1056 1057 return nil 1058 } 1059 1060 // assertMetadataLength returns an error if given metadata length 1061 // is greater than a pre-defined maxMetadataLen. 1062 func (k Keeper) assertMetadataLength(metadata, description string) error { 1063 if metadata != "" && uint64(len(metadata)) > k.config.MaxMetadataLen { 1064 return errorsmod.Wrapf(errors.ErrMaxLimit, description) 1065 } 1066 return nil 1067 } 1068 1069 // assertSummaryLength returns an error if given summary length 1070 // is greater than a pre-defined 40*MaxMetadataLen. 1071 func (k Keeper) assertSummaryLength(summary string) error { 1072 if summary != "" && uint64(len(summary)) > 40*k.config.MaxMetadataLen { 1073 return errorsmod.Wrapf(errors.ErrMaxLimit, "proposal summary is too long") 1074 } 1075 return nil 1076 } 1077 1078 // validateDecisionPolicies loops through all decision policies from the group, 1079 // and calls each of their Validate() method. 1080 func (k Keeper) validateDecisionPolicies(ctx sdk.Context, g group.GroupInfo) error { 1081 it, err := k.groupPolicyByGroupIndex.Get(ctx.KVStore(k.key), g.Id) 1082 if err != nil { 1083 return err 1084 } 1085 defer it.Close() 1086 1087 for { 1088 var groupPolicy group.GroupPolicyInfo 1089 _, err = it.LoadNext(&groupPolicy) 1090 if errors.ErrORMIteratorDone.Is(err) { 1091 break 1092 } 1093 if err != nil { 1094 return err 1095 } 1096 1097 err = groupPolicy.DecisionPolicy.GetCachedValue().(group.DecisionPolicy).Validate(g, k.config) 1098 if err != nil { 1099 return err 1100 } 1101 } 1102 1103 return nil 1104 } 1105 1106 // validateProposers checks that all proposers addresses are valid. 1107 // It as well verifies that there is no duplicate address. 1108 func (k Keeper) validateProposers(proposers []string) error { 1109 index := make(map[string]struct{}, len(proposers)) 1110 for _, proposer := range proposers { 1111 if _, exists := index[proposer]; exists { 1112 return errorsmod.Wrapf(errors.ErrDuplicate, "address: %s", proposer) 1113 } 1114 1115 _, err := k.accKeeper.AddressCodec().StringToBytes(proposer) 1116 if err != nil { 1117 return errorsmod.Wrapf(err, "proposer address %s", proposer) 1118 } 1119 1120 index[proposer] = struct{}{} 1121 } 1122 1123 return nil 1124 } 1125 1126 // validateMembers checks that all members addresses are valid. 1127 // additionally it verifies that there is no duplicate address 1128 // and the member weight is non-negative. 1129 // Note: in state, a member's weight MUST be positive. However, in some Msgs, 1130 // it's possible to set a zero member weight, for example in 1131 // MsgUpdateGroupMembers to denote that we're removing a member. 1132 // It returns an error if any of the above conditions is not met. 1133 func (k Keeper) validateMembers(members []group.MemberRequest) error { 1134 index := make(map[string]struct{}, len(members)) 1135 for _, member := range members { 1136 if _, exists := index[member.Address]; exists { 1137 return errorsmod.Wrapf(errors.ErrDuplicate, "address: %s", member.Address) 1138 } 1139 1140 _, err := k.accKeeper.AddressCodec().StringToBytes(member.Address) 1141 if err != nil { 1142 return errorsmod.Wrapf(err, "member address %s", member.Address) 1143 } 1144 1145 if _, err := math.NewNonNegativeDecFromString(member.Weight); err != nil { 1146 return errorsmod.Wrap(err, "weight must be non negative") 1147 } 1148 1149 index[member.Address] = struct{}{} 1150 } 1151 1152 return nil 1153 } 1154 1155 // isProposer checks that an address is a proposer of a given proposal. 1156 func isProposer(proposal group.Proposal, address string) bool { 1157 for _, proposer := range proposal.Proposers { 1158 if proposer == address { 1159 return true 1160 } 1161 } 1162 1163 return false 1164 } 1165 1166 func validateMsgs(msgs []sdk.Msg) error { 1167 for i, msg := range msgs { 1168 m, ok := msg.(sdk.HasValidateBasic) 1169 if !ok { 1170 continue 1171 } 1172 1173 if err := m.ValidateBasic(); err != nil { 1174 return errorsmod.Wrapf(err, "msg %d", i) 1175 } 1176 } 1177 1178 return nil 1179 }