package keeper import ( "fmt" "github.com/cosmos/cosmos-sdk/client" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/gov/types" v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" ) // SubmitProposal create new proposal given an array of messages func (keeper Keeper) SubmitProposal(ctx sdk.Context, messages []sdk.Msg, metadata string) (v1.Proposal, error) { err := keeper.assertMetadataLength(metadata) if err != nil { return v1.Proposal{}, err } // Will hold a comma-separated string of all Msg type URLs. msgsStr := "" // Loop through all messages and confirm that each has a handler and the gov module account // as the only signer for _, msg := range messages { msgsStr += fmt.Sprintf(",%s", sdk.MsgTypeURL(msg)) // perform a basic validation of the message if err := msg.ValidateBasic(); err != nil { return v1.Proposal{}, sdkerrors.Wrap(types.ErrInvalidProposalMsg, err.Error()) } signers := msg.GetSigners() if len(signers) != 1 { return v1.Proposal{}, types.ErrInvalidSigner } // assert that the governance module account is the only signer of the messages if !signers[0].Equals(keeper.GetGovernanceAccount(ctx).GetAddress()) { return v1.Proposal{}, sdkerrors.Wrapf(types.ErrInvalidSigner, signers[0].String()) } // use the msg service router to see that there is a valid route for that message. handler := keeper.router.Handler(msg) if handler == nil { return v1.Proposal{}, sdkerrors.Wrap(types.ErrUnroutableProposalMsg, sdk.MsgTypeURL(msg)) } // Only if it's a MsgExecLegacyContent do we try to execute the // proposal in a cached context. // For other Msgs, we do not verify the proposal messages any further. // They may fail upon execution. // ref: https://github.com/cosmos/cosmos-sdk/pull/10868#discussion_r784872842 if msg, ok := msg.(*v1.MsgExecLegacyContent); ok { cacheCtx, _ := ctx.CacheContext() if _, err := handler(cacheCtx, msg); err != nil { return v1.Proposal{}, sdkerrors.Wrap(types.ErrNoProposalHandlerExists, err.Error()) } } } proposalID, err := keeper.GetProposalID(ctx) if err != nil { return v1.Proposal{}, err } submitTime := ctx.BlockHeader().Time depositPeriod := keeper.GetDepositParams(ctx).MaxDepositPeriod proposal, err := v1.NewProposal(messages, proposalID, metadata, submitTime, submitTime.Add(*depositPeriod)) if err != nil { return v1.Proposal{}, err } keeper.SetProposal(ctx, proposal) keeper.InsertInactiveProposalQueue(ctx, proposalID, *proposal.DepositEndTime) keeper.SetProposalID(ctx, proposalID+1) // called right after a proposal is submitted keeper.AfterProposalSubmission(ctx, proposalID) ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeSubmitProposal, sdk.NewAttribute(types.AttributeKeyProposalID, fmt.Sprintf("%d", proposalID)), sdk.NewAttribute(types.AttributeKeyProposalMessages, msgsStr), ), ) return proposal, nil } // GetProposal get proposal from store by ProposalID. // Panics if can't unmarshal the proposal. func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID uint64) (v1.Proposal, bool) { store := ctx.KVStore(keeper.storeKey) bz := store.Get(types.ProposalKey(proposalID)) if bz == nil { return v1.Proposal{}, false } var proposal v1.Proposal if err := keeper.UnmarshalProposal(bz, &proposal); err != nil { panic(err) } return proposal, true } // SetProposal set a proposal to store. // Panics if can't marshal the proposal. func (keeper Keeper) SetProposal(ctx sdk.Context, proposal v1.Proposal) { bz, err := keeper.MarshalProposal(proposal) if err != nil { panic(err) } store := ctx.KVStore(keeper.storeKey) store.Set(types.ProposalKey(proposal.Id), bz) } // DeleteProposal deletes a proposal from store. // Panics if the proposal doesn't exist. func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposalID uint64) { store := ctx.KVStore(keeper.storeKey) proposal, ok := keeper.GetProposal(ctx, proposalID) if !ok { panic(fmt.Sprintf("couldn't find proposal with id#%d", proposalID)) } if proposal.DepositEndTime != nil { keeper.RemoveFromInactiveProposalQueue(ctx, proposalID, *proposal.DepositEndTime) } if proposal.VotingEndTime != nil { keeper.RemoveFromActiveProposalQueue(ctx, proposalID, *proposal.VotingEndTime) } store.Delete(types.ProposalKey(proposalID)) } // IterateProposals iterates over the all the proposals and performs a callback function. // Panics when the iterator encounters a proposal which can't be unmarshaled. func (keeper Keeper) IterateProposals(ctx sdk.Context, cb func(proposal v1.Proposal) (stop bool)) { store := ctx.KVStore(keeper.storeKey) iterator := sdk.KVStorePrefixIterator(store, types.ProposalsKeyPrefix) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { var proposal v1.Proposal err := keeper.UnmarshalProposal(iterator.Value(), &proposal) if err != nil { panic(err) } if cb(proposal) { break } } } // GetProposals returns all the proposals from store func (keeper Keeper) GetProposals(ctx sdk.Context) (proposals v1.Proposals) { keeper.IterateProposals(ctx, func(proposal v1.Proposal) bool { proposals = append(proposals, &proposal) return false }) return } // GetProposalsFiltered retrieves proposals filtered by a given set of params which // include pagination parameters along with voter and depositor addresses and a // proposal status. The voter address will filter proposals by whether or not // that address has voted on proposals. The depositor address will filter proposals // by whether or not that address has deposited to them. Finally, status will filter // proposals by status. // // NOTE: If no filters are provided, all proposals will be returned in paginated // form. func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, params v1.QueryProposalsParams) v1.Proposals { proposals := keeper.GetProposals(ctx) filteredProposals := make([]*v1.Proposal, 0, len(proposals)) for _, p := range proposals { matchVoter, matchDepositor, matchStatus := true, true, true // match status (if supplied/valid) if v1.ValidProposalStatus(params.ProposalStatus) { matchStatus = p.Status == params.ProposalStatus } // match voter address (if supplied) if len(params.Voter) > 0 { _, matchVoter = keeper.GetVote(ctx, p.Id, params.Voter) } // match depositor (if supplied) if len(params.Depositor) > 0 { _, matchDepositor = keeper.GetDeposit(ctx, p.Id, params.Depositor) } if matchVoter && matchDepositor && matchStatus { filteredProposals = append(filteredProposals, p) } } start, end := client.Paginate(len(filteredProposals), params.Page, params.Limit, 100) if start < 0 || end < 0 { filteredProposals = []*v1.Proposal{} } else { filteredProposals = filteredProposals[start:end] } return filteredProposals } // GetProposalID gets the highest proposal ID func (keeper Keeper) GetProposalID(ctx sdk.Context) (proposalID uint64, err error) { store := ctx.KVStore(keeper.storeKey) bz := store.Get(types.ProposalIDKey) if bz == nil { return 0, sdkerrors.Wrap(types.ErrInvalidGenesis, "initial proposal ID hasn't been set") } proposalID = types.GetProposalIDFromBytes(bz) return proposalID, nil } // SetProposalID sets the new proposal ID to the store func (keeper Keeper) SetProposalID(ctx sdk.Context, proposalID uint64) { store := ctx.KVStore(keeper.storeKey) store.Set(types.ProposalIDKey, types.GetProposalIDBytes(proposalID)) } func (keeper Keeper) ActivateVotingPeriod(ctx sdk.Context, proposal v1.Proposal) { startTime := ctx.BlockHeader().Time proposal.VotingStartTime = &startTime votingPeriod := keeper.GetVotingParams(ctx).VotingPeriod endTime := proposal.VotingStartTime.Add(*votingPeriod) proposal.VotingEndTime = &endTime proposal.Status = v1.StatusVotingPeriod keeper.SetProposal(ctx, proposal) keeper.RemoveFromInactiveProposalQueue(ctx, proposal.Id, *proposal.DepositEndTime) keeper.InsertActiveProposalQueue(ctx, proposal.Id, *proposal.VotingEndTime) } func (keeper Keeper) MarshalProposal(proposal v1.Proposal) ([]byte, error) { bz, err := keeper.cdc.Marshal(&proposal) if err != nil { return nil, err } return bz, nil } func (keeper Keeper) UnmarshalProposal(bz []byte, proposal *v1.Proposal) error { err := keeper.cdc.Unmarshal(bz, proposal) if err != nil { return err } return nil }