diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 88845a7315..caaf23593b 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -680,7 +680,7 @@ func TestDeposit(t *testing.T) { // query proposal totalCoins := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromTendermintPower(10))} proposal = getProposal(t, port, proposalID) - require.True(t, proposal.GetTotalDeposit().IsEqual(totalCoins)) + require.True(t, proposal.TotalDeposit.IsEqual(totalCoins)) // query deposit deposit := getDeposit(t, port, proposalID, addr) @@ -718,7 +718,7 @@ func TestVote(t *testing.T) { // query proposal proposal := getProposal(t, port, proposalID) require.Equal(t, "Test", proposal.GetTitle()) - require.Equal(t, gov.StatusVotingPeriod, proposal.GetStatus()) + require.Equal(t, gov.StatusVotingPeriod, proposal.Status) // vote resultTx = doVote(t, port, seed, name1, pw, addr, proposalID, "Yes", fees) @@ -855,13 +855,13 @@ func TestProposalsQuery(t *testing.T) { // Only proposals #1 should be in Deposit Period proposals := getProposalsFilterStatus(t, port, gov.StatusDepositPeriod) require.Len(t, proposals, 1) - require.Equal(t, proposalID1, proposals[0].GetProposalID()) + require.Equal(t, proposalID1, proposals[0].ProposalID) // Only proposals #2 and #3 should be in Voting Period proposals = getProposalsFilterStatus(t, port, gov.StatusVotingPeriod) require.Len(t, proposals, 2) - require.Equal(t, proposalID2, proposals[0].GetProposalID()) - require.Equal(t, proposalID3, proposals[1].GetProposalID()) + require.Equal(t, proposalID2, proposals[0].ProposalID) + require.Equal(t, proposalID3, proposals[1].ProposalID) // Addr1 votes on proposals #2 & #3 resultTx = doVote(t, port, seeds[0], names[0], passwords[0], addrs[0], proposalID2, "Yes", fees) @@ -875,31 +875,31 @@ func TestProposalsQuery(t *testing.T) { // Test query all proposals proposals = getProposalsAll(t, port) - require.Equal(t, proposalID1, (proposals[0]).GetProposalID()) - require.Equal(t, proposalID2, (proposals[1]).GetProposalID()) - require.Equal(t, proposalID3, (proposals[2]).GetProposalID()) + require.Equal(t, proposalID1, (proposals[0]).ProposalID) + require.Equal(t, proposalID2, (proposals[1]).ProposalID) + require.Equal(t, proposalID3, (proposals[2]).ProposalID) // Test query deposited by addr1 proposals = getProposalsFilterDepositor(t, port, addrs[0]) - require.Equal(t, proposalID1, (proposals[0]).GetProposalID()) + require.Equal(t, proposalID1, (proposals[0]).ProposalID) // Test query deposited by addr2 proposals = getProposalsFilterDepositor(t, port, addrs[1]) - require.Equal(t, proposalID2, (proposals[0]).GetProposalID()) - require.Equal(t, proposalID3, (proposals[1]).GetProposalID()) + require.Equal(t, proposalID2, (proposals[0]).ProposalID) + require.Equal(t, proposalID3, (proposals[1]).ProposalID) // Test query voted by addr1 proposals = getProposalsFilterVoter(t, port, addrs[0]) - require.Equal(t, proposalID2, (proposals[0]).GetProposalID()) - require.Equal(t, proposalID3, (proposals[1]).GetProposalID()) + require.Equal(t, proposalID2, (proposals[0]).ProposalID) + require.Equal(t, proposalID3, (proposals[1]).ProposalID) // Test query voted by addr2 proposals = getProposalsFilterVoter(t, port, addrs[1]) - require.Equal(t, proposalID3, (proposals[0]).GetProposalID()) + require.Equal(t, proposalID3, (proposals[0]).ProposalID) // Test query voted and deposited by addr1 proposals = getProposalsFilterVoterDepositor(t, port, addrs[0], addrs[0]) - require.Equal(t, proposalID2, (proposals[0]).GetProposalID()) + require.Equal(t, proposalID2, (proposals[0]).ProposalID) // Test query votes on Proposal 2 votes := getVotes(t, port, proposalID2) diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 3b4784fe2a..5bdb325f4a 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -463,12 +463,12 @@ func TestGaiaCLISubmitProposal(t *testing.T) { // Ensure propsal is directly queryable proposal1 := f.QueryGovProposal(1) - require.Equal(t, uint64(1), proposal1.GetProposalID()) - require.Equal(t, gov.StatusDepositPeriod, proposal1.GetStatus()) + require.Equal(t, uint64(1), proposal1.ProposalID) + require.Equal(t, gov.StatusDepositPeriod, proposal1.Status) // Ensure query proposals returns properly proposalsQuery = f.QueryGovProposals() - require.Equal(t, uint64(1), proposalsQuery[0].GetProposalID()) + require.Equal(t, uint64(1), proposalsQuery[0].ProposalID) // Query the deposits on the proposal deposit := f.QueryGovDeposit(1, fooAddr) @@ -507,8 +507,8 @@ func TestGaiaCLISubmitProposal(t *testing.T) { // Fetch the proposal and ensure it is now in the voting period proposal1 = f.QueryGovProposal(1) - require.Equal(t, uint64(1), proposal1.GetProposalID()) - require.Equal(t, gov.StatusVotingPeriod, proposal1.GetStatus()) + require.Equal(t, uint64(1), proposal1.ProposalID) + require.Equal(t, gov.StatusVotingPeriod, proposal1.Status) // Test vote generate only success, stdout, stderr = f.TxGovVote(1, gov.OptionYes, keyFoo, "--generate-only") @@ -544,7 +544,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { // Ensure the proposal returns as in the voting period proposalsQuery = f.QueryGovProposals("--status=VotingPeriod") - require.Equal(t, uint64(1), proposalsQuery[0].GetProposalID()) + require.Equal(t, uint64(1), proposalsQuery[0].ProposalID) // submit a second test proposal f.TxGovSubmitProposal(keyFoo, "Text", "Apples", "test", sdk.NewCoin(denom, proposalTokens), "-y") @@ -552,7 +552,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { // Test limit on proposals query proposalsQuery = f.QueryGovProposals("--limit=1") - require.Equal(t, uint64(2), proposalsQuery[0].GetProposalID()) + require.Equal(t, uint64(2), proposalsQuery[0].ProposalID) f.Cleanup() } diff --git a/docs/spec/governance/02_state.md b/docs/spec/governance/02_state.md index 4a2fd08d1d..11d26d20ab 100644 --- a/docs/spec/governance/02_state.md +++ b/docs/spec/governance/02_state.md @@ -84,13 +84,11 @@ This type is used in a temp map when tallying ## Proposals -`Proposals` are an item to be voted on. +`Proposals` are an item to be voted on. It contains the `ProposalContent` which denotes what this proposal is about, and the other fields, which are the mutable state of the governance process. ```go type Proposal struct { - Title string // Title of the proposal - Description string // Description of the proposal - Type ProposalType // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + ProposalContent // Proposal content interface TotalDeposit sdk.Coins // Current deposit on this proposal. Initial value is set at InitialDeposit Deposits []Deposit // List of deposits on the proposal SubmitTime time.Time // Time of the block where TxGovSubmitProposal was included @@ -108,6 +106,17 @@ type Proposal struct { } ``` +`ProposalContent`s are an interface which contains the information about the `Proposal` where it is provided from an external source, including the proposer. Governance process itself does not evaluate about the internal content. + +```go +type ProposalContent interface { + GetTitle() string + GetDescription() string + ProposalType() ProposalKind +} + +``` + We also mention a method to update the tally for a given proposal: ```go diff --git a/x/gov/client/cli/query.go b/x/gov/client/cli/query.go index 4d2b8ddde2..0290b6d962 100644 --- a/x/gov/client/cli/query.go +++ b/x/gov/client/cli/query.go @@ -226,7 +226,7 @@ $ gaiacli query gov votes 1 var proposal gov.Proposal cdc.MustUnmarshalJSON(res, &proposal) - propStatus := proposal.GetStatus() + propStatus := proposal.Status if !(propStatus == gov.StatusVotingPeriod || propStatus == gov.StatusDepositPeriod) { res, err = gcutils.QueryVotesByTxQuery(cdc, cliCtx, params) } else { @@ -339,7 +339,7 @@ $ gaiacli query gov deposits 1 var proposal gov.Proposal cdc.MustUnmarshalJSON(res, &proposal) - propStatus := proposal.GetStatus() + propStatus := proposal.Status if !(propStatus == gov.StatusVotingPeriod || propStatus == gov.StatusDepositPeriod) { res, err = gcutils.QueryDepositsByTxQuery(cdc, cliCtx, params) } else { diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index 5bfd676a9c..55ba566f19 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -266,7 +266,7 @@ func queryDepositsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Ha // For inactive proposals we must query the txs directly to get the deposits // as they're no longer in state. - propStatus := proposal.GetStatus() + propStatus := proposal.Status if !(propStatus == gov.StatusVotingPeriod || propStatus == gov.StatusDepositPeriod) { res, err = gcutils.QueryDepositsByTxQuery(cdc, cliCtx, params) } else { @@ -489,7 +489,7 @@ func queryVotesOnProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) // For inactive proposals we must query the txs directly to get the votes // as they're no longer in state. - propStatus := proposal.GetStatus() + propStatus := proposal.Status if !(propStatus == gov.StatusVotingPeriod || propStatus == gov.StatusDepositPeriod) { res, err = gcutils.QueryVotesByTxQuery(cdc, cliCtx, params) } else { diff --git a/x/gov/codec.go b/x/gov/codec.go index 10fecb3bcd..44167711da 100644 --- a/x/gov/codec.go +++ b/x/gov/codec.go @@ -12,8 +12,9 @@ func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgDeposit{}, "cosmos-sdk/MsgDeposit", nil) cdc.RegisterConcrete(MsgVote{}, "cosmos-sdk/MsgVote", nil) - cdc.RegisterInterface((*Proposal)(nil), nil) - cdc.RegisterConcrete(&TextProposal{}, "gov/TextProposal", nil) + cdc.RegisterInterface((*ProposalContent)(nil), nil) + cdc.RegisterConcrete(TextProposal{}, "gov/TextProposal", nil) + cdc.RegisterConcrete(SoftwareUpgradeProposal{}, "gov/SoftwareUpgradeProposal", nil) } func init() { diff --git a/x/gov/endblocker.go b/x/gov/endblocker.go index 294a0e44f7..3da7f3a1da 100644 --- a/x/gov/endblocker.go +++ b/x/gov/endblocker.go @@ -18,7 +18,10 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) sdk.Tags { var proposalID uint64 keeper.cdc.MustUnmarshalBinaryLengthPrefixed(inactiveIterator.Value(), &proposalID) - inactiveProposal := keeper.GetProposal(ctx, proposalID) + inactiveProposal, ok := keeper.GetProposal(ctx, proposalID) + if !ok { + panic(fmt.Sprintf("proposal %d does not exist", proposalID)) + } keeper.DeleteProposal(ctx, proposalID) keeper.DeleteDeposits(ctx, proposalID) // delete any associated deposits (burned) @@ -28,10 +31,10 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) sdk.Tags { logger.Info( fmt.Sprintf("proposal %d (%s) didn't meet minimum deposit of %s (had only %s); deleted", - inactiveProposal.GetProposalID(), + inactiveProposal.ProposalID, inactiveProposal.GetTitle(), keeper.GetDepositParams(ctx).MinDeposit, - inactiveProposal.GetTotalDeposit(), + inactiveProposal.TotalDeposit, ), ) } @@ -43,28 +46,31 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) sdk.Tags { var proposalID uint64 keeper.cdc.MustUnmarshalBinaryLengthPrefixed(activeIterator.Value(), &proposalID) - activeProposal := keeper.GetProposal(ctx, proposalID) + activeProposal, ok := keeper.GetProposal(ctx, proposalID) + if !ok { + panic(fmt.Sprintf("proposal %d does not exist", proposalID)) + } passes, tallyResults := tally(ctx, keeper, activeProposal) var tagValue string if passes { - keeper.RefundDeposits(ctx, activeProposal.GetProposalID()) - activeProposal.SetStatus(StatusPassed) + keeper.RefundDeposits(ctx, activeProposal.ProposalID) + activeProposal.Status = StatusPassed tagValue = tags.ActionProposalPassed } else { - keeper.DeleteDeposits(ctx, activeProposal.GetProposalID()) - activeProposal.SetStatus(StatusRejected) + keeper.DeleteDeposits(ctx, activeProposal.ProposalID) + activeProposal.Status = StatusRejected tagValue = tags.ActionProposalRejected } - activeProposal.SetFinalTallyResult(tallyResults) + activeProposal.FinalTallyResult = tallyResults keeper.SetProposal(ctx, activeProposal) - keeper.RemoveFromActiveProposalQueue(ctx, activeProposal.GetVotingEndTime(), activeProposal.GetProposalID()) + keeper.RemoveFromActiveProposalQueue(ctx, activeProposal.VotingEndTime, activeProposal.ProposalID) logger.Info( fmt.Sprintf( "proposal %d (%s) tallied; passed: %v", - activeProposal.GetProposalID(), activeProposal.GetTitle(), passes, + activeProposal.ProposalID, activeProposal.GetTitle(), passes, ), ) diff --git a/x/gov/endblocker_test.go b/x/gov/endblocker_test.go index 624fc3e54d..86e9a4ef2f 100644 --- a/x/gov/endblocker_test.go +++ b/x/gov/endblocker_test.go @@ -208,7 +208,9 @@ func TestTickPassedVotingPeriod(t *testing.T) { require.True(t, activeQueue.Valid()) var activeProposalID uint64 keeper.cdc.UnmarshalBinaryLengthPrefixed(activeQueue.Value(), &activeProposalID) - require.Equal(t, StatusVotingPeriod, keeper.GetProposal(ctx, activeProposalID).GetStatus()) + proposal, ok := keeper.GetProposal(ctx, activeProposalID) + require.True(t, ok) + require.Equal(t, StatusVotingPeriod, proposal.Status) depositsIterator := keeper.GetDeposits(ctx, proposalID) require.True(t, depositsIterator.Valid()) depositsIterator.Close() diff --git a/x/gov/genesis.go b/x/gov/genesis.go index 6426f30df7..aabc5bf685 100644 --- a/x/gov/genesis.go +++ b/x/gov/genesis.go @@ -122,11 +122,11 @@ func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { k.setVote(ctx, vote.ProposalID, vote.Vote.Voter, vote.Vote) } for _, proposal := range data.Proposals { - switch proposal.GetStatus() { + switch proposal.Status { case StatusDepositPeriod: - k.InsertInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposal.GetProposalID()) + k.InsertInactiveProposalQueue(ctx, proposal.DepositEndTime, proposal.ProposalID) case StatusVotingPeriod: - k.InsertActiveProposalQueue(ctx, proposal.GetVotingEndTime(), proposal.GetProposalID()) + k.InsertActiveProposalQueue(ctx, proposal.VotingEndTime, proposal.ProposalID) } k.SetProposal(ctx, proposal) } @@ -142,7 +142,7 @@ func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState { var votes []VoteWithMetadata proposals := k.GetProposalsFiltered(ctx, nil, nil, StatusNil, 0) for _, proposal := range proposals { - proposalID := proposal.GetProposalID() + proposalID := proposal.ProposalID depositsIterator := k.GetDeposits(ctx, proposalID) defer depositsIterator.Close() for ; depositsIterator.Valid(); depositsIterator.Next() { diff --git a/x/gov/genesis_test.go b/x/gov/genesis_test.go index ef19aa3001..599cf89493 100644 --- a/x/gov/genesis_test.go +++ b/x/gov/genesis_test.go @@ -33,9 +33,12 @@ func TestEqualProposals(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - // Create two proposals - proposal1 := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposal2 := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + // Submit two proposals + proposal := testProposal() + proposal1, err := keeper.SubmitProposal(ctx, proposal) + require.NoError(t, err) + proposal2, err := keeper.SubmitProposal(ctx, proposal) + require.NoError(t, err) // They are similar but their IDs should be different require.NotEqual(t, proposal1, proposal2) @@ -48,11 +51,15 @@ func TestEqualProposals(t *testing.T) { require.False(t, state1.Equal(state2)) // Now make proposals identical by setting both IDs to 55 - proposal1.SetProposalID(55) - proposal2.SetProposalID(55) + proposal1.ProposalID = 55 + proposal2.ProposalID = 55 require.Equal(t, proposal1, proposal1) require.True(t, ProposalEqual(proposal1, proposal2)) + // Reassign proposals into state + state1.Proposals[0] = proposal1 + state2.Proposals[0] = proposal2 + // State should be identical now.. require.Equal(t, state1, state2) require.True(t, state1.Equal(state2)) @@ -69,17 +76,24 @@ func TestImportExportQueues(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) // Create two proposals, put the second into the voting period - proposal1 := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID1 := proposal1.GetProposalID() + proposal := testProposal() + proposal1, err := keeper.SubmitProposal(ctx, proposal) + require.NoError(t, err) + proposalID1 := proposal1.ProposalID - proposal2 := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID2 := proposal2.GetProposalID() + proposal2, err := keeper.SubmitProposal(ctx, proposal) + require.NoError(t, err) + proposalID2 := proposal2.ProposalID _, votingStarted := keeper.AddDeposit(ctx, proposalID2, addrs[0], keeper.GetDepositParams(ctx).MinDeposit) require.True(t, votingStarted) - require.True(t, keeper.GetProposal(ctx, proposalID1).GetStatus() == StatusDepositPeriod) - require.True(t, keeper.GetProposal(ctx, proposalID2).GetStatus() == StatusVotingPeriod) + proposal1, ok := keeper.GetProposal(ctx, proposalID1) + require.True(t, ok) + proposal2, ok = keeper.GetProposal(ctx, proposalID2) + require.True(t, ok) + require.True(t, proposal1.Status == StatusDepositPeriod) + require.True(t, proposal2.Status == StatusVotingPeriod) genAccs := mapp.AccountKeeper.GetAllAccounts(ctx) @@ -96,12 +110,19 @@ func TestImportExportQueues(t *testing.T) { ctx2 = ctx2.WithBlockTime(ctx2.BlockHeader().Time.Add(keeper2.GetDepositParams(ctx2).MaxDepositPeriod).Add(keeper2.GetVotingParams(ctx2).VotingPeriod)) // Make sure that they are still in the DepositPeriod and VotingPeriod respectively - require.True(t, keeper2.GetProposal(ctx2, proposalID1).GetStatus() == StatusDepositPeriod) - require.True(t, keeper2.GetProposal(ctx2, proposalID2).GetStatus() == StatusVotingPeriod) + proposal1, ok = keeper2.GetProposal(ctx2, proposalID1) + require.True(t, ok) + proposal2, ok = keeper2.GetProposal(ctx2, proposalID2) + require.True(t, ok) + require.True(t, proposal1.Status == StatusDepositPeriod) + require.True(t, proposal2.Status == StatusVotingPeriod) // Run the endblocker. Check to make sure that proposal1 is removed from state, and proposal2 is finished VotingPeriod. EndBlocker(ctx2, keeper2) - require.Nil(t, keeper2.GetProposal(ctx2, proposalID1)) - require.True(t, keeper2.GetProposal(ctx2, proposalID2).GetStatus() == StatusRejected) + proposal1, ok = keeper2.GetProposal(ctx2, proposalID1) + require.False(t, ok) + proposal2, ok = keeper2.GetProposal(ctx2, proposalID2) + require.True(t, ok) + require.True(t, proposal2.Status == StatusRejected) } diff --git a/x/gov/handler.go b/x/gov/handler.go index 133ea541b0..e51f118951 100644 --- a/x/gov/handler.go +++ b/x/gov/handler.go @@ -25,8 +25,20 @@ func NewHandler(keeper Keeper) sdk.Handler { } func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitProposal) sdk.Result { - proposal := keeper.NewTextProposal(ctx, msg.Title, msg.Description, msg.ProposalType) - proposalID := proposal.GetProposalID() + var content ProposalContent + switch msg.ProposalType { + case ProposalTypeText: + content = NewTextProposal(msg.Title, msg.Description) + case ProposalTypeSoftwareUpgrade: + content = NewSoftwareUpgradeProposal(msg.Title, msg.Description) + default: + return ErrInvalidProposalType(keeper.codespace, msg.ProposalType).Result() + } + proposal, err := keeper.SubmitProposal(ctx, content) + if err != nil { + return err.Result() + } + proposalID := proposal.ProposalID proposalIDStr := fmt.Sprintf("%d", proposalID) err, votingStarted := keeper.AddDeposit(ctx, proposalID, msg.Proposer, msg.InitialDeposit) diff --git a/x/gov/keeper.go b/x/gov/keeper.go index 5eadfaaaca..46085cf2d9 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper.go @@ -95,57 +95,58 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramsKeeper params.Keeper, } // Proposals - -// Creates a NewProposal -func (keeper Keeper) NewTextProposal(ctx sdk.Context, title string, description string, proposalType ProposalKind) Proposal { +func (keeper Keeper) SubmitProposal(ctx sdk.Context, content ProposalContent) (proposal Proposal, err sdk.Error) { proposalID, err := keeper.getNewProposalID(ctx) if err != nil { - return nil + return } - var proposal Proposal = &TextProposal{ - ProposalID: proposalID, - Title: title, - Description: description, - ProposalType: proposalType, + + submitTime := ctx.BlockHeader().Time + depositPeriod := keeper.GetDepositParams(ctx).MaxDepositPeriod + + proposal = Proposal{ + ProposalContent: content, + ProposalID: proposalID, + Status: StatusDepositPeriod, FinalTallyResult: EmptyTallyResult(), TotalDeposit: sdk.NewCoins(), - SubmitTime: ctx.BlockHeader().Time, + SubmitTime: submitTime, + DepositEndTime: submitTime.Add(depositPeriod), } - depositPeriod := keeper.GetDepositParams(ctx).MaxDepositPeriod - proposal.SetDepositEndTime(proposal.GetSubmitTime().Add(depositPeriod)) - keeper.SetProposal(ctx, proposal) - keeper.InsertInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposalID) - return proposal + keeper.InsertInactiveProposalQueue(ctx, proposal.DepositEndTime, proposalID) + return } // Get Proposal from store by ProposalID -func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID uint64) Proposal { +func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID uint64) (proposal Proposal, ok bool) { store := ctx.KVStore(keeper.storeKey) bz := store.Get(KeyProposal(proposalID)) if bz == nil { - return nil + return } - var proposal Proposal keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposal) - return proposal + return proposal, true } // Implements sdk.AccountKeeper. func (keeper Keeper) SetProposal(ctx sdk.Context, proposal Proposal) { store := ctx.KVStore(keeper.storeKey) bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposal) - store.Set(KeyProposal(proposal.GetProposalID()), bz) + store.Set(KeyProposal(proposal.ProposalID), bz) } // Implements sdk.AccountKeeper. func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposalID uint64) { store := ctx.KVStore(keeper.storeKey) - proposal := keeper.GetProposal(ctx, proposalID) - keeper.RemoveFromInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposalID) - keeper.RemoveFromActiveProposalQueue(ctx, proposal.GetVotingEndTime(), proposalID) + proposal, ok := keeper.GetProposal(ctx, proposalID) + if !ok { + panic("DeleteProposal cannot fail to GetProposal") + } + keeper.RemoveFromInactiveProposalQueue(ctx, proposal.DepositEndTime, proposalID) + keeper.RemoveFromActiveProposalQueue(ctx, proposal.VotingEndTime, proposalID) store.Delete(KeyProposal(proposalID)) } @@ -182,13 +183,13 @@ func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddr } } - proposal := keeper.GetProposal(ctx, proposalID) - if proposal == nil { + proposal, ok := keeper.GetProposal(ctx, proposalID) + if !ok { continue } if validProposalStatus(status) { - if proposal.GetStatus() != status { + if proposal.Status != status { continue } } @@ -245,14 +246,14 @@ func (keeper Keeper) peekCurrentProposalID(ctx sdk.Context) (proposalID uint64, } func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal Proposal) { - proposal.SetVotingStartTime(ctx.BlockHeader().Time) + proposal.VotingStartTime = ctx.BlockHeader().Time votingPeriod := keeper.GetVotingParams(ctx).VotingPeriod - proposal.SetVotingEndTime(proposal.GetVotingStartTime().Add(votingPeriod)) - proposal.SetStatus(StatusVotingPeriod) + proposal.VotingEndTime = proposal.VotingStartTime.Add(votingPeriod) + proposal.Status = StatusVotingPeriod keeper.SetProposal(ctx, proposal) - keeper.RemoveFromInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposal.GetProposalID()) - keeper.InsertActiveProposalQueue(ctx, proposal.GetVotingEndTime(), proposal.GetProposalID()) + keeper.RemoveFromInactiveProposalQueue(ctx, proposal.DepositEndTime, proposal.ProposalID) + keeper.InsertActiveProposalQueue(ctx, proposal.VotingEndTime, proposal.ProposalID) } // Params @@ -294,11 +295,11 @@ func (keeper Keeper) setTallyParams(ctx sdk.Context, tallyParams TallyParams) { // Adds a vote on a specific proposal func (keeper Keeper) AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, option VoteOption) sdk.Error { - proposal := keeper.GetProposal(ctx, proposalID) - if proposal == nil { + proposal, ok := keeper.GetProposal(ctx, proposalID) + if !ok { return ErrUnknownProposal(keeper.codespace, proposalID) } - if proposal.GetStatus() != StatusVotingPeriod { + if proposal.Status != StatusVotingPeriod { return ErrInactiveProposal(keeper.codespace, proposalID) } @@ -369,13 +370,13 @@ func (keeper Keeper) setDeposit(ctx sdk.Context, proposalID uint64, depositorAdd // Activates voting period when appropriate func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, depositAmount sdk.Coins) (sdk.Error, bool) { // Checks to see if proposal exists - proposal := keeper.GetProposal(ctx, proposalID) - if proposal == nil { + proposal, ok := keeper.GetProposal(ctx, proposalID) + if !ok { return ErrUnknownProposal(keeper.codespace, proposalID), false } // Check if proposal is still depositable - if (proposal.GetStatus() != StatusDepositPeriod) && (proposal.GetStatus() != StatusVotingPeriod) { + if (proposal.Status != StatusDepositPeriod) && (proposal.Status != StatusVotingPeriod) { return ErrAlreadyFinishedProposal(keeper.codespace, proposalID), false } @@ -387,12 +388,12 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositorAdd } // Update proposal - proposal.SetTotalDeposit(proposal.GetTotalDeposit().Add(depositAmount)) + proposal.TotalDeposit = proposal.TotalDeposit.Add(depositAmount) keeper.SetProposal(ctx, proposal) // Check if deposit has provided sufficient total funds to transition the proposal into the voting period activatedVotingPeriod := false - if proposal.GetStatus() == StatusDepositPeriod && proposal.GetTotalDeposit().IsAllGTE(keeper.GetDepositParams(ctx).MinDeposit) { + if proposal.Status == StatusDepositPeriod && proposal.TotalDeposit.IsAllGTE(keeper.GetDepositParams(ctx).MinDeposit) { keeper.activateVotingPeriod(ctx, proposal) activatedVotingPeriod = true } diff --git a/x/gov/keeper_test.go b/x/gov/keeper_test.go index 3e6cdecaf1..67905fd2da 100644 --- a/x/gov/keeper_test.go +++ b/x/gov/keeper_test.go @@ -19,11 +19,14 @@ func TestGetSetProposal(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID := proposal.GetProposalID() + tp := testProposal() + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID keeper.SetProposal(ctx, proposal) - gotProposal := keeper.GetProposal(ctx, proposalID) + gotProposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) require.True(t, ProposalEqual(proposal, gotProposal)) } @@ -35,14 +38,16 @@ func TestIncrementProposalNumber(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposal6 := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + tp := testProposal() + keeper.SubmitProposal(ctx, tp) + keeper.SubmitProposal(ctx, tp) + keeper.SubmitProposal(ctx, tp) + keeper.SubmitProposal(ctx, tp) + keeper.SubmitProposal(ctx, tp) + proposal6, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) - require.Equal(t, uint64(6), proposal6.GetProposalID()) + require.Equal(t, uint64(6), proposal6.ProposalID) } func TestActivateVotingPeriod(t *testing.T) { @@ -52,19 +57,25 @@ func TestActivateVotingPeriod(t *testing.T) { mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - require.True(t, proposal.GetVotingStartTime().Equal(time.Time{})) + tp := testProposal() + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + + require.True(t, proposal.VotingStartTime.Equal(time.Time{})) keeper.activateVotingPeriod(ctx, proposal) - require.True(t, proposal.GetVotingStartTime().Equal(ctx.BlockHeader().Time)) + require.True(t, proposal.VotingStartTime.Equal(ctx.BlockHeader().Time)) - activeIterator := keeper.ActiveProposalQueueIterator(ctx, proposal.GetVotingEndTime()) + proposal, ok := keeper.GetProposal(ctx, proposal.ProposalID) + require.True(t, ok) + + activeIterator := keeper.ActiveProposalQueueIterator(ctx, proposal.VotingEndTime) require.True(t, activeIterator.Valid()) var proposalID uint64 keeper.cdc.UnmarshalBinaryLengthPrefixed(activeIterator.Value(), &proposalID) - require.Equal(t, proposalID, proposal.GetProposalID()) + require.Equal(t, proposalID, proposal.ProposalID) activeIterator.Close() } @@ -76,8 +87,11 @@ func TestDeposits(t *testing.T) { mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID := proposal.GetProposalID() + + tp := testProposal() + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID fourStake := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromTendermintPower(4))) fiveStake := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromTendermintPower(5))) @@ -87,12 +101,14 @@ func TestDeposits(t *testing.T) { expTokens := sdk.TokensFromTendermintPower(42) require.Equal(t, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, expTokens)), addr0Initial) - require.True(t, proposal.GetTotalDeposit().IsEqual(sdk.NewCoins())) + require.True(t, proposal.TotalDeposit.IsEqual(sdk.NewCoins())) // Check no deposits at beginning deposit, found := keeper.GetDeposit(ctx, proposalID, addrs[1]) require.False(t, found) - require.True(t, keeper.GetProposal(ctx, proposalID).GetVotingStartTime().Equal(time.Time{})) + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + require.True(t, proposal.VotingStartTime.Equal(time.Time{})) // Check first deposit err, votingStarted := keeper.AddDeposit(ctx, proposalID, addrs[0], fourStake) @@ -102,7 +118,9 @@ func TestDeposits(t *testing.T) { require.True(t, found) require.Equal(t, fourStake, deposit.Amount) require.Equal(t, addrs[0], deposit.Depositor) - require.Equal(t, fourStake, keeper.GetProposal(ctx, proposalID).GetTotalDeposit()) + proposal, ok = keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + require.Equal(t, fourStake, proposal.TotalDeposit) require.Equal(t, addr0Initial.Sub(fourStake), keeper.ck.GetCoins(ctx, addrs[0])) // Check a second deposit from same address @@ -113,7 +131,9 @@ func TestDeposits(t *testing.T) { require.True(t, found) require.Equal(t, fourStake.Add(fiveStake), deposit.Amount) require.Equal(t, addrs[0], deposit.Depositor) - require.Equal(t, fourStake.Add(fiveStake), keeper.GetProposal(ctx, proposalID).GetTotalDeposit()) + proposal, ok = keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + require.Equal(t, fourStake.Add(fiveStake), proposal.TotalDeposit) require.Equal(t, addr0Initial.Sub(fourStake).Sub(fiveStake), keeper.ck.GetCoins(ctx, addrs[0])) // Check third deposit from a new address @@ -124,11 +144,15 @@ func TestDeposits(t *testing.T) { require.True(t, found) require.Equal(t, addrs[1], deposit.Depositor) require.Equal(t, fourStake, deposit.Amount) - require.Equal(t, fourStake.Add(fiveStake).Add(fourStake), keeper.GetProposal(ctx, proposalID).GetTotalDeposit()) + proposal, ok = keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + require.Equal(t, fourStake.Add(fiveStake).Add(fourStake), proposal.TotalDeposit) require.Equal(t, addr1Initial.Sub(fourStake), keeper.ck.GetCoins(ctx, addrs[1])) // Check that proposal moved to voting period - require.True(t, keeper.GetProposal(ctx, proposalID).GetVotingStartTime().Equal(ctx.BlockHeader().Time)) + proposal, ok = keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + require.True(t, proposal.VotingStartTime.Equal(ctx.BlockHeader().Time)) // Test deposit iterator depositsIterator := keeper.GetDeposits(ctx, proposalID) @@ -164,10 +188,13 @@ func TestVotes(t *testing.T) { mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID := proposal.GetProposalID() - proposal.SetStatus(StatusVotingPeriod) + tp := testProposal() + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + + proposal.Status = StatusVotingPeriod keeper.SetProposal(ctx, proposal) // Test first vote @@ -224,20 +251,25 @@ func TestProposalQueues(t *testing.T) { mapp.InitChainer(ctx, abci.RequestInitChain{}) // create test proposals - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) + tp := testProposal() + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) - inactiveIterator := keeper.InactiveProposalQueueIterator(ctx, proposal.GetDepositEndTime()) + inactiveIterator := keeper.InactiveProposalQueueIterator(ctx, proposal.DepositEndTime) require.True(t, inactiveIterator.Valid()) var proposalID uint64 keeper.cdc.UnmarshalBinaryLengthPrefixed(inactiveIterator.Value(), &proposalID) - require.Equal(t, proposalID, proposal.GetProposalID()) + require.Equal(t, proposalID, proposal.ProposalID) inactiveIterator.Close() keeper.activateVotingPeriod(ctx, proposal) - activeIterator := keeper.ActiveProposalQueueIterator(ctx, proposal.GetVotingEndTime()) + proposal, ok := keeper.GetProposal(ctx, proposal.ProposalID) + require.True(t, ok) + + activeIterator := keeper.ActiveProposalQueueIterator(ctx, proposal.VotingEndTime) require.True(t, activeIterator.Valid()) keeper.cdc.UnmarshalBinaryLengthPrefixed(activeIterator.Value(), &proposalID) - require.Equal(t, proposalID, proposal.GetProposalID()) + require.Equal(t, proposalID, proposal.ProposalID) activeIterator.Close() } diff --git a/x/gov/proposals.go b/x/gov/proposals.go index 94ffc151bf..46af23f40f 100644 --- a/x/gov/proposals.go +++ b/x/gov/proposals.go @@ -9,81 +9,12 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// Proposal interface -type Proposal interface { - GetProposalID() uint64 - SetProposalID(uint64) +// Proposal is a struct used by gov module internally +// embedds ProposalContent with additional fields to record the status of the proposal process +type Proposal struct { + ProposalContent `json:"proposal_content"` // Proposal content interface - GetTitle() string - SetTitle(string) - - GetDescription() string - SetDescription(string) - - GetProposalType() ProposalKind - SetProposalType(ProposalKind) - - GetStatus() ProposalStatus - SetStatus(ProposalStatus) - - GetFinalTallyResult() TallyResult - SetFinalTallyResult(TallyResult) - - GetSubmitTime() time.Time - SetSubmitTime(time.Time) - - GetDepositEndTime() time.Time - SetDepositEndTime(time.Time) - - GetTotalDeposit() sdk.Coins - SetTotalDeposit(sdk.Coins) - - GetVotingStartTime() time.Time - SetVotingStartTime(time.Time) - - GetVotingEndTime() time.Time - SetVotingEndTime(time.Time) - - String() string -} - -// Proposals is an array of proposal -type Proposals []Proposal - -func (p Proposals) String() string { - out := "ID - (Status) [Type] Title\n" - for _, prop := range p { - out += fmt.Sprintf("%d - (%s) [%s] %s\n", - prop.GetProposalID(), prop.GetStatus(), - prop.GetProposalType(), prop.GetTitle()) - } - return strings.TrimSpace(out) -} - -// checks if two proposals are equal -func ProposalEqual(proposalA Proposal, proposalB Proposal) bool { - if proposalA.GetProposalID() == proposalB.GetProposalID() && - proposalA.GetTitle() == proposalB.GetTitle() && - proposalA.GetDescription() == proposalB.GetDescription() && - proposalA.GetProposalType() == proposalB.GetProposalType() && - proposalA.GetStatus() == proposalB.GetStatus() && - proposalA.GetFinalTallyResult().Equals(proposalB.GetFinalTallyResult()) && - proposalA.GetSubmitTime().Equal(proposalB.GetSubmitTime()) && - proposalA.GetDepositEndTime().Equal(proposalB.GetDepositEndTime()) && - proposalA.GetTotalDeposit().IsEqual(proposalB.GetTotalDeposit()) && - proposalA.GetVotingStartTime().Equal(proposalB.GetVotingStartTime()) && - proposalA.GetVotingEndTime().Equal(proposalB.GetVotingEndTime()) { - return true - } - return false -} - -// Text Proposals -type TextProposal struct { - ProposalID uint64 `json:"proposal_id"` // ID of the proposal - Title string `json:"title"` // Title of the proposal - Description string `json:"description"` // Description of the proposal - ProposalType ProposalKind `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + ProposalID uint64 `json:"proposal_id"` // ID of the proposal Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected} FinalTallyResult TallyResult `json:"final_tally_result"` // Result of Tallys @@ -96,55 +27,83 @@ type TextProposal struct { VotingEndTime time.Time `json:"voting_end_time"` // Time that the VotingPeriod for this proposal will end and votes will be tallied } -// Implements Proposal Interface -var _ Proposal = (*TextProposal)(nil) +// nolint +func (p Proposal) String() string { + return fmt.Sprintf(`Proposal %d: + Title: %s + Type: %s + Status: %s + Submit Time: %s + Deposit End Time: %s + Total Deposit: %s + Voting Start Time: %s + Voting End Time: %s`, p.ProposalID, p.GetTitle(), p.ProposalType(), + p.Status, p.SubmitTime, p.DepositEndTime, + p.TotalDeposit, p.VotingStartTime, p.VotingEndTime) +} + +// ProposalContent is an interface that has title, description, and proposaltype +// that the governance module can use to identify them and generate human readable messages +// ProposalContent can have additional fields, which will handled by ProposalHandlers +// via type assertion, e.g. parameter change amount in ParameterChangeProposal +type ProposalContent interface { + GetTitle() string + GetDescription() string + ProposalType() ProposalKind +} + +// Proposals is an array of proposal +type Proposals []Proposal // nolint -func (tp TextProposal) GetProposalID() uint64 { return tp.ProposalID } -func (tp *TextProposal) SetProposalID(proposalID uint64) { tp.ProposalID = proposalID } -func (tp TextProposal) GetTitle() string { return tp.Title } -func (tp *TextProposal) SetTitle(title string) { tp.Title = title } -func (tp TextProposal) GetDescription() string { return tp.Description } -func (tp *TextProposal) SetDescription(description string) { tp.Description = description } -func (tp TextProposal) GetProposalType() ProposalKind { return tp.ProposalType } -func (tp *TextProposal) SetProposalType(proposalType ProposalKind) { tp.ProposalType = proposalType } -func (tp TextProposal) GetStatus() ProposalStatus { return tp.Status } -func (tp *TextProposal) SetStatus(status ProposalStatus) { tp.Status = status } -func (tp TextProposal) GetFinalTallyResult() TallyResult { return tp.FinalTallyResult } -func (tp *TextProposal) SetFinalTallyResult(tallyResult TallyResult) { - tp.FinalTallyResult = tallyResult -} -func (tp TextProposal) GetSubmitTime() time.Time { return tp.SubmitTime } -func (tp *TextProposal) SetSubmitTime(submitTime time.Time) { tp.SubmitTime = submitTime } -func (tp TextProposal) GetDepositEndTime() time.Time { return tp.DepositEndTime } -func (tp *TextProposal) SetDepositEndTime(depositEndTime time.Time) { - tp.DepositEndTime = depositEndTime -} -func (tp TextProposal) GetTotalDeposit() sdk.Coins { return tp.TotalDeposit } -func (tp *TextProposal) SetTotalDeposit(totalDeposit sdk.Coins) { tp.TotalDeposit = totalDeposit } -func (tp TextProposal) GetVotingStartTime() time.Time { return tp.VotingStartTime } -func (tp *TextProposal) SetVotingStartTime(votingStartTime time.Time) { - tp.VotingStartTime = votingStartTime -} -func (tp TextProposal) GetVotingEndTime() time.Time { return tp.VotingEndTime } -func (tp *TextProposal) SetVotingEndTime(votingEndTime time.Time) { - tp.VotingEndTime = votingEndTime +func (p Proposals) String() string { + out := "ID - (Status) [Type] Title\n" + for _, prop := range p { + out += fmt.Sprintf("%d - (%s) [%s] %s\n", + prop.ProposalID, prop.Status, + prop.ProposalType(), prop.GetTitle()) + } + return strings.TrimSpace(out) } -func (tp TextProposal) String() string { - return fmt.Sprintf(`Proposal %d: - Title: %s - Type: %s - Status: %s - Submit Time: %s - Deposit End Time: %s - Total Deposit: %s - Voting Start Time: %s - Voting End Time: %s`, tp.ProposalID, tp.Title, tp.ProposalType, - tp.Status, tp.SubmitTime, tp.DepositEndTime, - tp.TotalDeposit, tp.VotingStartTime, tp.VotingEndTime) +// Text Proposals +type TextProposal struct { + Title string `json:"title"` // Title of the proposal + Description string `json:"description"` // Description of the proposal } +func NewTextProposal(title, description string) TextProposal { + return TextProposal{ + Title: title, + Description: description, + } +} + +// Implements Proposal Interface +var _ ProposalContent = TextProposal{} + +// nolint +func (tp TextProposal) GetTitle() string { return tp.Title } +func (tp TextProposal) GetDescription() string { return tp.Description } +func (tp TextProposal) ProposalType() ProposalKind { return ProposalTypeText } + +// Software Upgrade Proposals +type SoftwareUpgradeProposal struct { + TextProposal +} + +func NewSoftwareUpgradeProposal(title, description string) SoftwareUpgradeProposal { + return SoftwareUpgradeProposal{ + TextProposal: NewTextProposal(title, description), + } +} + +// Implements Proposal Interface +var _ ProposalContent = SoftwareUpgradeProposal{} + +// nolint +func (sup SoftwareUpgradeProposal) ProposalType() ProposalKind { return ProposalTypeSoftwareUpgrade } + // ProposalQueue type ProposalQueue []uint64 diff --git a/x/gov/querier.go b/x/gov/querier.go index b9f0fbf7f0..881e2d53f3 100644 --- a/x/gov/querier.go +++ b/x/gov/querier.go @@ -99,8 +99,8 @@ func queryProposal(ctx sdk.Context, path []string, req abci.RequestQuery, keeper return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) } - proposal := keeper.GetProposal(ctx, params.ProposalID) - if proposal == nil { + proposal, ok := keeper.GetProposal(ctx, params.ProposalID) + if !ok { return nil, ErrUnknownProposal(DefaultCodespace, params.ProposalID) } @@ -205,17 +205,17 @@ func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke proposalID := params.ProposalID - proposal := keeper.GetProposal(ctx, proposalID) - if proposal == nil { + proposal, ok := keeper.GetProposal(ctx, proposalID) + if !ok { return nil, ErrUnknownProposal(DefaultCodespace, proposalID) } var tallyResult TallyResult - if proposal.GetStatus() == StatusDepositPeriod { + if proposal.Status == StatusDepositPeriod { tallyResult = EmptyTallyResult() - } else if proposal.GetStatus() == StatusPassed || proposal.GetStatus() == StatusRejected { - tallyResult = proposal.GetFinalTallyResult() + } else if proposal.Status == StatusPassed || proposal.Status == StatusRejected { + tallyResult = proposal.FinalTallyResult } else { // proposal is in voting period _, tallyResult = tally(ctx, keeper, proposal) diff --git a/x/gov/querier_test.go b/x/gov/querier_test.go index 25e8220b56..9c4edfcd53 100644 --- a/x/gov/querier_test.go +++ b/x/gov/querier_test.go @@ -227,12 +227,12 @@ func testQueries(t *testing.T) { // Only proposal #1 should be in Deposit Period proposals := getQueriedProposals(t, ctx, cdc, querier, nil, nil, StatusDepositPeriod, 0) require.Len(t, proposals, 1) - require.Equal(t, proposalID1, proposals[0].GetProposalID()) + require.Equal(t, proposalID1, proposals[0].ProposalID) // Only proposals #2 and #3 should be in Voting Period proposals = getQueriedProposals(t, ctx, cdc, querier, nil, nil, StatusVotingPeriod, 0) require.Len(t, proposals, 2) - require.Equal(t, proposalID2, proposals[0].GetProposalID()) - require.Equal(t, proposalID3, proposals[1].GetProposalID()) + require.Equal(t, proposalID2, proposals[0].ProposalID) + require.Equal(t, proposalID3, proposals[1].ProposalID) // Addrs[0] votes on proposals #2 & #3 handler(ctx, NewMsgVote(addrs[0], proposalID2, OptionYes)) @@ -243,8 +243,8 @@ func testQueries(t *testing.T) { // Test query voted by addrs[0] proposals = getQueriedProposals(t, ctx, cdc, querier, nil, addrs[0], StatusNil, 0) - require.Equal(t, proposalID2, (proposals[0]).GetProposalID()) - require.Equal(t, proposalID3, (proposals[1]).GetProposalID()) + require.Equal(t, proposalID2, (proposals[0]).ProposalID) + require.Equal(t, proposalID3, (proposals[1]).ProposalID) // Test query votes on Proposal 2 votes := getQueriedVotes(t, ctx, cdc, querier, proposalID2) @@ -263,26 +263,26 @@ func testQueries(t *testing.T) { // Test query all proposals proposals = getQueriedProposals(t, ctx, cdc, querier, nil, nil, StatusNil, 0) - require.Equal(t, proposalID1, (proposals[0]).GetProposalID()) - require.Equal(t, proposalID2, (proposals[1]).GetProposalID()) - require.Equal(t, proposalID3, (proposals[2]).GetProposalID()) + require.Equal(t, proposalID1, (proposals[0]).ProposalID) + require.Equal(t, proposalID2, (proposals[1]).ProposalID) + require.Equal(t, proposalID3, (proposals[2]).ProposalID) // Test query voted by addrs[1] proposals = getQueriedProposals(t, ctx, cdc, querier, nil, addrs[1], StatusNil, 0) - require.Equal(t, proposalID3, (proposals[0]).GetProposalID()) + require.Equal(t, proposalID3, (proposals[0]).ProposalID) // Test query deposited by addrs[0] proposals = getQueriedProposals(t, ctx, cdc, querier, addrs[0], nil, StatusNil, 0) - require.Equal(t, proposalID1, (proposals[0]).GetProposalID()) + require.Equal(t, proposalID1, (proposals[0]).ProposalID) // Test query deposited by addr2 proposals = getQueriedProposals(t, ctx, cdc, querier, addrs[1], nil, StatusNil, 0) - require.Equal(t, proposalID2, (proposals[0]).GetProposalID()) - require.Equal(t, proposalID3, (proposals[1]).GetProposalID()) + require.Equal(t, proposalID2, (proposals[0]).ProposalID) + require.Equal(t, proposalID3, (proposals[1]).ProposalID) // Test query voted AND deposited by addr1 proposals = getQueriedProposals(t, ctx, cdc, querier, addrs[0], addrs[0], StatusNil, 0) - require.Equal(t, proposalID2, (proposals[0]).GetProposalID()) + require.Equal(t, proposalID2, (proposals[0]).ProposalID) // Test Tally Query tally := getQueriedTally(t, ctx, cdc, querier, proposalID2) diff --git a/x/gov/tally.go b/x/gov/tally.go index bdc5003fac..d8ce2e8d22 100644 --- a/x/gov/tally.go +++ b/x/gov/tally.go @@ -49,7 +49,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall }) // iterate over all the votes - votesIterator := keeper.GetVotes(ctx, proposal.GetProposalID()) + votesIterator := keeper.GetVotes(ctx, proposal.ProposalID) defer votesIterator.Close() for ; votesIterator.Valid(); votesIterator.Next() { vote := &Vote{} diff --git a/x/gov/tally_test.go b/x/gov/tally_test.go index 47077d52d8..a84abd711d 100644 --- a/x/gov/tally_test.go +++ b/x/gov/tally_test.go @@ -53,12 +53,16 @@ func TestTallyNoOneVotes(t *testing.T) { createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 5}) staking.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID := proposal.GetProposalID() - proposal.SetStatus(StatusVotingPeriod) + tp := TextProposal{"Test", "description"} + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = StatusVotingPeriod keeper.SetProposal(ctx, proposal) - passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, tallyResults := tally(ctx, keeper, proposal) require.False(t, passes) require.True(t, tallyResults.Equals(EmptyTallyResult())) @@ -81,15 +85,19 @@ func TestTallyNoQuorum(t *testing.T) { createValidators(t, stakingHandler, ctx, valAddrs, []int64{2, 5}) staking.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID := proposal.GetProposalID() - proposal.SetStatus(StatusVotingPeriod) + tp := TextProposal{"Test", "description"} + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = StatusVotingPeriod keeper.SetProposal(ctx, proposal) - err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) + err = keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) require.Nil(t, err) - passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, _ := tally(ctx, keeper, proposal) require.False(t, passes) } @@ -110,17 +118,21 @@ func TestTallyOnlyValidatorsAllYes(t *testing.T) { createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 5}) staking.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID := proposal.GetProposalID() - proposal.SetStatus(StatusVotingPeriod) + tp := TextProposal{"Test", "description"} + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = StatusVotingPeriod keeper.SetProposal(ctx, proposal) - err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) + err = keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes) require.Nil(t, err) - passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, tallyResults := tally(ctx, keeper, proposal) require.True(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -143,17 +155,21 @@ func TestTallyOnlyValidators51No(t *testing.T) { createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 6}) staking.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID := proposal.GetProposalID() - proposal.SetStatus(StatusVotingPeriod) + tp := TextProposal{"Test", "description"} + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = StatusVotingPeriod keeper.SetProposal(ctx, proposal) - err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) + err = keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo) require.Nil(t, err) - passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, _ := tally(ctx, keeper, proposal) require.False(t, passes) } @@ -175,19 +191,23 @@ func TestTallyOnlyValidators51Yes(t *testing.T) { createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7}) staking.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID := proposal.GetProposalID() - proposal.SetStatus(StatusVotingPeriod) + tp := TextProposal{"Test", "description"} + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = StatusVotingPeriod keeper.SetProposal(ctx, proposal) - err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) + err = keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) require.Nil(t, err) - passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, tallyResults := tally(ctx, keeper, proposal) require.True(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -210,19 +230,23 @@ func TestTallyOnlyValidatorsVetoed(t *testing.T) { createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7}) staking.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID := proposal.GetProposalID() - proposal.SetStatus(StatusVotingPeriod) + tp := TextProposal{"Test", "description"} + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = StatusVotingPeriod keeper.SetProposal(ctx, proposal) - err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) + err = keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNoWithVeto) require.Nil(t, err) - passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, tallyResults := tally(ctx, keeper, proposal) require.False(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -245,19 +269,23 @@ func TestTallyOnlyValidatorsAbstainPasses(t *testing.T) { createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7}) staking.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID := proposal.GetProposalID() - proposal.SetStatus(StatusVotingPeriod) + tp := TextProposal{"Test", "description"} + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = StatusVotingPeriod keeper.SetProposal(ctx, proposal) - err := keeper.AddVote(ctx, proposalID, addrs[0], OptionAbstain) + err = keeper.AddVote(ctx, proposalID, addrs[0], OptionAbstain) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[2], OptionYes) require.Nil(t, err) - passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, tallyResults := tally(ctx, keeper, proposal) require.True(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -280,19 +308,23 @@ func TestTallyOnlyValidatorsAbstainFails(t *testing.T) { createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7}) staking.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID := proposal.GetProposalID() - proposal.SetStatus(StatusVotingPeriod) + tp := TextProposal{"Test", "description"} + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = StatusVotingPeriod keeper.SetProposal(ctx, proposal) - err := keeper.AddVote(ctx, proposalID, addrs[0], OptionAbstain) + err = keeper.AddVote(ctx, proposalID, addrs[0], OptionAbstain) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) require.Nil(t, err) - passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, tallyResults := tally(ctx, keeper, proposal) require.False(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -315,17 +347,21 @@ func TestTallyOnlyValidatorsNonVoter(t *testing.T) { createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7}) staking.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID := proposal.GetProposalID() - proposal.SetStatus(StatusVotingPeriod) + tp := TextProposal{"Test", "description"} + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = StatusVotingPeriod keeper.SetProposal(ctx, proposal) - err := keeper.AddVote(ctx, proposalID, addrs[1], OptionYes) + err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) require.Nil(t, err) - passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, tallyResults := tally(ctx, keeper, proposal) require.False(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -352,12 +388,14 @@ func TestTallyDelgatorOverride(t *testing.T) { delegator1Msg := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens)) stakingHandler(ctx, delegator1Msg) - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID := proposal.GetProposalID() - proposal.SetStatus(StatusVotingPeriod) + tp := TextProposal{"Test", "description"} + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = StatusVotingPeriod keeper.SetProposal(ctx, proposal) - err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) + err = keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes) require.Nil(t, err) @@ -366,7 +404,9 @@ func TestTallyDelgatorOverride(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[3], OptionNo) require.Nil(t, err) - passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, tallyResults := tally(ctx, keeper, proposal) require.False(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -393,19 +433,23 @@ func TestTallyDelgatorInherit(t *testing.T) { delegator1Msg := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens)) stakingHandler(ctx, delegator1Msg) - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID := proposal.GetProposalID() - proposal.SetStatus(StatusVotingPeriod) + tp := TextProposal{"Test", "description"} + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = StatusVotingPeriod keeper.SetProposal(ctx, proposal) - err := keeper.AddVote(ctx, proposalID, addrs[0], OptionNo) + err = keeper.AddVote(ctx, proposalID, addrs[0], OptionNo) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[2], OptionYes) require.Nil(t, err) - passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, tallyResults := tally(ctx, keeper, proposal) require.True(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -434,12 +478,14 @@ func TestTallyDelgatorMultipleOverride(t *testing.T) { delegator1Msg2 := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[1]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens)) stakingHandler(ctx, delegator1Msg2) - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID := proposal.GetProposalID() - proposal.SetStatus(StatusVotingPeriod) + tp := TextProposal{"Test", "description"} + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = StatusVotingPeriod keeper.SetProposal(ctx, proposal) - err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) + err = keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes) require.Nil(t, err) @@ -448,7 +494,9 @@ func TestTallyDelgatorMultipleOverride(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[3], OptionNo) require.Nil(t, err) - passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, tallyResults := tally(ctx, keeper, proposal) require.False(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -490,19 +538,23 @@ func TestTallyDelgatorMultipleInherit(t *testing.T) { staking.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID := proposal.GetProposalID() - proposal.SetStatus(StatusVotingPeriod) + tp := TextProposal{"Test", "description"} + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = StatusVotingPeriod keeper.SetProposal(ctx, proposal) - err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) + err = keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) require.Nil(t, err) - passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, tallyResults := tally(ctx, keeper, proposal) require.False(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -538,19 +590,23 @@ func TestTallyJailedValidator(t *testing.T) { staking.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposalID := proposal.GetProposalID() - proposal.SetStatus(StatusVotingPeriod) + tp := TextProposal{"Test", "description"} + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = StatusVotingPeriod keeper.SetProposal(ctx, proposal) - err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) + err = keeper.AddVote(ctx, proposalID, addrs[0], OptionYes) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo) require.Nil(t, err) err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) require.Nil(t, err) - passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, tallyResults := tally(ctx, keeper, proposal) require.True(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) diff --git a/x/gov/test_common.go b/x/gov/test_common.go index 0c0d74306e..a14251841d 100644 --- a/x/gov/test_common.go +++ b/x/gov/test_common.go @@ -146,3 +146,12 @@ func SortByteArrays(src [][]byte) [][]byte { sort.Sort(sorted) return sorted } + +func testProposal() TextProposal { + return NewTextProposal("Test", "description") +} + +// checks if two proposals are equal (note: slow, for tests only) +func ProposalEqual(proposalA Proposal, proposalB Proposal) bool { + return bytes.Equal(msgCdc.MustMarshalBinaryBare(proposalA), msgCdc.MustMarshalBinaryBare(proposalB)) +}