mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-25 15:55:18 +00:00
improve proposal tests
This commit is contained in:
parent
ebb6366837
commit
196ecf7f30
@ -7,6 +7,12 @@ import (
|
||||
"github.com/kava-labs/kava/x/committee/types"
|
||||
)
|
||||
|
||||
// Avoid cluttering test cases with long function names
|
||||
func i(in int64) sdk.Int { return sdk.NewInt(in) }
|
||||
func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) }
|
||||
func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) }
|
||||
func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) }
|
||||
|
||||
// proposalVoteMap collects up votes into a map indexed by proposalID
|
||||
func getProposalVoteMap(k keeper.Keeper, ctx sdk.Context) map[uint64]([]types.Vote) {
|
||||
|
||||
|
@ -15,8 +15,6 @@ import (
|
||||
"github.com/kava-labs/kava/x/committee/types"
|
||||
)
|
||||
|
||||
func d(s string) sdk.Dec { return sdk.MustNewDecFromStr(s) }
|
||||
|
||||
type KeeperTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
|
@ -78,6 +78,7 @@ func (k Keeper) AddVote(ctx sdk.Context, proposalID uint64, voter sdk.AccAddress
|
||||
}
|
||||
|
||||
// GetProposalResult calculates if a proposal currently has enough votes to pass.
|
||||
// TODO rename GetProposalTally?
|
||||
func (k Keeper) GetProposalResult(ctx sdk.Context, proposalID uint64) (bool, sdk.Error) {
|
||||
pr, found := k.GetProposal(ctx, proposalID)
|
||||
if !found {
|
||||
@ -114,18 +115,19 @@ func (k Keeper) EnactProposal(ctx sdk.Context, proposalID uint64) sdk.Error {
|
||||
return types.ErrUnknownProposal(k.codespace, proposalID)
|
||||
}
|
||||
|
||||
// Run the proposal's changes through the associated handler, but using a cached version of state to ensure changes are not permanent if an error occurs.
|
||||
handler := k.router.GetRoute(pr.ProposalRoute())
|
||||
cacheCtx, writeCache := ctx.CacheContext()
|
||||
if err := handler(cacheCtx, pr.PubProposal); err != nil {
|
||||
if err := k.ValidatePubProposal(ctx, pr.PubProposal); err != nil {
|
||||
return err
|
||||
}
|
||||
// write state to the underlying multi-store
|
||||
writeCache()
|
||||
handler := k.router.GetRoute(pr.ProposalRoute())
|
||||
if err := handler(ctx, pr.PubProposal); err != nil {
|
||||
// the handler should not error as it was checked in ValidatePubProposal
|
||||
panic(fmt.Sprintf("unexpected handler error: %s", err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseExpiredProposals removes proposals (and associated votes) that have past their deadline.
|
||||
// TODO rename to RemoveExpiredProposals?
|
||||
func (k Keeper) CloseExpiredProposals(ctx sdk.Context) {
|
||||
|
||||
k.IterateProposals(ctx, func(proposal types.Proposal) bool {
|
||||
@ -147,7 +149,7 @@ func (k Keeper) CloseExpiredProposals(ctx sdk.Context) {
|
||||
}
|
||||
|
||||
// ValidatePubProposal checks if a pubproposal is valid.
|
||||
func (k Keeper) ValidatePubProposal(ctx sdk.Context, pubProposal types.PubProposal) sdk.Error {
|
||||
func (k Keeper) ValidatePubProposal(ctx sdk.Context, pubProposal types.PubProposal) (returnErr sdk.Error) {
|
||||
if pubProposal == nil {
|
||||
return types.ErrInvalidPubProposal(k.codespace, "pub proposal cannot be nil")
|
||||
}
|
||||
@ -162,6 +164,17 @@ func (k Keeper) ValidatePubProposal(ctx sdk.Context, pubProposal types.PubPropos
|
||||
// Run the proposal's changes through the associated handler using a cached version of state to ensure changes are not permanent.
|
||||
cacheCtx, _ := ctx.CacheContext()
|
||||
handler := k.router.GetRoute(pubProposal.ProposalRoute())
|
||||
|
||||
// Handle an edge case where a param change proposal causes the proposal handler to panic.
|
||||
// A param change proposal with a registered subspace value but unregistered key value will cause a panic in the param change proposal handler.
|
||||
// This defer will catch panics and return a normal error: `recover()` gets the panic value, then the enclosing function's return value is swapped for an error.
|
||||
// reference: https://stackoverflow.com/questions/33167282/how-to-return-a-value-in-a-go-function-that-panics?noredirect=1&lq=1
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
returnErr = types.ErrInvalidPubProposal(k.codespace, fmt.Sprintf("proposal handler panicked: %s", r))
|
||||
}
|
||||
}()
|
||||
|
||||
if err := handler(cacheCtx, pubProposal); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -169,6 +182,7 @@ func (k Keeper) ValidatePubProposal(ctx sdk.Context, pubProposal types.PubPropos
|
||||
}
|
||||
|
||||
// DeleteProposalAndVotes removes a proposal and its associated votes.
|
||||
// TODO move to keeper.go
|
||||
func (k Keeper) DeleteProposalAndVotes(ctx sdk.Context, proposalID uint64) {
|
||||
var votes []types.Vote
|
||||
k.IterateVotes(ctx, proposalID, func(vote types.Vote) bool {
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||
"github.com/kava-labs/kava/x/committee"
|
||||
"github.com/kava-labs/kava/x/committee/types"
|
||||
)
|
||||
@ -33,7 +34,7 @@ func (suite *KeeperTestSuite) TestSubmitProposal() {
|
||||
pubProposal types.PubProposal
|
||||
proposer sdk.AccAddress
|
||||
committeeID uint64
|
||||
expectPass bool
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "normal",
|
||||
@ -41,7 +42,7 @@ func (suite *KeeperTestSuite) TestSubmitProposal() {
|
||||
pubProposal: gov.NewTextProposal("A Title", "A description of this proposal."),
|
||||
proposer: normalCom.Members[0],
|
||||
committeeID: normalCom.ID,
|
||||
expectPass: true,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid proposal",
|
||||
@ -49,7 +50,7 @@ func (suite *KeeperTestSuite) TestSubmitProposal() {
|
||||
pubProposal: nil,
|
||||
proposer: normalCom.Members[0],
|
||||
committeeID: normalCom.ID,
|
||||
expectPass: false,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing committee",
|
||||
@ -57,7 +58,7 @@ func (suite *KeeperTestSuite) TestSubmitProposal() {
|
||||
pubProposal: gov.NewTextProposal("A Title", "A description of this proposal."),
|
||||
proposer: suite.addresses[0],
|
||||
committeeID: 0,
|
||||
expectPass: false,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "not a member",
|
||||
@ -65,7 +66,7 @@ func (suite *KeeperTestSuite) TestSubmitProposal() {
|
||||
pubProposal: gov.NewTextProposal("A Title", "A description of this proposal."),
|
||||
proposer: suite.addresses[4],
|
||||
committeeID: normalCom.ID,
|
||||
expectPass: false,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "not enough permissions",
|
||||
@ -73,7 +74,7 @@ func (suite *KeeperTestSuite) TestSubmitProposal() {
|
||||
pubProposal: gov.NewTextProposal("A Title", "A description of this proposal."),
|
||||
proposer: noPermissionsCom.Members[0],
|
||||
committeeID: noPermissionsCom.ID,
|
||||
expectPass: false,
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
@ -91,14 +92,14 @@ func (suite *KeeperTestSuite) TestSubmitProposal() {
|
||||
|
||||
id, err := keeper.SubmitProposal(ctx, tc.proposer, tc.committeeID, tc.pubProposal)
|
||||
|
||||
if tc.expectPass {
|
||||
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.ProposalDuration), pr.Deadline)
|
||||
} else {
|
||||
suite.NotNil(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -117,32 +118,32 @@ func (suite *KeeperTestSuite) TestAddVote() {
|
||||
proposalID uint64
|
||||
voter sdk.AccAddress
|
||||
voteTime time.Time
|
||||
expectPass bool
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "normal",
|
||||
proposalID: types.DefaultNextProposalID,
|
||||
voter: normalCom.Members[0],
|
||||
expectPass: true,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "nonexistent proposal",
|
||||
proposalID: 9999999,
|
||||
voter: normalCom.Members[0],
|
||||
expectPass: false,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "voter not committee member",
|
||||
proposalID: types.DefaultNextProposalID,
|
||||
voter: suite.addresses[4],
|
||||
expectPass: false,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "proposal expired",
|
||||
proposalID: types.DefaultNextProposalID,
|
||||
voter: normalCom.Members[0],
|
||||
voteTime: firstBlockTime.Add(normalCom.ProposalDuration),
|
||||
expectPass: false,
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
@ -162,12 +163,12 @@ func (suite *KeeperTestSuite) TestAddVote() {
|
||||
ctx = ctx.WithBlockTime(tc.voteTime)
|
||||
err = keeper.AddVote(ctx, tc.proposalID, tc.voter)
|
||||
|
||||
if tc.expectPass {
|
||||
if tc.expectErr {
|
||||
suite.NotNil(err)
|
||||
} else {
|
||||
suite.NoError(err)
|
||||
_, found := keeper.GetVote(ctx, tc.proposalID, tc.voter)
|
||||
suite.True(found)
|
||||
} else {
|
||||
suite.NotNil(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -190,7 +191,7 @@ func (suite *KeeperTestSuite) TestGetProposalResult() {
|
||||
committee types.Committee
|
||||
votes []types.Vote
|
||||
proposalPasses bool
|
||||
expectPass bool
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "enough votes",
|
||||
@ -202,7 +203,7 @@ func (suite *KeeperTestSuite) TestGetProposalResult() {
|
||||
{ProposalID: defaultID, Voter: suite.addresses[3]},
|
||||
},
|
||||
proposalPasses: true,
|
||||
expectPass: true,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "not enough votes",
|
||||
@ -211,7 +212,7 @@ func (suite *KeeperTestSuite) TestGetProposalResult() {
|
||||
{ProposalID: defaultID, Voter: suite.addresses[0]},
|
||||
},
|
||||
proposalPasses: false,
|
||||
expectPass: true,
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
@ -238,11 +239,11 @@ func (suite *KeeperTestSuite) TestGetProposalResult() {
|
||||
|
||||
proposalPasses, err := keeper.GetProposalResult(ctx, defaultID)
|
||||
|
||||
if tc.expectPass {
|
||||
if tc.expectErr {
|
||||
suite.NotNil(err)
|
||||
} else {
|
||||
suite.NoError(err)
|
||||
suite.Equal(tc.proposalPasses, proposalPasses)
|
||||
} else {
|
||||
suite.NotNil(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -272,27 +273,40 @@ func (suite *KeeperTestSuite) TestValidatePubProposal() {
|
||||
testcases := []struct {
|
||||
name string
|
||||
pubProposal types.PubProposal
|
||||
expectPass bool
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
name: "valid (text proposal)",
|
||||
pubProposal: gov.NewTextProposal("A Title", "A description of this proposal."),
|
||||
expectPass: true,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid (param change proposal)",
|
||||
pubProposal: params.NewParameterChangeProposal(
|
||||
"Change the debt limit",
|
||||
"This proposal changes the debt limit of the cdp module.",
|
||||
[]params.ParamChange{{
|
||||
Subspace: cdptypes.ModuleName,
|
||||
Key: string(cdptypes.KeyGlobalDebtLimit),
|
||||
Value: string(types.ModuleCdc.MustMarshalJSON(cs(c("usdx", 100000000000)))),
|
||||
}},
|
||||
),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid (missing title)",
|
||||
pubProposal: gov.TextProposal{Description: "A description of this proposal."},
|
||||
expectPass: false,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid (unregistered)",
|
||||
pubProposal: UnregisteredProposal{gov.TextProposal{Title: "A Title", Description: "A description of this proposal."}},
|
||||
expectPass: false,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid (nil)",
|
||||
pubProposal: nil,
|
||||
expectPass: false,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid (proposal handler fails)",
|
||||
@ -300,24 +314,35 @@ func (suite *KeeperTestSuite) TestValidatePubProposal() {
|
||||
"A Title",
|
||||
"A description of this proposal.",
|
||||
[]params.ParamChange{{
|
||||
Subspace: "non existant",
|
||||
Key: "non existant",
|
||||
Value: "nonsense",
|
||||
Subspace: "nonsense-subspace",
|
||||
Key: "nonsense-key",
|
||||
Value: "nonsense-value",
|
||||
}},
|
||||
),
|
||||
expectPass: false,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid (proposal handler panics)",
|
||||
pubProposal: params.NewParameterChangeProposal(
|
||||
"A Title",
|
||||
"A description of this proposal.",
|
||||
[]params.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,
|
||||
},
|
||||
// Some proposals can cause the proposal handler to panic.
|
||||
// However panics will be caught when the proposal is first submitted so should avoid making it onto the chain.
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
suite.Run(tc.name, func() {
|
||||
err := suite.keeper.ValidatePubProposal(suite.ctx, tc.pubProposal)
|
||||
if tc.expectPass {
|
||||
suite.NoError(err)
|
||||
} else {
|
||||
if tc.expectErr {
|
||||
suite.NotNil(err)
|
||||
} else {
|
||||
suite.NoError(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user