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 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. # 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 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 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. # 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. # 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 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: # 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 # - 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" sdk "github.com/cosmos/cosmos-sdk/types"
txtypes "github.com/cosmos/cosmos-sdk/types/tx" txtypes "github.com/cosmos/cosmos-sdk/types/tx"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 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" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
govv1types "github.com/cosmos/cosmos-sdk/x/gov/types/v1" govv1types "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
@ -39,6 +40,7 @@ import (
communitytypes "github.com/kava-labs/kava/x/community/types" communitytypes "github.com/kava-labs/kava/x/community/types"
earntypes "github.com/kava-labs/kava/x/earn/types" earntypes "github.com/kava-labs/kava/x/earn/types"
evmutiltypes "github.com/kava-labs/kava/x/evmutil/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" kavadisttypes "github.com/kava-labs/kava/x/kavadist/types"
) )
@ -58,11 +60,13 @@ type Chain struct {
EncodingConfig kavaparams.EncodingConfig EncodingConfig kavaparams.EncodingConfig
Auth authtypes.QueryClient Auth authtypes.QueryClient
Authz authz.QueryClient
Bank banktypes.QueryClient Bank banktypes.QueryClient
Cdp cdptypes.QueryClient Cdp cdptypes.QueryClient
Committee committeetypes.QueryClient Committee committeetypes.QueryClient
Community communitytypes.QueryClient Community communitytypes.QueryClient
Distribution distrtypes.QueryClient Distribution distrtypes.QueryClient
Incentive incentivetypes.QueryClient
Kavadist kavadisttypes.QueryClient Kavadist kavadisttypes.QueryClient
Earn earntypes.QueryClient Earn earntypes.QueryClient
Evm evmtypes.QueryClient Evm evmtypes.QueryClient
@ -120,11 +124,13 @@ func NewChain(t *testing.T, details *runner.ChainDetails, fundedAccountMnemonic
} }
chain.Auth = authtypes.NewQueryClient(grpcConn) chain.Auth = authtypes.NewQueryClient(grpcConn)
chain.Authz = authz.NewQueryClient(grpcConn)
chain.Bank = banktypes.NewQueryClient(grpcConn) chain.Bank = banktypes.NewQueryClient(grpcConn)
chain.Cdp = cdptypes.NewQueryClient(grpcConn) chain.Cdp = cdptypes.NewQueryClient(grpcConn)
chain.Committee = committeetypes.NewQueryClient(grpcConn) chain.Committee = committeetypes.NewQueryClient(grpcConn)
chain.Community = communitytypes.NewQueryClient(grpcConn) chain.Community = communitytypes.NewQueryClient(grpcConn)
chain.Distribution = distrtypes.NewQueryClient(grpcConn) chain.Distribution = distrtypes.NewQueryClient(grpcConn)
chain.Incentive = incentivetypes.NewQueryClient(grpcConn)
chain.Kavadist = kavadisttypes.NewQueryClient(grpcConn) chain.Kavadist = kavadisttypes.NewQueryClient(grpcConn)
chain.Earn = earntypes.NewQueryClient(grpcConn) chain.Earn = earntypes.NewQueryClient(grpcConn)
chain.Evm = evmtypes.NewQueryClient(grpcConn) chain.Evm = evmtypes.NewQueryClient(grpcConn)
@ -224,6 +230,21 @@ func (chain *Chain) QuerySdkForBalances(addr sdk.AccAddress) sdk.Coins {
return res.Balances 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 // GetModuleBalances returns the balance of a requested module account
func (chain *Chain) GetModuleBalances(moduleName string) sdk.Coins { func (chain *Chain) GetModuleBalances(moduleName string) sdk.Coins {
addr := authtypes.NewModuleAddress(moduleName) addr := authtypes.NewModuleAddress(moduleName)