diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4cd8c704..ff4e5ba3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -30,7 +30,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: 1.22 + go-version: 1.23 check-latest: true - uses: technote-space/get-diff-action@v6.1.2 id: git_diff @@ -72,7 +72,7 @@ jobs: - name: Setup go uses: actions/setup-go@v5 with: - go-version: 1.22 + go-version: 1.23 check-latest: true - run: | go mod tidy diff --git a/x/gov/abci.go b/x/gov/abci.go index 87a4eb2b..99ca77ec 100644 --- a/x/gov/abci.go +++ b/x/gov/abci.go @@ -185,8 +185,15 @@ func EndBlocker(ctx sdk.Context, k *keeper.Keeper) error { return false, err } - if !quorumReached { + // schedule the next tally only if quorum is not reached and voting period is not over + if !quorumReached && proposal.VotingEndTime.After(ctx.BlockTime()) { nextTallyTime := ctx.BlockTime().Add(params.EmergencyTallyInterval) + + // if the next tally time is after the voting end time, set it to the voting end time + if nextTallyTime.After(*proposal.VotingEndTime) { + nextTallyTime = *proposal.VotingEndTime + } + if err = k.EmergencyProposalsQueue.Set(ctx, collections.Join(nextTallyTime, proposal.Id), proposal.Id); err != nil { return false, err } diff --git a/x/gov/abci_test.go b/x/gov/abci_test.go index 5fddd242..3f552d14 100644 --- a/x/gov/abci_test.go +++ b/x/gov/abci_test.go @@ -259,3 +259,52 @@ func TestTickSingleProposal(t *testing.T) { }) } } + +func TestEmergencyProposal_Rejected_VotingPeriodOver(t *testing.T) { + app := createAppWithSimpleValidators(t) + ctx := app.BaseApp.NewContext(false) + initTime := ctx.BlockHeader().Time + + govMsgSvr := keeper.NewMsgServerImpl(app.GovKeeper) + propMsg := createTextProposalMsg(t, emergencyMinDeposit[0].Amount.Int64(), false) + _, err := govMsgSvr.SubmitProposal(ctx, propMsg) + require.NoError(t, err) + + newHeader := ctx.BlockHeader() + newHeader.Time = ctx.BlockHeader().Time.Add(time.Minute) + ctx = ctx.WithBlockHeader(newHeader) + + proposal, err := app.GovKeeper.Proposals.Get(ctx, 1) + require.NoError(t, err) + require.True(t, proposal.Emergency) + require.True(t, proposal.EmergencyStartTime.Equal(ctx.BlockTime().Add(-time.Minute))) + require.True(t, proposal.EmergencyNextTallyTime.Equal(ctx.BlockTime().Add(emergencyTallyInterval-time.Minute))) + require.True(t, proposal.SubmitTime.Equal(initTime)) + require.True(t, proposal.DepositEndTime.Equal(initTime.Add(depositPeriod))) + require.Equal(t, proposal.Status, v1.StatusVotingPeriod) + require.True(t, proposal.VotingStartTime.Equal(ctx.BlockTime().Add(-time.Minute))) + require.True(t, proposal.VotingEndTime.Equal(ctx.BlockTime().Add(votingPeriod-time.Minute))) + + // not enough votes + + newHeader = ctx.BlockHeader() + newHeader.Time = ctx.BlockHeader().Time.Add(time.Minute) + ctx = ctx.WithBlockHeader(newHeader) + + err = gov.EndBlocker(ctx, app.GovKeeper) + require.NoError(t, err) + proposal, err = app.GovKeeper.Proposals.Get(ctx, 1) + require.NoError(t, err) + require.Equal(t, proposal.Status, v1.StatusVotingPeriod) + + // Voting period is over; the proposal should be finished + newHeader = ctx.BlockHeader() + newHeader.Time = ctx.BlockHeader().Time.Add(votingPeriod) + ctx = ctx.WithBlockHeader(newHeader) + + err = gov.EndBlocker(ctx, app.GovKeeper) + require.NoError(t, err) + proposal, err = app.GovKeeper.Proposals.Get(ctx, 1) + require.NoError(t, err) + require.Equal(t, proposal.Status, v1.StatusRejected) +} diff --git a/x/gov/common_test.go b/x/gov/common_test.go index 0f43b793..e7d6a2f7 100644 --- a/x/gov/common_test.go +++ b/x/gov/common_test.go @@ -59,7 +59,7 @@ var ( emergencyTallyInterval = time.Minute * 10 ) -func createDefaultApp(t *testing.T) *initiaapp.InitiaApp { +func createDefaultApp(_ *testing.T) *initiaapp.InitiaApp { app := initiaapp.SetupWithGenesisAccounts(nil, authtypes.GenesisAccounts{ &authtypes.BaseAccount{Address: addrs[0].String()}, }, @@ -124,7 +124,7 @@ func createTextProposalMsg(t *testing.T, initialTokenAmount int64, expedited boo return newProposalMsg } -func createDepositMsg(t *testing.T, depositor sdk.AccAddress, proposalID uint64, amount sdk.Coins) *v1.MsgDeposit { +func createDepositMsg(_ *testing.T, depositor sdk.AccAddress, proposalID uint64, amount sdk.Coins) *v1.MsgDeposit { newDepositMsg := v1.NewMsgDeposit( depositor, proposalID, @@ -133,7 +133,7 @@ func createDepositMsg(t *testing.T, depositor sdk.AccAddress, proposalID uint64, return newDepositMsg } -func createVoteMsg(t *testing.T, voter sdk.AccAddress, proposalID uint64, option v1.VoteOption) *v1.MsgVote { +func createVoteMsg(_ *testing.T, voter sdk.AccAddress, proposalID uint64, option v1.VoteOption) *v1.MsgVote { newVoteMsg := v1.NewMsgVote( voter, proposalID, diff --git a/x/gov/types/params.go b/x/gov/types/params.go index 9ef6f39e..e0ba5af1 100644 --- a/x/gov/types/params.go +++ b/x/gov/types/params.go @@ -210,6 +210,9 @@ func (p Params) Validate(ac address.Codec) error { if p.EmergencyTallyInterval.Seconds() <= 0 { return fmt.Errorf("emergency tally interval must be positive: %s", p.EmergencyTallyInterval) } + if p.EmergencyTallyInterval.Seconds() >= p.VotingPeriod.Seconds() { + return fmt.Errorf("emergency tally interval %s must be strictly less than the voting period %s", p.EmergencyTallyInterval, p.VotingPeriod) + } if minEmergencyDeposit := sdk.Coins(p.EmergencyMinDeposit); !minEmergencyDeposit.IsAllGTE(p.ExpeditedMinDeposit) { return fmt.Errorf("emergency minimum deposit must be greater than or equal to minimum deposit") diff --git a/x/gov/types/params_test.go b/x/gov/types/params_test.go index fc3c5b8b..58bdca6d 100644 --- a/x/gov/types/params_test.go +++ b/x/gov/types/params_test.go @@ -30,6 +30,9 @@ func Test_Params(t *testing.T) { params.EmergencyTallyInterval = 0 require.Error(t, params.Validate(ac)) + params.EmergencyTallyInterval = params.VotingPeriod + require.Error(t, params.Validate(ac)) + params.EmergencyTallyInterval = _emergencyTallyInterval _minDeposit := params.MinDeposit