260 lines
8.3 KiB
Go
260 lines
8.3 KiB
Go
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"
|
|
"github.com/cosmos/cosmos-sdk/x/gov/types/v1beta2"
|
|
)
|
|
|
|
// SubmitProposal create new proposal given an array of messages
|
|
func (keeper Keeper) SubmitProposal(ctx sdk.Context, messages []sdk.Msg) (v1beta2.Proposal, error) {
|
|
// 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 v1beta2.Proposal{}, sdkerrors.Wrap(types.ErrInvalidProposalMsg, err.Error())
|
|
}
|
|
|
|
signers := msg.GetSigners()
|
|
if len(signers) != 1 {
|
|
return v1beta2.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 v1beta2.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 v1beta2.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.(*v1beta2.MsgExecLegacyContent); ok {
|
|
cacheCtx, _ := ctx.CacheContext()
|
|
if _, err := handler(cacheCtx, msg); err != nil {
|
|
return v1beta2.Proposal{}, sdkerrors.Wrap(types.ErrNoProposalHandlerExists, err.Error())
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
proposalID, err := keeper.GetProposalID(ctx)
|
|
if err != nil {
|
|
return v1beta2.Proposal{}, err
|
|
}
|
|
|
|
submitTime := ctx.BlockHeader().Time
|
|
depositPeriod := keeper.GetDepositParams(ctx).MaxDepositPeriod
|
|
|
|
proposal, err := v1beta2.NewProposal(messages, proposalID, submitTime, submitTime.Add(*depositPeriod))
|
|
if err != nil {
|
|
return v1beta2.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) (v1beta2.Proposal, bool) {
|
|
store := ctx.KVStore(keeper.storeKey)
|
|
|
|
bz := store.Get(types.ProposalKey(proposalID))
|
|
if bz == nil {
|
|
return v1beta2.Proposal{}, false
|
|
}
|
|
|
|
var proposal v1beta2.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 v1beta2.Proposal) {
|
|
bz, err := keeper.MarshalProposal(proposal)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
store := ctx.KVStore(keeper.storeKey)
|
|
store.Set(types.ProposalKey(proposal.ProposalId), 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 v1beta2.Proposal) (stop bool)) {
|
|
store := ctx.KVStore(keeper.storeKey)
|
|
|
|
iterator := sdk.KVStorePrefixIterator(store, types.ProposalsKeyPrefix)
|
|
defer iterator.Close()
|
|
|
|
for ; iterator.Valid(); iterator.Next() {
|
|
var proposal v1beta2.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 v1beta2.Proposals) {
|
|
keeper.IterateProposals(ctx, func(proposal v1beta2.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 v1beta2.QueryProposalsParams) v1beta2.Proposals {
|
|
proposals := keeper.GetProposals(ctx)
|
|
filteredProposals := make([]*v1beta2.Proposal, 0, len(proposals))
|
|
|
|
for _, p := range proposals {
|
|
matchVoter, matchDepositor, matchStatus := true, true, true
|
|
|
|
// match status (if supplied/valid)
|
|
if v1beta2.ValidProposalStatus(params.ProposalStatus) {
|
|
matchStatus = p.Status == params.ProposalStatus
|
|
}
|
|
|
|
// match voter address (if supplied)
|
|
if len(params.Voter) > 0 {
|
|
_, matchVoter = keeper.GetVote(ctx, p.ProposalId, params.Voter)
|
|
}
|
|
|
|
// match depositor (if supplied)
|
|
if len(params.Depositor) > 0 {
|
|
_, matchDepositor = keeper.GetDeposit(ctx, p.ProposalId, 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 = []*v1beta2.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 v1beta2.Proposal) {
|
|
startTime := ctx.BlockHeader().Time
|
|
proposal.VotingStartTime = &startTime
|
|
votingPeriod := keeper.GetVotingParams(ctx).VotingPeriod
|
|
endTime := proposal.VotingStartTime.Add(*votingPeriod)
|
|
proposal.VotingEndTime = &endTime
|
|
proposal.Status = v1beta2.StatusVotingPeriod
|
|
keeper.SetProposal(ctx, proposal)
|
|
|
|
keeper.RemoveFromInactiveProposalQueue(ctx, proposal.ProposalId, *proposal.DepositEndTime)
|
|
keeper.InsertActiveProposalQueue(ctx, proposal.ProposalId, *proposal.VotingEndTime)
|
|
}
|
|
|
|
func (keeper Keeper) MarshalProposal(proposal v1beta2.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 *v1beta2.Proposal) error {
|
|
err := keeper.cdc.Unmarshal(bz, proposal)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|