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:
parent
3423442ab1
commit
7661f62737
@ -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
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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) })
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user