1549 lines
51 KiB
Go
1549 lines
51 KiB
Go
package keeper_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/mock/gomock"
|
|
|
|
"cosmossdk.io/collections"
|
|
sdkmath "cosmossdk.io/math"
|
|
"cosmossdk.io/x/gov/keeper"
|
|
v1 "cosmossdk.io/x/gov/types/v1"
|
|
stakingtypes "cosmossdk.io/x/staking/types"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec/address"
|
|
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
)
|
|
|
|
type tallyFixture struct {
|
|
t *testing.T
|
|
proposal v1.Proposal
|
|
valAddrs []sdk.ValAddress
|
|
delAddrs []sdk.AccAddress
|
|
keeper *keeper.Keeper
|
|
ctx sdk.Context
|
|
mocks mocks
|
|
}
|
|
|
|
var (
|
|
// handy functions
|
|
setTotalBonded = func(s tallyFixture, n int64) {
|
|
s.mocks.stakingKeeper.EXPECT().ValidatorAddressCodec().Return(address.NewBech32Codec("cosmosvaloper")).AnyTimes()
|
|
s.mocks.stakingKeeper.EXPECT().TotalBondedTokens(gomock.Any()).Return(sdkmath.NewInt(n), nil)
|
|
}
|
|
delegatorVote = func(s tallyFixture, voter sdk.AccAddress, delegations []stakingtypes.Delegation, vote v1.VoteOption) {
|
|
err := s.keeper.AddVote(s.ctx, s.proposal.Id, voter, v1.NewNonSplitVoteOption(vote), "")
|
|
require.NoError(s.t, err)
|
|
s.mocks.stakingKeeper.EXPECT().
|
|
IterateDelegations(s.ctx, voter, gomock.Any()).
|
|
DoAndReturn(
|
|
func(ctx context.Context, voter sdk.AccAddress, fn func(index int64, d sdk.DelegationI) bool) error {
|
|
for i, d := range delegations {
|
|
fn(int64(i), d)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
validatorVote = func(s tallyFixture, voter sdk.ValAddress, vote v1.VoteOption) {
|
|
// validatorVote is like delegatorVote but without delegations
|
|
delegatorVote(s, sdk.AccAddress(voter), nil, vote)
|
|
}
|
|
)
|
|
|
|
func TestTally_Standard(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setup func(tallyFixture)
|
|
expectedPass bool
|
|
expectedBurn bool
|
|
expectedTally v1.TallyResult
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "no votes, no bonded tokens: prop fails",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 0)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "no votes: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "one validator votes: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_THREE)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "0",
|
|
NoCount: "1000000",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "1000000",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "one account votes without delegation: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
delegatorVote(s, s.delAddrs[0], nil, v1.VoteOption_VOTE_OPTION_ONE)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "one delegator votes: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
del0Addr, err := s.mocks.acctKeeper.AddressCodec().BytesToString(s.delAddrs[0])
|
|
require.NoError(t, err)
|
|
val0Addr, err := s.mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(s.valAddrs[0])
|
|
require.NoError(t, err)
|
|
delegations := []stakingtypes.Delegation{{
|
|
DelegatorAddress: del0Addr,
|
|
ValidatorAddress: val0Addr,
|
|
Shares: sdkmath.LegacyNewDec(42),
|
|
}}
|
|
delegatorVote(s, s.delAddrs[0], delegations, v1.VoteOption_VOTE_OPTION_ONE)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "42",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "42",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "one delegator votes yes, validator votes also yes: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
del0Addr, err := s.mocks.acctKeeper.AddressCodec().BytesToString(s.delAddrs[0])
|
|
require.NoError(t, err)
|
|
val0Addr, err := s.mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(s.valAddrs[0])
|
|
require.NoError(t, err)
|
|
delegations := []stakingtypes.Delegation{{
|
|
DelegatorAddress: del0Addr,
|
|
ValidatorAddress: val0Addr,
|
|
Shares: sdkmath.LegacyNewDec(42),
|
|
}}
|
|
delegatorVote(s, s.delAddrs[0], delegations, v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "1000000",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "1000000",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "one delegator votes yes, validator votes no: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
del0Addr, err := s.mocks.acctKeeper.AddressCodec().BytesToString(s.delAddrs[0])
|
|
require.NoError(t, err)
|
|
val0Addr, err := s.mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(s.valAddrs[0])
|
|
require.NoError(t, err)
|
|
delegations := []stakingtypes.Delegation{{
|
|
DelegatorAddress: del0Addr,
|
|
ValidatorAddress: val0Addr,
|
|
Shares: sdkmath.LegacyNewDec(42),
|
|
}}
|
|
delegatorVote(s, s.delAddrs[0], delegations, v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_THREE)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "42",
|
|
AbstainCount: "0",
|
|
NoCount: "999958",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "42",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "999958",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
// one delegator delegates 42 shares to 2 different validators (21 each)
|
|
// delegator votes yes
|
|
// first validator votes yes
|
|
// second validator votes no
|
|
// third validator (no delegation) votes abstain
|
|
name: "delegator with mixed delegations: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
del0Addr, err := s.mocks.acctKeeper.AddressCodec().BytesToString(s.delAddrs[0])
|
|
require.NoError(t, err)
|
|
val0Addr, err := s.mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(s.valAddrs[0])
|
|
require.NoError(t, err)
|
|
val1Addr, err := s.mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(s.valAddrs[1])
|
|
require.NoError(t, err)
|
|
delegations := []stakingtypes.Delegation{
|
|
{
|
|
DelegatorAddress: del0Addr,
|
|
ValidatorAddress: val0Addr,
|
|
Shares: sdkmath.LegacyNewDec(21),
|
|
},
|
|
{
|
|
DelegatorAddress: del0Addr,
|
|
ValidatorAddress: val1Addr,
|
|
Shares: sdkmath.LegacyNewDec(21),
|
|
},
|
|
}
|
|
delegatorVote(s, s.delAddrs[0], delegations, v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_THREE)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_TWO)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "1000021",
|
|
AbstainCount: "1000000",
|
|
NoCount: "999979",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "1000021",
|
|
OptionTwoCount: "1000000",
|
|
OptionThreeCount: "999979",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "quorum reached with only abstain: prop fails",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_TWO)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_TWO)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_TWO)
|
|
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_TWO)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "4000000",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "4000000",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "quorum reached with veto>1/3: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[4], v1.VoteOption_VOTE_OPTION_FOUR)
|
|
validatorVote(s, s.valAddrs[5], v1.VoteOption_VOTE_OPTION_FOUR)
|
|
validatorVote(s, s.valAddrs[6], v1.VoteOption_VOTE_OPTION_FOUR)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "4000000",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "3000000",
|
|
OptionOneCount: "4000000",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "3000000",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "quorum reached with yes<=.5: prop fails",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_THREE)
|
|
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_THREE)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "2000000",
|
|
AbstainCount: "0",
|
|
NoCount: "2000000",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "2000000",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "2000000",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "quorum reached with yes>.5: prop succeeds",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[4], v1.VoteOption_VOTE_OPTION_THREE)
|
|
validatorVote(s, s.valAddrs[5], v1.VoteOption_VOTE_OPTION_THREE)
|
|
validatorVote(s, s.valAddrs[6], v1.VoteOption_VOTE_OPTION_FOUR)
|
|
},
|
|
expectedPass: true,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "4000000",
|
|
AbstainCount: "0",
|
|
NoCount: "2000000",
|
|
NoWithVetoCount: "1000000",
|
|
OptionOneCount: "4000000",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "2000000",
|
|
OptionFourCount: "1000000",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "quorum reached thanks to abstain, yes>.5: prop succeeds",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_THREE)
|
|
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_TWO)
|
|
validatorVote(s, s.valAddrs[4], v1.VoteOption_VOTE_OPTION_TWO)
|
|
validatorVote(s, s.valAddrs[5], v1.VoteOption_VOTE_OPTION_TWO)
|
|
},
|
|
expectedPass: true,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "2000000",
|
|
AbstainCount: "3000000",
|
|
NoCount: "1000000",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "2000000",
|
|
OptionTwoCount: "3000000",
|
|
OptionThreeCount: "1000000",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "quorum reached with spam > all other votes: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
|
|
// spam votes
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_SPAM)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_SPAM)
|
|
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_SPAM)
|
|
validatorVote(s, s.valAddrs[4], v1.VoteOption_VOTE_OPTION_SPAM)
|
|
validatorVote(s, s.valAddrs[5], v1.VoteOption_VOTE_OPTION_SPAM)
|
|
validatorVote(s, s.valAddrs[6], v1.VoteOption_VOTE_OPTION_SPAM)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "1000000",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "1000000",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "6000000",
|
|
},
|
|
},
|
|
{
|
|
name: "quorum reached, yes quorum not reached: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
params, _ := s.keeper.Params.Get(s.ctx)
|
|
params.YesQuorum = "0.7"
|
|
_ = s.keeper.Params.Set(s.ctx, params)
|
|
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_THREE)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_TWO)
|
|
validatorVote(s, s.valAddrs[4], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[5], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[6], v1.VoteOption_VOTE_OPTION_TWO)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "3000000",
|
|
AbstainCount: "2000000",
|
|
NoCount: "1000000",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "3000000",
|
|
OptionTwoCount: "2000000",
|
|
OptionThreeCount: "1000000",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
govKeeper, mocks, _, ctx := setupGovKeeper(t, mockAccountKeeperExpectations)
|
|
params := v1.DefaultParams()
|
|
// Ensure params value are different than false
|
|
params.BurnVoteQuorum = true
|
|
params.BurnVoteVeto = true
|
|
err := govKeeper.Params.Set(ctx, params)
|
|
require.NoError(t, err)
|
|
var (
|
|
numVals = 10
|
|
numDelegators = 5
|
|
addrs = simtestutil.CreateRandomAccounts(numVals + numDelegators)
|
|
valAddrs = simtestutil.ConvertAddrsToValAddrs(addrs[:numVals])
|
|
delAddrs = addrs[numVals:]
|
|
)
|
|
// Mocks a bunch of validators
|
|
mocks.stakingKeeper.EXPECT().
|
|
IterateBondedValidatorsByPower(ctx, gomock.Any()).
|
|
DoAndReturn(
|
|
func(ctx context.Context, fn func(index int64, validator sdk.ValidatorI) bool) error {
|
|
for i := int64(0); i < int64(numVals); i++ {
|
|
valAddr, err := mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(valAddrs[i])
|
|
require.NoError(t, err)
|
|
fn(i, stakingtypes.Validator{
|
|
OperatorAddress: valAddr,
|
|
Status: stakingtypes.Bonded,
|
|
Tokens: sdkmath.NewInt(1000000),
|
|
DelegatorShares: sdkmath.LegacyNewDec(1000000),
|
|
})
|
|
}
|
|
return nil
|
|
})
|
|
|
|
// Submit and activate a proposal
|
|
proposal, err := govKeeper.SubmitProposal(ctx, TestProposal, "", "title", "summary", delAddrs[0], v1.ProposalType_PROPOSAL_TYPE_STANDARD)
|
|
require.NoError(t, err)
|
|
err = govKeeper.ActivateVotingPeriod(ctx, proposal)
|
|
require.NoError(t, err)
|
|
suite := tallyFixture{
|
|
t: t,
|
|
proposal: proposal,
|
|
valAddrs: valAddrs,
|
|
delAddrs: delAddrs,
|
|
ctx: ctx,
|
|
keeper: govKeeper,
|
|
mocks: mocks,
|
|
}
|
|
tt.setup(suite)
|
|
|
|
pass, burn, tally, err := govKeeper.Tally(ctx, proposal)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.expectedPass, pass, "wrong pass")
|
|
assert.Equal(t, tt.expectedBurn, burn, "wrong burn")
|
|
assert.Equal(t, tt.expectedTally, tally)
|
|
// Assert votes removal after tally
|
|
rng := collections.NewPrefixedPairRange[uint64, sdk.AccAddress](proposal.Id)
|
|
_, err = suite.keeper.Votes.Iterate(suite.ctx, rng)
|
|
assert.NoError(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTally_Expedited(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setup func(tallyFixture)
|
|
expectedPass bool
|
|
expectedBurn bool
|
|
expectedTally v1.TallyResult
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "no votes, no bonded tokens: prop fails",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 0)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "no votes: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "one validator votes: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_THREE)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "0",
|
|
NoCount: "1000000",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "1000000",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "one account votes without delegation: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
delegatorVote(s, s.delAddrs[0], nil, v1.VoteOption_VOTE_OPTION_ONE)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "one delegator votes: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
del0Addr, err := s.mocks.acctKeeper.AddressCodec().BytesToString(s.delAddrs[0])
|
|
require.NoError(t, err)
|
|
val0Addr, err := s.mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(s.valAddrs[0])
|
|
require.NoError(t, err)
|
|
delegations := []stakingtypes.Delegation{{
|
|
DelegatorAddress: del0Addr,
|
|
ValidatorAddress: val0Addr,
|
|
Shares: sdkmath.LegacyNewDec(42),
|
|
}}
|
|
delegatorVote(s, s.delAddrs[0], delegations, v1.VoteOption_VOTE_OPTION_ONE)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "42",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "42",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "one delegator votes yes, validator votes also yes: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
del0Addr, err := s.mocks.acctKeeper.AddressCodec().BytesToString(s.delAddrs[0])
|
|
require.NoError(t, err)
|
|
val0Addr, err := s.mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(s.valAddrs[0])
|
|
require.NoError(t, err)
|
|
delegations := []stakingtypes.Delegation{{
|
|
DelegatorAddress: del0Addr,
|
|
ValidatorAddress: val0Addr,
|
|
Shares: sdkmath.LegacyNewDec(42),
|
|
}}
|
|
delegatorVote(s, s.delAddrs[0], delegations, v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "1000000",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "1000000",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "one delegator votes yes, validator votes no: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
del0Addr, err := s.mocks.acctKeeper.AddressCodec().BytesToString(s.delAddrs[0])
|
|
require.NoError(t, err)
|
|
val0Addr, err := s.mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(s.valAddrs[0])
|
|
require.NoError(t, err)
|
|
delegations := []stakingtypes.Delegation{{
|
|
DelegatorAddress: del0Addr,
|
|
ValidatorAddress: val0Addr,
|
|
Shares: sdkmath.LegacyNewDec(42),
|
|
}}
|
|
delegatorVote(s, s.delAddrs[0], delegations, v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_THREE)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "42",
|
|
AbstainCount: "0",
|
|
NoCount: "999958",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "42",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "999958",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
// one delegator delegates 42 shares to 2 different validators (21 each)
|
|
// delegator votes yes
|
|
// first validator votes yes
|
|
// second validator votes no
|
|
// third validator (no delegation) votes abstain
|
|
name: "delegator with mixed delegations: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
del0Addr, err := s.mocks.acctKeeper.AddressCodec().BytesToString(s.delAddrs[0])
|
|
require.NoError(t, err)
|
|
val0Addr, err := s.mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(s.valAddrs[0])
|
|
require.NoError(t, err)
|
|
val1Addr, err := s.mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(s.valAddrs[1])
|
|
require.NoError(t, err)
|
|
delegations := []stakingtypes.Delegation{
|
|
{
|
|
DelegatorAddress: del0Addr,
|
|
ValidatorAddress: val0Addr,
|
|
Shares: sdkmath.LegacyNewDec(21),
|
|
},
|
|
{
|
|
DelegatorAddress: del0Addr,
|
|
ValidatorAddress: val1Addr,
|
|
Shares: sdkmath.LegacyNewDec(21),
|
|
},
|
|
}
|
|
delegatorVote(s, s.delAddrs[0], delegations, v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_THREE)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_TWO)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "1000021",
|
|
AbstainCount: "1000000",
|
|
NoCount: "999979",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "1000021",
|
|
OptionTwoCount: "1000000",
|
|
OptionThreeCount: "999979",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "quorum reached with only abstain: prop fails",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_TWO)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_TWO)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_TWO)
|
|
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_TWO)
|
|
validatorVote(s, s.valAddrs[4], v1.VoteOption_VOTE_OPTION_TWO)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "5000000",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "5000000",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "quorum reached with veto>1/3: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[4], v1.VoteOption_VOTE_OPTION_FOUR)
|
|
validatorVote(s, s.valAddrs[5], v1.VoteOption_VOTE_OPTION_FOUR)
|
|
validatorVote(s, s.valAddrs[6], v1.VoteOption_VOTE_OPTION_FOUR)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "4000000",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "3000000",
|
|
OptionOneCount: "4000000",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "3000000",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "quorum reached with yes<=.5: prop fails",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_THREE)
|
|
validatorVote(s, s.valAddrs[4], v1.VoteOption_VOTE_OPTION_THREE)
|
|
validatorVote(s, s.valAddrs[5], v1.VoteOption_VOTE_OPTION_THREE)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "3000000",
|
|
AbstainCount: "0",
|
|
NoCount: "3000000",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "3000000",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "3000000",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "quorum reached with yes<=.667: expedited prop fails",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[4], v1.VoteOption_VOTE_OPTION_THREE)
|
|
validatorVote(s, s.valAddrs[5], v1.VoteOption_VOTE_OPTION_THREE)
|
|
validatorVote(s, s.valAddrs[6], v1.VoteOption_VOTE_OPTION_FOUR)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "4000000",
|
|
AbstainCount: "0",
|
|
NoCount: "2000000",
|
|
NoWithVetoCount: "1000000",
|
|
OptionOneCount: "4000000",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "2000000",
|
|
OptionFourCount: "1000000",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "quorum reached with yes>.667: expedited prop succeeds",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[4], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[5], v1.VoteOption_VOTE_OPTION_THREE)
|
|
validatorVote(s, s.valAddrs[6], v1.VoteOption_VOTE_OPTION_FOUR)
|
|
},
|
|
expectedPass: true,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "5000000",
|
|
AbstainCount: "0",
|
|
NoCount: "1000000",
|
|
NoWithVetoCount: "1000000",
|
|
OptionOneCount: "5000000",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "1000000",
|
|
OptionFourCount: "1000000",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "quorum reached with spam > all other votes: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
|
|
// spam votes
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_SPAM)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_SPAM)
|
|
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_SPAM)
|
|
validatorVote(s, s.valAddrs[4], v1.VoteOption_VOTE_OPTION_SPAM)
|
|
validatorVote(s, s.valAddrs[5], v1.VoteOption_VOTE_OPTION_SPAM)
|
|
validatorVote(s, s.valAddrs[6], v1.VoteOption_VOTE_OPTION_SPAM)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "1000000",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "1000000",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "6000000",
|
|
},
|
|
},
|
|
{
|
|
name: "quorum reached, yes quorum not reached: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
params, _ := s.keeper.Params.Get(s.ctx)
|
|
params.YesQuorum = "0.7"
|
|
_ = s.keeper.Params.Set(s.ctx, params)
|
|
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_THREE)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_TWO)
|
|
validatorVote(s, s.valAddrs[4], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[5], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[6], v1.VoteOption_VOTE_OPTION_TWO)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "3000000",
|
|
AbstainCount: "2000000",
|
|
NoCount: "1000000",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "3000000",
|
|
OptionTwoCount: "2000000",
|
|
OptionThreeCount: "1000000",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
govKeeper, mocks, _, ctx := setupGovKeeper(t, mockAccountKeeperExpectations)
|
|
params := v1.DefaultParams()
|
|
// Ensure params value are different than false
|
|
params.BurnVoteQuorum = true
|
|
params.BurnVoteVeto = true
|
|
err := govKeeper.Params.Set(ctx, params)
|
|
require.NoError(t, err)
|
|
var (
|
|
numVals = 10
|
|
numDelegators = 5
|
|
addrs = simtestutil.CreateRandomAccounts(numVals + numDelegators)
|
|
valAddrs = simtestutil.ConvertAddrsToValAddrs(addrs[:numVals])
|
|
delAddrs = addrs[numVals:]
|
|
)
|
|
// Mocks a bunch of validators
|
|
mocks.stakingKeeper.EXPECT().
|
|
IterateBondedValidatorsByPower(ctx, gomock.Any()).
|
|
DoAndReturn(
|
|
func(ctx context.Context, fn func(index int64, validator sdk.ValidatorI) bool) error {
|
|
for i := int64(0); i < int64(numVals); i++ {
|
|
valAddr, err := mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(valAddrs[i])
|
|
require.NoError(t, err)
|
|
fn(i, stakingtypes.Validator{
|
|
OperatorAddress: valAddr,
|
|
Status: stakingtypes.Bonded,
|
|
Tokens: sdkmath.NewInt(1000000),
|
|
DelegatorShares: sdkmath.LegacyNewDec(1000000),
|
|
})
|
|
}
|
|
return nil
|
|
})
|
|
|
|
// Submit and activate a proposal
|
|
proposal, err := govKeeper.SubmitProposal(ctx, TestProposal, "", "title", "summary", delAddrs[0], v1.ProposalType_PROPOSAL_TYPE_EXPEDITED)
|
|
require.NoError(t, err)
|
|
err = govKeeper.ActivateVotingPeriod(ctx, proposal)
|
|
require.NoError(t, err)
|
|
suite := tallyFixture{
|
|
t: t,
|
|
proposal: proposal,
|
|
valAddrs: valAddrs,
|
|
delAddrs: delAddrs,
|
|
ctx: ctx,
|
|
keeper: govKeeper,
|
|
mocks: mocks,
|
|
}
|
|
tt.setup(suite)
|
|
|
|
pass, burn, tally, err := govKeeper.Tally(ctx, proposal)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.expectedPass, pass, "wrong pass")
|
|
assert.Equal(t, tt.expectedBurn, burn, "wrong burn")
|
|
assert.Equal(t, tt.expectedTally, tally)
|
|
// Assert votes removal after tally
|
|
rng := collections.NewPrefixedPairRange[uint64, sdk.AccAddress](proposal.Id)
|
|
_, err = suite.keeper.Votes.Iterate(suite.ctx, rng)
|
|
assert.NoError(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTally_Optimistic(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setup func(tallyFixture)
|
|
expectedPass bool
|
|
expectedBurn bool
|
|
expectedTally v1.TallyResult
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "no votes, no bonded tokens: prop fails",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 0)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "no votes: prop passes",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
},
|
|
expectedPass: true,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "one delegator votes: threshold no not reached, prop passes",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
del0Addr, err := s.mocks.acctKeeper.AddressCodec().BytesToString(s.delAddrs[0])
|
|
require.NoError(t, err)
|
|
val0Addr, err := s.mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(s.valAddrs[0])
|
|
require.NoError(t, err)
|
|
delegations := []stakingtypes.Delegation{{
|
|
DelegatorAddress: del0Addr,
|
|
ValidatorAddress: val0Addr,
|
|
Shares: sdkmath.LegacyNewDec(42),
|
|
}}
|
|
delegatorVote(s, s.delAddrs[0], delegations, v1.VoteOption_VOTE_OPTION_THREE)
|
|
},
|
|
expectedPass: true,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "0",
|
|
NoCount: "42",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "42",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "no vote threshold reached: prop fails",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_THREE)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_THREE)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_THREE)
|
|
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_THREE)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "0",
|
|
NoCount: "4000000",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "4000000",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
govKeeper, mocks, _, ctx := setupGovKeeper(t, mockAccountKeeperExpectations)
|
|
params := v1.DefaultParams()
|
|
// Ensure params value are different than false
|
|
params.BurnVoteQuorum = true
|
|
params.BurnVoteVeto = true
|
|
err := govKeeper.Params.Set(ctx, params)
|
|
require.NoError(t, err)
|
|
var (
|
|
numVals = 10
|
|
numDelegators = 5
|
|
addrs = simtestutil.CreateRandomAccounts(numVals + numDelegators)
|
|
valAddrs = simtestutil.ConvertAddrsToValAddrs(addrs[:numVals])
|
|
delAddrs = addrs[numVals:]
|
|
)
|
|
// Mocks a bunch of validators
|
|
mocks.stakingKeeper.EXPECT().
|
|
IterateBondedValidatorsByPower(ctx, gomock.Any()).
|
|
DoAndReturn(
|
|
func(ctx context.Context, fn func(index int64, validator sdk.ValidatorI) bool) error {
|
|
for i := int64(0); i < int64(numVals); i++ {
|
|
valAddr, err := mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(valAddrs[i])
|
|
require.NoError(t, err)
|
|
fn(i, stakingtypes.Validator{
|
|
OperatorAddress: valAddr,
|
|
Status: stakingtypes.Bonded,
|
|
Tokens: sdkmath.NewInt(1000000),
|
|
DelegatorShares: sdkmath.LegacyNewDec(1000000),
|
|
})
|
|
}
|
|
return nil
|
|
})
|
|
|
|
// Submit and activate a proposal
|
|
proposal, err := govKeeper.SubmitProposal(ctx, TestProposal, "", "title", "summary", delAddrs[0], v1.ProposalType_PROPOSAL_TYPE_OPTIMISTIC)
|
|
require.NoError(t, err)
|
|
err = govKeeper.ActivateVotingPeriod(ctx, proposal)
|
|
require.NoError(t, err)
|
|
suite := tallyFixture{
|
|
t: t,
|
|
proposal: proposal,
|
|
valAddrs: valAddrs,
|
|
delAddrs: delAddrs,
|
|
ctx: ctx,
|
|
keeper: govKeeper,
|
|
mocks: mocks,
|
|
}
|
|
tt.setup(suite)
|
|
|
|
pass, burn, tally, err := govKeeper.Tally(ctx, proposal)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.expectedPass, pass, "wrong pass")
|
|
assert.Equal(t, tt.expectedBurn, burn, "wrong burn")
|
|
assert.Equal(t, tt.expectedTally, tally)
|
|
// Assert votes removal after tally
|
|
rng := collections.NewPrefixedPairRange[uint64, sdk.AccAddress](proposal.Id)
|
|
_, err = suite.keeper.Votes.Iterate(suite.ctx, rng)
|
|
assert.NoError(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTally_MultipleChoice(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setup func(tallyFixture)
|
|
expectedPass bool
|
|
expectedBurn bool
|
|
expectedTally v1.TallyResult
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "no votes, no bonded tokens: prop fails",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 0)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "no votes: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "one validator votes: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_THREE)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "0",
|
|
NoCount: "1000000",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "1000000",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "one account votes without delegation: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
delegatorVote(s, s.delAddrs[0], nil, v1.VoteOption_VOTE_OPTION_ONE)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "one delegator votes: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
del0Addr, err := s.mocks.acctKeeper.AddressCodec().BytesToString(s.delAddrs[0])
|
|
require.NoError(t, err)
|
|
val0Addr, err := s.mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(s.valAddrs[0])
|
|
require.NoError(t, err)
|
|
delegations := []stakingtypes.Delegation{{
|
|
DelegatorAddress: del0Addr,
|
|
ValidatorAddress: val0Addr,
|
|
Shares: sdkmath.LegacyNewDec(42),
|
|
}}
|
|
delegatorVote(s, s.delAddrs[0], delegations, v1.VoteOption_VOTE_OPTION_ONE)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "42",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "42",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "one delegator votes yes, validator votes also yes: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
del0Addr, err := s.mocks.acctKeeper.AddressCodec().BytesToString(s.delAddrs[0])
|
|
require.NoError(t, err)
|
|
val0Addr, err := s.mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(s.valAddrs[0])
|
|
require.NoError(t, err)
|
|
delegations := []stakingtypes.Delegation{{
|
|
DelegatorAddress: del0Addr,
|
|
ValidatorAddress: val0Addr,
|
|
Shares: sdkmath.LegacyNewDec(42),
|
|
}}
|
|
delegatorVote(s, s.delAddrs[0], delegations, v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "1000000",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "1000000",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "one delegator votes yes, validator votes no: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
del0Addr, err := s.mocks.acctKeeper.AddressCodec().BytesToString(s.delAddrs[0])
|
|
require.NoError(t, err)
|
|
val0Addr, err := s.mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(s.valAddrs[0])
|
|
require.NoError(t, err)
|
|
delegations := []stakingtypes.Delegation{{
|
|
DelegatorAddress: del0Addr,
|
|
ValidatorAddress: val0Addr,
|
|
Shares: sdkmath.LegacyNewDec(42),
|
|
}}
|
|
delegatorVote(s, s.delAddrs[0], delegations, v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_THREE)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "42",
|
|
AbstainCount: "0",
|
|
NoCount: "999958",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "42",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "999958",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
// one delegator delegates 42 shares to 2 different validators (21 each)
|
|
// delegator votes yes
|
|
// first validator votes yes
|
|
// second validator votes no
|
|
// third validator (no delegation) votes abstain
|
|
name: "delegator with mixed delegations: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
del0Addr, err := s.mocks.acctKeeper.AddressCodec().BytesToString(s.delAddrs[0])
|
|
require.NoError(t, err)
|
|
val0Addr, err := s.mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(s.valAddrs[0])
|
|
require.NoError(t, err)
|
|
val1Addr, err := s.mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(s.valAddrs[1])
|
|
require.NoError(t, err)
|
|
delegations := []stakingtypes.Delegation{
|
|
{
|
|
DelegatorAddress: del0Addr,
|
|
ValidatorAddress: val0Addr,
|
|
Shares: sdkmath.LegacyNewDec(21),
|
|
},
|
|
{
|
|
DelegatorAddress: del0Addr,
|
|
ValidatorAddress: val1Addr,
|
|
Shares: sdkmath.LegacyNewDec(21),
|
|
},
|
|
}
|
|
delegatorVote(s, s.delAddrs[0], delegations, v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_THREE)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_TWO)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true, // burn because quorum not reached
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "1000021",
|
|
AbstainCount: "1000000",
|
|
NoCount: "999979",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "1000021",
|
|
OptionTwoCount: "1000000",
|
|
OptionThreeCount: "999979",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "quorum reached with only abstain: always passes in multiple choice tally",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_TWO)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_TWO)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_TWO)
|
|
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_TWO)
|
|
},
|
|
expectedPass: true,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "0",
|
|
AbstainCount: "4000000",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "0",
|
|
OptionTwoCount: "4000000",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "quorum reached with a majority of option 4: always passes in multiple choice tally",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[4], v1.VoteOption_VOTE_OPTION_FOUR)
|
|
validatorVote(s, s.valAddrs[5], v1.VoteOption_VOTE_OPTION_FOUR)
|
|
validatorVote(s, s.valAddrs[6], v1.VoteOption_VOTE_OPTION_FOUR)
|
|
},
|
|
expectedPass: true,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "4000000",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "3000000",
|
|
OptionOneCount: "4000000",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "3000000",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "quorum reached, equality: always passes in multiple choice tally",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_ONE)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_THREE)
|
|
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_THREE)
|
|
},
|
|
expectedPass: true,
|
|
expectedBurn: false,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "2000000",
|
|
AbstainCount: "0",
|
|
NoCount: "2000000",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "2000000",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "2000000",
|
|
OptionFourCount: "0",
|
|
SpamCount: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "quorum reached with spam > all other votes: prop fails/burn deposit",
|
|
setup: func(s tallyFixture) {
|
|
setTotalBonded(s, 10000000)
|
|
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
|
|
// spam votes
|
|
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_SPAM)
|
|
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_SPAM)
|
|
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_SPAM)
|
|
validatorVote(s, s.valAddrs[4], v1.VoteOption_VOTE_OPTION_SPAM)
|
|
validatorVote(s, s.valAddrs[5], v1.VoteOption_VOTE_OPTION_SPAM)
|
|
validatorVote(s, s.valAddrs[6], v1.VoteOption_VOTE_OPTION_SPAM)
|
|
},
|
|
expectedPass: false,
|
|
expectedBurn: true,
|
|
expectedTally: v1.TallyResult{
|
|
YesCount: "1000000",
|
|
AbstainCount: "0",
|
|
NoCount: "0",
|
|
NoWithVetoCount: "0",
|
|
OptionOneCount: "1000000",
|
|
OptionTwoCount: "0",
|
|
OptionThreeCount: "0",
|
|
OptionFourCount: "0",
|
|
SpamCount: "6000000",
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
govKeeper, mocks, _, ctx := setupGovKeeper(t, mockAccountKeeperExpectations)
|
|
params := v1.DefaultParams()
|
|
// Ensure params value are different than false
|
|
params.BurnVoteQuorum = true
|
|
params.BurnVoteVeto = true
|
|
err := govKeeper.Params.Set(ctx, params)
|
|
require.NoError(t, err)
|
|
var (
|
|
numVals = 10
|
|
numDelegators = 5
|
|
addrs = simtestutil.CreateRandomAccounts(numVals + numDelegators)
|
|
valAddrs = simtestutil.ConvertAddrsToValAddrs(addrs[:numVals])
|
|
delAddrs = addrs[numVals:]
|
|
)
|
|
// Mocks a bunch of validators
|
|
mocks.stakingKeeper.EXPECT().
|
|
IterateBondedValidatorsByPower(ctx, gomock.Any()).
|
|
DoAndReturn(
|
|
func(ctx context.Context, fn func(index int64, validator sdk.ValidatorI) bool) error {
|
|
for i := int64(0); i < int64(numVals); i++ {
|
|
valAddr, err := mocks.stakingKeeper.ValidatorAddressCodec().BytesToString(valAddrs[i])
|
|
require.NoError(t, err)
|
|
fn(i, stakingtypes.Validator{
|
|
OperatorAddress: valAddr,
|
|
Status: stakingtypes.Bonded,
|
|
Tokens: sdkmath.NewInt(1000000),
|
|
DelegatorShares: sdkmath.LegacyNewDec(1000000),
|
|
})
|
|
}
|
|
return nil
|
|
})
|
|
|
|
// Submit and activate a proposal
|
|
proposal, err := govKeeper.SubmitProposal(ctx, nil, "", "title", "summary", delAddrs[0], v1.ProposalType_PROPOSAL_TYPE_MULTIPLE_CHOICE)
|
|
require.NoError(t, err)
|
|
err = govKeeper.ProposalVoteOptions.Set(ctx, proposal.Id, v1.ProposalVoteOptions{
|
|
OptionOne: "Vote Option 1",
|
|
OptionTwo: "Vote Option 2",
|
|
OptionThree: "Vote Option 3",
|
|
OptionFour: "Vote Option 4",
|
|
})
|
|
require.NoError(t, err)
|
|
err = govKeeper.ActivateVotingPeriod(ctx, proposal)
|
|
require.NoError(t, err)
|
|
suite := tallyFixture{
|
|
t: t,
|
|
proposal: proposal,
|
|
valAddrs: valAddrs,
|
|
delAddrs: delAddrs,
|
|
ctx: ctx,
|
|
keeper: govKeeper,
|
|
mocks: mocks,
|
|
}
|
|
tt.setup(suite)
|
|
|
|
pass, burn, tally, err := govKeeper.Tally(ctx, proposal)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.expectedPass, pass, "wrong pass")
|
|
assert.Equal(t, tt.expectedBurn, burn, "wrong burn")
|
|
assert.Equal(t, tt.expectedTally, tally)
|
|
// Assert votes removal after tally
|
|
rng := collections.NewPrefixedPairRange[uint64, sdk.AccAddress](proposal.Id)
|
|
_, err = suite.keeper.Votes.Iterate(suite.ctx, rng)
|
|
assert.NoError(t, err)
|
|
})
|
|
}
|
|
}
|