2020-03-11 19:52:25 +00:00
package keeper_test
import (
2020-03-12 17:05:40 +00:00
"time"
2020-03-11 19:52:25 +00:00
2020-03-26 20:17:49 +00:00
"github.com/cosmos/cosmos-sdk/codec"
2020-03-11 19:52:25 +00:00
sdk "github.com/cosmos/cosmos-sdk/types"
2022-01-08 00:39:27 +00:00
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
2023-04-04 00:08:45 +00:00
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
2020-04-30 14:23:41 +00:00
2024-02-06 22:54:10 +00:00
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
2020-03-11 19:52:25 +00:00
"github.com/kava-labs/kava/app"
2022-01-08 00:39:27 +00:00
// bep3types "github.com/kava-labs/kava/x/bep3/types"
// cdptypes "github.com/kava-labs/kava/x/cdp/types"
"github.com/kava-labs/kava/x/committee/testutil"
2020-03-11 19:52:25 +00:00
"github.com/kava-labs/kava/x/committee/types"
2022-01-08 00:39:27 +00:00
// "github.com/kava-labs/kava/x/pricefeed"
2020-03-11 19:52:25 +00:00
)
2022-01-08 00:39:27 +00:00
// func newCDPGenesisState(params cdptypes.Params) app.GenesisState {
// genesis := cdptypes.DefaultGenesisState()
// genesis.Params = params
// return app.GenesisState{cdptypes.ModuleName: cdptypes.ModuleCdc.MustMarshalJSON(genesis)}
// }
// func newBep3GenesisState(params bep3types.Params) app.GenesisState {
// genesis := bep3types.DefaultGenesisState()
// genesis.Params = params
// return app.GenesisState{bep3types.ModuleName: bep3types.ModuleCdc.MustMarshalJSON(genesis)}
// }
// func newPricefeedGenState(assets []string, prices []sdk.Dec) app.GenesisState {
// if len(assets) != len(prices) {
// panic("assets and prices must be the same length")
// }
// pfGenesis := pricefeed.DefaultGenesisState()
// for i := range assets {
// pfGenesis.Params.Markets = append(
// pfGenesis.Params.Markets,
// pricefeed.Market{
// MarketID: assets[i] + ":usd", BaseAsset: assets[i], QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true,
// })
// pfGenesis.PostedPrices = append(
// pfGenesis.PostedPrices,
// pricefeed.PostedPrice{
// MarketID: assets[i] + ":usd",
// OracleAddress: sdk.AccAddress{},
// Price: prices[i],
// Expiry: time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC),
// })
// }
// return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)}
// }
// func (suite *keeperTestSuite) TestSubmitProposal() {
// defaultCommitteeID := uint64(12)
// normalCom := types.BaseCommittee{
// ID: defaultCommitteeID,
// Description: "This committee is for testing.",
// Members: suite.Addresses[:2],
// Permissions: []types.Permission{&types.GodPermission{}},
// VoteThreshold: testutil.D("0.667"),
// ProposalDuration: time.Hour * 24 * 7,
// TallyOption: types.TALLY_OPTION_FIRST_PAST_THE_POST,
// }
// noPermissionsCom := normalCom
// noPermissionsCom.Permissions = []types.Permission{}
// paramChangePermissionsCom := normalCom
// paramChangePermissionsCom.Permissions = []types.Permission{
// types.paramsproposalbParamChangePermission{
// AllowedParams: types.AllowedParams{
// {Subspace: cdptypes.ModuleName, Key: string(cdptypes.KeyDebtThreshold)},
// {Subspace: cdptypes.ModuleName, Key: string(cdptypes.KeyCollateralParams)},
// },
// AllowedCollateralParams: types.AllowedCollateralParams{
// types.AllowedCollateralParam{
// Type: "bnb-a",
// DebtLimit: true,
// StabilityFee: true,
// },
// },
// },
// }
// testCP := cdptypes.CollateralParams{{
// Denom: "bnb",
// Type: "bnb-a",
// LiquidationRatio: testutil.D("1.5"),
// DebtLimit: testutil.C("usdx", 1000000000000),
// StabilityFee: testutil.D("1.000000001547125958"), // %5 apr
// LiquidationPenalty: testutil.D("0.05"),
// AuctionSize: i(100),
// Prefix: 0x20,
// ConversionFactor: i(6),
// LiquidationMarketID: "bnb:usd",
// SpotMarketID: "bnb:usd",
// }}
// testCDPParams := cdptypes.DefaultParams()
// testCDPParams.CollateralParams = testCP
// testCDPParams.GlobalDebtLimit = testCP[0].DebtLimit
// newValidCP := make(cdptypes.CollateralParams, len(testCP))
// copy(newValidCP, testCP)
// newValidCP[0].DebtLimit = testutil.C("usdx", 500000000000)
// newInvalidCP := make(cdptypes.CollateralParams, len(testCP))
// copy(newInvalidCP, testCP)
// newInvalidCP[0].SpotMarketID = "btc:usd"
// testcases := []struct {
// name string
// committee types.BaseCommittee
// pubProposal types.PubProposal
// proposer sdk.AccAddress
// committeeID uint64
// expectErr bool
// }{
// {
// name: "normal text proposal",
// committee: normalCom,
// pubProposal: govtypes.NewTextProposal("A Title", "A description of this proposal."),
// proposer: normalCom.Members[0],
// committeeID: normalCom.ID,
// expectErr: false,
// },
// {
// name: "normal param change proposal",
// committee: normalCom,
// pubProposal: paramsproposal.NewParameterChangeProposal(
// "A Title", "A description of this proposal.",
// []paramsproposal.ParamChange{
// {
// Subspace: "cdp", Key: string(cdptypes.KeyDebtThreshold), Value: string(suite.app.Codec().MustMarshalJSON(i(1000000))),
// },
// },
// ),
// proposer: normalCom.Members[0],
// committeeID: normalCom.ID,
// expectErr: false,
// },
// {
// name: "invalid proposal",
// committee: normalCom,
// pubProposal: nil,
// proposer: normalCom.Members[0],
// committeeID: normalCom.ID,
// expectErr: true,
// },
// {
// name: "missing committee",
// // no committee
// pubProposal: govtypes.NewTextProposal("A Title", "A description of this proposal."),
// proposer: suite.Addresses[0],
// committeeID: 0,
// expectErr: true,
// },
// {
// name: "not a member",
// committee: normalCom,
// pubProposal: govtypes.NewTextProposal("A Title", "A description of this proposal."),
// proposer: suite.Addresses[4],
// committeeID: normalCom.ID,
// expectErr: true,
// },
// {
// name: "not enough permissions",
// committee: noPermissionsCom,
// pubProposal: govtypes.NewTextProposal("A Title", "A description of this proposal."),
// proposer: noPermissionsCom.Members[0],
// committeeID: noPermissionsCom.ID,
// expectErr: true,
// },
// {
// name: "valid sub param change",
// committee: paramChangePermissionsCom,
// pubProposal: paramsproposal.NewParameterChangeProposal(
// "A Title", "A description of this proposal.",
// []paramsproposal.ParamChange{
// {
// Subspace: "cdp",
// Key: string(cdptypes.KeyDebtThreshold),
// Value: string(suite.app.Codec().MustMarshalJSON(i(1000000000))),
// },
// {
// Subspace: "cdp",
// Key: string(cdptypes.KeyCollateralParams),
// Value: string(suite.app.Codec().MustMarshalJSON(newValidCP)),
// },
// },
// ),
// proposer: paramChangePermissionsCom.Members[0],
// committeeID: paramChangePermissionsCom.ID,
// expectErr: false,
// },
// {
// name: "invalid sub param change permission",
// committee: paramChangePermissionsCom,
// pubProposal: paramsproposal.NewParameterChangeProposal(
// "A Title", "A description of this proposal.",
// []paramsproposal.ParamChange{
// {
// Subspace: "cdp",
// Key: string(cdptypes.KeyDebtThreshold),
// Value: string(suite.app.Codec().MustMarshalJSON(i(1000000000))),
// },
// {
// Subspace: "cdp",
// Key: string(cdptypes.KeyCollateralParams),
// Value: string(suite.app.Codec().MustMarshalJSON(newInvalidCP)),
// },
// },
// ),
// proposer: paramChangePermissionsCom.Members[0],
// committeeID: paramChangePermissionsCom.ID,
// expectErr: true,
// },
// }
// for _, tc := range testcases {
// suite.Run(tc.name, func() {
// // Create local testApp because suite doesn't run the SetupTest function for subtests
// tApp := app.NewTestApp()
// keeper := tApp.GetCommitteeKeeper()
// ctx := tApp.NewContext(true, tmproto.Header{})
// tApp.InitializeFromGenesisStates(
// newPricefeedGenState([]string{"bnb"}, []sdk.Dec{testutil.D("15.01")}),
// newCDPGenesisState(testCDPParams),
// )
// // Cast BaseCommittee to MemberCommittee (if required) to meet Committee interface requirement
// if tc.committee.ID == defaultCommitteeID {
// keeper.SetCommittee(ctx, types.MustNewMemberCommittee(
// }
// id, err := keeper.SubmitProposal(ctx, tc.proposer, tc.committeeID, tc.pubProposal)
// if tc.expectErr {
// suite.NotNil(err)
// } else {
// suite.NoError(err)
// pr, found := keeper.GetProposal(ctx, id)
// suite.True(found)
// suite.Equal(tc.committeeID, pr.CommitteeID)
// suite.Equal(ctx.BlockTime().Add(tc.committee.GetProposalDuration()), pr.Deadline)
// }
// })
// }
// }
func ( suite * keeperTestSuite ) TestAddVote ( ) {
memberCom := types . MustNewMemberCommittee (
1 ,
"This member committee is for testing." ,
suite . Addresses [ : 2 ] ,
[ ] types . Permission { & types . GodPermission { } } ,
testutil . D ( "0.667" ) ,
time . Hour * 24 * 7 ,
types . TALLY_OPTION_FIRST_PAST_THE_POST ,
)
tokenCom := types . MustNewTokenCommittee (
12 ,
"This token committee is for testing." ,
suite . Addresses [ : 2 ] ,
[ ] types . Permission { & types . GodPermission { } } ,
testutil . D ( "0.4" ) ,
time . Hour * 24 * 7 ,
types . TALLY_OPTION_FIRST_PAST_THE_POST ,
sdk . Dec { } ,
"hard" ,
)
nonMemberAddr := suite . Addresses [ 4 ]
2020-03-12 17:05:40 +00:00
firstBlockTime := time . Date ( 1998 , time . January , 1 , 1 , 0 , 0 , 0 , time . UTC )
2020-03-11 19:52:25 +00:00
testcases := [ ] struct {
name string
proposalID uint64
2021-06-07 16:08:03 +00:00
committee types . Committee
2020-03-11 19:52:25 +00:00
voter sdk . AccAddress
2021-06-07 16:08:03 +00:00
voteType types . VoteType
2020-03-12 17:05:40 +00:00
voteTime time . Time
2020-04-25 16:39:59 +00:00
expectErr bool
2020-03-11 19:52:25 +00:00
} {
{
2021-06-07 16:08:03 +00:00
name : "normal MemberCommittee" ,
committee : memberCom ,
2020-03-11 19:52:25 +00:00
proposalID : types . DefaultNextProposalID ,
2021-06-07 16:08:03 +00:00
voter : memberCom . Members [ 0 ] ,
2022-01-08 00:39:27 +00:00
voteType : types . VOTE_TYPE_YES ,
2021-06-07 16:08:03 +00:00
expectErr : false ,
} ,
{
name : "normal TokenCommittee" ,
committee : tokenCom ,
proposalID : types . DefaultNextProposalID ,
voter : nonMemberAddr ,
2022-01-08 00:39:27 +00:00
voteType : types . VOTE_TYPE_YES ,
2020-04-25 16:39:59 +00:00
expectErr : false ,
2020-03-11 19:52:25 +00:00
} ,
{
name : "nonexistent proposal" ,
2021-06-07 16:08:03 +00:00
committee : memberCom ,
2020-03-11 19:52:25 +00:00
proposalID : 9999999 ,
2021-06-07 16:08:03 +00:00
voter : memberCom . Members [ 0 ] ,
2022-01-08 00:39:27 +00:00
voteType : types . VOTE_TYPE_YES ,
2020-04-25 16:39:59 +00:00
expectErr : true ,
2020-03-11 19:52:25 +00:00
} ,
{
2021-06-07 16:08:03 +00:00
name : "proposal expired" ,
committee : memberCom ,
2020-03-11 19:52:25 +00:00
proposalID : types . DefaultNextProposalID ,
2021-06-07 16:08:03 +00:00
voter : memberCom . Members [ 0 ] ,
voteTime : firstBlockTime . Add ( memberCom . ProposalDuration ) ,
2022-01-08 00:39:27 +00:00
voteType : types . VOTE_TYPE_YES ,
2020-04-25 16:39:59 +00:00
expectErr : true ,
2020-03-11 19:52:25 +00:00
} ,
2020-03-12 17:05:40 +00:00
{
2021-06-07 16:08:03 +00:00
name : "MemberCommittee: voter not committee member" ,
committee : memberCom ,
2020-03-12 17:05:40 +00:00
proposalID : types . DefaultNextProposalID ,
2021-06-07 16:08:03 +00:00
voter : nonMemberAddr ,
2022-01-08 00:39:27 +00:00
voteType : types . VOTE_TYPE_YES ,
2021-06-07 16:08:03 +00:00
expectErr : true ,
} ,
{
name : "MemberCommittee: voter votes no" ,
committee : memberCom ,
proposalID : types . DefaultNextProposalID ,
voter : memberCom . Members [ 0 ] ,
2022-01-08 00:39:27 +00:00
voteType : types . VOTE_TYPE_NO ,
2020-04-25 16:39:59 +00:00
expectErr : true ,
2020-03-12 17:05:40 +00:00
} ,
2020-03-11 19:52:25 +00:00
}
for _ , tc := range testcases {
suite . Run ( tc . name , func ( ) {
2020-03-30 13:06:31 +00:00
// Create local testApp because suite doesn't run the SetupTest function for subtests
2020-03-11 19:52:25 +00:00
tApp := app . NewTestApp ( )
keeper := tApp . GetCommitteeKeeper ( )
2022-01-08 00:39:27 +00:00
ctx := tApp . NewContext ( true , tmproto . Header { Height : 1 , Time : firstBlockTime } )
2020-03-11 19:52:25 +00:00
tApp . InitializeFromGenesisStates ( )
// setup the committee and proposal
2021-06-07 16:08:03 +00:00
keeper . SetCommittee ( ctx , tc . committee )
2023-04-04 00:08:45 +00:00
_ , err := keeper . SubmitProposal ( ctx , tc . committee . GetMembers ( ) [ 0 ] , tc . committee . GetID ( ) , govv1beta1 . NewTextProposal ( "A Title" , "A description of this proposal." ) )
2020-03-11 19:52:25 +00:00
suite . NoError ( err )
2020-03-12 17:05:40 +00:00
ctx = ctx . WithBlockTime ( tc . voteTime )
2021-06-07 16:08:03 +00:00
err = keeper . AddVote ( ctx , tc . proposalID , tc . voter , tc . voteType )
2020-03-11 19:52:25 +00:00
2020-04-25 16:39:59 +00:00
if tc . expectErr {
suite . NotNil ( err )
} else {
2020-03-11 19:52:25 +00:00
suite . NoError ( err )
_ , found := keeper . GetVote ( ctx , tc . proposalID , tc . voter )
suite . True ( found )
}
} )
}
}
2022-01-08 00:39:27 +00:00
func ( suite * keeperTestSuite ) TestTallyMemberCommitteeVotes ( ) {
memberCom := types . MustNewMemberCommittee (
12 ,
"This committee is for testing." ,
suite . Addresses [ : 5 ] ,
[ ] types . Permission { & types . GodPermission { } } ,
testutil . D ( "0.667" ) ,
time . Hour * 24 * 7 ,
types . TALLY_OPTION_DEADLINE ,
)
2021-06-07 16:08:03 +00:00
var defaultProposalID uint64 = 1
firstBlockTime := time . Date ( 1998 , time . January , 1 , 1 , 0 , 0 , 0 , time . UTC )
testcases := [ ] struct {
name string
votes [ ] types . Vote
expectedVoteCount sdk . Dec
} {
{
name : "has 0 votes" ,
votes : [ ] types . Vote { } ,
2022-01-08 00:39:27 +00:00
expectedVoteCount : testutil . D ( "0" ) ,
2021-06-07 16:08:03 +00:00
} ,
{
name : "has 1 vote" ,
votes : [ ] types . Vote {
2022-01-08 00:39:27 +00:00
{ ProposalID : defaultProposalID , Voter : suite . Addresses [ 0 ] , VoteType : types . VOTE_TYPE_YES } ,
2021-06-07 16:08:03 +00:00
} ,
2022-01-08 00:39:27 +00:00
expectedVoteCount : testutil . D ( "1" ) ,
2021-06-07 16:08:03 +00:00
} ,
{
name : "has multiple votes" ,
votes : [ ] types . Vote {
2022-01-08 00:39:27 +00:00
{ ProposalID : defaultProposalID , Voter : suite . Addresses [ 0 ] , VoteType : types . VOTE_TYPE_YES } ,
{ ProposalID : defaultProposalID , Voter : suite . Addresses [ 1 ] , VoteType : types . VOTE_TYPE_YES } ,
{ ProposalID : defaultProposalID , Voter : suite . Addresses [ 2 ] , VoteType : types . VOTE_TYPE_YES } ,
{ ProposalID : defaultProposalID , Voter : suite . Addresses [ 3 ] , VoteType : types . VOTE_TYPE_YES } ,
2021-06-07 16:08:03 +00:00
} ,
2022-01-08 00:39:27 +00:00
expectedVoteCount : testutil . D ( "4" ) ,
2021-06-07 16:08:03 +00:00
} ,
}
for _ , tc := range testcases {
// Set up test app
tApp := app . NewTestApp ( )
keeper := tApp . GetCommitteeKeeper ( )
2022-01-08 00:39:27 +00:00
ctx := tApp . NewContext ( true , tmproto . Header { Height : 1 , Time : firstBlockTime } )
2021-06-07 16:08:03 +00:00
// Initialize test app with genesis state
tApp . InitializeFromGenesisStates (
committeeGenState (
2022-01-08 00:39:27 +00:00
tApp . AppCodec ( ) ,
2021-06-07 16:08:03 +00:00
[ ] types . Committee { memberCom } ,
2022-01-08 00:39:27 +00:00
[ ] types . Proposal { types . MustNewProposal (
2023-04-04 00:08:45 +00:00
govv1beta1 . NewTextProposal ( "A Title" , "A description of this proposal." ) ,
2022-01-08 00:39:27 +00:00
defaultProposalID ,
memberCom . GetID ( ) ,
firstBlockTime . Add ( time . Hour * 24 * 7 ) ,
) } ,
2021-06-07 16:08:03 +00:00
tc . votes ,
) ,
)
// Check that all votes are counted
currentVotes := keeper . TallyMemberCommitteeVotes ( ctx , defaultProposalID )
suite . Equal ( tc . expectedVoteCount , currentVotes )
}
}
2022-01-08 00:39:27 +00:00
func ( suite * keeperTestSuite ) TestTallyTokenCommitteeVotes ( ) {
tokenCom := types . MustNewTokenCommittee (
12 ,
"This committee is for testing." ,
suite . Addresses [ : 5 ] ,
[ ] types . Permission { & types . GodPermission { } } ,
testutil . D ( "0.667" ) ,
time . Hour * 24 * 7 ,
types . TALLY_OPTION_DEADLINE ,
testutil . D ( "0.4" ) ,
"hard" ,
)
2021-06-07 16:08:03 +00:00
var defaultProposalID uint64 = 1
firstBlockTime := time . Date ( 1998 , time . January , 1 , 1 , 0 , 0 , 0 , time . UTC )
2022-01-08 00:39:27 +00:00
genAddrs := suite . Addresses [ : 8 ] // Genesis accounts
2021-06-07 16:08:03 +00:00
genCoinCounts := [ ] int64 { 0 , 0 , 0 , 10 , 20 , 30 , 40 , 50 } // Genesis token balances
testcases := [ ] struct {
name string
votes [ ] types . Vote
expectedYesVoteCount sdk . Dec
expectedNoVoteCount sdk . Dec
expectedTotalVoteCount sdk . Dec
} {
{
name : "has 0 votes" ,
votes : [ ] types . Vote { } ,
2022-01-08 00:39:27 +00:00
expectedYesVoteCount : testutil . D ( "0" ) ,
expectedNoVoteCount : testutil . D ( "0" ) ,
expectedTotalVoteCount : testutil . D ( "0" ) ,
2021-06-07 16:08:03 +00:00
} ,
{
name : "counts token holder 'Yes' votes" ,
votes : [ ] types . Vote {
2022-01-08 00:39:27 +00:00
{ ProposalID : defaultProposalID , Voter : genAddrs [ 4 ] , VoteType : types . VOTE_TYPE_YES } , // Token holder
2021-06-07 16:08:03 +00:00
} ,
expectedYesVoteCount : sdk . NewDec ( genCoinCounts [ 4 ] ) ,
2022-01-08 00:39:27 +00:00
expectedNoVoteCount : testutil . D ( "0" ) ,
2021-06-07 16:08:03 +00:00
expectedTotalVoteCount : sdk . NewDec ( genCoinCounts [ 4 ] ) ,
} ,
{
name : "does not count non-token holder 'Yes' votes" ,
votes : [ ] types . Vote {
2022-01-08 00:39:27 +00:00
{ ProposalID : defaultProposalID , Voter : genAddrs [ 4 ] , VoteType : types . VOTE_TYPE_YES } , // Token holder
{ ProposalID : defaultProposalID , Voter : genAddrs [ 0 ] , VoteType : types . VOTE_TYPE_YES } , // Non-token holder
2021-06-07 16:08:03 +00:00
} ,
expectedYesVoteCount : sdk . NewDec ( genCoinCounts [ 4 ] ) ,
2022-01-08 00:39:27 +00:00
expectedNoVoteCount : testutil . D ( "0" ) ,
2021-06-07 16:08:03 +00:00
expectedTotalVoteCount : sdk . NewDec ( genCoinCounts [ 4 ] ) ,
} ,
{
name : "counts multiple 'Yes' votes from token holders" ,
votes : [ ] types . Vote {
2022-01-08 00:39:27 +00:00
{ ProposalID : defaultProposalID , Voter : genAddrs [ 4 ] , VoteType : types . VOTE_TYPE_YES } , // Token holder
{ ProposalID : defaultProposalID , Voter : genAddrs [ 5 ] , VoteType : types . VOTE_TYPE_YES } , // Token holder
{ ProposalID : defaultProposalID , Voter : genAddrs [ 6 ] , VoteType : types . VOTE_TYPE_YES } , // Token holder
2021-06-07 16:08:03 +00:00
} ,
expectedYesVoteCount : sdk . NewDec ( genCoinCounts [ 4 ] + genCoinCounts [ 5 ] + genCoinCounts [ 6 ] ) ,
2022-01-08 00:39:27 +00:00
expectedNoVoteCount : testutil . D ( "0" ) ,
2021-06-07 16:08:03 +00:00
expectedTotalVoteCount : sdk . NewDec ( genCoinCounts [ 4 ] + genCoinCounts [ 5 ] + genCoinCounts [ 6 ] ) ,
} ,
{
name : "counts token holder 'No' votes" ,
votes : [ ] types . Vote {
2022-01-08 00:39:27 +00:00
{ ProposalID : defaultProposalID , Voter : genAddrs [ 4 ] , VoteType : types . VOTE_TYPE_NO } , // Token holder
2021-06-07 16:08:03 +00:00
} ,
2022-01-08 00:39:27 +00:00
expectedYesVoteCount : testutil . D ( "0" ) ,
2021-06-07 16:08:03 +00:00
expectedNoVoteCount : sdk . NewDec ( genCoinCounts [ 4 ] ) ,
expectedTotalVoteCount : sdk . NewDec ( genCoinCounts [ 4 ] ) ,
} ,
{
name : "does not count non-token holder 'No' votes" ,
votes : [ ] types . Vote {
2022-01-08 00:39:27 +00:00
{ ProposalID : defaultProposalID , Voter : genAddrs [ 4 ] , VoteType : types . VOTE_TYPE_NO } , // Token holder
{ ProposalID : defaultProposalID , Voter : genAddrs [ 0 ] , VoteType : types . VOTE_TYPE_NO } , // Non-token holder
2021-06-07 16:08:03 +00:00
} ,
2022-01-08 00:39:27 +00:00
expectedYesVoteCount : testutil . D ( "0" ) ,
2021-06-07 16:08:03 +00:00
expectedNoVoteCount : sdk . NewDec ( genCoinCounts [ 4 ] ) ,
expectedTotalVoteCount : sdk . NewDec ( genCoinCounts [ 4 ] ) ,
} ,
{
name : "counts multiple 'No' votes from token holders" ,
votes : [ ] types . Vote {
2022-01-08 00:39:27 +00:00
{ ProposalID : defaultProposalID , Voter : genAddrs [ 4 ] , VoteType : types . VOTE_TYPE_NO } , // Token holder
{ ProposalID : defaultProposalID , Voter : genAddrs [ 5 ] , VoteType : types . VOTE_TYPE_NO } , // Token holder
{ ProposalID : defaultProposalID , Voter : genAddrs [ 6 ] , VoteType : types . VOTE_TYPE_NO } , // Token holder
2021-06-07 16:08:03 +00:00
} ,
2022-01-08 00:39:27 +00:00
expectedYesVoteCount : testutil . D ( "0" ) ,
2021-06-07 16:08:03 +00:00
expectedNoVoteCount : sdk . NewDec ( genCoinCounts [ 4 ] + genCoinCounts [ 5 ] + genCoinCounts [ 6 ] ) ,
expectedTotalVoteCount : sdk . NewDec ( genCoinCounts [ 4 ] + genCoinCounts [ 5 ] + genCoinCounts [ 6 ] ) ,
} ,
{
name : "includes token holder 'Abstain' votes in total vote count" ,
votes : [ ] types . Vote {
2022-01-08 00:39:27 +00:00
{ ProposalID : defaultProposalID , Voter : genAddrs [ 4 ] , VoteType : types . VOTE_TYPE_ABSTAIN } , // Token holder
2021-06-07 16:08:03 +00:00
} ,
2022-01-08 00:39:27 +00:00
expectedYesVoteCount : testutil . D ( "0" ) ,
expectedNoVoteCount : testutil . D ( "0" ) ,
2021-06-07 16:08:03 +00:00
expectedTotalVoteCount : sdk . NewDec ( genCoinCounts [ 4 ] ) ,
} ,
}
// Convert accounts/token balances into format expected by genesis generation
var genCoins [ ] sdk . Coins
var totalSupply sdk . Coins
for _ , amount := range genCoinCounts {
2022-01-08 00:39:27 +00:00
userCoin := testutil . C ( "hard" , amount )
genCoins = append ( genCoins , testutil . Cs ( userCoin ) )
2021-06-07 16:08:03 +00:00
totalSupply = totalSupply . Add ( userCoin )
}
for _ , tc := range testcases {
// Set up test app
tApp := app . NewTestApp ( )
keeper := tApp . GetCommitteeKeeper ( )
2022-01-08 00:39:27 +00:00
ctx := tApp . NewContext ( true , tmproto . Header { Height : 1 , Time : firstBlockTime } )
2021-06-07 16:08:03 +00:00
// Initialize test app with genesis state
tApp . InitializeFromGenesisStates (
committeeGenState (
2022-01-08 00:39:27 +00:00
tApp . AppCodec ( ) ,
2021-06-07 16:08:03 +00:00
[ ] types . Committee { tokenCom } ,
2022-01-08 00:39:27 +00:00
[ ] types . Proposal { types . MustNewProposal (
2023-04-04 00:08:45 +00:00
govv1beta1 . NewTextProposal ( "A Title" , "A description of this proposal." ) ,
2022-01-08 00:39:27 +00:00
defaultProposalID ,
tokenCom . GetID ( ) ,
firstBlockTime . Add ( time . Hour * 24 * 7 ) ,
) } ,
2021-06-07 16:08:03 +00:00
tc . votes ,
) ,
2022-01-08 00:39:27 +00:00
app . NewFundedGenStateWithCoins ( tApp . AppCodec ( ) , genCoins , genAddrs ) ,
2021-06-07 16:08:03 +00:00
)
yesVotes , noVotes , currVotes , possibleVotes := keeper . TallyTokenCommitteeVotes ( ctx , defaultProposalID , tokenCom . TallyDenom )
// Check that all Yes votes are counted according to their weight
suite . Equal ( tc . expectedYesVoteCount , yesVotes )
// Check that all No votes are counted according to their weight
suite . Equal ( tc . expectedNoVoteCount , noVotes )
// Check that all non-Yes votes are counted according to their weight
suite . Equal ( tc . expectedTotalVoteCount , currVotes )
// Check that possible votes equals the number of members on the committee
2023-04-04 00:08:45 +00:00
suite . Equal ( sdk . NewDecFromInt ( totalSupply . AmountOf ( tokenCom . GetTallyDenom ( ) ) ) , possibleVotes )
2021-06-07 16:08:03 +00:00
}
}
2022-01-08 00:39:27 +00:00
func ( suite * keeperTestSuite ) TestGetMemberCommitteeProposalResult ( ) {
memberCom := types . MustNewMemberCommittee (
12 ,
"This committee is for testing." ,
suite . Addresses [ : 5 ] ,
[ ] types . Permission { & types . GodPermission { } } ,
testutil . D ( "0.667" ) ,
time . Hour * 24 * 7 ,
types . TALLY_OPTION_DEADLINE ,
)
2020-03-26 20:17:49 +00:00
var defaultID uint64 = 1
firstBlockTime := time . Date ( 1998 , time . January , 1 , 1 , 0 , 0 , 0 , time . UTC )
testcases := [ ] struct {
name string
committee types . Committee
votes [ ] types . Vote
proposalPasses bool
} {
{
2020-03-27 18:34:03 +00:00
name : "enough votes" ,
2021-06-07 16:08:03 +00:00
committee : memberCom ,
2020-03-26 20:17:49 +00:00
votes : [ ] types . Vote {
2022-01-08 00:39:27 +00:00
{ ProposalID : defaultID , Voter : suite . Addresses [ 0 ] , VoteType : types . VOTE_TYPE_YES } ,
{ ProposalID : defaultID , Voter : suite . Addresses [ 1 ] , VoteType : types . VOTE_TYPE_YES } ,
{ ProposalID : defaultID , Voter : suite . Addresses [ 2 ] , VoteType : types . VOTE_TYPE_YES } ,
{ ProposalID : defaultID , Voter : suite . Addresses [ 3 ] , VoteType : types . VOTE_TYPE_YES } ,
2020-03-26 20:17:49 +00:00
} ,
proposalPasses : true ,
} ,
{
2020-03-27 18:34:03 +00:00
name : "not enough votes" ,
2021-06-07 16:08:03 +00:00
committee : memberCom ,
2020-03-26 20:17:49 +00:00
votes : [ ] types . Vote {
2022-01-08 00:39:27 +00:00
{ ProposalID : defaultID , Voter : suite . Addresses [ 0 ] , VoteType : types . VOTE_TYPE_YES } ,
2020-03-26 20:17:49 +00:00
} ,
proposalPasses : false ,
} ,
2020-03-11 19:52:25 +00:00
}
2020-03-26 20:17:49 +00:00
for _ , tc := range testcases {
suite . Run ( tc . name , func ( ) {
2020-03-30 13:06:31 +00:00
// Create local testApp because suite doesn't run the SetupTest function for subtests
2020-03-26 20:17:49 +00:00
tApp := app . NewTestApp ( )
keeper := tApp . GetCommitteeKeeper ( )
2022-01-08 00:39:27 +00:00
ctx := tApp . NewContext ( true , tmproto . Header { Height : 1 , Time : firstBlockTime } )
2020-03-26 20:17:49 +00:00
tApp . InitializeFromGenesisStates (
committeeGenState (
2022-01-08 00:39:27 +00:00
tApp . AppCodec ( ) ,
2020-03-26 20:17:49 +00:00
[ ] types . Committee { tc . committee } ,
2022-01-08 00:39:27 +00:00
[ ] types . Proposal { types . MustNewProposal (
2023-04-04 00:08:45 +00:00
govv1beta1 . NewTextProposal ( "A Title" , "A description of this proposal." ) ,
2022-01-08 00:39:27 +00:00
defaultID ,
tc . committee . GetID ( ) ,
firstBlockTime . Add ( time . Hour * 24 * 7 ) ,
) } ,
2020-03-26 20:17:49 +00:00
tc . votes ,
) ,
)
2021-06-07 16:08:03 +00:00
proposalPasses := keeper . GetMemberCommitteeProposalResult ( ctx , defaultID , tc . committee )
suite . Equal ( tc . proposalPasses , proposalPasses )
} )
}
}
2020-03-26 20:17:49 +00:00
2022-01-08 00:39:27 +00:00
func ( suite * keeperTestSuite ) TestGetTokenCommitteeProposalResult ( ) {
tokenCom := types . MustNewTokenCommittee (
12 ,
"This committee is for testing." ,
suite . Addresses [ : 5 ] ,
[ ] types . Permission { & types . GodPermission { } } ,
testutil . D ( "0.667" ) ,
time . Hour * 24 * 7 ,
types . TALLY_OPTION_DEADLINE ,
testutil . D ( "0.4" ) ,
"hard" ,
)
2021-06-07 16:08:03 +00:00
var defaultID uint64 = 1
firstBlockTime := time . Date ( 1998 , time . January , 1 , 1 , 0 , 0 , 0 , time . UTC )
2022-01-08 00:39:27 +00:00
genAddrs := suite . Addresses [ : 8 ] // Genesis accounts
2021-06-07 16:08:03 +00:00
genCoinCounts := [ ] int64 { 0 , 0 , 0 , 10 , 20 , 30 , 40 , 50 } // Genesis token balances
// ---------------------- Polling information ----------------------
// 150hard total token supply: 150 possible votes
// 40% quroum: 60 votes required to meet quroum
// 66.67% voting threshold: 2/3rds of votes must be Yes votes
// -----------------------------------------------------------------
testcases := [ ] struct {
name string
2022-01-08 00:39:27 +00:00
committee * types . TokenCommittee
2021-06-07 16:08:03 +00:00
votes [ ] types . Vote
proposalPasses bool
} {
{
name : "not enough votes to meet quroum" ,
committee : tokenCom ,
votes : [ ] types . Vote {
2022-01-08 00:39:27 +00:00
{ ProposalID : defaultID , Voter : genAddrs [ 7 ] , VoteType : types . VOTE_TYPE_YES } , // Holds 50 tokens
2021-06-07 16:08:03 +00:00
} ,
proposalPasses : false , // 60 vote quroum; 50 total votes; 50 yes votes. Doesn't pass 40% quroum.
} ,
{
name : "enough votes to meet quroum and enough Yes votes to pass voting threshold" ,
committee : tokenCom ,
votes : [ ] types . Vote {
2022-01-08 00:39:27 +00:00
{ ProposalID : defaultID , Voter : genAddrs [ 3 ] , VoteType : types . VOTE_TYPE_NO } , // Holds 10 tokens
{ ProposalID : defaultID , Voter : genAddrs [ 7 ] , VoteType : types . VOTE_TYPE_YES } , // Holds 50 tokens
2021-06-07 16:08:03 +00:00
} ,
proposalPasses : true , // 60 vote quroum; 60 total votes; 50 Yes votes. Passes the 66.67% voting threshold.
} ,
{
name : "enough votes to meet quroum via Abstain votes and enough Yes votes to pass voting threshold" ,
committee : tokenCom ,
votes : [ ] types . Vote {
2022-01-08 00:39:27 +00:00
{ ProposalID : defaultID , Voter : genAddrs [ 3 ] , VoteType : types . VOTE_TYPE_ABSTAIN } , // Holds 10 tokens
{ ProposalID : defaultID , Voter : genAddrs [ 7 ] , VoteType : types . VOTE_TYPE_YES } , // Holds 50 tokens
2021-06-07 16:08:03 +00:00
} ,
proposalPasses : true , // 60 vote quroum; 60 total votes; 50 Yes votes. Passes the 66.67% voting threshold.
} ,
{
name : "enough votes to meet quroum but not enough Yes votes to pass voting threshold" ,
committee : tokenCom ,
votes : [ ] types . Vote {
2022-01-08 00:39:27 +00:00
{ ProposalID : defaultID , Voter : genAddrs [ 4 ] , VoteType : types . VOTE_TYPE_YES } , // Holds 20 tokens
{ ProposalID : defaultID , Voter : genAddrs [ 6 ] , VoteType : types . VOTE_TYPE_NO } , // Holds 40 tokens
2021-06-07 16:08:03 +00:00
} ,
proposalPasses : false , // 60 vote quroum; 60 total votes; 20 Yes votes. Doesn't pass 66.67% voting threshold.
} ,
{
name : "enough votes to pass voting threshold (multiple Yes votes, multiple No votes)" ,
committee : tokenCom ,
votes : [ ] types . Vote {
2022-01-08 00:39:27 +00:00
{ ProposalID : defaultID , Voter : genAddrs [ 3 ] , VoteType : types . VOTE_TYPE_YES } , // Holds 10 tokens
{ ProposalID : defaultID , Voter : genAddrs [ 4 ] , VoteType : types . VOTE_TYPE_YES } , // Holds 20 tokens
{ ProposalID : defaultID , Voter : genAddrs [ 5 ] , VoteType : types . VOTE_TYPE_YES } , // Holds 30 tokens
{ ProposalID : defaultID , Voter : genAddrs [ 6 ] , VoteType : types . VOTE_TYPE_NO } , // Holds 40 tokens
{ ProposalID : defaultID , Voter : genAddrs [ 7 ] , VoteType : types . VOTE_TYPE_YES } , // Holds 50 tokens
2021-06-07 16:08:03 +00:00
} ,
proposalPasses : true , // 60 vote quroum; 150 total votes; 110 Yes votes. Passes the 66.67% voting threshold.
} ,
{
name : "not enough votes to pass voting threshold (multiple Yes votes, multiple No votes)" ,
committee : tokenCom ,
votes : [ ] types . Vote {
2022-01-08 00:39:27 +00:00
{ ProposalID : defaultID , Voter : genAddrs [ 3 ] , VoteType : types . VOTE_TYPE_YES } , // Holds 10 tokens
{ ProposalID : defaultID , Voter : genAddrs [ 4 ] , VoteType : types . VOTE_TYPE_YES } , // Holds 20 tokens
{ ProposalID : defaultID , Voter : genAddrs [ 5 ] , VoteType : types . VOTE_TYPE_YES } , // Holds 30 tokens
{ ProposalID : defaultID , Voter : genAddrs [ 6 ] , VoteType : types . VOTE_TYPE_YES } , // Holds 40 tokens
{ ProposalID : defaultID , Voter : genAddrs [ 7 ] , VoteType : types . VOTE_TYPE_NO } , // Holds 50 tokens
2021-06-07 16:08:03 +00:00
} ,
proposalPasses : false , // 60 vote quroum; 150 total votes; 100 Yes votes. Doesn't pass 66.67% voting threshold.
} ,
}
// Convert accounts/token balances into format expected by genesis generation
var genCoins [ ] sdk . Coins
var totalSupply sdk . Coins
for _ , amount := range genCoinCounts {
2022-01-08 00:39:27 +00:00
userCoin := testutil . C ( "hard" , amount )
genCoins = append ( genCoins , testutil . Cs ( userCoin ) )
2021-06-07 16:08:03 +00:00
totalSupply = totalSupply . Add ( userCoin )
}
for _ , tc := range testcases {
suite . Run ( tc . name , func ( ) {
// Create local testApp because suite doesn't run the SetupTest function for subtests
tApp := app . NewTestApp ( )
keeper := tApp . GetCommitteeKeeper ( )
2022-01-08 00:39:27 +00:00
ctx := tApp . NewContext ( true , tmproto . Header { Height : 1 , Time : firstBlockTime } )
2021-06-07 16:08:03 +00:00
tApp . InitializeFromGenesisStates (
committeeGenState (
2022-01-08 00:39:27 +00:00
tApp . AppCodec ( ) ,
2021-06-07 16:08:03 +00:00
[ ] types . Committee { tc . committee } ,
2022-01-08 00:39:27 +00:00
[ ] types . Proposal { types . MustNewProposal (
2023-04-04 00:08:45 +00:00
govv1beta1 . NewTextProposal ( "A Title" , "A description of this proposal." ) ,
2022-01-08 00:39:27 +00:00
defaultID ,
tc . committee . GetID ( ) ,
firstBlockTime . Add ( time . Hour * 24 * 7 ) ,
) } ,
2021-06-07 16:08:03 +00:00
tc . votes ,
) ,
2022-01-08 00:39:27 +00:00
app . NewFundedGenStateWithCoins ( tApp . AppCodec ( ) , genCoins , genAddrs ) ,
2021-06-07 16:08:03 +00:00
)
proposalPasses := keeper . GetTokenCommitteeProposalResult ( ctx , defaultID , tc . committee )
suite . Equal ( tc . proposalPasses , proposalPasses )
2020-03-26 20:17:49 +00:00
} )
}
}
2022-01-08 00:39:27 +00:00
func ( suite * keeperTestSuite ) TestCloseProposal ( ) {
memberCom := types . MustNewMemberCommittee (
12 ,
"This committee is for testing." ,
suite . Addresses [ : 5 ] ,
[ ] types . Permission { & types . GodPermission { } } ,
testutil . D ( "0.667" ) ,
time . Hour * 24 * 7 ,
types . TALLY_OPTION_DEADLINE ,
)
2021-06-07 16:08:03 +00:00
var proposalID uint64 = 1
firstBlockTime := time . Date ( 1998 , time . January , 1 , 1 , 0 , 0 , 0 , time . UTC )
tApp := app . NewTestApp ( )
keeper := tApp . GetCommitteeKeeper ( )
2022-01-08 00:39:27 +00:00
ctx := tApp . NewContext ( true , tmproto . Header { Height : 1 , Time : firstBlockTime } )
2021-06-07 16:08:03 +00:00
tApp . InitializeFromGenesisStates (
committeeGenState (
2022-01-08 00:39:27 +00:00
tApp . AppCodec ( ) ,
2021-06-07 16:08:03 +00:00
[ ] types . Committee { memberCom } ,
2022-01-08 00:39:27 +00:00
[ ] types . Proposal { types . MustNewProposal (
2023-04-04 00:08:45 +00:00
govv1beta1 . NewTextProposal ( "A Title" , "A description of this proposal." ) ,
2022-01-08 00:39:27 +00:00
proposalID ,
memberCom . GetID ( ) ,
firstBlockTime . Add ( time . Hour * 24 * 7 ) ,
) } ,
2021-06-07 16:08:03 +00:00
[ ] types . Vote { } ,
) ,
)
// Confirm proposal exists
proposal , found := keeper . GetProposal ( ctx , proposalID )
suite . True ( found )
2021-06-22 14:49:46 +00:00
2021-06-07 16:08:03 +00:00
// Close proposal
keeper . CloseProposal ( ctx , proposal , types . Passed )
2021-06-22 14:49:46 +00:00
events := ctx . EventManager ( ) . Events ( )
event := events [ 0 ]
suite . Require ( ) . Equal ( "proposal_close" , event . Type )
hasProposalTallyAttr := false
for _ , attr := range event . Attributes {
if string ( attr . GetKey ( ) ) == "proposal_tally" {
hasProposalTallyAttr = true
valueStr := string ( attr . GetValue ( ) )
suite . Contains ( valueStr , "proposal_id" )
suite . Contains ( valueStr , "yes_votes" )
suite . Contains ( valueStr , "current_votes" )
suite . Contains ( valueStr , "possible_votes" )
suite . Contains ( valueStr , "vote_threshold" )
suite . Contains ( valueStr , "quorum" )
}
}
suite . Require ( ) . True ( hasProposalTallyAttr )
2021-06-07 16:08:03 +00:00
// Confirm proposal doesn't exist
_ , found = keeper . GetProposal ( ctx , proposalID )
suite . False ( found )
}
2022-01-08 00:39:27 +00:00
func committeeGenState ( cdc codec . Codec , committees [ ] types . Committee , proposals [ ] types . Proposal , votes [ ] types . Vote ) app . GenesisState {
2020-03-26 20:17:49 +00:00
gs := types . NewGenesisState (
uint64 ( len ( proposals ) + 1 ) ,
committees ,
proposals ,
votes ,
)
2022-01-08 00:39:27 +00:00
return app . GenesisState { types . ModuleName : cdc . MustMarshalJSON ( gs ) }
2020-03-11 19:52:25 +00:00
}
2022-01-08 00:39:27 +00:00
func bankGenState ( cdc codec . Codec , coins sdk . Coins ) app . GenesisState {
gs := banktypes . DefaultGenesisState ( )
gs . Supply = coins
return app . GenesisState { banktypes . ModuleName : cdc . MustMarshalJSON ( gs ) }
2021-06-07 16:08:03 +00:00
}
2020-04-27 12:57:47 +00:00
type UnregisteredPubProposal struct {
2023-04-04 00:08:45 +00:00
govv1beta1 . TextProposal
2020-03-11 19:52:25 +00:00
}
2020-04-27 12:57:47 +00:00
func ( UnregisteredPubProposal ) ProposalRoute ( ) string { return "unregistered" }
func ( UnregisteredPubProposal ) ProposalType ( ) string { return "unregistered" }
2020-03-11 19:52:25 +00:00
2022-01-08 00:39:27 +00:00
var _ types . PubProposal = & UnregisteredPubProposal { }
// func (suite *keeperTestSuite) TestValidatePubProposal() {
// testcases := []struct {
// name string
// pubProposal types.PubProposal
// expectErr bool
// }{
// {
// name: "valid (text proposal)",
// pubProposal: govtypes.NewTextProposal("A Title", "A description of this proposal."),
// expectErr: false,
// },
// {
// name: "valid (param change proposal)",
// pubProposal: paramsproposal.NewParameterChangeProposal(
// "Change the debt limit",
// "This proposal changes the debt limit of the cdp module.",
// []paramsproposal.ParamChange{{
// Subspace: cdptypes.ModuleName,
// Key: string(cdptypes.KeyGlobalDebtLimit),
// Value: string(types.ModuleCdc.MustMarshalJSON(c("usdx", 100000000000))),
// }},
// ),
// expectErr: false,
// },
// {
// name: "invalid (missing title)",
// pubProposal: govtypes.TextProposal{Description: "A description of this proposal."},
// expectErr: true,
// },
// {
// name: "invalid (unregistered)",
// pubProposal: UnregisteredPubProposal{govtypes.TextProposal{Title: "A Title", Description: "A description of this proposal."}},
// expectErr: true,
// },
// {
// name: "invalid (nil)",
// pubProposal: nil,
// expectErr: true,
// },
// {
// name: "invalid (proposal handler fails)",
// pubProposal: paramsproposal.NewParameterChangeProposal(
// "A Title",
// "A description of this proposal.",
// []paramsproposal.ParamChange{{
// Subspace: "nonsense-subspace",
// Key: "nonsense-key",
// Value: "nonsense-value",
// }},
// ),
// expectErr: true,
// },
// {
// name: "invalid (proposal handler panics)",
// pubProposal: paramsproposal.NewParameterChangeProposal(
// "A Title",
// "A description of this proposal.",
// []paramsproposal.ParamChange{{
// Subspace: cdptypes.ModuleName,
// Key: "nonsense-key", // a valid Subspace but invalid Key will trigger a panic in the paramchange propsal handler
// Value: "nonsense-value",
// }},
// ),
// expectErr: true,
// },
// {
// name: "invalid (proposal handler fails - invalid json)",
// pubProposal: paramsproposal.NewParameterChangeProposal(
// "A Title",
// "A description of this proposal.",
// []paramsproposal.ParamChange{{
// Subspace: cdptypes.ModuleName,
// Key: string(cdptypes.KeyGlobalDebtLimit),
// Value: `{"denom": "usdx",`,
// }},
// ),
// expectErr: true,
// },
// }
// for _, tc := range testcases {
// suite.Run(tc.name, func() {
// err := suite.keeper.ValidatePubProposal(suite.ctx, tc.pubProposal)
// if tc.expectErr {
// suite.NotNil(err)
// } else {
// suite.NoError(err)
// }
// })
// }
// }
// func (suite *keeperTestSuite) TestProcessProposals() {
// firstBlockTime := time.Date(1998, time.January, 1, 1, 0, 0, 0, time.UTC)
// genAddrs := suite.Addresses[:4] // Genesis accounts
// genCoinCounts := []int64{1, 1, 1, 1} // Genesis token balances
// // Convert accounts/token balances into format expected by genesis generation
// var genCoins []sdk.Coins
// var totalSupply sdk.Coins
// for _, amount := range genCoinCounts {
// userCoin := testutil.C("hard", amount)
// genCoins = append(genCoins, testutil.Cs(userCoin))
// totalSupply = totalSupply.Add(userCoin)
// }
// // Set up committees
// committees := []types.Committee{
// // 1. FPTP MemberCommmittee
// types.MustNewMemberCommittee(
// 1,
// "FTPT MemberCommittee",
// genAddrs,
// []types.Permission{&types.GodPermission{}},
// testutil.D("0.667"),
// time.Hour*24*7,
// types.TALLY_OPTION_FIRST_PAST_THE_POST,
// ),
// // 2. FPTP TokenCommittee
// types.MustNewTokenCommittee(
// 2,
// "FTPT TokenCommittee",
// genAddrs,
// []types.Permission{&types.GodPermission{}},
// testutil.D("0.667"),
// time.Hour*24*7,
// types.TALLY_OPTION_FIRST_PAST_THE_POST,
// testutil.D("0.30"),
// "hard",
// ),
// // 3. Deadline MemberCommmittee
// types.MustNewMemberCommittee(
// 3,
// "Deadline MemberCommittee",
// genAddrs,
// []types.Permission{&types.GodPermission{}},
// testutil.D("0.667"),
// time.Hour*24*7,
// types.TALLY_OPTION_DEADLINE,
// ),
// // 4. Deadline TokenCommittee
// types.MustNewTokenCommittee(
// 4,
// "Deadline TokenCommittee",
// genAddrs,
// []types.Permission{&types.GodPermission{}},
// testutil.D("0.667"),
// time.Hour*24*7,
// types.TALLY_OPTION_DEADLINE,
// testutil.D("0.30"),
// "hard",
// ),
// // 5. PTP MemberCommmittee without permissions
// types.MustNewMemberCommittee(
// 5,
// "FTPT MemberCommittee without permissions",
// genAddrs,
// nil,
// testutil.D("0.667"),
// time.Hour*24*7,
// types.TALLY_OPTION_FIRST_PAST_THE_POST,
// ),
// }
// // Set up proposals that correspond 1:1 with each committee
// proposals := []types.Proposal{
// types.MustNewProposal(
// govtypes.NewTextProposal("Proposal 1", "This proposal is for the FPTP MemberCommmittee."),
// 1,
// 1,
// firstBlockTime.Add(7*24*time.Hour),
// ),
// types.MustNewProposal(
// govtypes.NewTextProposal("Proposal 2", "This proposal is for the FPTP TokenCommittee."),
// 2,
// 2,
// firstBlockTime.Add(7*24*time.Hour),
// ),
// types.MustNewProposal(
// govtypes.NewTextProposal("Proposal 3", "This proposal is for the Deadline MemberCommmittee."),
// 3,
// 3,
// firstBlockTime.Add(7*24*time.Hour),
// ),
// types.MustNewProposal(
// govtypes.NewTextProposal("Proposal 4", "This proposal is for the Deadline TokenCommittee."),
// 4,
// 4,
// firstBlockTime.Add(7*24*time.Hour),
// ),
// types.MustNewProposal(
// govtypes.NewTextProposal("Proposal 5", "This proposal is for the FPTP MemberCommmittee without permissions."),
// 5,
// 5,
// firstBlockTime.Add(7*24*time.Hour),
// ),
// }
// // Each test case targets 1 committee/proposal via targeted votes
// testcases := []struct {
// name string
// ID uint64
// votes []types.Vote
// expectedToCompleteBeforeDeadline bool
// expectedOutcome types.ProposalOutcome
// }{
// {
// name: "FPTP MemberCommittee proposal does not have enough votes to pass",
// ID: 1,
// votes: []types.Vote{
// {ProposalID: 1, Voter: genAddrs[0], VoteType: types.VOTE_TYPE_YES},
// },
// expectedToCompleteBeforeDeadline: false,
// expectedOutcome: types.Failed,
// },
// {
// name: "FPTP MemberCommittee proposal has enough votes to pass before deadline",
// ID: 1,
// votes: []types.Vote{
// {ProposalID: 1, Voter: genAddrs[0], VoteType: types.VOTE_TYPE_YES},
// {ProposalID: 1, Voter: genAddrs[1], VoteType: types.VOTE_TYPE_YES},
// {ProposalID: 1, Voter: genAddrs[2], VoteType: types.VOTE_TYPE_YES},
// },
// expectedToCompleteBeforeDeadline: true,
// expectedOutcome: types.Passed,
// },
// {
// name: "FPTP TokenCommittee proposal does not have enough votes to pass",
// ID: 2,
// votes: []types.Vote{
// {ProposalID: 2, Voter: genAddrs[0], VoteType: types.VOTE_TYPE_YES},
// },
// expectedToCompleteBeforeDeadline: false,
// expectedOutcome: types.Failed,
// },
// {
// name: "FPTP TokenCommittee proposal has enough votes to pass before deadline",
// ID: 2,
// votes: []types.Vote{
// {ProposalID: 2, Voter: genAddrs[0], VoteType: types.VOTE_TYPE_YES},
// {ProposalID: 2, Voter: genAddrs[1], VoteType: types.VOTE_TYPE_YES},
// {ProposalID: 2, Voter: genAddrs[2], VoteType: types.VOTE_TYPE_YES},
// },
// expectedToCompleteBeforeDeadline: true,
// expectedOutcome: types.Passed,
// },
// {
// name: "Deadline MemberCommittee proposal with enough votes to pass only passes after deadline",
// ID: 3,
// votes: []types.Vote{
// {ProposalID: 3, Voter: genAddrs[0], VoteType: types.VOTE_TYPE_YES},
// {ProposalID: 3, Voter: genAddrs[1], VoteType: types.VOTE_TYPE_YES},
// {ProposalID: 3, Voter: genAddrs[2], VoteType: types.VOTE_TYPE_YES},
// },
// expectedOutcome: types.Passed,
// },
// {
// name: "Deadline MemberCommittee proposal doesn't have enough votes to pass",
// ID: 3,
// votes: []types.Vote{
// {ProposalID: 3, Voter: genAddrs[0], VoteType: types.VOTE_TYPE_YES},
// },
// expectedOutcome: types.Failed,
// },
// {
// name: "Deadline TokenCommittee proposal with enough votes to pass only passes after deadline",
// ID: 4,
// votes: []types.Vote{
// {ProposalID: 4, Voter: genAddrs[0], VoteType: types.VOTE_TYPE_YES},
// {ProposalID: 4, Voter: genAddrs[1], VoteType: types.VOTE_TYPE_YES},
// {ProposalID: 4, Voter: genAddrs[2], VoteType: types.VOTE_TYPE_YES},
// },
// expectedOutcome: types.Passed,
// },
// {
// name: "Deadline TokenCommittee proposal doesn't have enough votes to pass",
// ID: 4,
// votes: []types.Vote{
// {ProposalID: 4, Voter: genAddrs[0], VoteType: types.VOTE_TYPE_YES},
// },
// expectedOutcome: types.Failed,
// },
// {
// name: "FPTP MemberCommittee doesn't have permissions to enact passed proposal",
// ID: 5,
// votes: []types.Vote{
// {ProposalID: 5, Voter: genAddrs[0], VoteType: types.VOTE_TYPE_YES},
// {ProposalID: 5, Voter: genAddrs[1], VoteType: types.VOTE_TYPE_YES},
// {ProposalID: 5, Voter: genAddrs[2], VoteType: types.VOTE_TYPE_YES},
// },
// expectedToCompleteBeforeDeadline: true,
// expectedOutcome: types.Invalid,
// },
// }
// for _, tc := range testcases {
// suite.Run(tc.name, func() {
// // Create local testApp because suite doesn't run the SetupTest function for subtests
// tApp := app.NewTestApp()
// keeper := tApp.GetCommitteeKeeper()
// ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: firstBlockTime})
// // Initialize all committees, proposals, and votes via Genesis
// tApp.InitializeFromGenesisStates(
// committeeGenState(tApp.AppCodec(), committees, proposals, tc.votes),
// bankGenState(tApp.AppCodec(), totalSupply),
// app.NewFundedGenStateWithCoins(tApp.AppCodec(), genCoins, genAddrs),
// )
// // Load committee from the store
// committee, found := keeper.GetCommittee(ctx, tc.ID)
// suite.True(found)
// // Process proposals
// ctx = ctx.WithBlockTime(firstBlockTime)
// keeper.ProcessProposals(ctx)
// // Fetch proposal and votes from the store
// votes := getProposalVoteMap(keeper, ctx)
// proposal, found := keeper.GetProposal(ctx, tc.ID)
// if committee.GetTallyOption() == types.TALLY_OPTION_FIRST_PAST_THE_POST {
// if tc.expectedToCompleteBeforeDeadline {
// suite.False(found)
// suite.Empty(votes[tc.ID])
// // Check proposal outcome
// outcome, err := getProposalOutcome(tc.ID, ctx.EventManager().Events(), tApp.LegacyAmino())
// suite.NoError(err)
// suite.Equal(tc.expectedOutcome, outcome)
// return
// } else {
// suite.True(found)
// suite.NotEmpty(votes[tc.ID])
// }
// }
// // Move block time to deadline
// ctx = ctx.WithBlockTime(proposal.Deadline)
// keeper.ProcessProposals(ctx)
// // Fetch proposal and votes from the store
// votes = getProposalVoteMap(keeper, ctx)
// proposal, found = keeper.GetProposal(ctx, tc.ID)
// suite.False(found)
// suite.Empty(votes[proposal.ID])
// // Check proposal outcome
// outcome, err := getProposalOutcome(tc.ID, ctx.EventManager().Events(), tApp.LegacyAmino())
// suite.NoError(err)
// suite.Equal(tc.expectedOutcome, outcome)
// })
// }
// }
// // getProposalOutcome checks the outcome of a proposal via a `proposal_close` event whose `proposal_id`
// // matches argument proposalID
// func getProposalOutcome(proposalID uint64, events sdk.Events, cdc *codec.LegacyAmino) (types.ProposalOutcome, error) {
// // Marshal proposal ID to match against event attribute
// x, _ := cdc.MarshalJSON(proposalID)
// marshaledID := x[1 : len(x)-1]
// for _, event := range events {
// if event.Type == types.EventTypeProposalClose {
// var proposalOutcome types.ProposalOutcome
// correctProposal := false
// for _, attribute := range event.Attributes {
// // Only get outcome of specific proposal
// if bytes.Compare(attribute.GetKey(), []byte("proposal_id")) == 0 {
// if bytes.Compare(attribute.GetValue(), marshaledID) == 0 {
// correctProposal = true
// }
// }
// // Match event attribute bytes to marshaled outcome
// if bytes.Compare(attribute.GetKey(), []byte(types.AttributeKeyProposalOutcome)) == 0 {
// outcome, err := types.MatchMarshaledOutcome(attribute.GetValue(), cdc)
// if err != nil {
// return 0, err
// }
// proposalOutcome = outcome
// }
// }
// // If this is the desired proposal, return the outcome
// if correctProposal {
// return proposalOutcome, nil
// }
// }
// }
// return 0, nil
// }