fix(group)!: Fix group min execution period (#13876)

* fix: don't check MinExecutionPeriod in `Allow`

* Check MinExecutionPeriod on doExecuteMsgs

* Fix TestExec

* Fix TestExec

* test exec pruned

* Fix submitproposal

* Add changelog

* typo

* revert some changes

* add minExecutionPeriod

* Add docs and specs
This commit is contained in:
Amaury 2022-11-16 13:37:06 +01:00 committed by GitHub
parent 3423442ab1
commit 7661f62737
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 179 additions and 85 deletions

View File

@ -114,6 +114,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (x/gov) [#12771](https://github.com/cosmos/cosmos-sdk/pull/12771) Initial deposit requirement for proposals at submission time.
* (x/staking) [#12967](https://github.com/cosmos/cosmos-sdk/pull/12967) `unbond` now creates only one unbonding delegation entry when multiple unbondings exist at a single height (e.g. through multiple messages in a transaction).
* (x/auth/vesting) [#13502](https://github.com/cosmos/cosmos-sdk/pull/13502) Add Amino Msg registration for `MsgCreatePeriodicVestingAccount`.
* (x/group) [#13876](https://github.com/cosmos/cosmos-sdk/pull/13876) Fix group MinExecutionPeriod that is checked on execution now, instead of voting period end.
### API Breaking Changes
@ -168,6 +169,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [#13794](https://github.com/cosmos/cosmos-sdk/pull/13794) Most methods on `types/module.AppModule` have been moved to
extension interfaces. `module.Manager.Modules` is now of type `map[string]interface{}` to support in parallel the new
`cosmossdk.io/core/appmodule.AppModule` API.
* (x/group) [#13876](https://github.com/cosmos/cosmos-sdk/pull/13876) Add `GetMinExecutionPeriod` method on DecisionPolicy interface.
### CLI Breaking Changes

View File

@ -109,6 +109,16 @@ A threshold decision policy defines a threshold of yes votes (based on a tally
of voter weights) that must be achieved in order for a proposal to pass. For
this decision policy, abstain and veto are simply treated as no's.
This decision policy also has a VotingPeriod window and a MinExecutionPeriod
window. The former defines the duration after proposal submission where members
are allowed to vote, after which tallying is performed. The latter specifies
the minimum duration after proposal submission where the proposal can be
executed. If set to 0, then the proposal is allowed to be executed immediately
on submission (using the `TRY_EXEC` option). Obviously, MinExecutionPeriod
cannot be greater than VotingPeriod+MaxExecutionPeriod (where MaxExecution is
the app-defined duration that specifies the window after voting ended where a
proposal can be executed).
#### Percentage decision policy
A percentage decision policy is similar to a threshold decision policy, except
@ -117,6 +127,9 @@ It's more suited for groups where the group members' weights can be updated, as
the percentage threshold stays the same, and doesn't depend on how those member
weights get updated.
Same as the Threshold decision policy, the percentage decision policy has the
two VotingPeriod and MinExecutionPeriod parameters.
### Proposal
Any member(s) of a group can submit a proposal for a group policy account to decide upon.

View File

@ -33,6 +33,8 @@ import (
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
)
const minExecutionPeriod = 5 * time.Second
type TestSuite struct {
suite.Suite
@ -98,7 +100,7 @@ func (s *TestSuite) SetupTest() {
policy := group.NewThresholdDecisionPolicy(
"2",
time.Second,
0,
minExecutionPeriod, // Must wait 5 seconds before executing proposal
)
policyReq := &group.MsgCreateGroupPolicy{
Admin: s.addrs[0].String(),
@ -1551,36 +1553,48 @@ func (s *TestSuite) TestGroupPoliciesByAdminOrGroup() {
func (s *TestSuite) TestSubmitProposal() {
addrs := s.addrs
addr1 := addrs[0]
addr2 := addrs[1]
addr2 := addrs[1] // Has weight 2
addr4 := addrs[3]
addr5 := addrs[4]
addr5 := addrs[4] // Has weight 1
myGroupID := s.groupID
accountAddr := s.groupPolicyAddr
msgSend := &banktypes.MsgSend{
FromAddress: s.groupPolicyAddr.String(),
ToAddress: addr2.String(),
Amount: sdk.Coins{sdk.NewInt64Coin("test", 100)},
}
// Create a new group policy to test TRY_EXEC
policyReq := &group.MsgCreateGroupPolicy{
Admin: addr1.String(),
GroupId: myGroupID,
}
policy := group.NewThresholdDecisionPolicy(
noMinExecPeriodPolicy := group.NewThresholdDecisionPolicy(
"2",
time.Second,
0, // no MinExecutionPeriod to test TRY_EXEC
)
err := policyReq.SetDecisionPolicy(noMinExecPeriodPolicy)
s.Require().NoError(err)
s.setNextAccount()
res, err := s.groupKeeper.CreateGroupPolicy(s.ctx, policyReq)
s.Require().NoError(err)
noMinExecPeriodPolicyAddr := sdk.MustAccAddressFromBech32(res.Address)
// Create a new group policy with super high threshold
bigThresholdPolicy := group.NewThresholdDecisionPolicy(
"100",
time.Second,
0,
minExecutionPeriod,
)
err := policyReq.SetDecisionPolicy(policy)
s.Require().NoError(err)
s.setNextAccount()
err = policyReq.SetDecisionPolicy(bigThresholdPolicy)
s.Require().NoError(err)
bigThresholdRes, err := s.groupKeeper.CreateGroupPolicy(s.ctx, policyReq)
s.Require().NoError(err)
bigThresholdAddr := bigThresholdRes.Address
msgSend := &banktypes.MsgSend{
FromAddress: noMinExecPeriodPolicyAddr.String(),
ToAddress: addr2.String(),
Amount: sdk.Coins{sdk.NewInt64Coin("test", 100)},
}
defaultProposal := group.Proposal{
GroupPolicyAddress: accountAddr.String(),
Status: group.PROPOSAL_STATUS_SUBMITTED,
@ -1699,13 +1713,13 @@ func (s *TestSuite) TestSubmitProposal() {
}
},
req: &group.MsgSubmitProposal{
GroupPolicyAddress: accountAddr.String(),
GroupPolicyAddress: noMinExecPeriodPolicyAddr.String(),
Proposers: []string{addr2.String()},
Exec: group.Exec_EXEC_TRY,
},
msgs: []sdk.Msg{msgSend},
expProposal: group.Proposal{
GroupPolicyAddress: accountAddr.String(),
GroupPolicyAddress: noMinExecPeriodPolicyAddr.String(),
Status: group.PROPOSAL_STATUS_ACCEPTED,
FinalTallyResult: group.TallyResult{
YesCount: "2",
@ -1716,10 +1730,10 @@ func (s *TestSuite) TestSubmitProposal() {
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
},
postRun: func(sdkCtx sdk.Context) {
s.bankKeeper.EXPECT().GetAllBalances(sdkCtx, accountAddr).Return(sdk.NewCoins(sdk.NewInt64Coin("test", 9900)))
s.bankKeeper.EXPECT().GetAllBalances(sdkCtx, noMinExecPeriodPolicyAddr).Return(sdk.NewCoins(sdk.NewInt64Coin("test", 9900)))
s.bankKeeper.EXPECT().GetAllBalances(sdkCtx, addr2).Return(sdk.NewCoins(sdk.NewInt64Coin("test", 100)))
fromBalances := s.bankKeeper.GetAllBalances(sdkCtx, accountAddr)
fromBalances := s.bankKeeper.GetAllBalances(sdkCtx, noMinExecPeriodPolicyAddr)
s.Require().Contains(fromBalances, sdk.NewInt64Coin("test", 9900))
toBalances := s.bankKeeper.GetAllBalances(sdkCtx, addr2)
s.Require().Contains(toBalances, sdk.NewInt64Coin("test", 100))
@ -1727,13 +1741,13 @@ func (s *TestSuite) TestSubmitProposal() {
},
"with try exec, not enough yes votes for proposal to pass": {
req: &group.MsgSubmitProposal{
GroupPolicyAddress: accountAddr.String(),
GroupPolicyAddress: noMinExecPeriodPolicyAddr.String(),
Proposers: []string{addr5.String()},
Exec: group.Exec_EXEC_TRY,
},
msgs: []sdk.Msg{msgSend},
expProposal: group.Proposal{
GroupPolicyAddress: accountAddr.String(),
GroupPolicyAddress: noMinExecPeriodPolicyAddr.String(),
Status: group.PROPOSAL_STATUS_SUBMITTED,
FinalTallyResult: group.TallyResult{
YesCount: "0", // Since tally doesn't pass Allow(), we consider the proposal not final
@ -2399,6 +2413,7 @@ func (s *TestSuite) TestExecProposal() {
msgs := []sdk.Msg{msgSend1}
return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_YES)
},
srcBlockTime: s.blockTime.Add(minExecutionPeriod), // After min execution period end
expProposalStatus: group.PROPOSAL_STATUS_ACCEPTED,
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
expBalance: true,
@ -2412,6 +2427,7 @@ func (s *TestSuite) TestExecProposal() {
return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_YES)
},
srcBlockTime: s.blockTime.Add(minExecutionPeriod), // After min execution period end
expProposalStatus: group.PROPOSAL_STATUS_ACCEPTED,
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
expBalance: true,
@ -2423,6 +2439,7 @@ func (s *TestSuite) TestExecProposal() {
msgs := []sdk.Msg{msgSend1}
return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_NO)
},
srcBlockTime: s.blockTime.Add(minExecutionPeriod), // After min execution period end
expProposalStatus: group.PROPOSAL_STATUS_REJECTED,
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
@ -2439,34 +2456,59 @@ func (s *TestSuite) TestExecProposal() {
},
expErr: true,
},
"Decision policy also applied on timeout": {
"Decision policy also applied on exactly voting period end": {
setupProposal: func(ctx context.Context) uint64 {
msgs := []sdk.Msg{msgSend1}
return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_NO)
},
srcBlockTime: s.blockTime.Add(time.Second),
srcBlockTime: s.blockTime.Add(time.Second), // Voting period is 1s
expProposalStatus: group.PROPOSAL_STATUS_REJECTED,
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
"Decision policy also applied after timeout": {
"Decision policy also applied after voting period end": {
setupProposal: func(ctx context.Context) uint64 {
msgs := []sdk.Msg{msgSend1}
return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_NO)
},
srcBlockTime: s.blockTime.Add(time.Second).Add(time.Millisecond),
srcBlockTime: s.blockTime.Add(time.Second).Add(time.Millisecond), // Voting period is 1s
expProposalStatus: group.PROPOSAL_STATUS_REJECTED,
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
"exec proposal before MinExecutionPeriod should fail": {
setupProposal: func(ctx context.Context) uint64 {
msgs := []sdk.Msg{msgSend1}
return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_YES)
},
srcBlockTime: s.blockTime.Add(4 * time.Second), // min execution date is 5s later after s.blockTime
expProposalStatus: group.PROPOSAL_STATUS_ACCEPTED,
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_FAILURE, // Because MinExecutionPeriod has not passed
},
"exec proposal at exactly MinExecutionPeriod should pass": {
setupProposal: func(ctx context.Context) uint64 {
msgs := []sdk.Msg{msgSend1}
s.bankKeeper.EXPECT().Send(gomock.Any(), msgSend1).Return(nil, nil)
return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_YES)
},
srcBlockTime: s.blockTime.Add(5 * time.Second), // min execution date is 5s later after s.blockTime
expProposalStatus: group.PROPOSAL_STATUS_ACCEPTED,
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
},
"prevent double execution when successful": {
setupProposal: func(ctx context.Context) uint64 {
myProposalID := submitProposalAndVote(ctx, s, []sdk.Msg{msgSend1}, proposers, group.VOTE_OPTION_YES)
s.bankKeeper.EXPECT().Send(gomock.Any(), msgSend1).Return(nil, nil)
// Wait after min execution period end before Exec
sdkCtx := sdk.UnwrapSDKContext(ctx)
sdkCtx = sdkCtx.WithBlockTime(sdkCtx.BlockTime().Add(minExecutionPeriod)) // MinExecutionPeriod is 5s
ctx = sdk.WrapSDKContext(sdkCtx)
_, err := s.groupKeeper.Exec(ctx, &group.MsgExec{Executor: addr1.String(), ProposalId: myProposalID})
s.Require().NoError(err)
return myProposalID
},
expErr: true, // since proposal is pruned after a successful MsgExec
srcBlockTime: s.blockTime.Add(minExecutionPeriod), // After min execution period end
expErr: true, // since proposal is pruned after a successful MsgExec
expProposalStatus: group.PROPOSAL_STATUS_ACCEPTED,
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
expBalance: true,
@ -2481,6 +2523,7 @@ func (s *TestSuite) TestExecProposal() {
return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_YES)
},
srcBlockTime: s.blockTime.Add(minExecutionPeriod), // After min execution period end
expProposalStatus: group.PROPOSAL_STATUS_ACCEPTED,
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_FAILURE,
},
@ -2489,6 +2532,11 @@ func (s *TestSuite) TestExecProposal() {
msgs := []sdk.Msg{msgSend2}
myProposalID := submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_YES)
// Wait after min execution period end before Exec
sdkCtx := sdk.UnwrapSDKContext(ctx)
sdkCtx = sdkCtx.WithBlockTime(sdkCtx.BlockTime().Add(minExecutionPeriod)) // MinExecutionPeriod is 5s
ctx = sdk.WrapSDKContext(sdkCtx)
s.bankKeeper.EXPECT().Send(gomock.Any(), msgSend2).Return(nil, fmt.Errorf("error"))
_, err := s.groupKeeper.Exec(ctx, &group.MsgExec{Executor: addr1.String(), ProposalId: myProposalID})
s.bankKeeper.EXPECT().Send(gomock.Any(), msgSend2).Return(nil, nil)
@ -2498,6 +2546,7 @@ func (s *TestSuite) TestExecProposal() {
return myProposalID
},
srcBlockTime: s.blockTime.Add(minExecutionPeriod), // After min execution period end
expProposalStatus: group.PROPOSAL_STATUS_ACCEPTED,
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
},
@ -2689,6 +2738,11 @@ func (s *TestSuite) TestExecPrunedProposalsAndVotes() {
s.bankKeeper.EXPECT().Send(gomock.Any(), msgSend2).Return(nil, fmt.Errorf("error"))
// Wait for min execution period end
sdkCtx := sdk.UnwrapSDKContext(ctx)
sdkCtx = sdkCtx.WithBlockTime(sdkCtx.BlockTime().Add(minExecutionPeriod))
ctx = sdk.WrapSDKContext(sdkCtx)
_, err := s.groupKeeper.Exec(ctx, &group.MsgExec{Executor: addr1.String(), ProposalId: myProposalID})
s.bankKeeper.EXPECT().Send(gomock.Any(), msgSend2).Return(nil, nil)
@ -2710,6 +2764,8 @@ func (s *TestSuite) TestExecPrunedProposalsAndVotes() {
sdkCtx = sdkCtx.WithBlockTime(spec.srcBlockTime)
}
// Wait for min execution period end
sdkCtx = sdkCtx.WithBlockTime(sdkCtx.BlockTime().Add(minExecutionPeriod))
ctx = sdk.WrapSDKContext(sdkCtx)
_, err := s.groupKeeper.Exec(ctx, &group.MsgExec{Executor: addr1.String(), ProposalId: proposalID})
if spec.expErr {
@ -3173,3 +3229,59 @@ func (s *TestSuite) createGroupAndGroupPolicy(
return policyAddr, groupID
}
func (s *TestSuite) TestTallyProposalsAtVPEnd() {
addrs := s.addrs
addr1 := addrs[0]
addr2 := addrs[1]
votingPeriod := time.Duration(4 * time.Minute)
minExecutionPeriod := votingPeriod + group.DefaultConfig().MaxExecutionPeriod
groupMsg := &group.MsgCreateGroupWithPolicy{
Admin: addr1.String(),
Members: []group.MemberRequest{
{Address: addr1.String(), Weight: "1"},
{Address: addr2.String(), Weight: "1"},
},
}
policy := group.NewThresholdDecisionPolicy(
"1",
votingPeriod,
minExecutionPeriod,
)
s.Require().NoError(groupMsg.SetDecisionPolicy(policy))
s.setNextAccount()
groupRes, err := s.groupKeeper.CreateGroupWithPolicy(s.ctx, groupMsg)
s.Require().NoError(err)
accountAddr := groupRes.GetGroupPolicyAddress()
groupPolicy, err := sdk.AccAddressFromBech32(accountAddr)
s.Require().NoError(err)
s.Require().NotNil(groupPolicy)
proposalRes, err := s.groupKeeper.SubmitProposal(s.ctx, &group.MsgSubmitProposal{
GroupPolicyAddress: accountAddr,
Proposers: []string{addr1.String()},
Messages: nil,
})
s.Require().NoError(err)
_, err = s.groupKeeper.Vote(s.ctx, &group.MsgVote{
ProposalId: proposalRes.ProposalId,
Voter: addr1.String(),
Option: group.VOTE_OPTION_YES,
})
s.Require().NoError(err)
// move forward in time
ctx := s.sdkCtx.WithBlockTime(s.sdkCtx.BlockTime().Add(votingPeriod + 1))
result, err := s.groupKeeper.TallyResult(ctx, &group.QueryTallyResultRequest{
ProposalId: proposalRes.ProposalId,
})
s.Require().Equal("1", result.Tally.YesCount)
s.Require().NoError(err)
s.Require().NoError(s.groupKeeper.TallyProposalsAtVPEnd(ctx))
s.NotPanics(func() { module.EndBlocker(ctx, s.groupKeeper) })
}

View File

@ -685,8 +685,7 @@ func (k Keeper) doTallyAndUpdate(ctx sdk.Context, p *group.Proposal, electorate
return err
}
sinceSubmission := ctx.BlockTime().Sub(p.SubmitTime) // duration passed since proposal submission.
result, err := policy.Allow(tallyResult, electorate.TotalWeight, sinceSubmission)
result, err := policy.Allow(tallyResult, electorate.TotalWeight)
if err != nil {
return sdkerrors.Wrap(err, "policy allow")
}
@ -752,7 +751,8 @@ func (k Keeper) Exec(goCtx context.Context, req *group.MsgExec) (*group.MsgExecR
return nil, err
}
if results, err := k.doExecuteMsgs(cacheCtx, k.router, proposal, addr); err != nil {
decisionPolicy := policyInfo.DecisionPolicy.GetCachedValue().(group.DecisionPolicy)
if results, err := k.doExecuteMsgs(cacheCtx, k.router, proposal, addr, decisionPolicy); err != nil {
proposal.ExecutorResult = group.PROPOSAL_EXECUTOR_RESULT_FAILURE
logs = fmt.Sprintf("proposal execution failed on proposal %d, because of error %s", id, err.Error())
k.Logger(ctx).Info("proposal execution failed", "cause", err, "proposalID", id)

View File

@ -12,7 +12,13 @@ import (
// doExecuteMsgs routes the messages to the registered handlers. Messages are limited to those that require no authZ or
// by the account of group policy only. Otherwise this gives access to other peoples accounts as the sdk middlewares are bypassed
func (s Keeper) doExecuteMsgs(ctx sdk.Context, router *baseapp.MsgServiceRouter, proposal group.Proposal, groupPolicyAcc sdk.AccAddress) ([]sdk.Result, error) {
func (s Keeper) doExecuteMsgs(ctx sdk.Context, router *baseapp.MsgServiceRouter, proposal group.Proposal, groupPolicyAcc sdk.AccAddress, decisionPolicy group.DecisionPolicy) ([]sdk.Result, error) {
// Ensure it's not too early to execute the messages.
minExecutionDate := proposal.SubmitTime.Add(decisionPolicy.GetMinExecutionPeriod())
if ctx.BlockTime().Before(minExecutionDate) {
return nil, errors.ErrInvalid.Wrapf("must wait until %s to execute proposal %d", minExecutionDate, proposal.Id)
}
// Ensure it's not too late to execute the messages.
// After https://github.com/cosmos/cosmos-sdk/issues/11245, proposals should
// be pruned automatically, so this function should not even be called, as

View File

@ -31,10 +31,14 @@ type DecisionPolicy interface {
// GetVotingPeriod returns the duration after proposal submission where
// votes are accepted.
GetVotingPeriod() time.Duration
// GetMinExecutionPeriod returns the minimum duration after submission
// where we can execution a proposal. It can be set to 0 or to a value
// lesser than VotingPeriod to allow TRY_EXEC.
GetMinExecutionPeriod() time.Duration
// Allow defines policy-specific logic to allow a proposal to pass or not,
// based on its tally result, the group's total power and the time since
// the proposal was submitted.
Allow(tallyResult TallyResult, totalPower string, sinceSubmission time.Duration) (DecisionPolicyResult, error)
Allow(tallyResult TallyResult, totalPower string) (DecisionPolicyResult, error)
ValidateBasic() error
Validate(g GroupInfo, config Config) error
@ -52,6 +56,10 @@ func (p ThresholdDecisionPolicy) GetVotingPeriod() time.Duration {
return p.Windows.VotingPeriod
}
func (p ThresholdDecisionPolicy) GetMinExecutionPeriod() time.Duration {
return p.Windows.MinExecutionPeriod
}
func (p ThresholdDecisionPolicy) ValidateBasic() error {
if _, err := math.NewPositiveDecFromString(p.Threshold); err != nil {
return sdkerrors.Wrap(err, "threshold")
@ -65,11 +73,7 @@ func (p ThresholdDecisionPolicy) ValidateBasic() error {
}
// Allow allows a proposal to pass when the tally of yes votes equals or exceeds the threshold before the timeout.
func (p ThresholdDecisionPolicy) Allow(tallyResult TallyResult, totalPower string, sinceSubmission time.Duration) (DecisionPolicyResult, error) {
if sinceSubmission < p.Windows.MinExecutionPeriod {
return DecisionPolicyResult{}, errors.ErrUnauthorized.Wrapf("must wait %s after submission before execution, currently at %s", p.Windows.MinExecutionPeriod, sinceSubmission)
}
func (p ThresholdDecisionPolicy) Allow(tallyResult TallyResult, totalPower string) (DecisionPolicyResult, error) {
threshold, err := math.NewPositiveDecFromString(p.Threshold)
if err != nil {
return DecisionPolicyResult{}, sdkerrors.Wrap(err, "threshold")
@ -154,6 +158,10 @@ func (p PercentageDecisionPolicy) GetVotingPeriod() time.Duration {
return p.Windows.VotingPeriod
}
func (p PercentageDecisionPolicy) GetMinExecutionPeriod() time.Duration {
return p.Windows.MinExecutionPeriod
}
func (p PercentageDecisionPolicy) ValidateBasic() error {
percentage, err := math.NewPositiveDecFromString(p.Percentage)
if err != nil {
@ -178,11 +186,7 @@ func (p *PercentageDecisionPolicy) Validate(g GroupInfo, config Config) error {
}
// Allow allows a proposal to pass when the tally of yes votes equals or exceeds the percentage threshold before the timeout.
func (p PercentageDecisionPolicy) Allow(tally TallyResult, totalPower string, sinceSubmission time.Duration) (DecisionPolicyResult, error) {
if sinceSubmission < p.Windows.MinExecutionPeriod {
return DecisionPolicyResult{}, errors.ErrUnauthorized.Wrapf("must wait %s after submission before execution, currently at %s", p.Windows.MinExecutionPeriod, sinceSubmission)
}
func (p PercentageDecisionPolicy) Allow(tally TallyResult, totalPower string) (DecisionPolicyResult, error) {
percentage, err := math.NewPositiveDecFromString(p.Percentage)
if err != nil {
return DecisionPolicyResult{}, sdkerrors.Wrap(err, "percentage")

View File

@ -239,30 +239,10 @@ func TestPercentageDecisionPolicyAllow(t *testing.T) {
},
false,
},
{
"time since submission < min execution period",
&group.PercentageDecisionPolicy{
Percentage: "0.5",
Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second * 10,
MinExecutionPeriod: time.Minute,
},
},
&group.TallyResult{
YesCount: "2",
NoCount: "0",
AbstainCount: "0",
NoWithVetoCount: "0",
},
"3",
time.Duration(time.Second * 50),
group.DecisionPolicyResult{},
true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
policyResult, err := tc.policy.Allow(*tc.tally, tc.totalPower, tc.votingDuration)
policyResult, err := tc.policy.Allow(*tc.tally, tc.totalPower)
if tc.expErr {
require.Error(t, err)
} else {
@ -393,33 +373,10 @@ func TestThresholdDecisionPolicyAllow(t *testing.T) {
},
false,
},
{
"time since submission < min execution period",
&group.ThresholdDecisionPolicy{
Threshold: "3",
Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second * 10,
MinExecutionPeriod: time.Minute,
},
},
&group.TallyResult{
YesCount: "3",
NoCount: "0",
AbstainCount: "0",
NoWithVetoCount: "0",
},
"3",
time.Duration(time.Second * 50),
group.DecisionPolicyResult{
Allow: false,
Final: true,
},
true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
policyResult, err := tc.policy.Allow(*tc.tally, tc.totalPower, tc.votingDuration)
policyResult, err := tc.policy.Allow(*tc.tally, tc.totalPower)
if tc.expErr {
require.Error(t, err)
} else {