Add upgrade handler and upgrade e2e tests (#1739)

- Add upgrade handler for mainnet, testnet, and e2e test
- Set validator minimum commission to 5%
- Initialize `x/community` parameters
- Add `banktypes.MsgSend` authz grant for `x/kavadist` for gov proposals
- Set `x/gov` Quorum param to 20%
- Set `x/incentive` earn rewards param for bkava to 600K KAVA per year
This commit is contained in:
drklee3 2023-11-01 17:22:38 -07:00 committed by GitHub
parent 67a66bebd0
commit 802f1c8112
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1718 additions and 5 deletions

View File

@ -1,3 +1,265 @@
package app
func (app App) RegisterUpgradeHandlers() {}
import (
"fmt"
"time"
sdkmath "cosmossdk.io/math"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/x/authz"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
communitytypes "github.com/kava-labs/kava/x/community/types"
kavadisttypes "github.com/kava-labs/kava/x/kavadist/types"
)
const (
UpgradeName_Mainnet = "v0.25.0"
UpgradeName_Testnet = "v0.25.0-alpha.0"
UpgradeName_E2ETest = "v0.25.0-testing"
)
var (
// KAVA to ukava - 6 decimals
kavaConversionFactor = sdk.NewInt(1000_000)
secondsPerYear = sdk.NewInt(365 * 24 * 60 * 60)
// 10 Million KAVA per year in staking rewards, inflation disable time 2024-01-01T00:00:00 UTC
CommunityParams_Mainnet = communitytypes.NewParams(
time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
// before switchover
sdkmath.LegacyZeroDec(),
// after switchover - 10M KAVA to ukava per year / seconds per year
sdkmath.LegacyNewDec(10_000_000).
MulInt(kavaConversionFactor).
QuoInt(secondsPerYear),
)
// Testnet -- 15 Trillion KAVA per year in staking rewards, inflation disable time 2023-11-16T00:00:00 UTC
CommunityParams_Testnet = communitytypes.NewParams(
time.Date(2023, 11, 16, 0, 0, 0, 0, time.UTC),
// before switchover
sdkmath.LegacyZeroDec(),
// after switchover
sdkmath.LegacyNewDec(15_000_000).
MulInt64(1_000_000). // 15M * 1M = 15T
MulInt(kavaConversionFactor).
QuoInt(secondsPerYear),
)
CommunityParams_E2E = communitytypes.NewParams(
time.Now().Add(10*time.Second).UTC(), // relative time for testing
sdkmath.LegacyNewDec(0), // stakingRewardsPerSecond
sdkmath.LegacyNewDec(1000), // upgradeTimeSetstakingRewardsPerSecond
)
// ValidatorMinimumCommission is the new 5% minimum commission rate for validators
ValidatorMinimumCommission = sdk.NewDecWithPrec(5, 2)
)
// RegisterUpgradeHandlers registers the upgrade handlers for the app.
func (app App) RegisterUpgradeHandlers() {
app.upgradeKeeper.SetUpgradeHandler(
UpgradeName_Mainnet,
upgradeHandler(app, UpgradeName_Mainnet, CommunityParams_Mainnet),
)
app.upgradeKeeper.SetUpgradeHandler(
UpgradeName_Testnet,
upgradeHandler(app, UpgradeName_Testnet, CommunityParams_Testnet),
)
app.upgradeKeeper.SetUpgradeHandler(
UpgradeName_E2ETest,
upgradeHandler(app, UpgradeName_Testnet, CommunityParams_E2E),
)
upgradeInfo, err := app.upgradeKeeper.ReadUpgradeInfoFromDisk()
if err != nil {
panic(err)
}
doUpgrade := upgradeInfo.Name == UpgradeName_Mainnet ||
upgradeInfo.Name == UpgradeName_Testnet ||
upgradeInfo.Name == UpgradeName_E2ETest
if doUpgrade && !app.upgradeKeeper.IsSkipHeight(upgradeInfo.Height) {
storeUpgrades := storetypes.StoreUpgrades{
Added: []string{
// x/community added store
communitytypes.ModuleName,
},
}
// configure store loader that checks if version == upgradeHeight and applies store upgrades
app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades))
}
}
// upgradeHandler returns an UpgradeHandler for the given upgrade parameters.
func upgradeHandler(
app App,
name string,
communityParams communitytypes.Params,
) upgradetypes.UpgradeHandler {
return func(
ctx sdk.Context,
plan upgradetypes.Plan,
fromVM module.VersionMap,
) (module.VersionMap, error) {
app.Logger().Info(fmt.Sprintf("running %s upgrade handler", name))
toVM, err := app.mm.RunMigrations(ctx, app.configurator, fromVM)
if err != nil {
return toVM, err
}
//
// Staking validator minimum commission
//
UpdateValidatorMinimumCommission(ctx, app)
//
// Community Params
//
app.communityKeeper.SetParams(ctx, communityParams)
app.Logger().Info(
"initialized x/community params",
"UpgradeTimeDisableInflation", communityParams.UpgradeTimeDisableInflation,
"StakingRewardsPerSecond", communityParams.StakingRewardsPerSecond,
"UpgradeTimeSetStakingRewardsPerSecond", communityParams.UpgradeTimeSetStakingRewardsPerSecond,
)
//
// Kavadist gov grant
//
msgGrant, err := authz.NewMsgGrant(
app.accountKeeper.GetModuleAddress(kavadisttypes.ModuleName), // granter
app.accountKeeper.GetModuleAddress(govtypes.ModuleName), // grantee
authz.NewGenericAuthorization(sdk.MsgTypeURL(&banktypes.MsgSend{})), // authorization
nil, // expiration
)
if err != nil {
return toVM, err
}
_, err = app.authzKeeper.Grant(ctx, msgGrant)
if err != nil {
return toVM, err
}
app.Logger().Info("created gov grant for kavadist funds")
//
// Gov Quorum
//
govTallyParams := app.govKeeper.GetTallyParams(ctx)
oldQuorum := govTallyParams.Quorum
govTallyParams.Quorum = sdkmath.LegacyMustNewDecFromStr("0.2").String()
app.govKeeper.SetTallyParams(ctx, govTallyParams)
app.Logger().Info(fmt.Sprintf("updated tally quorum from %s to %s", oldQuorum, govTallyParams.Quorum))
//
// Incentive Params
//
UpdateIncentiveParams(ctx, app)
return toVM, nil
}
}
// UpdateValidatorMinimumCommission updates the commission rate for all
// validators to be at least the new min commission rate, and sets the minimum
// commission rate in the staking params.
func UpdateValidatorMinimumCommission(
ctx sdk.Context,
app App,
) {
resultCount := make(map[stakingtypes.BondStatus]int)
// Iterate over *all* validators including inactive
app.stakingKeeper.IterateValidators(
ctx,
func(index int64, validator stakingtypes.ValidatorI) (stop bool) {
// Skip if validator commission is already >= 5%
if validator.GetCommission().GTE(ValidatorMinimumCommission) {
return false
}
val, ok := validator.(stakingtypes.Validator)
if !ok {
panic("expected stakingtypes.Validator")
}
// Set minimum commission rate to 5%, when commission is < 5%
val.Commission.Rate = ValidatorMinimumCommission
val.Commission.UpdateTime = ctx.BlockTime()
// Update MaxRate if necessary
if val.Commission.MaxRate.LT(ValidatorMinimumCommission) {
val.Commission.MaxRate = ValidatorMinimumCommission
}
if err := app.stakingKeeper.BeforeValidatorModified(ctx, val.GetOperator()); err != nil {
panic(fmt.Sprintf("failed to call BeforeValidatorModified: %s", err))
}
app.stakingKeeper.SetValidator(ctx, val)
// Keep track of counts just for logging purposes
switch val.GetStatus() {
case stakingtypes.Bonded:
resultCount[stakingtypes.Bonded]++
case stakingtypes.Unbonded:
resultCount[stakingtypes.Unbonded]++
case stakingtypes.Unbonding:
resultCount[stakingtypes.Unbonding]++
}
return false
},
)
app.Logger().Info(
"updated validator minimum commission rate for all existing validators",
stakingtypes.BondStatusBonded, resultCount[stakingtypes.Bonded],
stakingtypes.BondStatusUnbonded, resultCount[stakingtypes.Unbonded],
stakingtypes.BondStatusUnbonding, resultCount[stakingtypes.Unbonding],
)
stakingParams := app.stakingKeeper.GetParams(ctx)
stakingParams.MinCommissionRate = ValidatorMinimumCommission
app.stakingKeeper.SetParams(ctx, stakingParams)
app.Logger().Info(
"updated x/staking params minimum commission rate",
"MinCommissionRate", stakingParams.MinCommissionRate,
)
}
// UpdateIncentiveParams modifies the earn rewards period for bkava to be 600K KAVA per year.
func UpdateIncentiveParams(
ctx sdk.Context,
app App,
) {
incentiveParams := app.incentiveKeeper.GetParams(ctx)
// bkava annualized rewards: 600K KAVA
newAmount := sdkmath.LegacyNewDec(600_000).
MulInt(kavaConversionFactor).
QuoInt(secondsPerYear).
TruncateInt()
for i := range incentiveParams.EarnRewardPeriods {
if incentiveParams.EarnRewardPeriods[i].CollateralType != "bkava" {
continue
}
// Update rewards per second via index
incentiveParams.EarnRewardPeriods[i].RewardsPerSecond = sdk.NewCoins(
sdk.NewCoin("ukava", newAmount),
)
}
app.incentiveKeeper.SetParams(ctx, incentiveParams)
}

241
app/upgrades_test.go Normal file
View File

@ -0,0 +1,241 @@
package app_test
import (
"testing"
"time"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/evmos/ethermint/crypto/ethsecp256k1"
"github.com/kava-labs/kava/app"
incentivetypes "github.com/kava-labs/kava/x/incentive/types"
"github.com/stretchr/testify/require"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
tmtime "github.com/tendermint/tendermint/types/time"
)
func TestUpgradeCommunityParams_Mainnet(t *testing.T) {
require.Equal(
t,
sdkmath.LegacyZeroDec().String(),
app.CommunityParams_Mainnet.StakingRewardsPerSecond.String(),
)
require.Equal(
t,
// Manually confirmed
"317097.919837645865043125",
app.CommunityParams_Mainnet.UpgradeTimeSetStakingRewardsPerSecond.String(),
"mainnet kava per second should be correct",
)
}
func TestUpgradeCommunityParams_Testnet(t *testing.T) {
require.Equal(
t,
sdkmath.LegacyZeroDec().String(),
app.CommunityParams_Testnet.StakingRewardsPerSecond.String(),
)
require.Equal(
t,
// Manually confirmed
"475646879756.468797564687975646",
app.CommunityParams_Testnet.UpgradeTimeSetStakingRewardsPerSecond.String(),
"testnet kava per second should be correct",
)
}
func TestUpdateValidatorMinimumCommission(t *testing.T) {
tApp := app.NewTestApp()
tApp.InitializeFromGenesisStates()
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()})
sk := tApp.GetStakingKeeper()
stakingParams := sk.GetParams(ctx)
stakingParams.MinCommissionRate = sdk.ZeroDec()
sk.SetParams(ctx, stakingParams)
// Set some validators with varying commission rates
vals := []struct {
name string
operatorAddr sdk.ValAddress
consPriv *ethsecp256k1.PrivKey
commissionRateMin sdk.Dec
commissionRateMax sdk.Dec
shouldBeUpdated bool
}{
{
name: "zero commission rate",
operatorAddr: sdk.ValAddress("val0"),
consPriv: generateConsKey(t),
commissionRateMin: sdk.ZeroDec(),
commissionRateMax: sdk.ZeroDec(),
shouldBeUpdated: true,
},
{
name: "0.01 commission rate",
operatorAddr: sdk.ValAddress("val1"),
consPriv: generateConsKey(t),
commissionRateMin: sdk.MustNewDecFromStr("0.01"),
commissionRateMax: sdk.MustNewDecFromStr("0.01"),
shouldBeUpdated: true,
},
{
name: "0.05 commission rate",
operatorAddr: sdk.ValAddress("val2"),
consPriv: generateConsKey(t),
commissionRateMin: sdk.MustNewDecFromStr("0.05"),
commissionRateMax: sdk.MustNewDecFromStr("0.05"),
shouldBeUpdated: false,
},
{
name: "0.06 commission rate",
operatorAddr: sdk.ValAddress("val3"),
consPriv: generateConsKey(t),
commissionRateMin: sdk.MustNewDecFromStr("0.06"),
commissionRateMax: sdk.MustNewDecFromStr("0.06"),
shouldBeUpdated: false,
},
{
name: "0.5 commission rate",
operatorAddr: sdk.ValAddress("val4"),
consPriv: generateConsKey(t),
commissionRateMin: sdk.MustNewDecFromStr("0.5"),
commissionRateMax: sdk.MustNewDecFromStr("0.5"),
shouldBeUpdated: false,
},
}
for _, v := range vals {
val, err := stakingtypes.NewValidator(
v.operatorAddr,
v.consPriv.PubKey(),
stakingtypes.Description{},
)
require.NoError(t, err)
val.Commission.Rate = v.commissionRateMin
val.Commission.MaxRate = v.commissionRateMax
err = sk.SetValidatorByConsAddr(ctx, val)
require.NoError(t, err)
sk.SetValidator(ctx, val)
}
require.NotPanics(
t, func() {
app.UpdateValidatorMinimumCommission(ctx, tApp.App)
},
)
stakingParamsAfter := sk.GetParams(ctx)
require.Equal(t, stakingParamsAfter.MinCommissionRate, app.ValidatorMinimumCommission)
// Check that all validators have a commission rate >= 5%
for _, val := range vals {
t.Run(val.name, func(t *testing.T) {
validator, found := sk.GetValidator(ctx, val.operatorAddr)
require.True(t, found, "validator should be found")
require.True(
t,
validator.GetCommission().GTE(app.ValidatorMinimumCommission),
"commission rate should be >= 5%",
)
require.True(
t,
validator.Commission.MaxRate.GTE(app.ValidatorMinimumCommission),
"commission rate max should be >= 5%, got %s",
validator.Commission.MaxRate,
)
if val.shouldBeUpdated {
require.Equal(
t,
ctx.BlockTime(),
validator.Commission.UpdateTime,
"commission update time should be set to block time",
)
} else {
require.Equal(
t,
time.Unix(0, 0).UTC(),
validator.Commission.UpdateTime,
"commission update time should not be changed -- default value is 0",
)
}
})
}
}
func TestUpdateIncentiveParams(t *testing.T) {
tApp := app.NewTestApp()
tApp.InitializeFromGenesisStates()
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()})
ik := tApp.GetIncentiveKeeper()
params := ik.GetParams(ctx)
startPeriod := time.Date(2021, 10, 26, 15, 0, 0, 0, time.UTC)
endPeriod := time.Date(2022, 10, 26, 15, 0, 0, 0, time.UTC)
params.EarnRewardPeriods = incentivetypes.MultiRewardPeriods{
incentivetypes.NewMultiRewardPeriod(
true,
"bkava",
startPeriod,
endPeriod,
sdk.NewCoins(
sdk.NewCoin("ukava", sdk.NewInt(159459)),
),
),
}
ik.SetParams(ctx, params)
beforeParams := ik.GetParams(ctx)
require.Equal(t, params, beforeParams, "initial incentive params should be set")
// -- UPGRADE
app.UpdateIncentiveParams(ctx, tApp.App)
// -- After
afterParams := ik.GetParams(ctx)
require.Len(
t,
afterParams.EarnRewardPeriods[0].RewardsPerSecond,
1,
"bkava earn reward period should only contain 1 coin",
)
require.Equal(
t,
// Manual calculation of
// 600,000 * 1000,000 / (365 * 24 * 60 * 60)
sdk.NewCoin("ukava", sdkmath.NewInt(19025)),
afterParams.EarnRewardPeriods[0].RewardsPerSecond[0],
"bkava earn reward period should be updated",
)
// Check that other params are not changed
afterParams.EarnRewardPeriods[0].RewardsPerSecond[0] = beforeParams.EarnRewardPeriods[0].RewardsPerSecond[0]
require.Equal(
t,
beforeParams,
afterParams,
"other param values should not be changed",
)
}
func generateConsKey(
t *testing.T,
) *ethsecp256k1.PrivKey {
t.Helper()
key, err := ethsecp256k1.GenerateKey()
require.NoError(t, err)
return key
}

View File

@ -19,14 +19,14 @@ E2E_SKIP_SHUTDOWN=false
# The following variables should be defined to run an upgrade.
# E2E_INCLUDE_AUTOMATED_UPGRADE when true enables the automated upgrade & corresponding tests in the suite.
E2E_INCLUDE_AUTOMATED_UPGRADE=false
E2E_INCLUDE_AUTOMATED_UPGRADE=true
# E2E_KAVA_UPGRADE_NAME is the name of the upgrade that must be in the current local image.
E2E_KAVA_UPGRADE_NAME=
E2E_KAVA_UPGRADE_NAME=v0.25.0-testing
# E2E_KAVA_UPGRADE_HEIGHT is the height at which the upgrade will be applied.
# If IBC tests are enabled this should be >30. Otherwise, this should be >10.
E2E_KAVA_UPGRADE_HEIGHT=
E2E_KAVA_UPGRADE_HEIGHT=35
# E2E_KAVA_UPGRADE_BASE_IMAGE_TAG is the tag of the docker image the chain should upgrade from.
E2E_KAVA_UPGRADE_BASE_IMAGE_TAG=
E2E_KAVA_UPGRADE_BASE_IMAGE_TAG=v0.24.0
# E2E_KAVA_ERC20_ADDRESS is the address of a pre-deployed ERC20 token with the following properties:
# - the E2E_KAVA_FUNDED_ACCOUNT_MNEMONIC has nonzero balance

View File

@ -0,0 +1,256 @@
package e2e_test
import (
"context"
"fmt"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/tests/util"
communitytypes "github.com/kava-labs/kava/x/community/types"
kavadisttypes "github.com/kava-labs/kava/x/kavadist/types"
)
func (suite *IntegrationTestSuite) TestUpgradeCommunityParams() {
suite.SkipIfUpgradeDisabled()
beforeUpgradeCtx := util.CtxAtHeight(suite.UpgradeHeight - 1)
afterUpgradeCtx := util.CtxAtHeight(suite.UpgradeHeight)
// Before params
kavaDistParamsBefore, err := suite.Kava.Kavadist.Params(beforeUpgradeCtx, &kavadisttypes.QueryParamsRequest{})
suite.NoError(err)
mintParamsBefore, err := suite.Kava.Mint.Params(beforeUpgradeCtx, &minttypes.QueryParamsRequest{})
suite.NoError(err)
// Before parameters
suite.Run("x/community and x/kavadist parameters before upgrade", func() {
_, err = suite.Kava.Community.Params(beforeUpgradeCtx, &communitytypes.QueryParamsRequest{})
suite.Error(err, "x/community should not have params before upgrade")
suite.Require().True(
kavaDistParamsBefore.Params.Active,
"x/kavadist should be active before upgrade",
)
suite.Require().True(
mintParamsBefore.Params.InflationMax.IsPositive(),
"x/mint inflation max should be positive before upgrade",
)
suite.Require().True(
mintParamsBefore.Params.InflationMin.IsPositive(),
"x/mint inflation min should be positive before upgrade",
)
})
// After upgrade, Before switchover - parameters
suite.Run("x/kavadist, x/mint, x/community parameters after upgrade, before switchover", func() {
kavaDistParamsAfter, err := suite.Kava.Kavadist.Params(afterUpgradeCtx, &kavadisttypes.QueryParamsRequest{})
suite.NoError(err)
mintParamsAfter, err := suite.Kava.Mint.Params(afterUpgradeCtx, &minttypes.QueryParamsRequest{})
suite.NoError(err)
communityParamsAfter, err := suite.Kava.Community.Params(afterUpgradeCtx, &communitytypes.QueryParamsRequest{})
suite.NoError(err)
suite.Equal(
kavaDistParamsBefore.Params,
kavaDistParamsAfter.Params,
"x/kavadist should be unaffected after upgrade",
)
suite.Equal(
mintParamsBefore.Params,
mintParamsAfter.Params,
"x/mint params should be unaffected after upgrade",
)
expectedParams := app.CommunityParams_E2E
// Make UpgradeTimeDisableInflation match so that we ignore it, because
// referencing app.CommunityParams_E2E in this test files is different
// from the one set in the upgrade handler. At least check that it is
// set to a non-zero value in the assertion below
expectedParams.UpgradeTimeDisableInflation = communityParamsAfter.Params.UpgradeTimeDisableInflation
suite.False(
communityParamsAfter.Params.UpgradeTimeDisableInflation.IsZero(),
"x/community switchover time should be set after upgrade",
)
suite.Equal(
expectedParams,
communityParamsAfter.Params,
"x/community params should be set to E2E params after upgrade",
)
})
suite.Require().Eventually(
func() bool {
// Get x/community for switchover time
params, err := suite.Kava.Community.Params(
context.Background(),
&communitytypes.QueryParamsRequest{},
)
suite.Require().NoError(err)
// Check that switchover time is set to zero, e.g. switchover happened
return params.Params.UpgradeTimeDisableInflation.Equal(time.Time{})
},
20*time.Second, 1*time.Second,
"switchover should happen and x/community params should be updated",
)
// Fetch exact block when inflation stop event emitted
_, switchoverHeight, err := suite.Kava.GetBeginBlockEventsFromQuery(
context.Background(),
fmt.Sprintf(
"%s.%s EXISTS",
communitytypes.EventTypeInflationStop,
communitytypes.AttributeKeyInflationDisableTime,
),
)
suite.Require().NoError(err)
suite.Require().NotZero(switchoverHeight)
beforeSwitchoverCtx := util.CtxAtHeight(switchoverHeight - 1)
afterSwitchoverCtx := util.CtxAtHeight(switchoverHeight)
suite.Run("x/kavadist, x/mint, x/community parameters after upgrade, after switchover", func() {
kavaDistParamsAfter, err := suite.Kava.Kavadist.Params(
afterSwitchoverCtx,
&kavadisttypes.QueryParamsRequest{},
)
suite.NoError(err)
mintParamsAfter, err := suite.Kava.Mint.Params(
afterSwitchoverCtx,
&minttypes.QueryParamsRequest{},
)
suite.NoError(err)
communityParamsAfter, err := suite.Kava.Community.Params(
afterSwitchoverCtx,
&communitytypes.QueryParamsRequest{},
)
suite.NoError(err)
suite.False(
kavaDistParamsAfter.Params.Active,
"x/kavadist should be disabled after upgrade",
)
suite.True(
mintParamsAfter.Params.InflationMax.IsZero(),
"x/mint inflation max should be zero after switchover",
)
suite.True(
mintParamsAfter.Params.InflationMin.IsZero(),
"x/mint inflation min should be zero after switchover",
)
suite.Equal(
time.Time{},
communityParamsAfter.Params.UpgradeTimeDisableInflation,
"x/community switchover time should be reset",
)
suite.Equal(
communityParamsAfter.Params.UpgradeTimeSetStakingRewardsPerSecond,
communityParamsAfter.Params.StakingRewardsPerSecond,
"x/community staking rewards per second should match upgrade time staking rewards per second",
)
})
suite.Run("x/kavadist, x/distribution, x/community balances after switchover", func() {
// Before balances - community pool fund consolidation
kavaDistBalBefore, err := suite.Kava.Kavadist.Balance(
beforeSwitchoverCtx,
&kavadisttypes.QueryBalanceRequest{},
)
suite.NoError(err)
distrBalBefore, err := suite.Kava.Distribution.CommunityPool(
beforeSwitchoverCtx,
&distrtypes.QueryCommunityPoolRequest{},
)
suite.NoError(err)
distrBalCoinsBefore, distrDustBefore := distrBalBefore.Pool.TruncateDecimal()
beforeCommPoolBalance, err := suite.Kava.Community.Balance(
beforeSwitchoverCtx,
&communitytypes.QueryBalanceRequest{},
)
suite.NoError(err)
// After balances
kavaDistBalAfter, err := suite.Kava.Kavadist.Balance(
afterSwitchoverCtx,
&kavadisttypes.QueryBalanceRequest{},
)
suite.NoError(err)
distrBalAfter, err := suite.Kava.Distribution.CommunityPool(
afterSwitchoverCtx,
&distrtypes.QueryCommunityPoolRequest{},
)
suite.NoError(err)
afterCommPoolBalance, err := suite.Kava.Community.Balance(
afterSwitchoverCtx,
&communitytypes.QueryBalanceRequest{},
)
suite.NoError(err)
expectedKavadistBal := sdk.NewCoins(sdk.NewCoin(
"ukava",
kavaDistBalBefore.Coins.AmountOf("ukava"),
))
suite.Equal(
expectedKavadistBal,
kavaDistBalAfter.Coins,
"x/kavadist balance should persist the ukava amount and move all other funds",
)
expectedKavadistTransferred := kavaDistBalBefore.Coins.Sub(expectedKavadistBal...)
// very low ukava balance after (ignoring dust in x/distribution)
// a small amount of tx fees can still end up here.
// dust should stay in x/distribution, but may not be the same so it's unchecked
distrCoinsAfter, distrDustAfter := distrBalAfter.Pool.TruncateDecimal()
suite.Empty(distrCoinsAfter, "expected no coins in x/distribution community pool")
// Fetch block results for paid staking rewards in the block
blockRes, err := suite.Kava.TmSignClient.BlockResults(
context.Background(),
&switchoverHeight,
)
suite.Require().NoError(err)
stakingRewardPaidEvents := util.FilterEventsByType(
blockRes.BeginBlockEvents,
communitytypes.EventTypeStakingRewardsPaid,
)
suite.Require().Len(stakingRewardPaidEvents, 1, "there should be only 1 staking reward paid event")
stakingRewardAmount := sdk.NewCoins()
for _, attr := range stakingRewardPaidEvents[0].Attributes {
if string(attr.Key) == communitytypes.AttributeKeyStakingRewardAmount {
stakingRewardAmount, err = sdk.ParseCoinsNormalized(string(attr.Value))
suite.Require().NoError(err)
break
}
}
expectedCommunityBal := beforeCommPoolBalance.Coins.
Add(distrBalCoinsBefore...).
Add(expectedKavadistTransferred...).
Sub(stakingRewardAmount...) // Remove staking rewards paid in the block
// x/kavadist and x/distribution community pools should be moved to x/community
suite.Equal(
expectedCommunityBal,
afterCommPoolBalance.Coins,
)
suite.Equal(
distrDustBefore,
distrDustAfter,
"x/distribution community pool dust should be unchanged",
)
})
}

View File

@ -0,0 +1,291 @@
package e2e_test
import (
"context"
"fmt"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
query "github.com/cosmos/cosmos-sdk/types/query"
"github.com/cosmos/cosmos-sdk/x/authz"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
"github.com/kava-labs/kava/tests/e2e/testutil"
"github.com/kava-labs/kava/tests/util"
)
const (
govModuleAcc = "kava10d07y265gmmuvt4z0w9aw880jnsr700jxh8cq5"
communityModuleAcc = "kava17d2wax0zhjrrecvaszuyxdf5wcu5a0p4qlx3t5"
kavadistModuleAcc = "kava1cj7njkw2g9fqx4e768zc75dp9sks8u9znxrf0w"
)
func (suite *IntegrationTestSuite) TestGovParamChanges() {
suite.SkipIfUpgradeDisabled()
beforeUpgradeCtx := util.CtxAtHeight(suite.UpgradeHeight - 1)
afterUpgradeCtx := util.CtxAtHeight(suite.UpgradeHeight)
// fetch gov parameters before upgrade
govBeforeParams, err := suite.Kava.Gov.Params(beforeUpgradeCtx, &govv1.QueryParamsRequest{ParamsType: "tallying"})
suite.Require().NoError(err)
// assert expected gov quorum before upgrade
suite.NotEqual(govBeforeParams.TallyParams.Quorum, "0.200000000000000000")
govAfterParams, err := suite.Kava.Gov.Params(afterUpgradeCtx, &govv1.QueryParamsRequest{ParamsType: "tallying"})
suite.Require().NoError(err)
// assert expected gov quorum after upgrade
suite.Equal(govAfterParams.TallyParams.Quorum, "0.200000000000000000")
}
func (suite *IntegrationTestSuite) TestAuthzParamChanges() {
suite.SkipIfUpgradeDisabled()
beforeUpgradeCtx := util.CtxAtHeight(suite.UpgradeHeight - 1)
afterUpgradeCtx := util.CtxAtHeight(suite.UpgradeHeight)
// fetch authz grants before upgrade
authzBeforeGrants, err := suite.Kava.Authz.Grants(beforeUpgradeCtx, &authz.QueryGrantsRequest{Granter: kavadistModuleAcc, Grantee: govModuleAcc, Pagination: &query.PageRequest{Limit: 1000, CountTotal: true}})
suite.Require().NoError(err)
suite.Require().Equal(authzBeforeGrants.Pagination.Total, uint64(len(authzBeforeGrants.Grants)), "expected all grants to have been requested")
// no kavadist -> gov grants
suite.Equal(0, len(authzBeforeGrants.Grants))
// fetch authz grants after upgrade
authzAfterGrants, err := suite.Kava.Authz.Grants(afterUpgradeCtx, &authz.QueryGrantsRequest{Granter: kavadistModuleAcc, Grantee: govModuleAcc, Pagination: &query.PageRequest{Limit: 1000, CountTotal: true}})
suite.Require().NoError(err)
suite.Require().Equal(authzAfterGrants.Pagination.Total, uint64(len(authzAfterGrants.Grants)), "expected all grants to have been requested")
// one kavadist -> gov grants
suite.Require().Equal(1, len(authzAfterGrants.Grants))
grant := authzAfterGrants.Grants[0]
var authorization authz.Authorization
suite.Kava.EncodingConfig.Marshaler.UnpackAny(grant.Authorization, &authorization)
genericAuthorization, ok := authorization.(*authz.GenericAuthorization)
suite.Require().True(ok, "expected generic authorization")
// kavadist allows gov to MsgSend it's funds
suite.Equal(sdk.MsgTypeURL(&banktypes.MsgSend{}), genericAuthorization.Msg)
// no expiration
var expectedExpiration *time.Time
suite.Equal(expectedExpiration, grant.Expiration)
}
func (suite *IntegrationTestSuite) TestModuleAccountGovTransfers() {
suite.SkipIfUpgradeDisabled()
suite.SkipIfKvtoolDisabled()
// the module account (authority) that executes the transfers
govAcc := sdk.MustAccAddressFromBech32(govModuleAcc)
// module accounts for gov transfer test cases
communityAcc := sdk.MustAccAddressFromBech32(communityModuleAcc)
kavadistAcc := sdk.MustAccAddressFromBech32(kavadistModuleAcc)
testCases := []struct {
name string
sender sdk.AccAddress
receiver sdk.AccAddress
amount sdk.Coin
}{
{
name: "transfer from community to kavadist for incentive rewards",
sender: communityAcc,
receiver: kavadistAcc,
amount: ukava(100e6),
},
{
name: "transfer from kavadist to community",
sender: kavadistAcc,
receiver: communityAcc,
amount: ukava(50e6),
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
// create msg exec for transfer between modules
msg := banktypes.NewMsgSend(
tc.sender,
tc.receiver,
sdk.NewCoins(tc.amount),
)
execMsg := authz.NewMsgExec(govAcc, []sdk.Msg{msg})
// ensure proposal passes
passBlock := suite.submitAndPassProposal([]sdk.Msg{&execMsg})
transfers := suite.getBankTransferAmountAtBlock(passBlock, tc.sender, tc.receiver)
suite.Require().Containsf(
transfers,
tc.amount,
"expected transfer of %s to be included in bank transfer events: %s",
tc.amount,
transfers,
)
})
}
}
func (suite *IntegrationTestSuite) submitAndPassProposal(msgs []sdk.Msg) int64 {
govParamsRes, err := suite.Kava.Gov.Params(context.Background(), &govv1.QueryParamsRequest{
ParamsType: govv1.ParamDeposit,
})
suite.NoError(err)
kavaAcc := suite.Kava.GetAccount(testutil.FundedAccountName)
proposalMsg, err := govv1.NewMsgSubmitProposal(
msgs,
govParamsRes.DepositParams.MinDeposit,
kavaAcc.SdkAddress.String(),
"",
)
suite.NoError(err)
gasLimit := 1e6
fee := ukava(1000)
req := util.KavaMsgRequest{
Msgs: []sdk.Msg{proposalMsg},
GasLimit: uint64(gasLimit),
FeeAmount: sdk.NewCoins(fee),
Memo: "this is a proposal please accept me",
}
res := kavaAcc.SignAndBroadcastKavaTx(req)
suite.Require().NoError(res.Err)
// Wait for proposal to be submitted
txRes, err := util.WaitForSdkTxCommit(suite.Kava.Tx, res.Result.TxHash, 6*time.Second)
suite.Require().NoError(err)
var govRes govv1.MsgSubmitProposalResponse
suite.decodeTxMsgResponse(txRes, &govRes)
// 2. Vote for proposal from whale account
whale := suite.Kava.GetAccount(testutil.FundedAccountName)
voteMsg := govv1.NewMsgVote(
whale.SdkAddress,
govRes.ProposalId,
govv1.OptionYes,
"",
)
voteReq := util.KavaMsgRequest{
Msgs: []sdk.Msg{voteMsg},
GasLimit: uint64(gasLimit),
FeeAmount: sdk.NewCoins(fee),
Memo: "voting",
}
voteRes := whale.SignAndBroadcastKavaTx(voteReq)
suite.Require().NoError(voteRes.Err)
_, err = util.WaitForSdkTxCommit(suite.Kava.Tx, voteRes.Result.TxHash, 6*time.Second)
suite.Require().NoError(err)
// 3. Wait until proposal passes
suite.Require().Eventually(func() bool {
proposalRes, err := suite.Kava.Gov.Proposal(context.Background(), &govv1.QueryProposalRequest{
ProposalId: govRes.ProposalId,
})
suite.NoError(err)
switch status := proposalRes.Proposal.Status; status {
case govv1.StatusDepositPeriod, govv1.StatusVotingPeriod:
return false
case govv1.StatusPassed:
return true
case govv1.StatusFailed, govv1.StatusRejected:
suite.Failf("proposal failed", "proposal failed with status %s", status.String())
return true
}
return false
}, 60*time.Second, 1*time.Second)
page := 1
perPage := 100
// Get the block the proposal was passed in
passBlock, err := suite.Kava.TmSignClient.BlockSearch(
context.Background(),
fmt.Sprintf(
"active_proposal.proposal_result = 'proposal_passed' AND active_proposal.proposal_id = %d",
govRes.ProposalId,
),
&page,
&perPage,
"asc",
)
suite.Require().NoError(err)
suite.Require().Equal(1, len(passBlock.Blocks), "passed proposal should be searchable")
return passBlock.Blocks[len(passBlock.Blocks)-1].Block.Height
}
// getBankTransferAmountAtBlock returns the amount of coins transferred between
// the given accounts in the block at the given height. Note that this returns
// a slice of sdk.Coin that can contain multiple coins of the SAME denom -- ie. NOT sdk.Coins
func (suite *IntegrationTestSuite) getBankTransferAmountAtBlock(
blockHeight int64,
sender sdk.AccAddress,
receiver sdk.AccAddress,
) []sdk.Coin {
// Fetch block results for paid staking rewards in the block
blockRes, err := suite.Kava.TmSignClient.BlockResults(
context.Background(),
&blockHeight,
)
suite.Require().NoError(err)
transferEvents := util.FilterEventsByType(
blockRes.EndBlockEvents, // gov proposals applied in EndBlocker
banktypes.EventTypeTransfer,
)
suite.Require().NotEmpty(transferEvents, "there should be at least 1 bank transfer event")
transfers := []sdk.Coin{}
event:
for _, event := range transferEvents {
if event.Type != banktypes.EventTypeTransfer {
suite.FailNowf(
"unexpected event type %s in block results",
event.Type,
)
}
for _, attr := range event.Attributes {
suite.T().Logf("event attr: %s = %s", string(attr.Key), string(attr.Value))
if string(attr.Key) == banktypes.AttributeKeyRecipient {
if string(attr.Value) != receiver.String() {
continue event
}
}
if string(attr.Key) == banktypes.AttributeKeySender {
if string(attr.Value) != sender.String() {
continue event
}
}
if string(attr.Key) == sdk.AttributeKeyAmount {
amount, err := sdk.ParseCoinNormalized(string(attr.Value))
suite.Require().NoError(err)
transfers = append(transfers, amount)
}
}
}
return transfers
}

View File

@ -0,0 +1,65 @@
package e2e_test
import (
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/tests/util"
incentivetypes "github.com/kava-labs/kava/x/incentive/types"
)
func (suite *IntegrationTestSuite) TestUpgradeIncentiveParams() {
suite.SkipIfUpgradeDisabled()
beforeUpgradeCtx := util.CtxAtHeight(suite.UpgradeHeight - 1)
afterUpgradeCtx := util.CtxAtHeight(suite.UpgradeHeight)
// Before params
incentiveParamsBefore, err := suite.Kava.Incentive.Params(
beforeUpgradeCtx,
&incentivetypes.QueryParamsRequest{},
)
suite.NoError(err)
incentiveParamsAfter, err := suite.Kava.Incentive.Params(
afterUpgradeCtx,
&incentivetypes.QueryParamsRequest{},
)
suite.NoError(err)
suite.Run("x/incentive parameters before upgrade", func() {
suite.Require().Len(
incentiveParamsBefore.Params.EarnRewardPeriods,
1,
"x/incentive should have 1 earn reward period before upgrade",
)
suite.Require().Equal(
sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(159_459))),
incentiveParamsBefore.Params.EarnRewardPeriods[0].RewardsPerSecond,
)
})
suite.Run("x/incentive parameters after upgrade", func() {
suite.Require().Len(
incentiveParamsAfter.Params.EarnRewardPeriods,
1,
"x/incentive should have 1 earn reward period before upgrade",
)
suite.Require().Equal(
// Manual calculation of
// 600,000 * 1000,000 / (365 * 24 * 60 * 60)
sdk.NewCoins(sdk.NewCoin("ukava", sdkmath.NewInt(19025))),
incentiveParamsAfter.Params.EarnRewardPeriods[0].RewardsPerSecond,
)
// No other changes
incentiveParamsAfter.Params.EarnRewardPeriods[0].RewardsPerSecond = incentiveParamsBefore.Params.EarnRewardPeriods[0].RewardsPerSecond
suite.Require().Equal(
incentiveParamsBefore,
incentiveParamsAfter,
"other param values should not be changed",
)
})
}

View File

@ -0,0 +1,474 @@
package e2e_test
import (
"context"
"fmt"
"strconv"
"time"
sdkmath "cosmossdk.io/math"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank/types"
distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
abci "github.com/tendermint/tendermint/abci/types"
coretypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/kava-labs/kava/tests/util"
communitytypes "github.com/kava-labs/kava/x/community/types"
kavadisttypes "github.com/kava-labs/kava/x/kavadist/types"
)
func (suite *IntegrationTestSuite) TestUpgradeInflation_Disable() {
suite.SkipIfUpgradeDisabled()
afterUpgradeCtx := util.CtxAtHeight(suite.UpgradeHeight)
// Get x/community for switchover time
params, err := suite.Kava.Community.Params(afterUpgradeCtx, &communitytypes.QueryParamsRequest{})
suite.Require().NoError(err)
// Sleep until switchover time + 6 seconds for extra block
sleepDuration := time.Until(params.Params.UpgradeTimeDisableInflation.Add(6 * time.Second))
time.Sleep(sleepDuration)
suite.Require().Eventually(func() bool {
communityParams, err := suite.Kava.Community.Params(afterUpgradeCtx, &communitytypes.QueryParamsRequest{})
suite.Require().NoError(err)
// After params are set in x/community -- non-zero switchover time
return !communityParams.Params.UpgradeTimeDisableInflation.Equal(time.Time{})
}, 20*time.Second, 3*time.Second)
// Fetch exact block when inflation stop event emitted
// This is run after the switchover, so we don't need to poll
_, switchoverHeight, err := suite.Kava.GetBeginBlockEventsFromQuery(
context.Background(),
fmt.Sprintf(
"%s.%s EXISTS",
communitytypes.EventTypeInflationStop,
communitytypes.AttributeKeyInflationDisableTime,
),
)
suite.Require().NoError(err)
suite.Require().NotZero(switchoverHeight)
// 1 block before switchover
beforeSwitchoverCtx := util.CtxAtHeight(switchoverHeight - 1)
afterSwitchoverCtx := util.CtxAtHeight(switchoverHeight)
suite.Run("x/mint, x/kavadist inflation before switchover", func() {
mintParams, err := suite.Kava.Mint.Params(
beforeSwitchoverCtx,
&minttypes.QueryParamsRequest{},
)
suite.NoError(err)
kavaDistParams, err := suite.Kava.Kavadist.Params(
beforeSwitchoverCtx,
&kavadisttypes.QueryParamsRequest{},
)
suite.NoError(err)
// Use .String() to compare Decs since x/mint uses the deprecated one,
// mismatch of types but same value.
suite.Equal(
sdkmath.LegacyMustNewDecFromStr("0.595000000000000000").String(),
mintParams.Params.InflationMin.String(),
"x/mint inflation min should be 59.5%% before switchover",
)
suite.Equal(
sdkmath.LegacyMustNewDecFromStr("0.595000000000000000").String(),
mintParams.Params.InflationMax.String(),
"x/mint inflation max should be 59.5%% before switchover",
)
suite.True(
kavaDistParams.Params.Active,
"x/kavadist should be active before switchover",
)
})
suite.Run("x/distribution community tax before switchover", func() {
distrParams, err := suite.Kava.Distribution.Params(
beforeSwitchoverCtx,
&distributiontypes.QueryParamsRequest{},
)
suite.NoError(err)
suite.Equal(
sdkmath.LegacyMustNewDecFromStr("0.949500000000000000").String(),
distrParams.Params.CommunityTax.String(),
"x/distribution community tax should be 94.95%% before switchover",
)
})
suite.Run("x/mint, x/kavadist inflation after switchover", func() {
mintParams, err := suite.Kava.Mint.Params(
afterSwitchoverCtx,
&minttypes.QueryParamsRequest{},
)
suite.NoError(err)
kavaDistParams, err := suite.Kava.Kavadist.Params(
afterSwitchoverCtx,
&kavadisttypes.QueryParamsRequest{},
)
suite.NoError(err)
suite.Equal(
sdkmath.LegacyZeroDec().String(),
mintParams.Params.InflationMin.String(),
"x/mint inflation min should be 0% after switchover",
)
suite.Equal(
sdkmath.LegacyZeroDec().String(),
mintParams.Params.InflationMax.String(),
"x/mint inflation max should be 0% after switchover",
)
suite.False(
kavaDistParams.Params.Active,
"x/kavadist should be inactive after switchover",
)
})
suite.Run("x/distribution community tax after switchover", func() {
distrParams, err := suite.Kava.Distribution.Params(
afterSwitchoverCtx,
&distributiontypes.QueryParamsRequest{},
)
suite.NoError(err)
suite.Equal(
sdkmath.LegacyZeroDec().String(),
distrParams.Params.CommunityTax.String(),
"x/distribution community tax should be 0%% before switchover",
)
})
// Ensure inflation was still active before switchover
suite.Run("positive mint events before switchover", func() {
// 1 block before switchover
queryHeight := switchoverHeight - 1
block, err := suite.Kava.TmSignClient.BlockResults(
context.Background(),
&queryHeight,
)
suite.Require().NoError(err)
// Mint events should only occur in begin block
mintEvents := util.FilterEventsByType(block.BeginBlockEvents, minttypes.EventTypeMint)
suite.Require().NotEmpty(mintEvents, "mint events should be emitted")
// Ensure mint amounts are non-zero
found := false
for _, event := range mintEvents {
for _, attribute := range event.Attributes {
// Bonded ratio and annual provisions unchecked
if string(attribute.Key) == minttypes.AttributeKeyInflation {
suite.Equal(
sdkmath.LegacyMustNewDecFromStr("0.595000000000000000").String(),
string(attribute.Value),
"inflation should be 59.5%% before switchover",
)
}
if string(attribute.Key) == sdk.AttributeKeyAmount {
found = true
// Parse as native go int, not necessary to use sdk.Int
value, err := strconv.Atoi(string(attribute.Value))
suite.Require().NoError(err)
suite.NotZero(value, "mint amount should be non-zero")
suite.Positive(value, "mint amount should be positive")
}
}
}
suite.True(found, "mint amount should be found")
})
suite.Run("staking denom supply increases before switchover", func() {
queryHeight := switchoverHeight - 2
supply1, err := suite.Kava.Bank.SupplyOf(
util.CtxAtHeight(queryHeight),
&types.QuerySupplyOfRequest{
Denom: suite.Kava.StakingDenom,
},
)
suite.Require().NoError(err)
suite.NotZero(supply1.Amount, "ukava supply should be non-zero")
// Next block
queryHeight += 1
supply2, err := suite.Kava.Bank.SupplyOf(
util.CtxAtHeight(queryHeight),
&types.QuerySupplyOfRequest{
Denom: suite.Kava.StakingDenom,
},
)
suite.Require().NoError(err)
suite.NotZero(supply2.Amount, "ukava supply should be non-zero")
suite.Truef(
supply2.Amount.Amount.GT(supply1.Amount.Amount),
"ukava supply before switchover should increase between blocks, %s > %s",
supply2.Amount.Amount.String(),
)
})
// Check if inflation is ACTUALLY disabled... check if any coins are being
// minted in the blocks after switchover
suite.Run("no minting after switchover", func() {
kavaSupply := sdk.NewCoin(suite.Kava.StakingDenom, sdkmath.ZeroInt())
// Next 5 blocks after switchover, ensure there's actually no more inflation
for i := 0; i < 5; i++ {
queryHeight := switchoverHeight + int64(i)
suite.Run(
fmt.Sprintf("x/mint events with 0 amount @ height=%d", queryHeight),
func() {
var block *coretypes.ResultBlockResults
suite.Require().Eventually(func() bool {
// Check begin block events
block, err = suite.Kava.TmSignClient.BlockResults(
context.Background(),
&queryHeight,
)
return err == nil
}, 20*time.Second, 3*time.Second)
var mintEvents []abci.Event
// Mint events should only occur in begin block, but we just include
// everything else just in case anything changes in x/mint
mintEventsBegin := util.FilterEventsByType(block.BeginBlockEvents, minttypes.EventTypeMint)
mintEventsEnd := util.FilterEventsByType(block.EndBlockEvents, minttypes.EventTypeMint)
mintEventsTx := util.FilterTxEventsByType(block.TxsResults, minttypes.EventTypeMint)
mintEvents = append(mintEvents, mintEventsBegin...)
mintEvents = append(mintEvents, mintEventsEnd...)
mintEvents = append(mintEvents, mintEventsTx...)
suite.Require().NotEmpty(mintEvents, "mint events should still be emitted")
// Ensure mint amounts are 0
found := false
for _, event := range mintEvents {
for _, attribute := range event.Attributes {
// Bonded ratio and annual provisions unchecked
if string(attribute.Key) == minttypes.AttributeKeyInflation {
suite.Equal(sdkmath.LegacyZeroDec().String(), string(attribute.Value))
}
if string(attribute.Key) == sdk.AttributeKeyAmount {
found = true
suite.Equal(sdkmath.ZeroInt().String(), string(attribute.Value))
}
}
}
suite.True(found, "mint amount should be found")
},
)
// Run this after the events check, since that one waits for the
// new block if necessary
suite.Run(
fmt.Sprintf("total staking denom supply should not change @ height=%d", queryHeight),
func() {
supplyRes, err := suite.Kava.Bank.SupplyOf(
util.CtxAtHeight(queryHeight),
&types.QuerySupplyOfRequest{
Denom: suite.Kava.StakingDenom,
},
)
suite.Require().NoError(err)
if kavaSupply.IsZero() {
// First iteration, set supply
kavaSupply = supplyRes.Amount
} else {
suite.Require().Equal(
kavaSupply,
supplyRes.Amount,
"ukava supply should not change",
)
}
},
)
}
})
suite.Run("no staking rewards from x/community before switchover", func() {
// 1 block before switchover
queryHeight := switchoverHeight - 1
block, err := suite.Kava.TmSignClient.BlockResults(
context.Background(),
&queryHeight,
)
suite.Require().NoError(err)
// Events are not emitted if amount is 0
stakingRewardEvents := util.FilterEventsByType(block.BeginBlockEvents, communitytypes.EventTypeStakingRewardsPaid)
suite.Require().Empty(stakingRewardEvents, "staking reward events should not be emitted")
})
suite.Run("staking rewards pay out from x/community after switchover", func() {
for i := 0; i < 5; i++ {
// after switchover
queryHeight := switchoverHeight + int64(i)
block, err := suite.Kava.TmSignClient.BlockResults(
context.Background(),
&queryHeight,
)
suite.Require().NoError(err)
stakingRewardEvents := util.FilterEventsByType(
block.BeginBlockEvents,
communitytypes.EventTypeStakingRewardsPaid,
)
suite.Require().NotEmptyf(
stakingRewardEvents,
"staking reward events should be emitted at height=%d",
queryHeight,
)
// Ensure amounts are non-zero
found := false
for _, attr := range stakingRewardEvents[0].Attributes {
if string(attr.Key) == communitytypes.AttributeKeyStakingRewardAmount {
coins, err := sdk.ParseCoinNormalized(string(attr.Value))
suite.Require().NoError(err, "staking reward amount should be parsable coins")
suite.Truef(
coins.Amount.IsPositive(),
"staking reward amount should be a positive amount at height=%d",
queryHeight,
)
found = true
}
}
suite.Truef(
found,
"staking reward amount should be found in events at height=%d",
queryHeight,
)
}
})
// Staking rewards can still be claimed
suite.Run("staking rewards claimable after switchover", func() {
suite.SkipIfKvtoolDisabled()
// Get the delegator of the only validator
validators, err := suite.Kava.Staking.Validators(
context.Background(),
&stakingtypes.QueryValidatorsRequest{},
)
suite.Require().NoError(err)
suite.Require().Positive(len(validators.Validators), "should only be at least 1 validator")
valAddr, err := sdk.ValAddressFromBech32(validators.Validators[0].OperatorAddress)
suite.Require().NoError(err)
accAddr := sdk.AccAddress(valAddr.Bytes())
balBefore, err := suite.Kava.Bank.Balance(
context.Background(),
&types.QueryBalanceRequest{
Address: accAddr.String(),
Denom: suite.Kava.StakingDenom,
},
)
suite.Require().NoError(err)
suite.Require().False(balBefore.Balance.IsZero(), "val staking denom balance should be non-zero")
delegationRewards, err := suite.Kava.Distribution.DelegationRewards(
context.Background(),
&distributiontypes.QueryDelegationRewardsRequest{
ValidatorAddress: valAddr.String(),
DelegatorAddress: accAddr.String(),
},
)
suite.Require().NoError(err)
suite.False(delegationRewards.Rewards.Empty())
suite.True(delegationRewards.Rewards.IsAllPositive(), "queried rewards should be positive")
withdrawRewardsMsg := distributiontypes.NewMsgWithdrawDelegatorReward(
accAddr,
valAddr,
)
// Get the validator private key from kava keyring
key, err := suite.Kava.Keyring.(unsafeExporter).ExportPrivateKeyObject(
"validator",
)
suite.Require().NoError(err)
acc := suite.Kava.AddNewSigningAccountFromPrivKey(
"validator",
key,
"",
suite.Kava.ChainID,
)
gasLimit := int64(2e5)
fee := ukava(200)
req := util.KavaMsgRequest{
Msgs: []sdk.Msg{withdrawRewardsMsg},
GasLimit: uint64(gasLimit),
FeeAmount: sdk.NewCoins(fee),
Memo: "give me my money",
}
res := acc.SignAndBroadcastKavaTx(req)
_, err = util.WaitForSdkTxCommit(suite.Kava.Tx, res.Result.TxHash, 6*time.Second)
suite.Require().NoError(err)
balAfter, err := suite.Kava.Bank.Balance(
context.Background(),
&types.QueryBalanceRequest{
Address: accAddr.String(),
Denom: suite.Kava.StakingDenom,
},
)
suite.Require().NoError(err)
suite.Require().False(balAfter.Balance.IsZero(), "val staking denom balance should be non-zero")
balIncrease := balAfter.Balance.
Sub(*balBefore.Balance).
Add(res.Tx.GetFee()[0]) // Add the fee back to balance to compare actual balances
queriedRewardsCoins, _ := delegationRewards.Rewards.TruncateDecimal()
suite.Require().Truef(
queriedRewardsCoins.AmountOf(suite.Kava.StakingDenom).
LTE(balIncrease.Amount),
"claimed rewards should be >= queried delegation rewards, got claimed %s vs queried %s",
balIncrease.Amount.String(),
queriedRewardsCoins.AmountOf(suite.Kava.StakingDenom).String(),
)
})
}
// unsafeExporter is implemented by key stores that support unsafe export
// of private keys' material.
type unsafeExporter interface {
// ExportPrivateKeyObject returns a private key in unarmored format.
ExportPrivateKeyObject(uid string) (cryptotypes.PrivKey, error)
}

View File

@ -0,0 +1,103 @@
package e2e_test
import (
"context"
sdkmath "cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/client/grpc/tmservice"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/kava-labs/kava/tests/util"
)
func (suite *IntegrationTestSuite) TestValMinCommission() {
suite.SkipIfUpgradeDisabled()
beforeUpgradeCtx := util.CtxAtHeight(suite.UpgradeHeight - 1)
afterUpgradeCtx := util.CtxAtHeight(suite.UpgradeHeight)
suite.Run("before upgrade", func() {
// Before params
beforeParams, err := suite.Kava.Staking.Params(beforeUpgradeCtx, &types.QueryParamsRequest{})
suite.Require().NoError(err)
suite.Require().Equal(
sdkmath.LegacyZeroDec().String(),
beforeParams.Params.MinCommissionRate.String(),
"min commission rate should be 0%% before upgrade",
)
// Before validators
beforeValidators, err := suite.Kava.Staking.Validators(beforeUpgradeCtx, &types.QueryValidatorsRequest{})
suite.Require().NoError(err)
for _, val := range beforeValidators.Validators {
// In kvtool gentx, the commission rate is set to 0, with max of 0.01
expectedRate := sdkmath.LegacyZeroDec()
expectedRateMax := sdkmath.LegacyMustNewDecFromStr("0.01")
suite.Require().Equalf(
expectedRate.String(),
val.Commission.CommissionRates.Rate.String(),
"validator %s should have commission rate of %s before upgrade",
val.OperatorAddress,
expectedRate,
)
suite.Require().Equalf(
expectedRateMax.String(),
val.Commission.CommissionRates.MaxRate.String(),
"validator %s should have max commission rate of %s before upgrade",
val.OperatorAddress,
expectedRateMax,
)
}
})
suite.Run("after upgrade", func() {
block, err := suite.Kava.Tm.GetBlockByHeight(context.Background(), &tmservice.GetBlockByHeightRequest{
Height: suite.UpgradeHeight,
})
suite.Require().NoError(err)
// After params
afterParams, err := suite.Kava.Staking.Params(afterUpgradeCtx, &types.QueryParamsRequest{})
suite.Require().NoError(err)
expectedMinRate := sdk.MustNewDecFromStr("0.05")
suite.Require().Equal(
expectedMinRate.String(),
afterParams.Params.MinCommissionRate.String(),
"min commission rate should be 5%% after upgrade",
)
// After validators
afterValidators, err := suite.Kava.Staking.Validators(afterUpgradeCtx, &types.QueryValidatorsRequest{})
suite.Require().NoError(err)
for _, val := range afterValidators.Validators {
suite.Require().Truef(
val.Commission.CommissionRates.Rate.GTE(expectedMinRate),
"validator %s should have commission rate of at least 5%%",
val.OperatorAddress,
)
suite.Require().Truef(
val.Commission.CommissionRates.MaxRate.GTE(expectedMinRate),
"validator %s should have max commission rate of at least 5%%",
val.OperatorAddress,
)
suite.Require().Truef(
val.Commission.UpdateTime.Equal(block.SdkBlock.Header.Time),
"validator %s should have commission update time set to block time, expected %s, got %s",
val.OperatorAddress,
block.SdkBlock.Header.Time,
val.Commission.UpdateTime,
)
}
})
}

View File

@ -14,6 +14,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
authz "github.com/cosmos/cosmos-sdk/x/authz"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
govv1types "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
@ -39,6 +40,7 @@ import (
communitytypes "github.com/kava-labs/kava/x/community/types"
earntypes "github.com/kava-labs/kava/x/earn/types"
evmutiltypes "github.com/kava-labs/kava/x/evmutil/types"
incentivetypes "github.com/kava-labs/kava/x/incentive/types"
kavadisttypes "github.com/kava-labs/kava/x/kavadist/types"
)
@ -58,11 +60,13 @@ type Chain struct {
EncodingConfig kavaparams.EncodingConfig
Auth authtypes.QueryClient
Authz authz.QueryClient
Bank banktypes.QueryClient
Cdp cdptypes.QueryClient
Committee committeetypes.QueryClient
Community communitytypes.QueryClient
Distribution distrtypes.QueryClient
Incentive incentivetypes.QueryClient
Kavadist kavadisttypes.QueryClient
Earn earntypes.QueryClient
Evm evmtypes.QueryClient
@ -120,11 +124,13 @@ func NewChain(t *testing.T, details *runner.ChainDetails, fundedAccountMnemonic
}
chain.Auth = authtypes.NewQueryClient(grpcConn)
chain.Authz = authz.NewQueryClient(grpcConn)
chain.Bank = banktypes.NewQueryClient(grpcConn)
chain.Cdp = cdptypes.NewQueryClient(grpcConn)
chain.Committee = committeetypes.NewQueryClient(grpcConn)
chain.Community = communitytypes.NewQueryClient(grpcConn)
chain.Distribution = distrtypes.NewQueryClient(grpcConn)
chain.Incentive = incentivetypes.NewQueryClient(grpcConn)
chain.Kavadist = kavadisttypes.NewQueryClient(grpcConn)
chain.Earn = earntypes.NewQueryClient(grpcConn)
chain.Evm = evmtypes.NewQueryClient(grpcConn)
@ -224,6 +230,21 @@ func (chain *Chain) QuerySdkForBalances(addr sdk.AccAddress) sdk.Coins {
return res.Balances
}
// QuerySdkForBalancesAtHeight gets the balance of a particular address on this Chain, at the specified height.
func (chain *Chain) QuerySdkForBalancesAtHeight(
height int64,
addr sdk.AccAddress,
) sdk.Coins {
res, err := chain.Bank.AllBalances(
util.CtxAtHeight(height),
&banktypes.QueryAllBalancesRequest{
Address: addr.String(),
},
)
require.NoError(chain.t, err)
return res.Balances
}
// GetModuleBalances returns the balance of a requested module account
func (chain *Chain) GetModuleBalances(moduleName string) sdk.Coins {
addr := authtypes.NewModuleAddress(moduleName)