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 }