add mre fields to committee type

This commit is contained in:
rhuairahrighairigh 2020-03-27 18:34:03 +00:00
parent 77553ed299
commit 57f4ca7c9a
11 changed files with 183 additions and 125 deletions

View File

@ -14,6 +14,8 @@ import (
"github.com/kava-labs/kava/x/committee"
)
func d(s string) sdk.Dec { return sdk.MustNewDecFromStr(s) }
type ModuleTestSuite struct {
suite.Suite
@ -38,6 +40,8 @@ func (suite *ModuleTestSuite) TestBeginBlock() {
ID: 12,
Members: suite.addresses[:2],
Permissions: []committee.Permission{committee.GodPermission{}},
VoteThreshold: d("0.8"),
MaxProposalDuration: time.Hour * 24 * 7,
}
suite.keeper.SetCommittee(suite.ctx, normalCom)
@ -51,7 +55,7 @@ func (suite *ModuleTestSuite) TestBeginBlock() {
suite.NoError(err)
// Run BeginBlocker
proposalDurationLaterCtx := suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(committee.MaxProposalDuration))
proposalDurationLaterCtx := suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(normalCom.MaxProposalDuration))
suite.NotPanics(func() {
committee.BeginBlocker(proposalDurationLaterCtx, abci.RequestBeginBlock{}, suite.keeper)
})

View File

@ -14,6 +14,7 @@ const (
DefaultNextProposalID = types.DefaultNextProposalID
DefaultParamspace = types.DefaultParamspace
EventTypeSubmitProposal = types.EventTypeSubmitProposal
MaxDescriptionLength = types.MaxDescriptionLength
ModuleName = types.ModuleName
ProposalTypeCommitteeChange = types.ProposalTypeCommitteeChange
ProposalTypeCommitteeDelete = types.ProposalTypeCommitteeDelete
@ -55,12 +56,10 @@ var (
// variable aliases
ProposalHandler = client.ProposalHandler
CommitteeKeyPrefix = types.CommitteeKeyPrefix
MaxProposalDuration = types.MaxProposalDuration
ModuleCdc = types.ModuleCdc
NextProposalIDKey = types.NextProposalIDKey
ProposalKeyPrefix = types.ProposalKeyPrefix
VoteKeyPrefix = types.VoteKeyPrefix
VoteThreshold = types.VoteThreshold
)
type (

View File

@ -4,6 +4,7 @@ import (
"fmt"
"io/ioutil"
"strconv"
"time"
"github.com/spf13/cobra"
@ -189,8 +190,15 @@ func mustGetExampleCommitteeChangeProposal(cdc *codec.Codec) string {
"A description of this proposal.",
types.NewCommittee(
1,
"The description of this committee.",
[]sdk.AccAddress{sdk.AccAddress(crypto.AddressHash([]byte("exampleAddres")))},
[]types.Permission{}, // TODO permissions
[]types.Permission{
types.ParamChangePermission{
AllowedParams: types.AllowedParams{{Subspace: "cdp", Key: "CircuitBreaker"}},
},
},
sdk.MustNewDecFromStr("0.8"),
time.Hour*24*7,
),
)
exampleChangeProposalBz, err := cdc.MarshalJSONIndent(exampleChangeProposal, "", " ")

View File

@ -2,10 +2,12 @@ package keeper_test
import (
"testing"
"time"
"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"
@ -13,6 +15,8 @@ import (
"github.com/kava-labs/kava/x/committee/types"
)
func d(s string) sdk.Dec { return sdk.MustNewDecFromStr(s) }
type KeeperTestSuite struct {
suite.Suite
@ -34,7 +38,11 @@ func (suite *KeeperTestSuite) TestGetSetDeleteCommittee() {
// setup test
com := types.Committee{
ID: 12,
// TODO other fields
Description: "This committee is for testing.",
Members: suite.addresses,
Permissions: []types.Permission{types.GodPermission{}},
VoteThreshold: d("0.667"),
MaxProposalDuration: time.Hour * 24 * 7,
}
// write and read from store
@ -57,7 +65,9 @@ func (suite *KeeperTestSuite) TestGetSetProposal() {
// test setup
prop := types.Proposal{
ID: 12,
// TODO other fields
CommitteeID: 0,
PubProposal: gov.NewTextProposal("A Title", "A description of this proposal."),
Deadline: time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC),
}
// write and read from store
@ -81,7 +91,6 @@ func (suite *KeeperTestSuite) TestGetSetVote() {
vote := types.Vote{
ProposalID: 12,
Voter: suite.addresses[0],
// TODO other fields
}
// write and read from store

View File

@ -30,7 +30,7 @@ func (k Keeper) SubmitProposal(ctx sdk.Context, proposer sdk.AccAddress, committ
}
// Get a new ID and store the proposal
deadline := ctx.BlockTime().Add(types.MaxProposalDuration)
deadline := ctx.BlockTime().Add(com.MaxProposalDuration)
proposalID, err := k.StoreNewProposal(ctx, pubProposal, committeeID, deadline)
if err != nil {
return 0, err
@ -82,7 +82,7 @@ func (k Keeper) GetProposalResult(ctx sdk.Context, proposalID uint64) (bool, sdk
numVotes := k.TallyVotes(ctx, proposalID)
proposalResult := sdk.NewDec(numVotes).GTE(types.VoteThreshold.MulInt64(int64(len(com.Members))))
proposalResult := sdk.NewDec(numVotes).GTE(com.VoteThreshold.MulInt64(int64(len(com.Members))))
return proposalResult, nil
}

View File

@ -17,14 +17,14 @@ import (
func (suite *KeeperTestSuite) TestSubmitProposal() {
normalCom := types.Committee{
ID: 12,
Description: "This committee is for testing.",
Members: suite.addresses[:2],
Permissions: []types.Permission{types.GodPermission{}},
VoteThreshold: d("0.667"),
MaxProposalDuration: time.Hour * 24 * 7,
}
noPermissionsCom := types.Committee{
ID: 12,
Members: suite.addresses[:2],
Permissions: []types.Permission{},
}
noPermissionsCom := normalCom
noPermissionsCom.Permissions = []types.Permission{}
testcases := []struct {
name string
@ -96,7 +96,7 @@ func (suite *KeeperTestSuite) TestSubmitProposal() {
pr, found := keeper.GetProposal(ctx, id)
suite.True(found)
suite.Equal(tc.committeeID, pr.CommitteeID)
suite.Equal(ctx.BlockTime().Add(types.MaxProposalDuration), pr.Deadline)
suite.Equal(ctx.BlockTime().Add(tc.committee.MaxProposalDuration), pr.Deadline)
} else {
suite.NotNil(err)
}
@ -141,7 +141,7 @@ func (suite *KeeperTestSuite) TestAddVote() {
name: "proposal expired",
proposalID: types.DefaultNextProposalID,
voter: normalCom.Members[0],
voteTime: firstBlockTime.Add(types.MaxProposalDuration),
voteTime: firstBlockTime.Add(normalCom.MaxProposalDuration),
expectPass: false,
},
}
@ -174,6 +174,14 @@ func (suite *KeeperTestSuite) TestAddVote() {
}
func (suite *KeeperTestSuite) TestGetProposalResult() {
normalCom := types.Committee{
ID: 12,
Description: "This committee is for testing.",
Members: suite.addresses[:5],
Permissions: []types.Permission{types.GodPermission{}},
VoteThreshold: d("0.667"),
MaxProposalDuration: time.Hour * 24 * 7,
}
var defaultID uint64 = 1
firstBlockTime := time.Date(1998, time.January, 1, 1, 0, 0, 0, time.UTC)
@ -186,11 +194,7 @@ func (suite *KeeperTestSuite) TestGetProposalResult() {
}{
{
name: "enough votes",
committee: types.Committee{
ID: 12,
Members: suite.addresses[:5],
Permissions: []types.Permission{types.GodPermission{}},
},
committee: normalCom,
votes: []types.Vote{
{ProposalID: defaultID, Voter: suite.addresses[0]},
{ProposalID: defaultID, Voter: suite.addresses[1]},
@ -202,11 +206,7 @@ func (suite *KeeperTestSuite) TestGetProposalResult() {
},
{
name: "not enough votes",
committee: types.Committee{
ID: 12,
Members: suite.addresses[:5],
Permissions: []types.Permission{types.GodPermission{}},
},
committee: normalCom,
votes: []types.Vote{
{ProposalID: defaultID, Voter: suite.addresses[0]},
},

View File

@ -3,6 +3,7 @@ package keeper_test
import (
"strings"
"testing"
"time"
"github.com/stretchr/testify/suite"
@ -12,6 +13,7 @@ import (
abci "github.com/tendermint/tendermint/abci/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/committee"
"github.com/kava-labs/kava/x/committee/keeper"
"github.com/kava-labs/kava/x/committee/types"
)
@ -20,6 +22,12 @@ const (
custom = "custom"
)
var testTime time.Time = time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC)
func NewCommitteeGenesisState(cdc *codec.Codec, gs committee.GenesisState) app.GenesisState {
return app.GenesisState{committee.ModuleName: cdc.MustMarshalJSON(gs)}
}
type QuerierTestSuite struct {
suite.Suite
@ -31,8 +39,7 @@ type QuerierTestSuite struct {
querier sdk.Querier
addresses []sdk.AccAddress
committees []types.Committee
proposals []types.Proposal
testGenesis types.GenesisState
votes map[uint64]([]types.Vote)
}
@ -44,37 +51,40 @@ func (suite *QuerierTestSuite) SetupTest() {
suite.querier = keeper.NewQuerier(suite.keeper)
_, suite.addresses = app.GeneratePrivKeyAddressPairs(5)
suite.app.InitializeFromGenesisStates()
// TODO replace below with genesis state
normalCom := types.Committee{
ID: 12,
Members: suite.addresses[:2],
suite.testGenesis = types.NewGenesisState(
3,
[]types.Committee{
{
ID: 1,
Description: "This committee is for testing.",
Members: suite.addresses[:3],
Permissions: []types.Permission{types.GodPermission{}},
}
suite.keeper.SetCommittee(suite.ctx, normalCom)
VoteThreshold: d("0.667"),
MaxProposalDuration: time.Hour * 24 * 7,
},
{
ID: 2,
Members: suite.addresses[2:],
Permissions: nil,
VoteThreshold: d("0.667"),
MaxProposalDuration: time.Hour * 24 * 7,
},
},
[]types.Proposal{
{ID: 1, CommitteeID: 1, PubProposal: gov.NewTextProposal("A Title", "A description of this proposal."), Deadline: testTime.Add(7 * 24 * time.Hour)},
{ID: 2, CommitteeID: 1, PubProposal: gov.NewTextProposal("Another Title", "A description of this other proposal."), Deadline: testTime.Add(21 * 24 * time.Hour)},
},
[]types.Vote{
{ProposalID: 1, Voter: suite.addresses[0]},
{ProposalID: 1, Voter: suite.addresses[1]},
{ProposalID: 2, Voter: suite.addresses[2]},
},
)
suite.app.InitializeFromGenesisStates(
NewCommitteeGenesisState(suite.cdc, suite.testGenesis),
)
pprop1 := gov.NewTextProposal("1A Title", "A description of this proposal.")
id1, err := suite.keeper.SubmitProposal(suite.ctx, normalCom.Members[0], normalCom.ID, pprop1)
suite.NoError(err)
pprop2 := gov.NewTextProposal("2A Title", "A description of this proposal.")
id2, err := suite.keeper.SubmitProposal(suite.ctx, normalCom.Members[0], normalCom.ID, pprop2)
suite.NoError(err)
err = suite.keeper.AddVote(suite.ctx, id1, normalCom.Members[0])
suite.NoError(err)
err = suite.keeper.AddVote(suite.ctx, id1, normalCom.Members[1])
suite.NoError(err)
err = suite.keeper.AddVote(suite.ctx, id2, normalCom.Members[1])
suite.NoError(err)
suite.committees = []types.Committee{}
suite.committees = []types.Committee{normalCom} // TODO
suite.proposals = []types.Proposal{}
suite.keeper.IterateProposals(suite.ctx, func(p types.Proposal) bool {
suite.proposals = append(suite.proposals, p)
return false
})
// Collect up votes into a map indexed by proposalID for convenience
suite.votes = map[uint64]([]types.Vote){}
suite.keeper.IterateProposals(suite.ctx, func(p types.Proposal) bool {
suite.keeper.IterateVotes(suite.ctx, p.ID, func(v types.Vote) bool {
@ -102,7 +112,7 @@ func (suite *QuerierTestSuite) TestQueryCommittees() {
suite.NoError(suite.cdc.UnmarshalJSON(bz, &committees))
// Check
suite.Equal(suite.committees, committees)
suite.Equal(suite.testGenesis.Committees, committees)
}
func (suite *QuerierTestSuite) TestQueryCommittee() {
@ -110,7 +120,7 @@ func (suite *QuerierTestSuite) TestQueryCommittee() {
// Set up request query
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryCommittee}, "/"),
Data: suite.cdc.MustMarshalJSON(types.NewQueryCommitteeParams(suite.committees[0].ID)),
Data: suite.cdc.MustMarshalJSON(types.NewQueryCommitteeParams(suite.testGenesis.Committees[0].ID)),
}
// Execute query and check the []byte result
@ -123,13 +133,13 @@ func (suite *QuerierTestSuite) TestQueryCommittee() {
suite.NoError(suite.cdc.UnmarshalJSON(bz, &committee))
// Check
suite.Equal(suite.committees[0], committee)
suite.Equal(suite.testGenesis.Committees[0], committee)
}
func (suite *QuerierTestSuite) TestQueryProposals() {
ctx := suite.ctx.WithIsCheckTx(false)
// Set up request query
comID := suite.proposals[0].CommitteeID
comID := suite.testGenesis.Proposals[0].CommitteeID
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryProposals}, "/"),
Data: suite.cdc.MustMarshalJSON(types.NewQueryCommitteeParams(comID)),
@ -146,7 +156,7 @@ func (suite *QuerierTestSuite) TestQueryProposals() {
// Check
expectedProposals := []types.Proposal{}
for _, p := range suite.proposals {
for _, p := range suite.testGenesis.Proposals {
if p.CommitteeID == comID {
expectedProposals = append(expectedProposals, p)
}
@ -159,7 +169,7 @@ func (suite *QuerierTestSuite) TestQueryProposal() {
// Set up request query
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryProposal}, "/"),
Data: suite.cdc.MustMarshalJSON(types.NewQueryProposalParams(suite.proposals[0].ID)),
Data: suite.cdc.MustMarshalJSON(types.NewQueryProposalParams(suite.testGenesis.Proposals[0].ID)),
}
// Execute query and check the []byte result
@ -172,13 +182,13 @@ func (suite *QuerierTestSuite) TestQueryProposal() {
suite.NoError(suite.cdc.UnmarshalJSON(bz, &proposal))
// Check
suite.Equal(suite.proposals[0], proposal)
suite.Equal(suite.testGenesis.Proposals[0], proposal)
}
func (suite *QuerierTestSuite) TestQueryVotes() {
ctx := suite.ctx.WithIsCheckTx(false)
// Set up request query
propID := suite.proposals[0].ID
propID := suite.testGenesis.Proposals[0].ID
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryVotes}, "/"),
Data: suite.cdc.MustMarshalJSON(types.NewQueryProposalParams(propID)),
@ -200,7 +210,7 @@ func (suite *QuerierTestSuite) TestQueryVotes() {
func (suite *QuerierTestSuite) TestQueryVote() {
ctx := suite.ctx.WithIsCheckTx(false) // ?
// Set up request query
propID := suite.proposals[0].ID
propID := suite.testGenesis.Proposals[0].ID
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryVote}, "/"),
Data: suite.cdc.MustMarshalJSON(types.NewQueryVoteParams(propID, suite.votes[propID][0].Voter)),
@ -222,7 +232,7 @@ func (suite *QuerierTestSuite) TestQueryVote() {
func (suite *QuerierTestSuite) TestQueryTally() {
ctx := suite.ctx.WithIsCheckTx(false) // ?
// Set up request query
propID := suite.proposals[0].ID
propID := suite.testGenesis.Proposals[0].ID
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryTally}, "/"),
Data: suite.cdc.MustMarshalJSON(types.NewQueryProposalParams(propID)),

View File

@ -39,13 +39,18 @@ func (suite *ProposalHandlerTestSuite) SetupTest() {
[]committee.Committee{
{
ID: 1,
Description: "This committee is for testing.",
Members: suite.addresses[:3],
Permissions: []types.Permission{types.GodPermission{}},
VoteThreshold: d("0.667"),
MaxProposalDuration: time.Hour * 24 * 7,
},
{
ID: 2,
Members: suite.addresses[2:],
Permissions: nil,
VoteThreshold: d("0.667"),
MaxProposalDuration: time.Hour * 24 * 7,
},
},
[]committee.Proposal{
@ -71,6 +76,8 @@ func (suite *ProposalHandlerTestSuite) TestProposalHandler_ChangeCommittee() {
committee.Committee{
ID: 34,
Members: suite.addresses[:1],
VoteThreshold: d("1"),
MaxProposalDuration: time.Hour * 24,
},
),
expectPass: true,
@ -81,9 +88,11 @@ func (suite *ProposalHandlerTestSuite) TestProposalHandler_ChangeCommittee() {
"A Title",
"A proposal description.",
committee.Committee{
ID: 1,
Members: suite.addresses,
ID: suite.testGenesis.Committees[0].ID,
Members: suite.addresses, // add new members
Permissions: suite.testGenesis.Committees[0].Permissions,
VoteThreshold: suite.testGenesis.Committees[0].VoteThreshold,
MaxProposalDuration: suite.testGenesis.Committees[0].MaxProposalDuration,
},
),
expectPass: true,
@ -93,9 +102,7 @@ func (suite *ProposalHandlerTestSuite) TestProposalHandler_ChangeCommittee() {
proposal: committee.NewCommitteeChangeProposal(
"A Title That Is Much Too Long And Really Quite Unreasonable Given That It Is Trying To Fullfill The Roll Of An Acceptable Governance Proposal Title That Should Succinctly Communicate The Goal And Contents Of The Proposed Proposal To All Parties Involved",
"A proposal description.",
committee.Committee{
ID: 34,
},
suite.testGenesis.Committees[0],
),
expectPass: false,
},
@ -105,9 +112,11 @@ func (suite *ProposalHandlerTestSuite) TestProposalHandler_ChangeCommittee() {
"A Title",
"A proposal description.",
committee.Committee{
ID: 1,
ID: suite.testGenesis.Committees[0].ID,
Members: append(suite.addresses, suite.addresses[0]), // duplicate address
Permissions: suite.testGenesis.Committees[0].Permissions,
VoteThreshold: suite.testGenesis.Committees[0].VoteThreshold,
MaxProposalDuration: suite.testGenesis.Committees[0].MaxProposalDuration,
},
),
expectPass: false,

View File

@ -11,6 +11,7 @@ import (
"github.com/tendermint/tendermint/crypto"
)
func d(s string) sdk.Dec { return sdk.MustNewDecFromStr(s) }
func TestGenesisState_Validate(t *testing.T) {
testTime := time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC)
addresses := []sdk.AccAddress{
@ -25,13 +26,19 @@ func TestGenesisState_Validate(t *testing.T) {
Committees: []Committee{
{
ID: 1,
Description: "This committee is for testing.",
Members: addresses[:3],
Permissions: []Permission{GodPermission{}},
VoteThreshold: d("0.667"),
MaxProposalDuration: time.Hour * 24 * 7,
},
{
ID: 2,
Description: "This committee is also for testing.",
Members: addresses[2:],
Permissions: nil,
VoteThreshold: d("0.8"),
MaxProposalDuration: time.Hour * 24 * 21,
},
},
Proposals: []Proposal{

View File

@ -16,6 +16,7 @@ func init() {
}
// GodPermission allows any governance proposal. It is used mainly for testing.
// TODO better name?
type GodPermission struct{}
var _ Permission = GodPermission{}

View File

@ -9,29 +9,28 @@ import (
"github.com/cosmos/cosmos-sdk/x/gov"
)
// TODO move these into params
var (
VoteThreshold sdk.Dec = sdk.MustNewDecFromStr("0.75")
MaxProposalDuration time.Duration = time.Hour * 24 * 7
)
const MaxCommitteeDescriptionLength int = 5000
// -------- Committees --------
// 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 `json:"id" yaml:"id"`
//Description string `json:"description" yaml:"description"`
Description string `json:"description" yaml:"description"`
Members []sdk.AccAddress `json:"members" yaml:"members"`
Permissions []Permission `json:"permissions" yaml:"permissions"`
// VoteThreshold sdk.Dec `json:"vote_threshold" yaml:"vote_threshold"`
// MaxProposalDuration time.Duration `json:"max_proposal_duration" yaml:"max_proposal_duration"`
VoteThreshold sdk.Dec `json:"vote_threshold" yaml:"vote_threshold"`
MaxProposalDuration time.Duration `json:"max_proposal_duration" yaml:"max_proposal_duration"`
}
func NewCommittee(id uint64, members []sdk.AccAddress, permissions []Permission) Committee {
func NewCommittee(id uint64, description string, members []sdk.AccAddress, permissions []Permission, threshold sdk.Dec, duration time.Duration) Committee {
return Committee{
ID: id,
Description: description,
Members: members,
Permissions: permissions,
VoteThreshold: threshold,
MaxProposalDuration: duration,
}
}
@ -74,6 +73,19 @@ func (c Committee) Validate() error {
if len(c.Members) == 0 {
return fmt.Errorf("committee %d invalid: cannot have zero members", c.ID)
}
if len(c.Description) > MaxCommitteeDescriptionLength {
return fmt.Errorf("invalid description")
}
if c.VoteThreshold.IsNil() || c.VoteThreshold.IsNegative() || c.VoteThreshold.GT(sdk.NewDec(1)) {
return fmt.Errorf("invalid threshold")
}
if c.MaxProposalDuration < 0 {
return fmt.Errorf("invalid time")
}
return nil
}
@ -118,5 +130,4 @@ func (p Proposal) String() string {
type Vote struct {
ProposalID uint64 `json:"proposal_id" yaml:"proposal_id"`
Voter sdk.AccAddress `json:"voter" yaml:"voter"`
// Option byte // TODO for now don't need more than just a yes as options
}