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

     1  package keeper
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"time"
     9  
    10  	"cosmossdk.io/collections"
    11  	errorsmod "cosmossdk.io/errors"
    12  
    13  	sdk "github.com/cosmos/cosmos-sdk/types"
    14  	"github.com/cosmos/cosmos-sdk/x/gov/types"
    15  	v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
    16  )
    17  
    18  // SubmitProposal creates a new proposal given an array of messages
    19  func (keeper Keeper) SubmitProposal(ctx context.Context, messages []sdk.Msg, metadata, title, summary string, proposer sdk.AccAddress, expedited bool) (v1.Proposal, error) {
    20  	sdkCtx := sdk.UnwrapSDKContext(ctx)
    21  	err := keeper.assertMetadataLength(metadata)
    22  	if err != nil {
    23  		return v1.Proposal{}, err
    24  	}
    25  
    26  	// assert summary is no longer than predefined max length of metadata
    27  	err = keeper.assertSummaryLength(summary)
    28  	if err != nil {
    29  		return v1.Proposal{}, err
    30  	}
    31  
    32  	// assert title is no longer than predefined max length of metadata
    33  	err = keeper.assertMetadataLength(title)
    34  	if err != nil {
    35  		return v1.Proposal{}, err
    36  	}
    37  
    38  	// Will hold a comma-separated string of all Msg type URLs.
    39  	msgsStr := ""
    40  
    41  	// Loop through all messages and confirm that each has a handler and the gov module account
    42  	// as the only signer
    43  	for _, msg := range messages {
    44  		msgsStr += fmt.Sprintf(",%s", sdk.MsgTypeURL(msg))
    45  
    46  		// perform a basic validation of the message
    47  		if m, ok := msg.(sdk.HasValidateBasic); ok {
    48  			if err := m.ValidateBasic(); err != nil {
    49  				return v1.Proposal{}, errorsmod.Wrap(types.ErrInvalidProposalMsg, err.Error())
    50  			}
    51  		}
    52  
    53  		signers, _, err := keeper.cdc.GetMsgV1Signers(msg)
    54  		if err != nil {
    55  			return v1.Proposal{}, err
    56  		}
    57  		if len(signers) != 1 {
    58  			return v1.Proposal{}, types.ErrInvalidSigner
    59  		}
    60  
    61  		// assert that the governance module account is the only signer of the messages
    62  		if !bytes.Equal(signers[0], keeper.GetGovernanceAccount(ctx).GetAddress()) {
    63  			return v1.Proposal{}, errorsmod.Wrapf(types.ErrInvalidSigner, sdk.AccAddress(signers[0]).String())
    64  		}
    65  
    66  		// use the msg service router to see that there is a valid route for that message.
    67  		handler := keeper.router.Handler(msg)
    68  		if handler == nil {
    69  			return v1.Proposal{}, errorsmod.Wrap(types.ErrUnroutableProposalMsg, sdk.MsgTypeURL(msg))
    70  		}
    71  
    72  		// Only if it's a MsgExecLegacyContent do we try to execute the
    73  		// proposal in a cached context.
    74  		// For other Msgs, we do not verify the proposal messages any further.
    75  		// They may fail upon execution.
    76  		// ref: https://github.com/cosmos/cosmos-sdk/pull/10868#discussion_r784872842
    77  		if msg, ok := msg.(*v1.MsgExecLegacyContent); ok {
    78  			cacheCtx, _ := sdkCtx.CacheContext()
    79  			if _, err := handler(cacheCtx, msg); err != nil {
    80  				if errors.Is(types.ErrNoProposalHandlerExists, err) {
    81  					return v1.Proposal{}, err
    82  				}
    83  				return v1.Proposal{}, errorsmod.Wrap(types.ErrInvalidProposalContent, err.Error())
    84  			}
    85  		}
    86  
    87  	}
    88  
    89  	proposalID, err := keeper.ProposalID.Next(ctx)
    90  	if err != nil {
    91  		return v1.Proposal{}, err
    92  	}
    93  
    94  	params, err := keeper.Params.Get(ctx)
    95  	if err != nil {
    96  		return v1.Proposal{}, err
    97  	}
    98  
    99  	submitTime := sdkCtx.BlockHeader().Time
   100  	depositPeriod := params.MaxDepositPeriod
   101  
   102  	proposal, err := v1.NewProposal(messages, proposalID, submitTime, submitTime.Add(*depositPeriod), metadata, title, summary, proposer, expedited)
   103  	if err != nil {
   104  		return v1.Proposal{}, err
   105  	}
   106  
   107  	err = keeper.SetProposal(ctx, proposal)
   108  	if err != nil {
   109  		return v1.Proposal{}, err
   110  	}
   111  	err = keeper.InactiveProposalsQueue.Set(ctx, collections.Join(*proposal.DepositEndTime, proposalID), proposalID)
   112  	if err != nil {
   113  		return v1.Proposal{}, err
   114  	}
   115  
   116  	// called right after a proposal is submitted
   117  	err = keeper.Hooks().AfterProposalSubmission(ctx, proposalID)
   118  	if err != nil {
   119  		return v1.Proposal{}, err
   120  	}
   121  
   122  	sdkCtx.EventManager().EmitEvent(
   123  		sdk.NewEvent(
   124  			types.EventTypeSubmitProposal,
   125  			sdk.NewAttribute(types.AttributeKeyProposalID, fmt.Sprintf("%d", proposalID)),
   126  			sdk.NewAttribute(types.AttributeKeyProposalProposer, proposer.String()),
   127  			sdk.NewAttribute(types.AttributeKeyProposalMessages, msgsStr),
   128  		),
   129  	)
   130  
   131  	return proposal, nil
   132  }
   133  
   134  // CancelProposal will cancel proposal before the voting period ends
   135  func (keeper Keeper) CancelProposal(ctx context.Context, proposalID uint64, proposer string) error {
   136  	sdkCtx := sdk.UnwrapSDKContext(ctx)
   137  	proposal, err := keeper.Proposals.Get(ctx, proposalID)
   138  	if err != nil {
   139  		return err
   140  	}
   141  
   142  	// Checking proposal have proposer or not because old proposal doesn't have proposer field,
   143  	// https://github.com/cosmos/cosmos-sdk/blob/v0.46.2/proto/cosmos/gov/v1/gov.proto#L43
   144  	if proposal.Proposer == "" {
   145  		return types.ErrInvalidProposal.Wrapf("proposal %d doesn't have proposer %s, so cannot be canceled", proposalID, proposer)
   146  	}
   147  
   148  	// Check creator of the proposal
   149  	if proposal.Proposer != proposer {
   150  		return types.ErrInvalidProposer.Wrapf("invalid proposer %s", proposer)
   151  	}
   152  
   153  	// Check if proposal is active or not
   154  	if (proposal.Status != v1.StatusDepositPeriod) && (proposal.Status != v1.StatusVotingPeriod) {
   155  		return types.ErrInvalidProposal.Wrap("proposal should be in the deposit or voting period")
   156  	}
   157  
   158  	// Check proposal voting period is ended.
   159  	if proposal.VotingEndTime != nil && proposal.VotingEndTime.Before(sdkCtx.BlockTime()) {
   160  		return types.ErrVotingPeriodEnded.Wrapf("voting period is already ended for this proposal %d", proposalID)
   161  	}
   162  
   163  	// burn the (deposits * proposal_cancel_rate) amount or sent to cancellation destination address.
   164  	// and deposits * (1 - proposal_cancel_rate) will be sent to depositors.
   165  	params, err := keeper.Params.Get(ctx)
   166  	if err != nil {
   167  		return err
   168  	}
   169  
   170  	err = keeper.ChargeDeposit(ctx, proposal.Id, params.ProposalCancelDest, params.ProposalCancelRatio)
   171  	if err != nil {
   172  		return err
   173  	}
   174  
   175  	if proposal.VotingStartTime != nil {
   176  		err = keeper.deleteVotes(ctx, proposal.Id)
   177  		if err != nil {
   178  			return err
   179  		}
   180  	}
   181  
   182  	err = keeper.DeleteProposal(ctx, proposal.Id)
   183  	if err != nil {
   184  		return err
   185  	}
   186  
   187  	keeper.Logger(ctx).Info(
   188  		"proposal is canceled by proposer",
   189  		"proposal", proposal.Id,
   190  		"proposer", proposal.Proposer,
   191  	)
   192  
   193  	return nil
   194  }
   195  
   196  // SetProposal sets a proposal to store.
   197  func (keeper Keeper) SetProposal(ctx context.Context, proposal v1.Proposal) error {
   198  	if proposal.Status == v1.StatusVotingPeriod {
   199  		err := keeper.VotingPeriodProposals.Set(ctx, proposal.Id, []byte{1})
   200  		if err != nil {
   201  			return err
   202  		}
   203  	} else {
   204  		err := keeper.VotingPeriodProposals.Remove(ctx, proposal.Id)
   205  		if err != nil {
   206  			return err
   207  		}
   208  	}
   209  
   210  	return keeper.Proposals.Set(ctx, proposal.Id, proposal)
   211  }
   212  
   213  // DeleteProposal deletes a proposal from store.
   214  func (keeper Keeper) DeleteProposal(ctx context.Context, proposalID uint64) error {
   215  	proposal, err := keeper.Proposals.Get(ctx, proposalID)
   216  	if err != nil {
   217  		return err
   218  	}
   219  
   220  	if proposal.DepositEndTime != nil {
   221  		err := keeper.InactiveProposalsQueue.Remove(ctx, collections.Join(*proposal.DepositEndTime, proposalID))
   222  		if err != nil {
   223  			return err
   224  		}
   225  	}
   226  	if proposal.VotingEndTime != nil {
   227  		err := keeper.ActiveProposalsQueue.Remove(ctx, collections.Join(*proposal.VotingEndTime, proposalID))
   228  		if err != nil {
   229  			return err
   230  		}
   231  
   232  		err = keeper.VotingPeriodProposals.Remove(ctx, proposalID)
   233  		if err != nil {
   234  			return err
   235  		}
   236  	}
   237  
   238  	return keeper.Proposals.Remove(ctx, proposalID)
   239  }
   240  
   241  // ActivateVotingPeriod activates the voting period of a proposal
   242  func (keeper Keeper) ActivateVotingPeriod(ctx context.Context, proposal v1.Proposal) error {
   243  	sdkCtx := sdk.UnwrapSDKContext(ctx)
   244  	startTime := sdkCtx.BlockHeader().Time
   245  	proposal.VotingStartTime = &startTime
   246  	var votingPeriod *time.Duration
   247  	params, err := keeper.Params.Get(ctx)
   248  	if err != nil {
   249  		return err
   250  	}
   251  
   252  	if proposal.Expedited {
   253  		votingPeriod = params.ExpeditedVotingPeriod
   254  	} else {
   255  		votingPeriod = params.VotingPeriod
   256  	}
   257  	endTime := proposal.VotingStartTime.Add(*votingPeriod)
   258  	proposal.VotingEndTime = &endTime
   259  	proposal.Status = v1.StatusVotingPeriod
   260  	err = keeper.SetProposal(ctx, proposal)
   261  	if err != nil {
   262  		return err
   263  	}
   264  
   265  	err = keeper.InactiveProposalsQueue.Remove(ctx, collections.Join(*proposal.DepositEndTime, proposal.Id))
   266  	if err != nil {
   267  		return err
   268  	}
   269  
   270  	return keeper.ActiveProposalsQueue.Set(ctx, collections.Join(*proposal.VotingEndTime, proposal.Id), proposal.Id)
   271  }