From e473d972ecdd34eecb3b6df5599e247a9271d718 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Wed, 11 Mar 2020 19:27:36 +0000 Subject: [PATCH] add vote tallying and tests --- app/app.go | 1 + x/committee/alias.go | 24 +++- x/committee/genesis.go | 24 ++++ x/committee/handler.go | 10 ++ x/committee/keeper/keeper.go | 133 ++++++++++++++++--- x/committee/keeper/keeper_test.go | 209 ++++++++++++++++++++++++++++-- x/committee/module.go | 29 ++--- x/committee/types/codec.go | 16 ++- x/committee/types/genesis.go | 51 ++++++++ x/committee/types/permissions.go | 4 + x/committee/types/types.go | 8 +- 11 files changed, 446 insertions(+), 63 deletions(-) create mode 100644 x/committee/genesis.go create mode 100644 x/committee/types/genesis.go diff --git a/app/app.go b/app/app.go index 151c5836..41a26c22 100644 --- a/app/app.go +++ b/app/app.go @@ -249,6 +249,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, app.committeeKeeper = committee.NewKeeper( app.cdc, keys[committee.StoreKey], + govRouter, // TODO blacklist module addresses? ) diff --git a/x/committee/alias.go b/x/committee/alias.go index 41aadd1b..48abeee3 100644 --- a/x/committee/alias.go +++ b/x/committee/alias.go @@ -8,29 +8,43 @@ import ( ) const ( - ModuleName = types.ModuleName - StoreKey = types.StoreKey + DefaultNextProposalID = types.DefaultNextProposalID + ModuleName = types.ModuleName + StoreKey = types.StoreKey ) var ( // function aliases - NewKeeper = keeper.NewKeeper - RegisterCodec = types.RegisterCodec + NewKeeper = keeper.NewKeeper + DefaultGenesisState = types.DefaultGenesisState + GetKeyFromID = types.GetKeyFromID + GetVoteKey = types.GetVoteKey + NewGenesisState = types.NewGenesisState + RegisterCodec = types.RegisterCodec + Uint64FromBytes = types.Uint64FromBytes // variable aliases - ModuleCdc = types.ModuleCdc + CommitteeKeyPrefix = types.CommitteeKeyPrefix + ModuleCdc = types.ModuleCdc + NextProposalIDKey = types.NextProposalIDKey + ProposalKeyPrefix = types.ProposalKeyPrefix + VoteKeyPrefix = types.VoteKeyPrefix + VoteThreshold = types.VoteThreshold ) type ( Keeper = keeper.Keeper Committee = types.Committee GeneralShutdownPermission = types.GeneralShutdownPermission + GenesisState = types.GenesisState + GodPermission = types.GodPermission GroupChangeProposal = types.GroupChangeProposal InflationRateChangePermission = types.InflationRateChangePermission MsgSubmitProposal = types.MsgSubmitProposal MsgVote = types.MsgVote Permission = types.Permission Proposal = types.Proposal + PubProposal = types.PubProposal ShutdownCDPDepsitPermission = types.ShutdownCDPDepsitPermission Vote = types.Vote ) diff --git a/x/committee/genesis.go b/x/committee/genesis.go new file mode 100644 index 00000000..fef015c9 --- /dev/null +++ b/x/committee/genesis.go @@ -0,0 +1,24 @@ +package committee + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// InitGenesis initializes the store state from a genesis state. +func InitGenesis(ctx sdk.Context, keeper Keeper, gs GenesisState) { + if err := gs.Validate(); err != nil { + panic(fmt.Sprintf("failed to validate %s genesis state: %s", ModuleName, err)) + } + + keeper.SetNextProposalID(ctx, gs.NextProposalID) + + // TODO set votes, committee, proposals +} + +// ExportGenesis returns a GenesisState for a given context and keeper. +func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { + // TODO + return GenesisState{} +} diff --git a/x/committee/handler.go b/x/committee/handler.go index f3f9b03f..158faa1f 100644 --- a/x/committee/handler.go +++ b/x/committee/handler.go @@ -40,6 +40,16 @@ func handleMsgSubmitProposal(ctx sdk.Context, k keeper.Keeper, msg types.MsgSubm func handleMsgVote(ctx sdk.Context, k keeper.Keeper, msg types.MsgVote) sdk.Result { err := keeper.AddVote(ctx, msg) + // Try closing proposal + _ = k.CloseOutProposal(ctx, proposalID) + + // if err.Error() == "note enough votes to close proposal" { // TODO + // return nil // This is not a reason to error + // } + // if err != nil { + // return err + // } + if err != nil { return err.Result() } diff --git a/x/committee/keeper/keeper.go b/x/committee/keeper/keeper.go index 535d3cb3..033deebd 100644 --- a/x/committee/keeper/keeper.go +++ b/x/committee/keeper/keeper.go @@ -5,7 +5,7 @@ import ( "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" - //govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/kava-labs/kava/x/committee/types" ) @@ -14,20 +14,28 @@ type Keeper struct { cdc *codec.Codec storeKey sdk.StoreKey - // TODO Proposal router - //router govtypes.Router + // Proposal router + router govtypes.Router } -func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey) Keeper { +func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, router govtypes.Router) Keeper { + // It is vital to seal the governance proposal router here as to not allow + // further handlers to be registered after the keeper is created since this + // could create invalid or non-deterministic behavior. + // TODO why? + // Not sealing the router because for some reason the function panics if it has already been sealed and there is no way to tell if has already been called. + // router.Seal() + return Keeper{ cdc: cdc, storeKey: storeKey, + router: router, } } -func (k Keeper) SubmitProposal(ctx sdk.Context, proposer sdk.AccAddress, proposal types.Proposal) (uint64, sdk.Error) { +func (k Keeper) SubmitProposal(ctx sdk.Context, proposer sdk.AccAddress, committeeID uint64, pubProposal types.PubProposal) (uint64, sdk.Error) { // Limit proposals to only be submitted by committee members - com, found := k.GetCommittee(ctx, proposal.CommitteeID) + com, found := k.GetCommittee(ctx, committeeID) if !found { return 0, sdk.ErrInternal("committee doesn't exist") } @@ -35,30 +43,29 @@ func (k Keeper) SubmitProposal(ctx sdk.Context, proposer sdk.AccAddress, proposa return 0, sdk.ErrInternal("only member can propose proposals") } - // Check proposal is valid - if err := proposal.ValidateBasic(); err != nil { - return 0, err - } - - // Check group has permissions to enact proposal. - if !com.HasPermissionsFor(proposal) { + // Check committee has permissions to enact proposal. + if !com.HasPermissionsFor(pubProposal) { return 0, sdk.ErrInternal("committee does not have permissions to enact proposal") } - // TODO validate proposal by running it with cached context like how gov does it - // what if it's not valid now but will be in the future? + // Check proposal is valid + // TODO what if it's not valid now but will be in the future? + // TODO does this need to be before permission check? + if err := k.ValidatePubProposal(ctx, pubProposal); err != nil { + return 0, err + } // Get a new ID and store the proposal - return k.StoreNewProposal(ctx, proposal) + return k.StoreNewProposal(ctx, committeeID, pubProposal) } func (k Keeper) AddVote(ctx sdk.Context, proposalID uint64, voter sdk.AccAddress) sdk.Error { // Validate - proposal, found := k.GetProposal(ctx, proposalID) + pr, found := k.GetProposal(ctx, proposalID) if !found { return sdk.ErrInternal("proposal not found") } - com, found := k.GetCommittee(ctx, proposal.CommitteeID) + com, found := k.GetCommittee(ctx, pr.CommitteeID) if !found { return sdk.ErrInternal("committee disbanded") } @@ -69,7 +76,70 @@ func (k Keeper) AddVote(ctx sdk.Context, proposalID uint64, voter sdk.AccAddress // Store vote, overwriting any prior vote k.SetVote(ctx, types.Vote{ProposalID: proposalID, Voter: voter}) - // TODO close vote if tally has been reached + return nil +} + +func (k Keeper) CloseOutProposal(ctx sdk.Context, proposalID uint64) sdk.Error { + pr, found := k.GetProposal(ctx, proposalID) + if !found { + return sdk.ErrInternal("proposal not found") + } + com, found := k.GetCommittee(ctx, pr.CommitteeID) + if !found { + return sdk.ErrInternal("committee disbanded") + } + + var votes []types.Vote + k.IterateVotes(ctx, proposalID, func(vote types.Vote) bool { + votes = append(votes, vote) + return false + }) + if sdk.NewDec(int64(len(votes))).GTE(types.VoteThreshold.MulInt64(int64(len(com.Members)))) { // TODO move vote counting stuff to committee methods // TODO add timeout check here - close if expired regardless of votes + // eneact vote + // The proposal handler may execute state mutating logic depending + // on the proposal content. If the handler fails, no state mutation + // is written and the error message is logged. + handler := k.router.GetRoute(pr.ProposalRoute()) + cacheCtx, writeCache := ctx.CacheContext() + err := handler(cacheCtx, pr.PubProposal) // need to pass pubProposal as the handlers type assert it into the concrete types + if err == nil { + // write state to the underlying multi-store + writeCache() + } // if handler returns error, then still delete the proposal - it's still over, but send an event + + // delete proposal and votes + k.DeleteProposal(ctx, proposalID) + for _, v := range votes { + k.DeleteVote(ctx, v.ProposalID, v.Voter) + } + } else { + return sdk.ErrInternal("note enough votes to close proposal") + } + return nil +} + +func (k Keeper) ValidatePubProposal(ctx sdk.Context, pubProposal types.PubProposal) sdk.Error { + // TODO not sure if the basic validation is required - should be run in msg.ValidateBasic + if pubProposal == nil { + return sdk.ErrInternal("proposal is empty") + } + if err := pubProposal.ValidateBasic(); err != nil { + return err + } + + if !k.router.HasRoute(pubProposal.ProposalRoute()) { + return sdk.ErrInternal("no handler found for proposal") + } + + // Execute the proposal content in a cache-wrapped context to validate the + // actual parameter changes before the proposal proceeds through the + // governance process. State is not persisted. + cacheCtx, _ := ctx.CacheContext() + handler := k.router.GetRoute(pubProposal.ProposalRoute()) + if err := handler(cacheCtx, pubProposal); err != nil { + return err + } + return nil } @@ -125,12 +195,16 @@ func (k Keeper) IncrementNextProposalID(ctx sdk.Context) sdk.Error { } // StoreNewProposal stores a proposal, adding a new ID -func (k Keeper) StoreNewProposal(ctx sdk.Context, proposal types.Proposal) (uint64, sdk.Error) { +func (k Keeper) StoreNewProposal(ctx sdk.Context, committeeID uint64, pubProposal types.PubProposal) (uint64, sdk.Error) { newProposalID, err := k.GetNextProposalID(ctx) if err != nil { return 0, err } - proposal.ID = newProposalID + proposal := types.Proposal{ + PubProposal: pubProposal, + ID: newProposalID, + CommitteeID: committeeID, + } k.SetProposal(ctx, proposal) @@ -166,6 +240,23 @@ func (k Keeper) DeleteProposal(ctx sdk.Context, proposalID uint64) { store.Delete(types.GetKeyFromID(proposalID)) } +// IterateVotes provides an iterator over all stored votes for a given proposal. +// For each vote, cb will be called. If cb returns true, the iterator will close and stop. +func (k Keeper) IterateVotes(ctx sdk.Context, proposalID uint64, cb func(vote types.Vote) (stop bool)) { + // iterate over the section of the votes store that has all votes for a particular proposal + iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), append(types.VoteKeyPrefix, types.GetKeyFromID(proposalID)...)) + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var vote types.Vote + k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &vote) + + if cb(vote) { + break + } + } +} + // GetVote gets a vote from the store. func (k Keeper) GetVote(ctx sdk.Context, proposalID uint64, voter sdk.AccAddress) (types.Vote, bool) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.VoteKeyPrefix) diff --git a/x/committee/keeper/keeper_test.go b/x/committee/keeper/keeper_test.go index 5244e9d2..5605ce99 100644 --- a/x/committee/keeper/keeper_test.go +++ b/x/committee/keeper/keeper_test.go @@ -1,16 +1,18 @@ package keeper_test import ( + "reflect" "testing" - "github.com/kava-labs/kava/x/committee/types" "github.com/stretchr/testify/suite" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov" abci "github.com/tendermint/tendermint/abci/types" "github.com/kava-labs/kava/app" "github.com/kava-labs/kava/x/committee/keeper" + "github.com/kava-labs/kava/x/committee/types" ) type KeeperTestSuite struct { @@ -27,25 +29,89 @@ func (suite *KeeperTestSuite) SetupTest() { suite.app = app.NewTestApp() suite.keeper = suite.app.GetCommitteeKeeper() suite.ctx = suite.app.NewContext(true, abci.Header{}) - _, suite.addresses = app.GeneratePrivKeyAddressPairs(2) + _, suite.addresses = app.GeneratePrivKeyAddressPairs(5) } func (suite *KeeperTestSuite) TestSubmitProposal() { + normalCom := types.Committee{ + ID: 12, + Members: suite.addresses[:2], + Permissions: []types.Permission{types.GodPermission{}}, + } + noPermissionsCom := types.Committee{ + ID: 12, + Members: suite.addresses[:2], + Permissions: []types.Permission{}, + } + testcases := []struct { - name string - proposal types.Proposal - proposer sdk.AccAddress - expectPass bool + name string + committee types.Committee + pubProposal types.PubProposal + proposer sdk.AccAddress + committeeID uint64 + expectPass bool }{ - {name: "empty proposal", proposer: suite.addresses[0], expectPass: false}, + { + name: "normal", + committee: normalCom, + pubProposal: gov.NewTextProposal("A Title", "A description of this proposal."), + proposer: normalCom.Members[0], + committeeID: normalCom.ID, + expectPass: true, + }, + { + name: "invalid proposal", + committee: normalCom, + pubProposal: nil, + proposer: normalCom.Members[0], + committeeID: normalCom.ID, + expectPass: false, + }, + { + name: "missing committee", + // no committee + pubProposal: gov.NewTextProposal("A Title", "A description of this proposal."), + proposer: suite.addresses[0], + committeeID: 0, + expectPass: false, + }, + { + name: "not a member", + committee: normalCom, + pubProposal: gov.NewTextProposal("A Title", "A description of this proposal."), + proposer: suite.addresses[4], + committeeID: normalCom.ID, + expectPass: false, + }, + { + name: "not enough permissions", + committee: noPermissionsCom, + pubProposal: gov.NewTextProposal("A Title", "A description of this proposal."), + proposer: noPermissionsCom.Members[0], + committeeID: noPermissionsCom.ID, + expectPass: false, + }, } for _, tc := range testcases { suite.Run(tc.name, func() { - _, err := suite.keeper.SubmitProposal(suite.ctx, tc.proposer, tc.proposal) + // Create local testApp because suite doesn't run the SetupTest function for subtests, which would mean the app state is not be reset between subtests. + tApp := app.NewTestApp() + keeper := tApp.GetCommitteeKeeper() + ctx := tApp.NewContext(true, abci.Header{}) + tApp.InitializeFromGenesisStates() + // setup committee (if required) + if !(reflect.DeepEqual(tc.committee, types.Committee{})) { + keeper.SetCommittee(ctx, tc.committee) + } + + id, err := keeper.SubmitProposal(ctx, tc.proposer, tc.committeeID, tc.pubProposal) + if tc.expectPass { suite.NoError(err) - // TODO suite.keeper.GetProposal(suite.ctx, tc.proposal.ID) + _, found := keeper.GetProposal(ctx, id) + suite.True(found) } else { suite.NotNil(err) } @@ -54,21 +120,140 @@ func (suite *KeeperTestSuite) TestSubmitProposal() { } func (suite *KeeperTestSuite) TestAddVote() { + normalCom := types.Committee{ + ID: 12, + Members: suite.addresses[:2], + Permissions: []types.Permission{types.GodPermission{}}, + } + testcases := []struct { name string proposalID uint64 voter sdk.AccAddress expectPass bool }{ - {name: "no proposal", proposalID: 9999999, voter: suite.addresses[0], expectPass: false}, + { + name: "normal", + proposalID: types.DefaultNextProposalID, + voter: normalCom.Members[0], + expectPass: true, + }, + { + name: "nonexistent proposal", + proposalID: 9999999, + voter: normalCom.Members[0], + expectPass: false, + }, + { + name: "voter not committee member", + proposalID: types.DefaultNextProposalID, + voter: suite.addresses[4], + expectPass: false, + }, } for _, tc := range testcases { suite.Run(tc.name, func() { - err := suite.keeper.AddVote(suite.ctx, tc.proposalID, tc.voter) + // Create local testApp because suite doesn't run the SetupTest function for subtests, which would mean the app state is not be reset between subtests. + tApp := app.NewTestApp() + keeper := tApp.GetCommitteeKeeper() + ctx := tApp.NewContext(true, abci.Header{}) + tApp.InitializeFromGenesisStates() + + // setup the committee and proposal + keeper.SetCommittee(ctx, normalCom) + _, err := keeper.SubmitProposal(ctx, normalCom.Members[0], normalCom.ID, gov.NewTextProposal("A Title", "A description of this proposal.")) + suite.NoError(err) + + err = keeper.AddVote(ctx, tc.proposalID, tc.voter) + + if tc.expectPass { + suite.NoError(err) + _, found := keeper.GetVote(ctx, tc.proposalID, tc.voter) + suite.True(found) + } else { + suite.NotNil(err) + } + }) + } +} + +func (suite *KeeperTestSuite) TestCloseOutProposal() { + // setup test + suite.app.InitializeFromGenesisStates() + // TODO replace below with genesis state + normalCom := types.Committee{ + ID: 12, + Members: suite.addresses[:2], + Permissions: []types.Permission{types.GodPermission{}}, + } + suite.keeper.SetCommittee(suite.ctx, normalCom) + pprop := gov.NewTextProposal("A Title", "A description of this proposal.") + id, err := suite.keeper.SubmitProposal(suite.ctx, normalCom.Members[0], normalCom.ID, pprop) + suite.NoError(err) + err = suite.keeper.AddVote(suite.ctx, id, normalCom.Members[0]) + suite.NoError(err) + err = suite.keeper.AddVote(suite.ctx, id, normalCom.Members[1]) + suite.NoError(err) + + // run test + err = suite.keeper.CloseOutProposal(suite.ctx, id) + + // check + suite.NoError(err) + _, found := suite.keeper.GetProposal(suite.ctx, id) + suite.False(found) + suite.keeper.IterateVotes(suite.ctx, id, func(v types.Vote) bool { + suite.Fail("found vote when none should exist") + return false + }) + +} + +type UnregisteredProposal struct { + gov.TextProposal +} + +func (UnregisteredProposal) ProposalRoute() string { return "unregistered" } +func (UnregisteredProposal) ProposalType() string { return "unregistered" } + +var _ types.PubProposal = UnregisteredProposal{} + +func (suite *KeeperTestSuite) TestValidatePubProposal() { + + testcases := []struct { + name string + pubProposal types.PubProposal + expectPass bool + }{ + { + name: "valid", + pubProposal: gov.NewTextProposal("A Title", "A description of this proposal."), + expectPass: true, + }, + { + name: "invalid (missing title)", + pubProposal: gov.TextProposal{Description: "A description of this proposal."}, + expectPass: false, + }, + { + name: "invalid (unregistered)", + pubProposal: UnregisteredProposal{gov.TextProposal{Title: "A Title", Description: "A description of this proposal."}}, + expectPass: false, + }, + { + name: "invalid (nil)", + pubProposal: nil, + expectPass: false, + }, + // TODO test case when the handler fails + } + + for _, tc := range testcases { + suite.Run(tc.name, func() { + err := suite.keeper.ValidatePubProposal(suite.ctx, tc.pubProposal) if tc.expectPass { suite.NoError(err) - // TODO GetVote } else { suite.NotNil(err) } diff --git a/x/committee/module.go b/x/committee/module.go index fe068a93..6372da75 100644 --- a/x/committee/module.go +++ b/x/committee/module.go @@ -22,7 +22,7 @@ var ( // AppModuleBasic app module basics object type AppModuleBasic struct{} -// Name get module name +// Name gets the module name func (AppModuleBasic) Name() string { return ModuleName } @@ -34,19 +34,17 @@ func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { // DefaultGenesis default genesis state func (AppModuleBasic) DefaultGenesis() json.RawMessage { - //return ModuleCdc.MustMarshalJSON(DefaultGenesisState()) - return nil + return ModuleCdc.MustMarshalJSON(DefaultGenesisState()) } // ValidateGenesis module validate genesis func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error { - // var gs GenesisState - // err := ModuleCdc.UnmarshalJSON(bz, &gs) - // if err != nil { - // return err - // } - // return gs.Validate() - return nil + var gs GenesisState + err := ModuleCdc.UnmarshalJSON(bz, &gs) + if err != nil { + return err + } + return gs.Validate() } // RegisterRESTRoutes registers the REST routes for the module. @@ -137,18 +135,17 @@ func (am AppModule) NewQuerierHandler() sdk.Querier { // InitGenesis module init-genesis func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate { - // var genesisState GenesisState - // ModuleCdc.MustUnmarshalJSON(data, &genesisState) - // InitGenesis(ctx, am.keeper, am.pricefeedKeeper, genesisState) + var genesisState GenesisState + ModuleCdc.MustUnmarshalJSON(data, &genesisState) + InitGenesis(ctx, am.keeper, genesisState) return []abci.ValidatorUpdate{} } // ExportGenesis module export genesis func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage { - // gs := ExportGenesis(ctx, am.keeper) - // return ModuleCdc.MustMarshalJSON(gs) - return nil + gs := ExportGenesis(ctx, am.keeper) + return ModuleCdc.MustMarshalJSON(gs) } // BeginBlock module begin-block diff --git a/x/committee/types/codec.go b/x/committee/types/codec.go index 898769d3..f44d1795 100644 --- a/x/committee/types/codec.go +++ b/x/committee/types/codec.go @@ -1,6 +1,8 @@ package types -import "github.com/cosmos/cosmos-sdk/codec" +import ( + "github.com/cosmos/cosmos-sdk/codec" +) // ModuleCdc generic sealed codec to be used throughout module var ModuleCdc *codec.Codec @@ -13,10 +15,10 @@ func init() { // RegisterCodec registers the necessary types for the module func RegisterCodec(cdc *codec.Codec) { - // TODO - // cdc.RegisterConcrete(MsgCreateCDP{}, "cdp/MsgCreateCDP", nil) - // cdc.RegisterConcrete(MsgDeposit{}, "cdp/MsgDeposit", nil) - // cdc.RegisterConcrete(MsgWithdraw{}, "cdp/MsgWithdraw", nil) - // cdc.RegisterConcrete(MsgDrawDebt{}, "cdp/MsgDrawDebt", nil) - // cdc.RegisterConcrete(MsgRepayDebt{}, "cdp/MsgRepayDebt", nil) + + // TODO need to register Content interface, however amino panics if you try and register it twice and helpfully doesn't provide a way to query registered types + //cdc.RegisterInterface((*gov.Content)(nil), nil) + + cdc.RegisterInterface((*Permission)(nil), nil) + cdc.RegisterConcrete(GodPermission{}, "kava/GodPermission", nil) } diff --git a/x/committee/types/genesis.go b/x/committee/types/genesis.go new file mode 100644 index 00000000..c44b14c6 --- /dev/null +++ b/x/committee/types/genesis.go @@ -0,0 +1,51 @@ +package types + +import ( + "bytes" +) + +// DefaultNextProposalID is the starting poiint for proposal IDs. +const DefaultNextProposalID uint64 = 1 + +// GenesisState is state that must be provided at chain genesis. +type GenesisState struct { + NextProposalID uint64 + Votes []Vote + Proposals []Proposal + Committees []Committee +} + +// NewGenesisState returns a new genesis state object for the module. +func NewGenesisState(nextProposalID uint64, votes []Vote, proposals []Proposal, committees []Committee) GenesisState { + return GenesisState{ + NextProposalID: nextProposalID, + Votes: votes, + Proposals: proposals, + Committees: committees, + } +} + +// DefaultGenesisState returns the default genesis state for the module. +func DefaultGenesisState() GenesisState { + return NewGenesisState( + DefaultNextProposalID, + []Vote{}, + []Proposal{}, + []Committee{}, + ) +} + +// Equal checks whether two gov GenesisState structs are equivalent +func (data GenesisState) Equal(data2 GenesisState) bool { + b1 := ModuleCdc.MustMarshalBinaryBare(data) + b2 := ModuleCdc.MustMarshalBinaryBare(data2) + return bytes.Equal(b1, b2) +} + +// IsEmpty returns true if a GenesisState is empty +func (data GenesisState) IsEmpty() bool { + return data.Equal(GenesisState{}) +} + +// Validate performs basic validation of genesis data. +func (gs GenesisState) Validate() error { return nil } diff --git a/x/committee/types/permissions.go b/x/committee/types/permissions.go index 8338ea1d..068fadc3 100644 --- a/x/committee/types/permissions.go +++ b/x/committee/types/permissions.go @@ -8,6 +8,10 @@ import ( // EXAMPLE PERMISSIONS ------------------------------ +type GodPermission struct{} + +func (GodPermission) Allows(gov.Content) bool { return true } + // Allow only changes to inflation_rate type InflationRateChangePermission struct{} diff --git a/x/committee/types/types.go b/x/committee/types/types.go index 4da5f720..08ff1db3 100644 --- a/x/committee/types/types.go +++ b/x/committee/types/types.go @@ -5,6 +5,8 @@ import ( "github.com/cosmos/cosmos-sdk/x/gov" ) +var VoteThreshold sdk.Dec = sdk.MustNewDecFromStr("0.75") + // A Committee is a collection of addresses that are allowed to vote and enact any governance proposal that passes their permissions. type Committee struct { ID uint64 // TODO or a name? @@ -39,10 +41,12 @@ type Permission interface { // GOV STUFF -------------------------- // Should be much the same as in gov module, except Proposals are linked to a committee ID. -var _ gov.Content = Proposal{} +// TODO not needed? var _ gov.Content = Proposal{} + +type PubProposal = gov.Content // TODO better name type Proposal struct { - gov.Content + PubProposal ID uint64 CommitteeID uint64 // TODO