Proposals can be submitted by any account via a MsgSubmitProposal
transaction.
Copy
// MsgSubmitProposal defines an sdk.Msg type that supports submitting arbitrary
// proposal Content.
message MsgSubmitProposal {
option (cosmos.msg.v1.signer) = "proposer";
repeated google.protobuf.Any messages = 1;
repeated cosmos.base.v1beta1.Coin initial_deposit = 2 [(gogoproto.nullable) = false];
string proposer = 3 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// metadata is any arbitrary metadata attached to the proposal.
string metadata = 4;
}
All sdk.Msgs passed into the messages field of a MsgSubmitProposal message
must be registered in the app's MsgServiceRouter. Each of these messages must
have one signer, namely the gov module account. And finally, the metadata length
must not be larger than the maxMetadataLen config passed into the gov keeper.
State modifications:
Generate new proposalID
Create new Proposal
Initialise Proposal's attributes
Decrease balance of sender by InitialDeposit
If MinDeposit is reached:
Push proposalID in ProposalProcessingQueue
Transfer InitialDeposit from the Proposer to the governance ModuleAccount
A MsgSubmitProposal transaction can be handled according to the following
pseudocode.
Copy
// PSEUDOCODE //// Check if MsgSubmitProposal is valid. If it is, create proposal //
upon receiving txGovSubmitProposal from sender do
if!correctlyFormatted(txGovSubmitProposal)// check if proposal is correctly formatted and the messages have routes to other modules. Includes fee payment.// check if all messages' unique Signer is the gov acct.// check if the metadata is not too long.
throw
initialDeposit = txGovSubmitProposal.InitialDeposit
if(initialDeposit.Atoms <=0) OR (sender.AtomBalance < initialDeposit.Atoms)// InitialDeposit is negative or null OR sender has insufficient funds
throw
if(txGovSubmitProposal.Type != ProposalTypePlainText) OR (txGovSubmitProposal.Type != ProposalTypeSoftwareUpgrade)
sender.AtomBalance -= initialDeposit.Atoms
depositParam =load(GlobalParams, 'DepositParam')
proposalID = generate new proposalID
proposal =NewProposal()
proposal.Messages = txGovSubmitProposal.Messages
proposal.Metadata = txGovSubmitProposal.Metadata
proposal.TotalDeposit = initialDeposit
proposal.SubmitTime =<CurrentTime>
proposal.DepositEndTime =<CurrentTime>.Add(depositParam.MaxDepositPeriod)
proposal.Deposits.append({initialDeposit, sender})
proposal.Submitter = sender
proposal.YesVotes =0
proposal.NoVotes =0
proposal.NoWithVetoVotes =0
proposal.AbstainVotes =0
proposal.CurrentStatus = ProposalStatusOpen
store(Proposals,<proposalID|'proposal'>, proposal)// Store proposal in Proposals mappingreturn proposalID
Once a proposal is submitted, if
Proposal.TotalDeposit < ActiveParam.MinDeposit, Atom holders can send
MsgDeposit transactions to increase the proposal's deposit.
Copy
// MsgDeposit defines a message to submit a deposit to an existing proposal.
message MsgDeposit {
option (cosmos.msg.v1.signer) = "depositor";
uint64 proposal_id = 1 [(gogoproto.jsontag) = "proposal_id"];
string depositor = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
repeated cosmos.base.v1beta1.Coin amount = 3 [(gogoproto.nullable) = false];
}
State modifications:
Decrease balance of sender by deposit
Add deposit of sender in proposal.Deposits
Increase proposal.TotalDeposit by sender's deposit
If MinDeposit is reached:
Push proposalID in ProposalProcessingQueueEnd
Transfer Deposit from the proposer to the governance ModuleAccount
A MsgDeposit transaction has to go through a number of checks to be valid.
These checks are outlined in the following pseudocode.
Copy
// PSEUDOCODE //// Check if MsgDeposit is valid. If it is, increase deposit and check if MinDeposit is reached
upon receiving txGovDeposit from sender do
// check if proposal is correctly formatted. Includes fee payment.if!correctlyFormatted(txGovDeposit)
throw
proposal =load(Proposals,<txGovDeposit.ProposalID|'proposal'>)// proposal is a const key, proposalID is variableif(proposal ==nil)// There is no proposal for this proposalID
throw
if(txGovDeposit.Deposit.Atoms <=0) OR (sender.AtomBalance < txGovDeposit.Deposit.Atoms) OR (proposal.CurrentStatus != ProposalStatusOpen)// deposit is negative or null// OR sender has insufficient funds// OR proposal is not open for deposit anymore
throw
depositParam =load(GlobalParams, 'DepositParam')if(CurrentBlock >= proposal.SubmitBlock + depositParam.MaxDepositPeriod)
proposal.CurrentStatus = ProposalStatusClosed
else// sender can deposit
sender.AtomBalance -= txGovDeposit.Deposit.Atoms
proposal.Deposits.append({txGovVote.Deposit, sender})
proposal.TotalDeposit.Plus(txGovDeposit.Deposit)if(proposal.TotalDeposit >= depositParam.MinDeposit)// MinDeposit is reached, vote opens
proposal.VotingStartBlock = CurrentBlock
proposal.CurrentStatus = ProposalStatusActive
ProposalProcessingQueue.push(txGovDeposit.ProposalID)store(Proposals,<txGovVote.ProposalID|'proposal'>, proposal)
Once ActiveParam.MinDeposit is reached, voting period starts. From there,
bonded Atom holders are able to send MsgVote transactions to cast their
vote on the proposal.
Note: Gas cost for this message has to take into account the future tallying of the vote in EndBlocker.
Next is a pseudocode outline of the way MsgVote transactions are
handled:
Copy
// PSEUDOCODE //// Check if MsgVote is valid. If it is, count vote//
upon receiving txGovVote from sender do
// check if proposal is correctly formatted. Includes fee payment.if!correctlyFormatted(txGovDeposit)
throw
proposal =load(Proposals,<txGovDeposit.ProposalID|'proposal'>)if(proposal ==nil)// There is no proposal for this proposalID
throw
if(proposal.CurrentStatus == ProposalStatusActive)// Sender can vote if// Proposal is active// Sender has some bondsstore(Governance,<txGovVote.ProposalID|'addresses'|sender>, txGovVote.Vote)// Voters can vote multiple times. Re-voting overrides previous vote. This is ok because tallying is done once at the end.