From d546f7226b9290a803842284a5efff9b708fa8c9 Mon Sep 17 00:00:00 2001 From: sampocs Date: Wed, 13 Dec 2023 20:46:37 -0600 Subject: [PATCH 01/19] added shell for upgrade handler --- app/upgrades.go | 13 ++++++ app/upgrades/v17/upgrades.go | 75 +++++++++++++++++++++++++++++++ app/upgrades/v17/upgrades_test.go | 24 ++++++++++ 3 files changed, 112 insertions(+) create mode 100644 app/upgrades/v17/upgrades.go create mode 100644 app/upgrades/v17/upgrades_test.go diff --git a/app/upgrades.go b/app/upgrades.go index ecc9c3472c..03bad9077c 100644 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -20,6 +20,7 @@ import ( v14 "github.com/Stride-Labs/stride/v16/app/upgrades/v14" v15 "github.com/Stride-Labs/stride/v16/app/upgrades/v15" v16 "github.com/Stride-Labs/stride/v16/app/upgrades/v16" + v17 "github.com/Stride-Labs/stride/v16/app/upgrades/v17" v2 "github.com/Stride-Labs/stride/v16/app/upgrades/v2" v3 "github.com/Stride-Labs/stride/v16/app/upgrades/v3" v4 "github.com/Stride-Labs/stride/v16/app/upgrades/v4" @@ -216,6 +217,18 @@ func (app *StrideApp) setupUpgradeHandlers(appOpts servertypes.AppOptions) { ), ) + // v17 upgrade handler + app.UpgradeKeeper.SetUpgradeHandler( + v17.UpgradeName, + v17.CreateUpgradeHandler( + app.mm, + app.configurator, + app.InterchainqueryKeeper, + app.RatelimitKeeper, + app.StakeibcKeeper, + ), + ) + upgradeInfo, err := app.UpgradeKeeper.ReadUpgradeInfoFromDisk() if err != nil { panic(fmt.Errorf("Failed to read upgrade info from disk: %w", err)) diff --git a/app/upgrades/v17/upgrades.go b/app/upgrades/v17/upgrades.go new file mode 100644 index 0000000000..ca4507a26d --- /dev/null +++ b/app/upgrades/v17/upgrades.go @@ -0,0 +1,75 @@ +package v17 + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + + icqkeeper "github.com/Stride-Labs/stride/v16/x/interchainquery/keeper" + ratelimitkeeper "github.com/Stride-Labs/stride/v16/x/ratelimit/keeper" + stakeibckeeper "github.com/Stride-Labs/stride/v16/x/stakeibc/keeper" +) + +var ( + UpgradeName = "v17" +) + +// CreateUpgradeHandler creates an SDK upgrade handler for v15 +func CreateUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + icqKeeper icqkeeper.Keeper, + ratelimitKeeper ratelimitkeeper.Keeper, + stakeibcKeeper stakeibckeeper.Keeper, +) upgradetypes.UpgradeHandler { + return func(ctx sdk.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + ctx.Logger().Info("Starting upgrade v17...") + + ctx.Logger().Info("Migrating host zones...") + + ctx.Logger().Info("Deleting all pending queries...") + + ctx.Logger().Info("Reseting slash query in progress...") + + ctx.Logger().Info("Updating community pool tax...") + + ctx.Logger().Info("Updating redemption rate bounds...") + + ctx.Logger().Info("Adding rate limits to Osmosis...") + + return mm.RunMigrations(ctx, configurator, vm) + } +} + +// Migrates the host zones to the new structure which supports community pool liquid staking +// We don't have to perform a true migration here since only new fields were added +// This will also register the relevant community pool ICA addresses +func MigrateHostZones() { + +} + +// Deletes all currently active queries (to remove ones that were stuck) +func DeleteAllPendingQueries() { + +} + +// Resets the slash query in progress flag for each validator +func ResetSlashQueryInProgress() { + +} + +// Increases the community pool tax from 2 to 5% +// This was from prop XXX which passed, but was deleted due to an ICS blacklist +func IncreaseCommunityPoolTax() { + +} + +// Updates the outer redemption rate bounds +func UpdatingRedemptionRateBounds() { + +} + +// Rate limits transfers to osmosis and deletes the injective rate limit +func UpdateRateLimits() { + +} diff --git a/app/upgrades/v17/upgrades_test.go b/app/upgrades/v17/upgrades_test.go new file mode 100644 index 0000000000..2812521a34 --- /dev/null +++ b/app/upgrades/v17/upgrades_test.go @@ -0,0 +1,24 @@ +package v17_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/Stride-Labs/stride/v16/app/apptesting" +) + +type UpgradeTestSuite struct { + apptesting.AppTestHelper +} + +func (s *UpgradeTestSuite) SetupTest() { + s.Setup() +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(UpgradeTestSuite)) +} + +func (s *UpgradeTestSuite) TestUpgrade() { +} From dc607569f5b4d8bf743fb3ec348534992dd90ded Mon Sep 17 00:00:00 2001 From: sampocs Date: Thu, 14 Dec 2023 13:33:13 -0600 Subject: [PATCH 02/19] first pass at upgrade handler --- app/upgrades.go | 1 + app/upgrades/v17/upgrades.go | 227 +++++++++++++++++- .../keeper/msg_server_register_host_zone.go | 9 +- 3 files changed, 223 insertions(+), 14 deletions(-) diff --git a/app/upgrades.go b/app/upgrades.go index 03bad9077c..674b56fdc8 100644 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -223,6 +223,7 @@ func (app *StrideApp) setupUpgradeHandlers(appOpts servertypes.AppOptions) { v17.CreateUpgradeHandler( app.mm, app.configurator, + app.DistrKeeper, app.InterchainqueryKeeper, app.RatelimitKeeper, app.StakeibcKeeper, diff --git a/app/upgrades/v17/upgrades.go b/app/upgrades/v17/upgrades.go index ca4507a26d..fced63664e 100644 --- a/app/upgrades/v17/upgrades.go +++ b/app/upgrades/v17/upgrades.go @@ -1,23 +1,56 @@ package v17 import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types" + connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" + "github.com/Stride-Labs/stride/v16/utils" icqkeeper "github.com/Stride-Labs/stride/v16/x/interchainquery/keeper" ratelimitkeeper "github.com/Stride-Labs/stride/v16/x/ratelimit/keeper" + ratelimittypes "github.com/Stride-Labs/stride/v16/x/ratelimit/types" stakeibckeeper "github.com/Stride-Labs/stride/v16/x/stakeibc/keeper" + stakeibctypes "github.com/Stride-Labs/stride/v16/x/stakeibc/types" ) var ( UpgradeName = "v17" + + OsmosisTransferChannelId = "channel-5" + + CommunityPoolTax = sdk.MustNewDecFromStr("0.05") + + RedemptionRateOuterMinAdjustment = sdk.MustNewDecFromStr("0.05") + RedemptionRateInnerMinAdjustment = sdk.MustNewDecFromStr("0.03") + RedemptionRateInnerMaxAdjustment = sdk.MustNewDecFromStr("0.05") + RedemptionRateOuterMaxAdjustment = sdk.MustNewDecFromStr("0.10") + + UpdatedRateLimits = map[string]sdkmath.Int{ + "comdex-1": sdkmath.ZeroInt(), // TVL: ~130k | <1M | No rate limit + "cosmoshub-4": sdkmath.NewInt(10), // TVL: ~50M | 30M+ | 10% RL + "evmos_9001-2": sdkmath.NewInt(50), // TVL: ~3M | 1M-15M+ | 50% RL + "injective-1": sdkmath.NewInt(50), // TVL: ~3M | 1M-15M+ | 50% RL + "juno-1": sdkmath.NewInt(50), // TVL: ~3M | 1M-15M+ | 50% RL + "osmosis-1": sdkmath.NewInt(25), // TVL: ~30M | 30M+ | 10% RL + "phoenix-1": sdkmath.ZeroInt(), // TVL: ~190k | <1M | No rate limit + "sommelier-3": sdkmath.ZeroInt(), // TVL: ~450k | <1M | No rate limit + "stargaze-1": sdkmath.NewInt(50), // TVL: 1.35M | 1M-15M+ | 50% RL + "umee-1": sdkmath.ZeroInt(), // TVL: ~200k | <1M | No rate limit + } ) // CreateUpgradeHandler creates an SDK upgrade handler for v15 func CreateUpgradeHandler( mm *module.Manager, configurator module.Configurator, + distributionkeeper distributionkeeper.Keeper, icqKeeper icqkeeper.Keeper, ratelimitKeeper ratelimitkeeper.Keeper, stakeibcKeeper stakeibckeeper.Keeper, @@ -26,16 +59,31 @@ func CreateUpgradeHandler( ctx.Logger().Info("Starting upgrade v17...") ctx.Logger().Info("Migrating host zones...") + if err := RegisterCommunityPoolAddresses(ctx, stakeibcKeeper); err != nil { + return vm, errorsmod.Wrapf(err, "unable to migrate host zones") + } ctx.Logger().Info("Deleting all pending queries...") + DeleteAllStaleQueries(ctx, icqKeeper) ctx.Logger().Info("Reseting slash query in progress...") + ResetSlashQueryInProgress(ctx, stakeibcKeeper) ctx.Logger().Info("Updating community pool tax...") + if err := IncreaseCommunityPoolTax(ctx, distributionkeeper); err != nil { + return vm, errorsmod.Wrapf(err, "unable to increase community pool tax") + } ctx.Logger().Info("Updating redemption rate bounds...") + UpdateRedemptionRateBounds(ctx, stakeibcKeeper) + + ctx.Logger().Info("Update rate limits thresholds...") + UpdateRateLimitThresholds(ctx, stakeibcKeeper, ratelimitKeeper) ctx.Logger().Info("Adding rate limits to Osmosis...") + if err := RateLimitToOsmosis(ctx, ratelimitKeeper); err != nil { + return vm, errorsmod.Wrapf(err, "unable to add rate limits to Osmosis") + } return mm.RunMigrations(ctx, configurator, vm) } @@ -43,33 +91,188 @@ func CreateUpgradeHandler( // Migrates the host zones to the new structure which supports community pool liquid staking // We don't have to perform a true migration here since only new fields were added +// (in other words, we can deserialize the old host zone structs into the new types) // This will also register the relevant community pool ICA addresses -func MigrateHostZones() { +func RegisterCommunityPoolAddresses(ctx sdk.Context, k stakeibckeeper.Keeper) error { + for _, hostZone := range k.GetAllHostZone(ctx) { + chainId := hostZone.ChainId -} + // Create and store a new community pool stake and redeem module address + stakeHoldingAddress := stakeibctypes.NewHostZoneModuleAddress( + chainId, + stakeibckeeper.CommunityPoolStakeHoldingAddressKey, + ) + redeemHoldingAddress := stakeibctypes.NewHostZoneModuleAddress( + chainId, + stakeibckeeper.CommunityPoolRedeemHoldingAddressKey, + ) + + if err := utils.CreateModuleAccount(ctx, k.AccountKeeper, stakeHoldingAddress); err != nil { + return errorsmod.Wrapf(err, "unable to create community pool stake account for host zone %s", chainId) + } + if err := utils.CreateModuleAccount(ctx, k.AccountKeeper, redeemHoldingAddress); err != nil { + return errorsmod.Wrapf(err, "unable to create community pool redeem account for host zone %s", chainId) + } -// Deletes all currently active queries (to remove ones that were stuck) -func DeleteAllPendingQueries() { + hostZone.CommunityPoolStakeHoldingAddress = stakeHoldingAddress.String() + hostZone.CommunityPoolRedeemHoldingAddress = redeemHoldingAddress.String() + k.SetHostZone(ctx, hostZone) + + // Register the deposit and return ICA addresses + // (these will get set in the OnChanAck callback) + // create community pool deposit account + connectionId := hostZone.ConnectionId + connectionEnd, found := k.IBCKeeper.ConnectionKeeper.GetConnection(ctx, connectionId) + if !found { + return errorsmod.Wrapf(connectiontypes.ErrConnectionNotFound, "connection %s not found", connectionId) + } + counterpartyConnectionId := connectionEnd.Counterparty.ConnectionId + + appVersion := string(icatypes.ModuleCdc.MustMarshalJSON(&icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: connectionId, + HostConnectionId: counterpartyConnectionId, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, + })) + + depositAccount := stakeibctypes.FormatHostZoneICAOwner(chainId, stakeibctypes.ICAAccountType_COMMUNITY_POOL_DEPOSIT) + if err := k.ICAControllerKeeper.RegisterInterchainAccount(ctx, connectionId, depositAccount, appVersion); err != nil { + return errorsmod.Wrapf(stakeibctypes.ErrFailedToRegisterHostZone, "failed to register community pool deposit ICA") + } + + returnAccount := stakeibctypes.FormatHostZoneICAOwner(chainId, stakeibctypes.ICAAccountType_COMMUNITY_POOL_RETURN) + if err := k.ICAControllerKeeper.RegisterInterchainAccount(ctx, connectionId, returnAccount, appVersion); err != nil { + return errorsmod.Wrapf(stakeibctypes.ErrFailedToRegisterHostZone, "failed to register community pool return ICA") + } + } + + return nil } -// Resets the slash query in progress flag for each validator -func ResetSlashQueryInProgress() { +// Deletes all stale queries +func DeleteAllStaleQueries(ctx sdk.Context, k icqkeeper.Keeper) { + for _, query := range k.AllQueries(ctx) { + if query.CallbackId == stakeibckeeper.ICQCallbackID_Delegation { + k.DeleteQuery(ctx, query.Id) + } + } +} +// Resets the slash query in progress flag for each validator +func ResetSlashQueryInProgress(ctx sdk.Context, k stakeibckeeper.Keeper) { + for _, hostZone := range k.GetAllHostZone(ctx) { + for i, validator := range hostZone.Validators { + validator.SlashQueryInProgress = false + hostZone.Validators[i] = validator + } + k.SetHostZone(ctx, hostZone) + } } // Increases the community pool tax from 2 to 5% -// This was from prop XXX which passed, but was deleted due to an ICS blacklist -func IncreaseCommunityPoolTax() { - +// This was from prop 223 which passed, but was deleted due to an ICS blacklist +func IncreaseCommunityPoolTax(ctx sdk.Context, k distributionkeeper.Keeper) error { + params := k.GetParams(ctx) + params.CommunityTax = CommunityPoolTax + return k.SetParams(ctx, params) } // Updates the outer redemption rate bounds -func UpdatingRedemptionRateBounds() { +func UpdateRedemptionRateBounds(ctx sdk.Context, k stakeibckeeper.Keeper) { + for _, hostZone := range k.GetAllHostZone(ctx) { + outerMinDelta := hostZone.RedemptionRate.Mul(RedemptionRateOuterMinAdjustment) + innerMinDelta := hostZone.RedemptionRate.Mul(RedemptionRateInnerMinAdjustment) + innerMaxDelta := hostZone.RedemptionRate.Mul(RedemptionRateInnerMaxAdjustment) + outerMaxDelta := hostZone.RedemptionRate.Mul(RedemptionRateOuterMaxAdjustment) + + outerMin := hostZone.RedemptionRate.Sub(outerMinDelta) + innerMin := hostZone.RedemptionRate.Sub(innerMinDelta) + innerMax := hostZone.RedemptionRate.Add(innerMaxDelta) + outerMax := hostZone.RedemptionRate.Add(outerMaxDelta) + + hostZone.MinRedemptionRate = outerMin + hostZone.MinInnerRedemptionRate = innerMin + hostZone.MaxInnerRedemptionRate = innerMax + hostZone.MaxRedemptionRate = outerMax + + k.SetHostZone(ctx, hostZone) + } +} + +// Update rate limits based on current TVL +func UpdateRateLimitThresholds(ctx sdk.Context, sk stakeibckeeper.Keeper, rk ratelimitkeeper.Keeper) { + for _, rateLimit := range rk.GetAllRateLimits(ctx) { + stDenom := rateLimit.Path.Denom + hostDenom := stDenom[2:] + // Lookup the associated host zone to get the chain ID + hostZone, err := sk.GetHostZoneFromHostDenom(ctx, stDenom) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("host zone not found for denom %s", hostDenom)) + continue + } + + // Determine the expected rate limit threshold for the chain + updatedThreshold, ok := UpdatedRateLimits[hostZone.ChainId] + if !ok { + ctx.Logger().Error(fmt.Sprintf("rate limit not specified for %s", hostZone.ChainId)) + continue + } + + // If the expected threshold is 0, that means there should be no rate limit + // Remove the rate limit in this case + if updatedThreshold.IsZero() { + rk.RemoveRateLimit(ctx, rateLimit.Path.Denom, rateLimit.Path.ChannelId) + continue + } + + rateLimit.Quota.MaxPercentRecv = updatedThreshold + rateLimit.Quota.MaxPercentSend = updatedThreshold + rk.SetRateLimit(ctx, rateLimit) + } } -// Rate limits transfers to osmosis and deletes the injective rate limit -func UpdateRateLimits() { +// Rate limits transfers to osmosis across each stToken +func RateLimitToOsmosis(ctx sdk.Context, k ratelimitkeeper.Keeper) error { + for _, rateLimit := range k.GetAllRateLimits(ctx) { + denom := rateLimit.Path.Denom + + channelValue := k.GetChannelValue(ctx, denom) + if channelValue.IsZero() { + return ratelimittypes.ErrZeroChannelValue + } + + // Ignore the rate limit if it already exists (e.g. stuosmo) + _, found := k.GetRateLimit(ctx, rateLimit.Path.Denom, OsmosisTransferChannelId) + if found { + continue + } + + // Create and store the rate limit object with the same bounds as + // the original rate limit + path := ratelimittypes.Path{ + Denom: denom, + ChannelId: OsmosisTransferChannelId, + } + quota := ratelimittypes.Quota{ + MaxPercentSend: rateLimit.Quota.MaxPercentSend, + MaxPercentRecv: rateLimit.Quota.MaxPercentRecv, + DurationHours: rateLimit.Quota.DurationHours, + } + flow := ratelimittypes.Flow{ + Inflow: sdkmath.ZeroInt(), + Outflow: sdkmath.ZeroInt(), + ChannelValue: channelValue, + } + + k.SetRateLimit(ctx, ratelimittypes.RateLimit{ + Path: &path, + Quota: "a, + Flow: &flow, + }) + } + return nil } diff --git a/x/stakeibc/keeper/msg_server_register_host_zone.go b/x/stakeibc/keeper/msg_server_register_host_zone.go index 36d812156e..91a56fdd07 100644 --- a/x/stakeibc/keeper/msg_server_register_host_zone.go +++ b/x/stakeibc/keeper/msg_server_register_host_zone.go @@ -16,6 +16,11 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +const ( + CommunityPoolStakeHoldingAddressKey = "community-pool-stake" + CommunityPoolRedeemHoldingAddressKey = "community-pool-redeem" +) + func (k msgServer) RegisterHostZone(goCtx context.Context, msg *types.MsgRegisterHostZone) (*types.MsgRegisterHostZoneResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) @@ -76,8 +81,8 @@ func (k msgServer) RegisterHostZone(goCtx context.Context, msg *types.MsgRegiste } // Create the host zone's community pool holding accounts - communityPoolStakeAddress := types.NewHostZoneModuleAddress(chainId, "community-pool-stake") - communityPoolRedeemAddress := types.NewHostZoneModuleAddress(chainId, "community-pool-redeem") + communityPoolStakeAddress := types.NewHostZoneModuleAddress(chainId, CommunityPoolStakeHoldingAddressKey) + communityPoolRedeemAddress := types.NewHostZoneModuleAddress(chainId, CommunityPoolRedeemHoldingAddressKey) if err := utils.CreateModuleAccount(ctx, k.AccountKeeper, communityPoolStakeAddress); err != nil { return nil, errorsmod.Wrapf(err, "unable to create community pool stake account for host zone %s", chainId) } From 9fc442b45f288fb9447592acd785bdddcacb47fb Mon Sep 17 00:00:00 2001 From: sampocs Date: Thu, 14 Dec 2023 13:33:13 -0600 Subject: [PATCH 03/19] first pass at upgrade handler --- app/upgrades.go | 1 + app/upgrades/v17/upgrades.go | 231 +++++++++++++++++- .../keeper/msg_server_register_host_zone.go | 9 +- 3 files changed, 227 insertions(+), 14 deletions(-) diff --git a/app/upgrades.go b/app/upgrades.go index 03bad9077c..674b56fdc8 100644 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -223,6 +223,7 @@ func (app *StrideApp) setupUpgradeHandlers(appOpts servertypes.AppOptions) { v17.CreateUpgradeHandler( app.mm, app.configurator, + app.DistrKeeper, app.InterchainqueryKeeper, app.RatelimitKeeper, app.StakeibcKeeper, diff --git a/app/upgrades/v17/upgrades.go b/app/upgrades/v17/upgrades.go index ca4507a26d..20e175361c 100644 --- a/app/upgrades/v17/upgrades.go +++ b/app/upgrades/v17/upgrades.go @@ -1,23 +1,60 @@ package v17 import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types" + connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" + "github.com/Stride-Labs/stride/v16/utils" icqkeeper "github.com/Stride-Labs/stride/v16/x/interchainquery/keeper" ratelimitkeeper "github.com/Stride-Labs/stride/v16/x/ratelimit/keeper" + ratelimittypes "github.com/Stride-Labs/stride/v16/x/ratelimit/types" stakeibckeeper "github.com/Stride-Labs/stride/v16/x/stakeibc/keeper" + stakeibctypes "github.com/Stride-Labs/stride/v16/x/stakeibc/types" ) var ( UpgradeName = "v17" + + // Community pool tax updated from 2 -> 5% + CommunityPoolTax = sdk.MustNewDecFromStr("0.05") + + // Rate limit bounds updated to give ~3 months of slack on outer bounds + RedemptionRateOuterMinAdjustment = sdk.MustNewDecFromStr("0.05") + RedemptionRateInnerMinAdjustment = sdk.MustNewDecFromStr("0.03") + RedemptionRateInnerMaxAdjustment = sdk.MustNewDecFromStr("0.05") + RedemptionRateOuterMaxAdjustment = sdk.MustNewDecFromStr("0.10") + + // Rate limits updated according to TVL + UpdatedRateLimits = map[string]sdkmath.Int{ + "comdex-1": sdkmath.ZeroInt(), // TVL: ~130k | <1M | No rate limit + "cosmoshub-4": sdkmath.NewInt(10), // TVL: ~50M | 30M+ | 10% RL + "evmos_9001-2": sdkmath.NewInt(50), // TVL: ~3M | 1M-15M+ | 50% RL + "injective-1": sdkmath.NewInt(50), // TVL: ~3M | 1M-15M+ | 50% RL + "juno-1": sdkmath.NewInt(50), // TVL: ~3M | 1M-15M+ | 50% RL + "osmosis-1": sdkmath.NewInt(25), // TVL: ~30M | 30M+ | 10% RL + "phoenix-1": sdkmath.ZeroInt(), // TVL: ~190k | <1M | No rate limit + "sommelier-3": sdkmath.ZeroInt(), // TVL: ~450k | <1M | No rate limit + "stargaze-1": sdkmath.NewInt(50), // TVL: 1.35M | 1M-15M+ | 50% RL + "umee-1": sdkmath.ZeroInt(), // TVL: ~200k | <1M | No rate limit + } + + // Osmo transfer channel is required for new rate limits + OsmosisTransferChannelId = "channel-5" ) // CreateUpgradeHandler creates an SDK upgrade handler for v15 func CreateUpgradeHandler( mm *module.Manager, configurator module.Configurator, + distributionkeeper distributionkeeper.Keeper, icqKeeper icqkeeper.Keeper, ratelimitKeeper ratelimitkeeper.Keeper, stakeibcKeeper stakeibckeeper.Keeper, @@ -26,16 +63,31 @@ func CreateUpgradeHandler( ctx.Logger().Info("Starting upgrade v17...") ctx.Logger().Info("Migrating host zones...") + if err := RegisterCommunityPoolAddresses(ctx, stakeibcKeeper); err != nil { + return vm, errorsmod.Wrapf(err, "unable to migrate host zones") + } ctx.Logger().Info("Deleting all pending queries...") + DeleteAllStaleQueries(ctx, icqKeeper) ctx.Logger().Info("Reseting slash query in progress...") + ResetSlashQueryInProgress(ctx, stakeibcKeeper) ctx.Logger().Info("Updating community pool tax...") + if err := IncreaseCommunityPoolTax(ctx, distributionkeeper); err != nil { + return vm, errorsmod.Wrapf(err, "unable to increase community pool tax") + } ctx.Logger().Info("Updating redemption rate bounds...") + UpdateRedemptionRateBounds(ctx, stakeibcKeeper) + + ctx.Logger().Info("Update rate limits thresholds...") + UpdateRateLimitThresholds(ctx, stakeibcKeeper, ratelimitKeeper) ctx.Logger().Info("Adding rate limits to Osmosis...") + if err := RateLimitToOsmosis(ctx, ratelimitKeeper); err != nil { + return vm, errorsmod.Wrapf(err, "unable to add rate limits to Osmosis") + } return mm.RunMigrations(ctx, configurator, vm) } @@ -43,33 +95,188 @@ func CreateUpgradeHandler( // Migrates the host zones to the new structure which supports community pool liquid staking // We don't have to perform a true migration here since only new fields were added +// (in other words, we can deserialize the old host zone structs into the new types) // This will also register the relevant community pool ICA addresses -func MigrateHostZones() { +func RegisterCommunityPoolAddresses(ctx sdk.Context, k stakeibckeeper.Keeper) error { + for _, hostZone := range k.GetAllHostZone(ctx) { + chainId := hostZone.ChainId -} + // Create and store a new community pool stake and redeem module address + stakeHoldingAddress := stakeibctypes.NewHostZoneModuleAddress( + chainId, + stakeibckeeper.CommunityPoolStakeHoldingAddressKey, + ) + redeemHoldingAddress := stakeibctypes.NewHostZoneModuleAddress( + chainId, + stakeibckeeper.CommunityPoolRedeemHoldingAddressKey, + ) + + if err := utils.CreateModuleAccount(ctx, k.AccountKeeper, stakeHoldingAddress); err != nil { + return errorsmod.Wrapf(err, "unable to create community pool stake account for host zone %s", chainId) + } + if err := utils.CreateModuleAccount(ctx, k.AccountKeeper, redeemHoldingAddress); err != nil { + return errorsmod.Wrapf(err, "unable to create community pool redeem account for host zone %s", chainId) + } -// Deletes all currently active queries (to remove ones that were stuck) -func DeleteAllPendingQueries() { + hostZone.CommunityPoolStakeHoldingAddress = stakeHoldingAddress.String() + hostZone.CommunityPoolRedeemHoldingAddress = redeemHoldingAddress.String() + k.SetHostZone(ctx, hostZone) + + // Register the deposit and return ICA addresses + // (these will get set in the OnChanAck callback) + // create community pool deposit account + connectionId := hostZone.ConnectionId + connectionEnd, found := k.IBCKeeper.ConnectionKeeper.GetConnection(ctx, connectionId) + if !found { + return errorsmod.Wrapf(connectiontypes.ErrConnectionNotFound, "connection %s not found", connectionId) + } + counterpartyConnectionId := connectionEnd.Counterparty.ConnectionId + + appVersion := string(icatypes.ModuleCdc.MustMarshalJSON(&icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: connectionId, + HostConnectionId: counterpartyConnectionId, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, + })) + + depositAccount := stakeibctypes.FormatHostZoneICAOwner(chainId, stakeibctypes.ICAAccountType_COMMUNITY_POOL_DEPOSIT) + if err := k.ICAControllerKeeper.RegisterInterchainAccount(ctx, connectionId, depositAccount, appVersion); err != nil { + return errorsmod.Wrapf(stakeibctypes.ErrFailedToRegisterHostZone, "failed to register community pool deposit ICA") + } + + returnAccount := stakeibctypes.FormatHostZoneICAOwner(chainId, stakeibctypes.ICAAccountType_COMMUNITY_POOL_RETURN) + if err := k.ICAControllerKeeper.RegisterInterchainAccount(ctx, connectionId, returnAccount, appVersion); err != nil { + return errorsmod.Wrapf(stakeibctypes.ErrFailedToRegisterHostZone, "failed to register community pool return ICA") + } + } + + return nil } -// Resets the slash query in progress flag for each validator -func ResetSlashQueryInProgress() { +// Deletes all stale queries +func DeleteAllStaleQueries(ctx sdk.Context, k icqkeeper.Keeper) { + for _, query := range k.AllQueries(ctx) { + if query.CallbackId == stakeibckeeper.ICQCallbackID_Delegation { + k.DeleteQuery(ctx, query.Id) + } + } +} +// Resets the slash query in progress flag for each validator +func ResetSlashQueryInProgress(ctx sdk.Context, k stakeibckeeper.Keeper) { + for _, hostZone := range k.GetAllHostZone(ctx) { + for i, validator := range hostZone.Validators { + validator.SlashQueryInProgress = false + hostZone.Validators[i] = validator + } + k.SetHostZone(ctx, hostZone) + } } // Increases the community pool tax from 2 to 5% -// This was from prop XXX which passed, but was deleted due to an ICS blacklist -func IncreaseCommunityPoolTax() { - +// This was from prop 223 which passed, but was deleted due to an ICS blacklist +func IncreaseCommunityPoolTax(ctx sdk.Context, k distributionkeeper.Keeper) error { + params := k.GetParams(ctx) + params.CommunityTax = CommunityPoolTax + return k.SetParams(ctx, params) } // Updates the outer redemption rate bounds -func UpdatingRedemptionRateBounds() { +func UpdateRedemptionRateBounds(ctx sdk.Context, k stakeibckeeper.Keeper) { + for _, hostZone := range k.GetAllHostZone(ctx) { + outerMinDelta := hostZone.RedemptionRate.Mul(RedemptionRateOuterMinAdjustment) + innerMinDelta := hostZone.RedemptionRate.Mul(RedemptionRateInnerMinAdjustment) + innerMaxDelta := hostZone.RedemptionRate.Mul(RedemptionRateInnerMaxAdjustment) + outerMaxDelta := hostZone.RedemptionRate.Mul(RedemptionRateOuterMaxAdjustment) + + outerMin := hostZone.RedemptionRate.Sub(outerMinDelta) + innerMin := hostZone.RedemptionRate.Sub(innerMinDelta) + innerMax := hostZone.RedemptionRate.Add(innerMaxDelta) + outerMax := hostZone.RedemptionRate.Add(outerMaxDelta) + + hostZone.MinRedemptionRate = outerMin + hostZone.MinInnerRedemptionRate = innerMin + hostZone.MaxInnerRedemptionRate = innerMax + hostZone.MaxRedemptionRate = outerMax + + k.SetHostZone(ctx, hostZone) + } +} + +// Update rate limits based on current TVL +func UpdateRateLimitThresholds(ctx sdk.Context, sk stakeibckeeper.Keeper, rk ratelimitkeeper.Keeper) { + for _, rateLimit := range rk.GetAllRateLimits(ctx) { + stDenom := rateLimit.Path.Denom + hostDenom := stDenom[2:] + // Lookup the associated host zone to get the chain ID + hostZone, err := sk.GetHostZoneFromHostDenom(ctx, stDenom) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("host zone not found for denom %s", hostDenom)) + continue + } + + // Determine the expected rate limit threshold for the chain + updatedThreshold, ok := UpdatedRateLimits[hostZone.ChainId] + if !ok { + ctx.Logger().Error(fmt.Sprintf("rate limit not specified for %s", hostZone.ChainId)) + continue + } + + // If the expected threshold is 0, that means there should be no rate limit + // Remove the rate limit in this case + if updatedThreshold.IsZero() { + rk.RemoveRateLimit(ctx, rateLimit.Path.Denom, rateLimit.Path.ChannelId) + continue + } + + rateLimit.Quota.MaxPercentRecv = updatedThreshold + rateLimit.Quota.MaxPercentSend = updatedThreshold + rk.SetRateLimit(ctx, rateLimit) + } } -// Rate limits transfers to osmosis and deletes the injective rate limit -func UpdateRateLimits() { +// Rate limits transfers to osmosis across each stToken +func RateLimitToOsmosis(ctx sdk.Context, k ratelimitkeeper.Keeper) error { + for _, rateLimit := range k.GetAllRateLimits(ctx) { + denom := rateLimit.Path.Denom + + channelValue := k.GetChannelValue(ctx, denom) + if channelValue.IsZero() { + return ratelimittypes.ErrZeroChannelValue + } + + // Ignore the rate limit if it already exists (e.g. stuosmo) + _, found := k.GetRateLimit(ctx, rateLimit.Path.Denom, OsmosisTransferChannelId) + if found { + continue + } + + // Create and store the rate limit object with the same bounds as + // the original rate limit + path := ratelimittypes.Path{ + Denom: denom, + ChannelId: OsmosisTransferChannelId, + } + quota := ratelimittypes.Quota{ + MaxPercentSend: rateLimit.Quota.MaxPercentSend, + MaxPercentRecv: rateLimit.Quota.MaxPercentRecv, + DurationHours: rateLimit.Quota.DurationHours, + } + flow := ratelimittypes.Flow{ + Inflow: sdkmath.ZeroInt(), + Outflow: sdkmath.ZeroInt(), + ChannelValue: channelValue, + } + + k.SetRateLimit(ctx, ratelimittypes.RateLimit{ + Path: &path, + Quota: "a, + Flow: &flow, + }) + } + return nil } diff --git a/x/stakeibc/keeper/msg_server_register_host_zone.go b/x/stakeibc/keeper/msg_server_register_host_zone.go index 36d812156e..91a56fdd07 100644 --- a/x/stakeibc/keeper/msg_server_register_host_zone.go +++ b/x/stakeibc/keeper/msg_server_register_host_zone.go @@ -16,6 +16,11 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +const ( + CommunityPoolStakeHoldingAddressKey = "community-pool-stake" + CommunityPoolRedeemHoldingAddressKey = "community-pool-redeem" +) + func (k msgServer) RegisterHostZone(goCtx context.Context, msg *types.MsgRegisterHostZone) (*types.MsgRegisterHostZoneResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) @@ -76,8 +81,8 @@ func (k msgServer) RegisterHostZone(goCtx context.Context, msg *types.MsgRegiste } // Create the host zone's community pool holding accounts - communityPoolStakeAddress := types.NewHostZoneModuleAddress(chainId, "community-pool-stake") - communityPoolRedeemAddress := types.NewHostZoneModuleAddress(chainId, "community-pool-redeem") + communityPoolStakeAddress := types.NewHostZoneModuleAddress(chainId, CommunityPoolStakeHoldingAddressKey) + communityPoolRedeemAddress := types.NewHostZoneModuleAddress(chainId, CommunityPoolRedeemHoldingAddressKey) if err := utils.CreateModuleAccount(ctx, k.AccountKeeper, communityPoolStakeAddress); err != nil { return nil, errorsmod.Wrapf(err, "unable to create community pool stake account for host zone %s", chainId) } From 040d093b657fbd4d8d1aef98eeb740845a906138 Mon Sep 17 00:00:00 2001 From: sampocs Date: Mon, 18 Dec 2023 20:57:17 -0600 Subject: [PATCH 04/19] added unit tests --- app/upgrades/v17/upgrades.go | 23 +- app/upgrades/v17/upgrades_test.go | 423 ++++++++++++++++++++++++++++++ 2 files changed, 440 insertions(+), 6 deletions(-) diff --git a/app/upgrades/v17/upgrades.go b/app/upgrades/v17/upgrades.go index 20e175361c..807e27ead0 100644 --- a/app/upgrades/v17/upgrades.go +++ b/app/upgrades/v17/upgrades.go @@ -26,12 +26,17 @@ var ( // Community pool tax updated from 2 -> 5% CommunityPoolTax = sdk.MustNewDecFromStr("0.05") - // Rate limit bounds updated to give ~3 months of slack on outer bounds + // Redemption rate bounds updated to give ~3 months of slack on outer bounds RedemptionRateOuterMinAdjustment = sdk.MustNewDecFromStr("0.05") RedemptionRateInnerMinAdjustment = sdk.MustNewDecFromStr("0.03") RedemptionRateInnerMaxAdjustment = sdk.MustNewDecFromStr("0.05") RedemptionRateOuterMaxAdjustment = sdk.MustNewDecFromStr("0.10") + // Osmosis will have a slighly larger buffer with the redemption rate + // since their yield is less predictable + OsmosisChainId = "osmosis-1" + OsmosisRedemptionRateBuffer = sdk.MustNewDecFromStr("0.02") + // Rate limits updated according to TVL UpdatedRateLimits = map[string]sdkmath.Int{ "comdex-1": sdkmath.ZeroInt(), // TVL: ~130k | <1M | No rate limit @@ -39,7 +44,7 @@ var ( "evmos_9001-2": sdkmath.NewInt(50), // TVL: ~3M | 1M-15M+ | 50% RL "injective-1": sdkmath.NewInt(50), // TVL: ~3M | 1M-15M+ | 50% RL "juno-1": sdkmath.NewInt(50), // TVL: ~3M | 1M-15M+ | 50% RL - "osmosis-1": sdkmath.NewInt(25), // TVL: ~30M | 30M+ | 10% RL + "osmosis-1": sdkmath.NewInt(10), // TVL: ~30M | 30M+ | 10% RL "phoenix-1": sdkmath.ZeroInt(), // TVL: ~190k | <1M | No rate limit "sommelier-3": sdkmath.ZeroInt(), // TVL: ~450k | <1M | No rate limit "stargaze-1": sdkmath.NewInt(50), // TVL: 1.35M | 1M-15M+ | 50% RL @@ -85,7 +90,7 @@ func CreateUpgradeHandler( UpdateRateLimitThresholds(ctx, stakeibcKeeper, ratelimitKeeper) ctx.Logger().Info("Adding rate limits to Osmosis...") - if err := RateLimitToOsmosis(ctx, ratelimitKeeper); err != nil { + if err := AddRateLimitToOsmosis(ctx, ratelimitKeeper); err != nil { return vm, errorsmod.Wrapf(err, "unable to add rate limits to Osmosis") } @@ -186,10 +191,16 @@ func IncreaseCommunityPoolTax(ctx sdk.Context, k distributionkeeper.Keeper) erro // Updates the outer redemption rate bounds func UpdateRedemptionRateBounds(ctx sdk.Context, k stakeibckeeper.Keeper) { for _, hostZone := range k.GetAllHostZone(ctx) { + // Give osmosis a bit more slack since OSMO stakers collect real yield + outerAdjustment := RedemptionRateOuterMaxAdjustment + if hostZone.ChainId == OsmosisChainId { + outerAdjustment = outerAdjustment.Add(OsmosisRedemptionRateBuffer) + } + outerMinDelta := hostZone.RedemptionRate.Mul(RedemptionRateOuterMinAdjustment) innerMinDelta := hostZone.RedemptionRate.Mul(RedemptionRateInnerMinAdjustment) innerMaxDelta := hostZone.RedemptionRate.Mul(RedemptionRateInnerMaxAdjustment) - outerMaxDelta := hostZone.RedemptionRate.Mul(RedemptionRateOuterMaxAdjustment) + outerMaxDelta := hostZone.RedemptionRate.Mul(outerAdjustment) outerMin := hostZone.RedemptionRate.Sub(outerMinDelta) innerMin := hostZone.RedemptionRate.Sub(innerMinDelta) @@ -212,7 +223,7 @@ func UpdateRateLimitThresholds(ctx sdk.Context, sk stakeibckeeper.Keeper, rk rat hostDenom := stDenom[2:] // Lookup the associated host zone to get the chain ID - hostZone, err := sk.GetHostZoneFromHostDenom(ctx, stDenom) + hostZone, err := sk.GetHostZoneFromHostDenom(ctx, hostDenom) if err != nil { ctx.Logger().Error(fmt.Sprintf("host zone not found for denom %s", hostDenom)) continue @@ -239,7 +250,7 @@ func UpdateRateLimitThresholds(ctx sdk.Context, sk stakeibckeeper.Keeper, rk rat } // Rate limits transfers to osmosis across each stToken -func RateLimitToOsmosis(ctx sdk.Context, k ratelimitkeeper.Keeper) error { +func AddRateLimitToOsmosis(ctx sdk.Context, k ratelimitkeeper.Keeper) error { for _, rateLimit := range k.GetAllRateLimits(ctx) { denom := rateLimit.Path.Denom diff --git a/app/upgrades/v17/upgrades_test.go b/app/upgrades/v17/upgrades_test.go index 2812521a34..41ec170b6b 100644 --- a/app/upgrades/v17/upgrades_test.go +++ b/app/upgrades/v17/upgrades_test.go @@ -1,13 +1,48 @@ package v17_test import ( + "fmt" "testing" + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" "github.com/stretchr/testify/suite" + icqtypes "github.com/Stride-Labs/stride/v16/x/interchainquery/types" + ratelimittypes "github.com/Stride-Labs/stride/v16/x/ratelimit/types" + "github.com/Stride-Labs/stride/v16/app/apptesting" + v17 "github.com/Stride-Labs/stride/v16/app/upgrades/v17" + stakeibckeeper "github.com/Stride-Labs/stride/v16/x/stakeibc/keeper" + stakeibctypes "github.com/Stride-Labs/stride/v16/x/stakeibc/types" ) +type UpdateRedemptionRateBounds struct { + ChainId string + CurrentRedemptionRate sdk.Dec + ExpectedMinOuterRedemptionRate sdk.Dec + ExpectedMinInnerRedemptionRate sdk.Dec + ExpectedMaxInnerRedemptionRate sdk.Dec + ExpectedMaxOuterRedemptionRate sdk.Dec +} + +type UpdateRateLimits struct { + ChainId string + ChannelId string + RateLimitDenom string + HostDenom string + Duration uint64 + Threshold sdkmath.Int +} + +type AddRateLimits struct { + ChannelId string + Denom string + ChannelValue sdkmath.Int +} + type UpgradeTestSuite struct { apptesting.AppTestHelper } @@ -21,4 +56,392 @@ func TestKeeperTestSuite(t *testing.T) { } func (s *UpgradeTestSuite) TestUpgrade() { + +} + +func (s *UpgradeTestSuite) TestRegisterCommunityPoolAddresses() { + // Create 3 host zones, with empty ICA addresses + chainIds := []string{} + for i := 1; i <= 3; i++ { + chainId := fmt.Sprintf("chain-%d", i) + connectionId := fmt.Sprintf("connection-%d", i) + clientId := fmt.Sprintf("07-tendermint-%d", i) + + s.App.StakeibcKeeper.SetHostZone(s.Ctx, stakeibctypes.HostZone{ + ChainId: chainId, + ConnectionId: connectionId, + }) + chainIds = append(chainIds, chainId) + + // Mock out the consensus state to test registering an interchain account + s.MockClientAndConnection(chainId, clientId, connectionId) + } + + // Register the accounts + err := v17.RegisterCommunityPoolAddresses(s.Ctx, s.App.StakeibcKeeper) + s.Require().NoError(err, "no error expected when registering ICA addresses") + + // Confirm the module accounts were created and stored on each host + for _, chainId := range chainIds { + hostZone, found := s.App.StakeibcKeeper.GetHostZone(s.Ctx, chainId) + s.Require().True(found, "host zone should have been found") + + s.Require().NotEmpty(hostZone.CommunityPoolStakeHoldingAddress, "stake holding for %s should not be empty", chainId) + s.Require().NotEmpty(hostZone.CommunityPoolRedeemHoldingAddress, "redeem holding for %s should not be empty", chainId) + + stakeHoldingAddress := sdk.MustAccAddressFromBech32(hostZone.CommunityPoolStakeHoldingAddress) + redeemHoldingAddress := sdk.MustAccAddressFromBech32(hostZone.CommunityPoolRedeemHoldingAddress) + + stakeHoldingAccount := s.App.AccountKeeper.GetAccount(s.Ctx, stakeHoldingAddress) + redeemHoldingAccount := s.App.AccountKeeper.GetAccount(s.Ctx, redeemHoldingAddress) + + s.Require().NotNil(stakeHoldingAccount, "stake holding account should have been registered for %s", chainId) + s.Require().NotNil(redeemHoldingAccount, "redeem holding account should have been registered for %s", chainId) + } + + // Confirm the ICAs were registered + // The addresses don't get set until the callback, but we can check the events and confirm they were registered + for _, chainId := range chainIds { + depositOwner := stakeibctypes.FormatHostZoneICAOwner(chainId, stakeibctypes.ICAAccountType_COMMUNITY_POOL_DEPOSIT) + returnOwner := stakeibctypes.FormatHostZoneICAOwner(chainId, stakeibctypes.ICAAccountType_COMMUNITY_POOL_RETURN) + + expectedDepositPortId, _ := icatypes.NewControllerPortID(depositOwner) + expectedReturnPortId, _ := icatypes.NewControllerPortID(returnOwner) + + s.CheckEventValueEmitted(channeltypes.EventTypeChannelOpenInit, channeltypes.AttributeKeyPortID, expectedDepositPortId) + s.CheckEventValueEmitted(channeltypes.EventTypeChannelOpenInit, channeltypes.AttributeKeyPortID, expectedReturnPortId) + } + +} + +func (s *UpgradeTestSuite) TestDeleteAllStaleQueries() { + // Create queries - half of which are for slashed (CallbackId: Delegation) + initialQueries := 10 + for i := 1; i <= initialQueries; i++ { + queryId := fmt.Sprintf("query-%d", i) + + // Alternate slash queries vs other query + callbackId := stakeibckeeper.ICQCallbackID_Delegation + if i%2 == 0 { + callbackId = stakeibckeeper.ICQCallbackID_FeeBalance // arbitrary + } + + s.App.InterchainqueryKeeper.SetQuery(s.Ctx, icqtypes.Query{ + Id: queryId, + CallbackId: callbackId, + }) + } + + // Delete stale queries + v17.DeleteAllStaleQueries(s.Ctx, s.App.InterchainqueryKeeper) + + // Check that only half the queries are remaining and none are for slashes + remainingQueries := s.App.InterchainqueryKeeper.AllQueries(s.Ctx) + s.Require().Equal(initialQueries/2, len(remainingQueries), "half the queries should have been removed") + + for _, query := range remainingQueries { + s.Require().NotEqual(stakeibckeeper.ICQCallbackID_Delegation, query.CallbackId, + "all slash queries should have been removed") + } +} + +func (s *UpgradeTestSuite) TestResetSlashQueryInProgress() { + // Set multiple host zones, each with multiple validators that have slash query in progress set to true + for hostIndex := 1; hostIndex <= 3; hostIndex++ { + chainId := fmt.Sprintf("chain-%d", hostIndex) + + validators := []*stakeibctypes.Validator{} + for validatorIndex := 1; validatorIndex <= 6; validatorIndex++ { + address := fmt.Sprintf("val-%d", validatorIndex) + + inProgress := true + if validatorIndex%2 == 0 { + inProgress = false + } + + validators = append(validators, &stakeibctypes.Validator{ + Address: address, + SlashQueryInProgress: inProgress, + }) + } + + s.App.StakeibcKeeper.SetHostZone(s.Ctx, stakeibctypes.HostZone{ + ChainId: chainId, + Validators: validators, + }) + } + + // Reset the slash queries + v17.ResetSlashQueryInProgress(s.Ctx, s.App.StakeibcKeeper) + + // Confirm they were all reset + for _, hostZone := range s.App.StakeibcKeeper.GetAllHostZone(s.Ctx) { + for _, validator := range hostZone.Validators { + s.Require().False(validator.SlashQueryInProgress, + "%s %s - slash query in progress should have been reset", hostZone.ChainId, validator.Address) + } + } +} + +func (s *UpgradeTestSuite) TestIncreaseCommunityPoolTax() { + // Set initial community pool tax to 2% + initialTax := sdk.MustNewDecFromStr("0.02") + params := s.App.DistrKeeper.GetParams(s.Ctx) + params.CommunityTax = initialTax + err := s.App.DistrKeeper.SetParams(s.Ctx, params) + s.Require().NoError(err, "no error expected when setting params") + + // Increase the tax + err = v17.IncreaseCommunityPoolTax(s.Ctx, s.App.DistrKeeper) + s.Require().NoError(err, "no error expected when increasing community pool tax") + + // Confirm it increased + updatedParams := s.App.DistrKeeper.GetParams(s.Ctx) + s.Require().Equal(v17.CommunityPoolTax.String(), updatedParams.CommunityTax.String(), + "community pool tax should have been updated") +} + +func (s *UpgradeTestSuite) TestUpdateRedemptionRateBounds() { + // Define test cases consisting of an initial redemption rate and expected bounds + testCases := []UpdateRedemptionRateBounds{ + { + ChainId: "chain-0", + CurrentRedemptionRate: sdk.MustNewDecFromStr("1.0"), + ExpectedMinOuterRedemptionRate: sdk.MustNewDecFromStr("0.95"), // 1 - 5% = 0.95 + ExpectedMinInnerRedemptionRate: sdk.MustNewDecFromStr("0.97"), // 1 - 3% = 0.97 + ExpectedMaxInnerRedemptionRate: sdk.MustNewDecFromStr("1.05"), // 1 + 5% = 1.05 + ExpectedMaxOuterRedemptionRate: sdk.MustNewDecFromStr("1.10"), // 1 + 10% = 1.1 + }, + { + ChainId: "chain-1", + CurrentRedemptionRate: sdk.MustNewDecFromStr("1.1"), + ExpectedMinOuterRedemptionRate: sdk.MustNewDecFromStr("1.045"), // 1.1 - 5% = 1.045 + ExpectedMinInnerRedemptionRate: sdk.MustNewDecFromStr("1.067"), // 1.1 - 3% = 1.067 + ExpectedMaxInnerRedemptionRate: sdk.MustNewDecFromStr("1.155"), // 1.1 + 5% = 1.155 + ExpectedMaxOuterRedemptionRate: sdk.MustNewDecFromStr("1.210"), // 1.1 + 10% = 1.21 + }, + { + // Max outer for osmo uses 12% instead of 10% + ChainId: v17.OsmosisChainId, + CurrentRedemptionRate: sdk.MustNewDecFromStr("1.25"), + ExpectedMinOuterRedemptionRate: sdk.MustNewDecFromStr("1.1875"), // 1.25 - 5% = 1.1875 + ExpectedMinInnerRedemptionRate: sdk.MustNewDecFromStr("1.2125"), // 1.25 - 3% = 1.2125 + ExpectedMaxInnerRedemptionRate: sdk.MustNewDecFromStr("1.3125"), // 1.25 + 5% = 1.3125 + ExpectedMaxOuterRedemptionRate: sdk.MustNewDecFromStr("1.4000"), // 1.25 + 12% = 1.400 + }, + } + + // Create a host zone for each test case + for _, tc := range testCases { + hostZone := stakeibctypes.HostZone{ + ChainId: tc.ChainId, + RedemptionRate: tc.CurrentRedemptionRate, + } + s.App.StakeibcKeeper.SetHostZone(s.Ctx, hostZone) + } + + // Update the redemption rate bounds + v17.UpdateRedemptionRateBounds(s.Ctx, s.App.StakeibcKeeper) + + // Confirm they were all updated + for _, tc := range testCases { + hostZone, found := s.App.StakeibcKeeper.GetHostZone(s.Ctx, tc.ChainId) + s.Require().True(found) + + s.Require().Equal(tc.ExpectedMinOuterRedemptionRate, hostZone.MinRedemptionRate, "%s - min outer", tc.ChainId) + s.Require().Equal(tc.ExpectedMinInnerRedemptionRate, hostZone.MinInnerRedemptionRate, "%s - min inner", tc.ChainId) + s.Require().Equal(tc.ExpectedMaxInnerRedemptionRate, hostZone.MaxInnerRedemptionRate, "%s - max inner", tc.ChainId) + s.Require().Equal(tc.ExpectedMaxOuterRedemptionRate, hostZone.MaxRedemptionRate, "%s - max outer", tc.ChainId) + } +} + +func (s *UpgradeTestSuite) TestUpdateRateLimitThresholds() { + initialThreshold := sdkmath.OneInt() + + // Define test cases consisting of an initial redemption rates and expected bounds + testCases := map[string]UpdateRateLimits{ + "cosmoshub": { + // 10% threshold + ChainId: "cosmoshub-4", + ChannelId: "channel-0", + HostDenom: "uatom", + RateLimitDenom: "stuatom", + Duration: 10, + Threshold: sdkmath.NewInt(10), + }, + "osmosis": { + // 10% threshold + ChainId: "osmosis-1", + ChannelId: "channel-1", + HostDenom: "uosmo", + RateLimitDenom: "stuosmo", + Duration: 20, + Threshold: sdkmath.NewInt(10), + }, + "juno": { + // No denom on matching host + ChainId: "juno-1", + ChannelId: "channel-2", + HostDenom: "ujuno", + RateLimitDenom: "different-denom", + Duration: 30, + }, + "sommelier": { + // Rate limit should get removed + ChainId: "sommelier-3", + ChannelId: "channel-3", + HostDenom: "usomm", + RateLimitDenom: "stusomm", + Duration: 40, + }, + } + + // Set rate limits and host zones + for _, tc := range testCases { + s.App.RatelimitKeeper.SetRateLimit(s.Ctx, ratelimittypes.RateLimit{ + Path: &ratelimittypes.Path{ + Denom: tc.RateLimitDenom, + ChannelId: tc.ChannelId, + }, + Quota: &ratelimittypes.Quota{ + MaxPercentSend: initialThreshold, + MaxPercentRecv: initialThreshold, + DurationHours: tc.Duration, + }, + }) + + s.App.StakeibcKeeper.SetHostZone(s.Ctx, stakeibctypes.HostZone{ + ChainId: tc.ChainId, + HostDenom: tc.HostDenom, + }) + } + + // Update rate limits + v17.UpdateRateLimitThresholds(s.Ctx, s.App.StakeibcKeeper, s.App.RatelimitKeeper) + + // Check that the osmo and gaia rate limits were updated + for _, chainName := range []string{"cosmoshub", "osmosis"} { + testCase := testCases[chainName] + actualRateLimit, found := s.App.RatelimitKeeper.GetRateLimit(s.Ctx, testCase.RateLimitDenom, testCase.ChannelId) + s.Require().True(found, "rate limit should have been found") + + // Check that the thresholds were updated + s.Require().Equal(testCase.Threshold, actualRateLimit.Quota.MaxPercentSend, "%s - max percent send", chainName) + s.Require().Equal(testCase.Threshold, actualRateLimit.Quota.MaxPercentRecv, "%s - max percent recv", chainName) + s.Require().Equal(testCase.Duration, actualRateLimit.Quota.DurationHours, "%s - duration", chainName) + } + + // Check that the juno rate limit was not touched + // (since there was no matching host denom + junoTestCase := testCases["juno"] + actualRateLimit, found := s.App.RatelimitKeeper.GetRateLimit(s.Ctx, junoTestCase.RateLimitDenom, junoTestCase.ChannelId) + s.Require().True(found, "juno rate limit should have been found") + + s.Require().Equal(initialThreshold, actualRateLimit.Quota.MaxPercentSend, "juno max percent send") + s.Require().Equal(initialThreshold, actualRateLimit.Quota.MaxPercentRecv, "juno max percent recv") + + // Check that the somm rate limit was removed + sommTestCase := testCases["sommelier"] + _, found = s.App.RatelimitKeeper.GetRateLimit(s.Ctx, sommTestCase.RateLimitDenom, sommTestCase.ChannelId) + s.Require().False(found, "somm rate limit should have been removed") +} + +func (s *UpgradeTestSuite) TestAddRateLimitToOsmosis() { + initialThreshold := sdkmath.OneInt() + initialFlow := sdkmath.NewInt(100) + initialDuration := uint64(24) + initialChannelValue := sdk.NewInt(1000) + + // Define the test cases for adding new rate limits + testCases := map[string]AddRateLimits{ + "cosmoshub": { + // Will add a new rate limit to osmo + Denom: "stuatom", + ChannelId: "channel-0", + ChannelValue: sdkmath.NewInt(100), + }, + "osmosis": { + // Rate limit already exists, should not be touched + Denom: "stuosmo", + ChannelId: v17.OsmosisTransferChannelId, + ChannelValue: sdkmath.NewInt(300), + }, + "stargaze": { + // Will add a new rate limit to stars + Denom: "stustars", + ChannelId: "channel-1", + ChannelValue: sdkmath.NewInt(200), + }, + } + + // Setup the initial rate limits + for _, tc := range testCases { + s.App.RatelimitKeeper.SetRateLimit(s.Ctx, ratelimittypes.RateLimit{ + Path: &ratelimittypes.Path{ + Denom: tc.Denom, + ChannelId: tc.ChannelId, + }, + Quota: &ratelimittypes.Quota{ + MaxPercentSend: initialThreshold, + MaxPercentRecv: initialThreshold, + DurationHours: initialDuration, + }, + Flow: &ratelimittypes.Flow{ + Outflow: initialFlow, + Inflow: initialFlow, + ChannelValue: initialChannelValue, + }, + }) + + // mint tokens so there's a supply for the channel value + s.FundAccount(s.TestAccs[0], sdk.NewCoin(tc.Denom, tc.ChannelValue)) + } + + // Add the rate limits to osmo + err := v17.AddRateLimitToOsmosis(s.Ctx, s.App.RatelimitKeeper) + s.Require().NoError(err, "no error expected when adding rate limit to osmosis") + + // Check that we have new rate limits for gaia and stars + newRateLimits := []string{"cosmoshub", "stargaze"} + updatedRateLimits := s.App.RatelimitKeeper.GetAllRateLimits(s.Ctx) + s.Require().Equal(len(testCases)+len(newRateLimits), len(updatedRateLimits), "number of ending rate limits") + + for _, chainName := range newRateLimits { + testCase := testCases[chainName] + + // Confirm the new rate limit was created + actualRateLimit, found := s.App.RatelimitKeeper.GetRateLimit(s.Ctx, testCase.Denom, v17.OsmosisTransferChannelId) + s.Require().True(found, "new rate limit for %s to osmosis should have been found", chainName) + + // Confirm the thresholds remained the same + s.Require().Equal(initialThreshold, actualRateLimit.Quota.MaxPercentSend, "%s - max percent send", chainName) + s.Require().Equal(initialThreshold, actualRateLimit.Quota.MaxPercentRecv, "%s - max percent recv", chainName) + s.Require().Equal(initialDuration, actualRateLimit.Quota.DurationHours, "%s - duration", chainName) + + // Confirm the flow as reset + s.Require().Zero(actualRateLimit.Flow.Outflow.Int64(), "%s - outflow", chainName) + s.Require().Zero(actualRateLimit.Flow.Inflow.Int64(), "%s - inflow", chainName) + s.Require().Equal(testCase.ChannelValue, actualRateLimit.Flow.ChannelValue, "%s - channel value", chainName) + } + + // Confirm the osmo rate limit was not touched + osmoTestCase := testCases["osmosis"] + osmoRateLimit, found := s.App.RatelimitKeeper.GetRateLimit(s.Ctx, osmoTestCase.Denom, v17.OsmosisTransferChannelId) + s.Require().True(found, "rate limit for osmosis should have been found") + + s.Require().Equal(osmoRateLimit.Flow.Outflow, osmoRateLimit.Flow.Outflow, "osmos outflow") + s.Require().Equal(osmoRateLimit.Flow.Inflow, osmoRateLimit.Flow.Inflow, "osmos inflow") + s.Require().Equal(osmoRateLimit.Flow.ChannelValue, osmoRateLimit.Flow.ChannelValue, "osmos channel value") + + // Add a rate limit with zero channel value and confirm we cannot add a rate limit with that denom to osmosis + nonExistentDenom := "denom" + s.App.RatelimitKeeper.SetRateLimit(s.Ctx, ratelimittypes.RateLimit{ + Path: &ratelimittypes.Path{ + Denom: nonExistentDenom, + ChannelId: "channel-6", + }, + }) + + err = v17.AddRateLimitToOsmosis(s.Ctx, s.App.RatelimitKeeper) + s.Require().ErrorContains(err, "channel value is zero") } From d5648a8091e722f1b2e2d6f02059cc27b3114036 Mon Sep 17 00:00:00 2001 From: sampocs Date: Tue, 19 Dec 2023 13:11:45 -0600 Subject: [PATCH 05/19] add unit test for full upgrade --- app/apptesting/test_helpers.go | 6 +- app/upgrades/v17/upgrades.go | 2 +- app/upgrades/v17/upgrades_test.go | 323 +++++++++++++++++++++++++++--- x/ratelimit/keeper/hooks.go | 5 +- 4 files changed, 301 insertions(+), 35 deletions(-) diff --git a/app/apptesting/test_helpers.go b/app/apptesting/test_helpers.go index ee6432706a..3d6700fd43 100644 --- a/app/apptesting/test_helpers.go +++ b/app/apptesting/test_helpers.go @@ -519,7 +519,11 @@ func (s *AppTestHelper) MockICAChannel(connectionId, channelId, owner, address s func (s *AppTestHelper) ConfirmUpgradeSucceededs(upgradeName string, upgradeHeight int64) { s.Ctx = s.Ctx.WithBlockHeight(upgradeHeight - 1) - plan := upgradetypes.Plan{Name: upgradeName, Height: upgradeHeight} + plan := upgradetypes.Plan{ + Name: upgradeName, + Height: upgradeHeight, + } + err := s.App.UpgradeKeeper.ScheduleUpgrade(s.Ctx, plan) s.Require().NoError(err) _, exists := s.App.UpgradeKeeper.GetUpgradePlan(s.Ctx) diff --git a/app/upgrades/v17/upgrades.go b/app/upgrades/v17/upgrades.go index 807e27ead0..1bb7466e26 100644 --- a/app/upgrades/v17/upgrades.go +++ b/app/upgrades/v17/upgrades.go @@ -69,7 +69,7 @@ func CreateUpgradeHandler( ctx.Logger().Info("Migrating host zones...") if err := RegisterCommunityPoolAddresses(ctx, stakeibcKeeper); err != nil { - return vm, errorsmod.Wrapf(err, "unable to migrate host zones") + return vm, errorsmod.Wrapf(err, "unable to register community pool addresses on host zones") } ctx.Logger().Info("Deleting all pending queries...") diff --git a/app/upgrades/v17/upgrades_test.go b/app/upgrades/v17/upgrades_test.go index 41ec170b6b..106f72e3bd 100644 --- a/app/upgrades/v17/upgrades_test.go +++ b/app/upgrades/v17/upgrades_test.go @@ -7,7 +7,6 @@ import ( sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" "github.com/stretchr/testify/suite" icqtypes "github.com/Stride-Labs/stride/v16/x/interchainquery/types" @@ -15,10 +14,24 @@ import ( "github.com/Stride-Labs/stride/v16/app/apptesting" v17 "github.com/Stride-Labs/stride/v16/app/upgrades/v17" + "github.com/Stride-Labs/stride/v16/x/stakeibc/keeper" stakeibckeeper "github.com/Stride-Labs/stride/v16/x/stakeibc/keeper" stakeibctypes "github.com/Stride-Labs/stride/v16/x/stakeibc/types" ) +const ( + GaiaChainId = "cosmoshub-4" + SommelierChainId = "sommelier-3" + + Atom = "uatom" + Osmo = "uosmo" + Somm = "usomm" + + StAtom = "st" + Atom + StOsmo = "st" + Osmo + StSomm = "st" + Somm +) + type UpdateRedemptionRateBounds struct { ChainId string CurrentRedemptionRate sdk.Dec @@ -56,7 +69,283 @@ func TestKeeperTestSuite(t *testing.T) { } func (s *UpgradeTestSuite) TestUpgrade() { + dummyUpgradeHeight := int64(5) + + // Setup store before upgrade + checkHostZonesAfterUpgrade := s.SetupHostZonesBeforeUpgrade() + checkRateLimitsAfterUpgrade := s.SetupRateLimitsBeforeUpgrade() + checkCommunityPoolTaxAfterUpgrade := s.SetupCommunityPoolTaxBeforeUpgrade() + checkQueriesAfterUpgrade := s.SetupQueriesBeforeUpgrade() + + // Submit upgrade and confirm handler succeeds + s.ConfirmUpgradeSucceededs("v17", dummyUpgradeHeight) + + // Check state after upgrade + checkHostZonesAfterUpgrade() + checkRateLimitsAfterUpgrade() + checkCommunityPoolTaxAfterUpgrade() + checkQueriesAfterUpgrade() +} + +// Helper function to check that the community pool stake and redeem holding +// module accounts were registered and stored on the host zone +func (s *UpgradeTestSuite) checkCommunityPoolModuleAccountsRegistered(chainId string) { + hostZone, found := s.App.StakeibcKeeper.GetHostZone(s.Ctx, chainId) + s.Require().True(found, "host zone should have been found") + + s.Require().NotEmpty(hostZone.CommunityPoolStakeHoldingAddress, "stake holding for %s should not be empty", chainId) + s.Require().NotEmpty(hostZone.CommunityPoolRedeemHoldingAddress, "redeem holding for %s should not be empty", chainId) + + stakeHoldingAddress := sdk.MustAccAddressFromBech32(hostZone.CommunityPoolStakeHoldingAddress) + redeemHoldingAddress := sdk.MustAccAddressFromBech32(hostZone.CommunityPoolRedeemHoldingAddress) + stakeHoldingAccount := s.App.AccountKeeper.GetAccount(s.Ctx, stakeHoldingAddress) + redeemHoldingAccount := s.App.AccountKeeper.GetAccount(s.Ctx, redeemHoldingAddress) + + s.Require().NotNil(stakeHoldingAccount, "stake holding account should have been registered for %s", chainId) + s.Require().NotNil(redeemHoldingAccount, "redeem holding account should have been registered for %s", chainId) +} + +// Helper function to check that the community pool deposit and return ICA accounts were registered for the host zone +// The addresses don't get set until the callback, but we can check that the expected ICA controller port was claimed +// by the ICA controller module +func (s *UpgradeTestSuite) checkCommunityPoolICAAccountsRegistered(chainId string) { + depositOwner := stakeibctypes.FormatHostZoneICAOwner(chainId, stakeibctypes.ICAAccountType_COMMUNITY_POOL_DEPOSIT) + returnOwner := stakeibctypes.FormatHostZoneICAOwner(chainId, stakeibctypes.ICAAccountType_COMMUNITY_POOL_RETURN) + + expectedDepositPortId, _ := icatypes.NewControllerPortID(depositOwner) + expectedReturnPortId, _ := icatypes.NewControllerPortID(returnOwner) + + depositPortIdRegistered := s.App.ICAControllerKeeper.IsBound(s.Ctx, expectedDepositPortId) + returnPortIdRegistered := s.App.ICAControllerKeeper.IsBound(s.Ctx, expectedReturnPortId) + + s.Require().True(depositPortIdRegistered, "deposit port %s should have been bound", expectedDepositPortId) + s.Require().True(returnPortIdRegistered, "return port %s should have been bound", expectedReturnPortId) +} + +func (s *UpgradeTestSuite) SetupHostZonesBeforeUpgrade() func() { + hostZones := []stakeibctypes.HostZone{ + { + ChainId: GaiaChainId, + HostDenom: Atom, + ConnectionId: "connection-1", + Validators: []*stakeibctypes.Validator{ + {Address: "val1", SlashQueryInProgress: false}, + {Address: "val2", SlashQueryInProgress: true}, + }, + RedemptionRate: sdk.MustNewDecFromStr("1.1"), + }, + { + ChainId: v17.OsmosisChainId, + HostDenom: Osmo, + ConnectionId: "connection-2", + Validators: []*stakeibctypes.Validator{ + {Address: "val3", SlashQueryInProgress: true}, + {Address: "val4", SlashQueryInProgress: false}, + }, + RedemptionRate: sdk.MustNewDecFromStr("1.2"), + }, + { + // This host is just added for the rate limit test + // No need to validate accounts and redemptino rates + ChainId: SommelierChainId, + HostDenom: Somm, + ConnectionId: "connection-3", + Validators: []*stakeibctypes.Validator{ + {Address: "val5", SlashQueryInProgress: true}, + {Address: "val6", SlashQueryInProgress: false}, + }, + RedemptionRate: sdk.MustNewDecFromStr("1.0"), + }, + } + + for i, hostZone := range hostZones { + s.App.StakeibcKeeper.SetHostZone(s.Ctx, hostZone) + + clientId := fmt.Sprintf("07-tendermint-%d", i) + s.MockClientAndConnection(hostZone.ChainId, clientId, hostZone.ConnectionId) + } + + // Return callback to check store after upgrade + return func() { + // Check that the module and ICA accounts were registered + s.checkCommunityPoolModuleAccountsRegistered(GaiaChainId) + s.checkCommunityPoolModuleAccountsRegistered(v17.OsmosisChainId) + + s.checkCommunityPoolICAAccountsRegistered(GaiaChainId) + s.checkCommunityPoolICAAccountsRegistered(v17.OsmosisChainId) + + // Check that the redemption rate bounds were set + gaiaHostZone, found := s.App.StakeibcKeeper.GetHostZone(s.Ctx, GaiaChainId) + s.Require().True(found) + + s.Require().Equal(sdk.MustNewDecFromStr("1.045"), gaiaHostZone.MinRedemptionRate, "gaia min outer") // 1.1 - 5% = 1.045 + s.Require().Equal(sdk.MustNewDecFromStr("1.067"), gaiaHostZone.MinInnerRedemptionRate, "gaia min inner") // 1.1 - 3% = 1.067 + s.Require().Equal(sdk.MustNewDecFromStr("1.155"), gaiaHostZone.MaxInnerRedemptionRate, "gaia max inner") // 1.1 + 5% = 1.155 + s.Require().Equal(sdk.MustNewDecFromStr("1.210"), gaiaHostZone.MaxRedemptionRate, "gaia max outer") // 1.1 + 10% = 1.21 + + osmoHostZone, found := s.App.StakeibcKeeper.GetHostZone(s.Ctx, "osmosis-1") + s.Require().True(found) + + s.Require().Equal(sdk.MustNewDecFromStr("1.140"), osmoHostZone.MinRedemptionRate, "osmo min outer") // 1.2 - 5% = 1.140 + s.Require().Equal(sdk.MustNewDecFromStr("1.164"), osmoHostZone.MinInnerRedemptionRate, "osmo min inner") // 1.2 - 3% = 1.164 + s.Require().Equal(sdk.MustNewDecFromStr("1.260"), osmoHostZone.MaxInnerRedemptionRate, "osmo max inner") // 1.2 + 5% = 1.260 + s.Require().Equal(sdk.MustNewDecFromStr("1.344"), osmoHostZone.MaxRedemptionRate, "osmo max outer") // 1.2 + 12% = 1.344 + + // Check that there are no slash queries in progress + for _, hostZone := range s.App.StakeibcKeeper.GetAllHostZone(s.Ctx) { + for _, validator := range hostZone.Validators { + s.Require().False(validator.SlashQueryInProgress, "slash query in progress should have been set to false") + } + } + } +} + +func (s *UpgradeTestSuite) SetupRateLimitsBeforeUpgrade() func() { + gaiaChannelId := "channel-0" + + initialThreshold := sdk.OneInt() + initialFlow := sdkmath.NewInt(10) + initialChannelValue := sdkmath.NewInt(100) + updatedChannelValue := sdkmath.NewInt(200) + + ratesLimits := []AddRateLimits{ + { + // Gaia rate limit + // Treshold should be updated, new gaia RL added + Denom: StAtom, + ChannelId: gaiaChannelId, + }, + { + // Osmo rate limit + // Treshold should be updated, no new RL added + Denom: StOsmo, + ChannelId: v17.OsmosisTransferChannelId, + }, + { + // Somm rate limit + // Should be removed + Denom: StSomm, + ChannelId: "channel-10", + }, + } + + // Add rate limits + // No need to register host zones since they're initialized in the setup host zone function + for _, rateLimit := range ratesLimits { + s.App.RatelimitKeeper.SetRateLimit(s.Ctx, ratelimittypes.RateLimit{ + Path: &ratelimittypes.Path{ + Denom: rateLimit.Denom, + ChannelId: rateLimit.ChannelId, + }, + Quota: &ratelimittypes.Quota{ + MaxPercentSend: initialThreshold, + MaxPercentRecv: initialThreshold, + DurationHours: 10, + }, + Flow: &ratelimittypes.Flow{ + Outflow: initialFlow, + Inflow: initialFlow, + ChannelValue: initialChannelValue, + }, + }) + + // mint the token for the channel value + s.FundAccount(s.TestAccs[0], sdk.NewCoin(rateLimit.Denom, updatedChannelValue)) + } + + // Return callback to check store after upgrade + return func() { + // Check that we have 3 RLs + // (1) stuosmo on Stride -> Osmosis + // (2) stuatom on Stride -> Gaia + // (3) stuatom on Stride -> Osmosis + acutalRateLimits := s.App.RatelimitKeeper.GetAllRateLimits(s.Ctx) + s.Require().Len(acutalRateLimits, 3, "there should be 3 rate limits at the end") + + // Check the stosmo rate limit + stOsmoRateLimit, found := s.App.RatelimitKeeper.GetRateLimit(s.Ctx, StOsmo, v17.OsmosisTransferChannelId) + s.Require().True(found) + + osmoThreshold := v17.UpdatedRateLimits[v17.OsmosisChainId] + s.Require().Equal(osmoThreshold, stOsmoRateLimit.Quota.MaxPercentSend, "stosmo max percent send") + s.Require().Equal(osmoThreshold, stOsmoRateLimit.Quota.MaxPercentRecv, "stosmo max percent recv") + s.Require().Equal(initialFlow, stOsmoRateLimit.Flow.Outflow, "stosmo outflow") + s.Require().Equal(initialFlow, stOsmoRateLimit.Flow.Inflow, "stosmo inflow") + s.Require().Equal(initialChannelValue, stOsmoRateLimit.Flow.ChannelValue, "stosmo channel value") + + // Check the stuatom -> Gaia rate limit + stAtomToGaiaRateLimit, found := s.App.RatelimitKeeper.GetRateLimit(s.Ctx, StAtom, gaiaChannelId) + s.Require().True(found) + + atomThreshold := v17.UpdatedRateLimits[GaiaChainId] + s.Require().Equal(atomThreshold, stAtomToGaiaRateLimit.Quota.MaxPercentSend, "statom -> gaia max percent send") + s.Require().Equal(atomThreshold, stAtomToGaiaRateLimit.Quota.MaxPercentRecv, "statom -> gaia max percent recv") + s.Require().Equal(initialFlow, stAtomToGaiaRateLimit.Flow.Outflow, "statom -> gaia outflow") + s.Require().Equal(initialFlow, stAtomToGaiaRateLimit.Flow.Inflow, "statom -> gaia inflow") + s.Require().Equal(initialChannelValue, stAtomToGaiaRateLimit.Flow.ChannelValue, "statom -> gaia channel value") + + // Check the stuatom -> Osmo rate limit + // The flow should be reset to 0 and the channel value should update + stAtomToOsmoRateLimit, found := s.App.RatelimitKeeper.GetRateLimit(s.Ctx, StAtom, v17.OsmosisTransferChannelId) + s.Require().True(found) + + s.Require().Equal(atomThreshold, stAtomToOsmoRateLimit.Quota.MaxPercentSend, "statom -> osmo max percent send") + s.Require().Equal(atomThreshold, stAtomToOsmoRateLimit.Quota.MaxPercentRecv, "statom -> osmo max percent recv") + + s.Require().Zero(stAtomToOsmoRateLimit.Flow.Outflow.Int64(), "statom -> osmo outflow") + s.Require().Zero(stAtomToOsmoRateLimit.Flow.Inflow.Int64(), "statom -> osmo inflow") + s.Require().Equal(updatedChannelValue, stAtomToOsmoRateLimit.Flow.ChannelValue, "statom -> osmo channel value") + } +} + +func (s *UpgradeTestSuite) SetupCommunityPoolTaxBeforeUpgrade() func() { + // Set initial community pool tax to 2% + initialTax := sdk.MustNewDecFromStr("0.02") + params := s.App.DistrKeeper.GetParams(s.Ctx) + params.CommunityTax = initialTax + err := s.App.DistrKeeper.SetParams(s.Ctx, params) + s.Require().NoError(err, "no error expected when setting params") + + // Return callback to check store after upgrade + return func() { + // Confirm the tax increased + updatedParams := s.App.DistrKeeper.GetParams(s.Ctx) + s.Require().Equal(v17.CommunityPoolTax.String(), updatedParams.CommunityTax.String(), + "community pool tax should have been updated") + } +} + +func (s *UpgradeTestSuite) SetupQueriesBeforeUpgrade() func() { + // Set two queries - one for a slash, and one that's not for a slash + queries := []icqtypes.Query{ + { + Id: "query-1", + CallbackId: keeper.ICQCallbackID_Delegation, + }, + { + Id: "query-2", + CallbackId: keeper.ICQCallbackID_Calibrate, + }, + } + for _, query := range queries { + s.App.InterchainqueryKeeper.SetQuery(s.Ctx, query) + } + + // Return callback to check store after upgrade + return func() { + // Confirm one query was removed + remainingQueries := s.App.InterchainqueryKeeper.AllQueries(s.Ctx) + s.Require().Len(remainingQueries, 1, "there should be only 1 remaining query") + + // Confirm the slash query was removed + _, found := s.App.InterchainqueryKeeper.GetQuery(s.Ctx, "query-1") + s.Require().False(found, "slash query should have been removed") + + // Confirm the other query is still there + _, found = s.App.InterchainqueryKeeper.GetQuery(s.Ctx, "query-2") + s.Require().True(found, "non-slash query should not have been removed") + } } func (s *UpgradeTestSuite) TestRegisterCommunityPoolAddresses() { @@ -81,37 +370,11 @@ func (s *UpgradeTestSuite) TestRegisterCommunityPoolAddresses() { err := v17.RegisterCommunityPoolAddresses(s.Ctx, s.App.StakeibcKeeper) s.Require().NoError(err, "no error expected when registering ICA addresses") - // Confirm the module accounts were created and stored on each host - for _, chainId := range chainIds { - hostZone, found := s.App.StakeibcKeeper.GetHostZone(s.Ctx, chainId) - s.Require().True(found, "host zone should have been found") - - s.Require().NotEmpty(hostZone.CommunityPoolStakeHoldingAddress, "stake holding for %s should not be empty", chainId) - s.Require().NotEmpty(hostZone.CommunityPoolRedeemHoldingAddress, "redeem holding for %s should not be empty", chainId) - - stakeHoldingAddress := sdk.MustAccAddressFromBech32(hostZone.CommunityPoolStakeHoldingAddress) - redeemHoldingAddress := sdk.MustAccAddressFromBech32(hostZone.CommunityPoolRedeemHoldingAddress) - - stakeHoldingAccount := s.App.AccountKeeper.GetAccount(s.Ctx, stakeHoldingAddress) - redeemHoldingAccount := s.App.AccountKeeper.GetAccount(s.Ctx, redeemHoldingAddress) - - s.Require().NotNil(stakeHoldingAccount, "stake holding account should have been registered for %s", chainId) - s.Require().NotNil(redeemHoldingAccount, "redeem holding account should have been registered for %s", chainId) - } - - // Confirm the ICAs were registered - // The addresses don't get set until the callback, but we can check the events and confirm they were registered + // Confirm the module accounts and ICAs were registered for _, chainId := range chainIds { - depositOwner := stakeibctypes.FormatHostZoneICAOwner(chainId, stakeibctypes.ICAAccountType_COMMUNITY_POOL_DEPOSIT) - returnOwner := stakeibctypes.FormatHostZoneICAOwner(chainId, stakeibctypes.ICAAccountType_COMMUNITY_POOL_RETURN) - - expectedDepositPortId, _ := icatypes.NewControllerPortID(depositOwner) - expectedReturnPortId, _ := icatypes.NewControllerPortID(returnOwner) - - s.CheckEventValueEmitted(channeltypes.EventTypeChannelOpenInit, channeltypes.AttributeKeyPortID, expectedDepositPortId) - s.CheckEventValueEmitted(channeltypes.EventTypeChannelOpenInit, channeltypes.AttributeKeyPortID, expectedReturnPortId) + s.checkCommunityPoolModuleAccountsRegistered(chainId) + s.checkCommunityPoolICAAccountsRegistered(chainId) } - } func (s *UpgradeTestSuite) TestDeleteAllStaleQueries() { diff --git a/x/ratelimit/keeper/hooks.go b/x/ratelimit/keeper/hooks.go index 4a055d976e..7478e40fa3 100644 --- a/x/ratelimit/keeper/hooks.go +++ b/x/ratelimit/keeper/hooks.go @@ -9,14 +9,13 @@ import ( ) // Before each hour epoch, check if any of the rate limits have expired, -// -// and reset them if they have +// and reset them if they have func (k Keeper) BeforeEpochStart(ctx sdk.Context, epochInfo epochstypes.EpochInfo) { if epochInfo.Identifier == epochstypes.HOUR_EPOCH { epochHour := uint64(epochInfo.CurrentEpoch) for _, rateLimit := range k.GetAllRateLimits(ctx) { - if epochHour%rateLimit.Quota.DurationHours == 0 { + if rateLimit.Quota.DurationHours != 0 && epochHour%rateLimit.Quota.DurationHours == 0 { err := k.ResetRateLimit(ctx, rateLimit.Path.Denom, rateLimit.Path.ChannelId) if err != nil { k.Logger(ctx).Error(fmt.Sprintf("Unable to reset quota for Denom: %s, ChannelId: %s", rateLimit.Path.Denom, rateLimit.Path.ChannelId)) From 9ced924b8dcb30c981fd57e8b25fc660079663ec Mon Sep 17 00:00:00 2001 From: sampocs Date: Tue, 19 Dec 2023 13:21:26 -0600 Subject: [PATCH 06/19] updated rate limits --- app/upgrades/v17/upgrades.go | 27 +++++++++++++++++---------- app/upgrades/v17/upgrades_test.go | 2 +- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/app/upgrades/v17/upgrades.go b/app/upgrades/v17/upgrades.go index 1bb7466e26..17c2389a74 100644 --- a/app/upgrades/v17/upgrades.go +++ b/app/upgrades/v17/upgrades.go @@ -38,17 +38,24 @@ var ( OsmosisRedemptionRateBuffer = sdk.MustNewDecFromStr("0.02") // Rate limits updated according to TVL + // Framework: + // < 2.5M: No rate limit + // 2.5M - 10M: 50% + // 10M - 20M: 25% + // 20M - 40M: 20% + // 40M - 50M: 15% + // > 50M: 10% UpdatedRateLimits = map[string]sdkmath.Int{ - "comdex-1": sdkmath.ZeroInt(), // TVL: ~130k | <1M | No rate limit - "cosmoshub-4": sdkmath.NewInt(10), // TVL: ~50M | 30M+ | 10% RL - "evmos_9001-2": sdkmath.NewInt(50), // TVL: ~3M | 1M-15M+ | 50% RL - "injective-1": sdkmath.NewInt(50), // TVL: ~3M | 1M-15M+ | 50% RL - "juno-1": sdkmath.NewInt(50), // TVL: ~3M | 1M-15M+ | 50% RL - "osmosis-1": sdkmath.NewInt(10), // TVL: ~30M | 30M+ | 10% RL - "phoenix-1": sdkmath.ZeroInt(), // TVL: ~190k | <1M | No rate limit - "sommelier-3": sdkmath.ZeroInt(), // TVL: ~450k | <1M | No rate limit - "stargaze-1": sdkmath.NewInt(50), // TVL: 1.35M | 1M-15M+ | 50% RL - "umee-1": sdkmath.ZeroInt(), // TVL: ~200k | <1M | No rate limit + "comdex-1": sdkmath.ZeroInt(), // TVL: ~130k | <2.5M | No rate limit + "cosmoshub-4": sdkmath.NewInt(10), // TVL: ~50M | 50M+ | 10% RL + "evmos_9001-2": sdkmath.NewInt(50), // TVL: ~3M | 2.5M-10M | 50% RL + "injective-1": sdkmath.NewInt(50), // TVL: ~3M | 2.5M-10M | 50% RL + "juno-1": sdkmath.NewInt(50), // TVL: ~3M | 2.5M-10M | 50% RL + "osmosis-1": sdkmath.NewInt(20), // TVL: ~30M | 20M-40M | 20% RL + "phoenix-1": sdkmath.ZeroInt(), // TVL: ~190k | <2.5M | No rate limit + "sommelier-3": sdkmath.ZeroInt(), // TVL: ~450k | <2.5M | No rate limit + "stargaze-1": sdkmath.ZeroInt(), // TVL: 1.35M | <2.5M | No rate limit + "umee-1": sdkmath.ZeroInt(), // TVL: ~200k | <2.5M | No rate limit } // Osmo transfer channel is required for new rate limits diff --git a/app/upgrades/v17/upgrades_test.go b/app/upgrades/v17/upgrades_test.go index 106f72e3bd..59d994a49d 100644 --- a/app/upgrades/v17/upgrades_test.go +++ b/app/upgrades/v17/upgrades_test.go @@ -539,7 +539,7 @@ func (s *UpgradeTestSuite) TestUpdateRateLimitThresholds() { HostDenom: "uosmo", RateLimitDenom: "stuosmo", Duration: 20, - Threshold: sdkmath.NewInt(10), + Threshold: sdkmath.NewInt(20), }, "juno": { // No denom on matching host From e95161d33b37ddabe3b67abfe15051a2aa709220 Mon Sep 17 00:00:00 2001 From: sampocs Date: Wed, 20 Dec 2023 09:51:21 -0600 Subject: [PATCH 07/19] nit comments --- app/upgrades/v17/upgrades.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/upgrades/v17/upgrades.go b/app/upgrades/v17/upgrades.go index 17c2389a74..e24bbcc76d 100644 --- a/app/upgrades/v17/upgrades.go +++ b/app/upgrades/v17/upgrades.go @@ -46,16 +46,16 @@ var ( // 40M - 50M: 15% // > 50M: 10% UpdatedRateLimits = map[string]sdkmath.Int{ - "comdex-1": sdkmath.ZeroInt(), // TVL: ~130k | <2.5M | No rate limit + "comdex-1": sdkmath.ZeroInt(), // TVL: ~150k | <2.5M | No rate limit "cosmoshub-4": sdkmath.NewInt(10), // TVL: ~50M | 50M+ | 10% RL "evmos_9001-2": sdkmath.NewInt(50), // TVL: ~3M | 2.5M-10M | 50% RL "injective-1": sdkmath.NewInt(50), // TVL: ~3M | 2.5M-10M | 50% RL "juno-1": sdkmath.NewInt(50), // TVL: ~3M | 2.5M-10M | 50% RL "osmosis-1": sdkmath.NewInt(20), // TVL: ~30M | 20M-40M | 20% RL - "phoenix-1": sdkmath.ZeroInt(), // TVL: ~190k | <2.5M | No rate limit - "sommelier-3": sdkmath.ZeroInt(), // TVL: ~450k | <2.5M | No rate limit - "stargaze-1": sdkmath.ZeroInt(), // TVL: 1.35M | <2.5M | No rate limit - "umee-1": sdkmath.ZeroInt(), // TVL: ~200k | <2.5M | No rate limit + "phoenix-1": sdkmath.ZeroInt(), // TVL: ~200k | <2.5M | No rate limit + "sommelier-3": sdkmath.ZeroInt(), // TVL: ~500k | <2.5M | No rate limit + "stargaze-1": sdkmath.ZeroInt(), // TVL: 1.5M | <2.5M | No rate limit + "umee-1": sdkmath.ZeroInt(), // TVL: ~150k | <2.5M | No rate limit } // Osmo transfer channel is required for new rate limits From 70ce9650b739f30dbcfc133be8dee309550c0b80 Mon Sep 17 00:00:00 2001 From: sampocs Date: Mon, 8 Jan 2024 20:22:02 -0600 Subject: [PATCH 08/19] Update app/upgrades/v17/upgrades.go Co-authored-by: riley-stride <104941670+riley-stride@users.noreply.github.com> --- app/upgrades/v17/upgrades.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/upgrades/v17/upgrades.go b/app/upgrades/v17/upgrades.go index e24bbcc76d..f0a99622fa 100644 --- a/app/upgrades/v17/upgrades.go +++ b/app/upgrades/v17/upgrades.go @@ -79,7 +79,7 @@ func CreateUpgradeHandler( return vm, errorsmod.Wrapf(err, "unable to register community pool addresses on host zones") } - ctx.Logger().Info("Deleting all pending queries...") + ctx.Logger().Info("Deleting all pending slash queries...") DeleteAllStaleQueries(ctx, icqKeeper) ctx.Logger().Info("Reseting slash query in progress...") From 4871a9ce828c43ddaeb1ea03b8906319a5dece66 Mon Sep 17 00:00:00 2001 From: sampocs Date: Mon, 8 Jan 2024 20:25:57 -0600 Subject: [PATCH 09/19] updated rate limits --- app/upgrades/v17/upgrades.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/upgrades/v17/upgrades.go b/app/upgrades/v17/upgrades.go index e24bbcc76d..5b4bf65afa 100644 --- a/app/upgrades/v17/upgrades.go +++ b/app/upgrades/v17/upgrades.go @@ -47,11 +47,11 @@ var ( // > 50M: 10% UpdatedRateLimits = map[string]sdkmath.Int{ "comdex-1": sdkmath.ZeroInt(), // TVL: ~150k | <2.5M | No rate limit - "cosmoshub-4": sdkmath.NewInt(10), // TVL: ~50M | 50M+ | 10% RL + "cosmoshub-4": sdkmath.NewInt(15), // TVL: ~45M | 40M-50M | 15% RL "evmos_9001-2": sdkmath.NewInt(50), // TVL: ~3M | 2.5M-10M | 50% RL - "injective-1": sdkmath.NewInt(50), // TVL: ~3M | 2.5M-10M | 50% RL + "injective-1": sdkmath.ZeroInt(), // TVL: ~1.5M | <2.5M | No rate limit "juno-1": sdkmath.NewInt(50), // TVL: ~3M | 2.5M-10M | 50% RL - "osmosis-1": sdkmath.NewInt(20), // TVL: ~30M | 20M-40M | 20% RL + "osmosis-1": sdkmath.NewInt(15), // TVL: ~45M | 40M-50M | 15% RL "phoenix-1": sdkmath.ZeroInt(), // TVL: ~200k | <2.5M | No rate limit "sommelier-3": sdkmath.ZeroInt(), // TVL: ~500k | <2.5M | No rate limit "stargaze-1": sdkmath.ZeroInt(), // TVL: 1.5M | <2.5M | No rate limit From 71e9917bd3b8fef4f545d28ec6bb648ad840af9d Mon Sep 17 00:00:00 2001 From: sampocs Date: Mon, 8 Jan 2024 20:28:16 -0600 Subject: [PATCH 10/19] removed inner RR updates --- app/upgrades/v17/upgrades.go | 8 -------- app/upgrades/v17/upgrades_test.go | 22 ++++------------------ 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/app/upgrades/v17/upgrades.go b/app/upgrades/v17/upgrades.go index 465add64be..5f11299a1f 100644 --- a/app/upgrades/v17/upgrades.go +++ b/app/upgrades/v17/upgrades.go @@ -28,8 +28,6 @@ var ( // Redemption rate bounds updated to give ~3 months of slack on outer bounds RedemptionRateOuterMinAdjustment = sdk.MustNewDecFromStr("0.05") - RedemptionRateInnerMinAdjustment = sdk.MustNewDecFromStr("0.03") - RedemptionRateInnerMaxAdjustment = sdk.MustNewDecFromStr("0.05") RedemptionRateOuterMaxAdjustment = sdk.MustNewDecFromStr("0.10") // Osmosis will have a slighly larger buffer with the redemption rate @@ -205,18 +203,12 @@ func UpdateRedemptionRateBounds(ctx sdk.Context, k stakeibckeeper.Keeper) { } outerMinDelta := hostZone.RedemptionRate.Mul(RedemptionRateOuterMinAdjustment) - innerMinDelta := hostZone.RedemptionRate.Mul(RedemptionRateInnerMinAdjustment) - innerMaxDelta := hostZone.RedemptionRate.Mul(RedemptionRateInnerMaxAdjustment) outerMaxDelta := hostZone.RedemptionRate.Mul(outerAdjustment) outerMin := hostZone.RedemptionRate.Sub(outerMinDelta) - innerMin := hostZone.RedemptionRate.Sub(innerMinDelta) - innerMax := hostZone.RedemptionRate.Add(innerMaxDelta) outerMax := hostZone.RedemptionRate.Add(outerMaxDelta) hostZone.MinRedemptionRate = outerMin - hostZone.MinInnerRedemptionRate = innerMin - hostZone.MaxInnerRedemptionRate = innerMax hostZone.MaxRedemptionRate = outerMax k.SetHostZone(ctx, hostZone) diff --git a/app/upgrades/v17/upgrades_test.go b/app/upgrades/v17/upgrades_test.go index 59d994a49d..44df8d61ed 100644 --- a/app/upgrades/v17/upgrades_test.go +++ b/app/upgrades/v17/upgrades_test.go @@ -36,8 +36,6 @@ type UpdateRedemptionRateBounds struct { ChainId string CurrentRedemptionRate sdk.Dec ExpectedMinOuterRedemptionRate sdk.Dec - ExpectedMinInnerRedemptionRate sdk.Dec - ExpectedMaxInnerRedemptionRate sdk.Dec ExpectedMaxOuterRedemptionRate sdk.Dec } @@ -179,18 +177,14 @@ func (s *UpgradeTestSuite) SetupHostZonesBeforeUpgrade() func() { gaiaHostZone, found := s.App.StakeibcKeeper.GetHostZone(s.Ctx, GaiaChainId) s.Require().True(found) - s.Require().Equal(sdk.MustNewDecFromStr("1.045"), gaiaHostZone.MinRedemptionRate, "gaia min outer") // 1.1 - 5% = 1.045 - s.Require().Equal(sdk.MustNewDecFromStr("1.067"), gaiaHostZone.MinInnerRedemptionRate, "gaia min inner") // 1.1 - 3% = 1.067 - s.Require().Equal(sdk.MustNewDecFromStr("1.155"), gaiaHostZone.MaxInnerRedemptionRate, "gaia max inner") // 1.1 + 5% = 1.155 - s.Require().Equal(sdk.MustNewDecFromStr("1.210"), gaiaHostZone.MaxRedemptionRate, "gaia max outer") // 1.1 + 10% = 1.21 + s.Require().Equal(sdk.MustNewDecFromStr("1.045"), gaiaHostZone.MinRedemptionRate, "gaia min outer") // 1.1 - 5% = 1.045 + s.Require().Equal(sdk.MustNewDecFromStr("1.210"), gaiaHostZone.MaxRedemptionRate, "gaia max outer") // 1.1 + 10% = 1.21 osmoHostZone, found := s.App.StakeibcKeeper.GetHostZone(s.Ctx, "osmosis-1") s.Require().True(found) - s.Require().Equal(sdk.MustNewDecFromStr("1.140"), osmoHostZone.MinRedemptionRate, "osmo min outer") // 1.2 - 5% = 1.140 - s.Require().Equal(sdk.MustNewDecFromStr("1.164"), osmoHostZone.MinInnerRedemptionRate, "osmo min inner") // 1.2 - 3% = 1.164 - s.Require().Equal(sdk.MustNewDecFromStr("1.260"), osmoHostZone.MaxInnerRedemptionRate, "osmo max inner") // 1.2 + 5% = 1.260 - s.Require().Equal(sdk.MustNewDecFromStr("1.344"), osmoHostZone.MaxRedemptionRate, "osmo max outer") // 1.2 + 12% = 1.344 + s.Require().Equal(sdk.MustNewDecFromStr("1.140"), osmoHostZone.MinRedemptionRate, "osmo min outer") // 1.2 - 5% = 1.140 + s.Require().Equal(sdk.MustNewDecFromStr("1.344"), osmoHostZone.MaxRedemptionRate, "osmo max outer") // 1.2 + 12% = 1.344 // Check that there are no slash queries in progress for _, hostZone := range s.App.StakeibcKeeper.GetAllHostZone(s.Ctx) { @@ -471,16 +465,12 @@ func (s *UpgradeTestSuite) TestUpdateRedemptionRateBounds() { ChainId: "chain-0", CurrentRedemptionRate: sdk.MustNewDecFromStr("1.0"), ExpectedMinOuterRedemptionRate: sdk.MustNewDecFromStr("0.95"), // 1 - 5% = 0.95 - ExpectedMinInnerRedemptionRate: sdk.MustNewDecFromStr("0.97"), // 1 - 3% = 0.97 - ExpectedMaxInnerRedemptionRate: sdk.MustNewDecFromStr("1.05"), // 1 + 5% = 1.05 ExpectedMaxOuterRedemptionRate: sdk.MustNewDecFromStr("1.10"), // 1 + 10% = 1.1 }, { ChainId: "chain-1", CurrentRedemptionRate: sdk.MustNewDecFromStr("1.1"), ExpectedMinOuterRedemptionRate: sdk.MustNewDecFromStr("1.045"), // 1.1 - 5% = 1.045 - ExpectedMinInnerRedemptionRate: sdk.MustNewDecFromStr("1.067"), // 1.1 - 3% = 1.067 - ExpectedMaxInnerRedemptionRate: sdk.MustNewDecFromStr("1.155"), // 1.1 + 5% = 1.155 ExpectedMaxOuterRedemptionRate: sdk.MustNewDecFromStr("1.210"), // 1.1 + 10% = 1.21 }, { @@ -488,8 +478,6 @@ func (s *UpgradeTestSuite) TestUpdateRedemptionRateBounds() { ChainId: v17.OsmosisChainId, CurrentRedemptionRate: sdk.MustNewDecFromStr("1.25"), ExpectedMinOuterRedemptionRate: sdk.MustNewDecFromStr("1.1875"), // 1.25 - 5% = 1.1875 - ExpectedMinInnerRedemptionRate: sdk.MustNewDecFromStr("1.2125"), // 1.25 - 3% = 1.2125 - ExpectedMaxInnerRedemptionRate: sdk.MustNewDecFromStr("1.3125"), // 1.25 + 5% = 1.3125 ExpectedMaxOuterRedemptionRate: sdk.MustNewDecFromStr("1.4000"), // 1.25 + 12% = 1.400 }, } @@ -512,8 +500,6 @@ func (s *UpgradeTestSuite) TestUpdateRedemptionRateBounds() { s.Require().True(found) s.Require().Equal(tc.ExpectedMinOuterRedemptionRate, hostZone.MinRedemptionRate, "%s - min outer", tc.ChainId) - s.Require().Equal(tc.ExpectedMinInnerRedemptionRate, hostZone.MinInnerRedemptionRate, "%s - min inner", tc.ChainId) - s.Require().Equal(tc.ExpectedMaxInnerRedemptionRate, hostZone.MaxInnerRedemptionRate, "%s - max inner", tc.ChainId) s.Require().Equal(tc.ExpectedMaxOuterRedemptionRate, hostZone.MaxRedemptionRate, "%s - max outer", tc.ChainId) } } From 942418999d740961f4afcbd12586d28af4e5e4f5 Mon Sep 17 00:00:00 2001 From: sampocs Date: Mon, 8 Jan 2024 20:38:44 -0600 Subject: [PATCH 11/19] nit --- app/upgrades/v17/upgrades.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/upgrades/v17/upgrades.go b/app/upgrades/v17/upgrades.go index 5f11299a1f..8c92614e1c 100644 --- a/app/upgrades/v17/upgrades.go +++ b/app/upgrades/v17/upgrades.go @@ -60,7 +60,7 @@ var ( OsmosisTransferChannelId = "channel-5" ) -// CreateUpgradeHandler creates an SDK upgrade handler for v15 +// CreateUpgradeHandler creates an SDK upgrade handler for v17 func CreateUpgradeHandler( mm *module.Manager, configurator module.Configurator, From d7204b88de39d82a765385899b920f90de170554 Mon Sep 17 00:00:00 2001 From: sampocs Date: Mon, 8 Jan 2024 22:12:29 -0600 Subject: [PATCH 12/19] added param migration --- app/upgrades/v17/upgrades.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/upgrades/v17/upgrades.go b/app/upgrades/v17/upgrades.go index 8c92614e1c..da3a879370 100644 --- a/app/upgrades/v17/upgrades.go +++ b/app/upgrades/v17/upgrades.go @@ -72,6 +72,9 @@ func CreateUpgradeHandler( return func(ctx sdk.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { ctx.Logger().Info("Starting upgrade v17...") + ctx.Logger().Info("Migrating stakeibc params...") + MigrateStakeibcParams(ctx, stakeibcKeeper) + ctx.Logger().Info("Migrating host zones...") if err := RegisterCommunityPoolAddresses(ctx, stakeibcKeeper); err != nil { return vm, errorsmod.Wrapf(err, "unable to register community pool addresses on host zones") @@ -103,6 +106,16 @@ func CreateUpgradeHandler( } } +// Migrate the stakeibc params to add the ValidatorWeightCap parameter +// +// NOTE: If a parameter is added, the old params cannot be unmarshalled +// to the new schema. To get around this, we have to set each parameter explicitly +// Considering all mainnet stakeibc params are set to the default, we can just use that +func MigrateStakeibcParams(ctx sdk.Context, k stakeibckeeper.Keeper) { + params := stakeibctypes.DefaultParams() + k.SetParams(ctx, params) +} + // Migrates the host zones to the new structure which supports community pool liquid staking // We don't have to perform a true migration here since only new fields were added // (in other words, we can deserialize the old host zone structs into the new types) From e8f2d0a4e24725809470383ebf11b6c3e109506b Mon Sep 17 00:00:00 2001 From: shellvish <104537253+shellvish@users.noreply.github.com> Date: Tue, 9 Jan 2024 11:47:20 -0700 Subject: [PATCH 13/19] Add Prop 225 to v17 Upgrade Handler (#1044) Co-authored-by: sampocs --- app/upgrades.go | 1 + app/upgrades/v17/upgrades.go | 22 ++++++++++++++++++++++ app/upgrades/v17/upgrades_test.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/app/upgrades.go b/app/upgrades.go index 674b56fdc8..642818edca 100644 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -223,6 +223,7 @@ func (app *StrideApp) setupUpgradeHandlers(appOpts servertypes.AppOptions) { v17.CreateUpgradeHandler( app.mm, app.configurator, + app.BankKeeper, app.DistrKeeper, app.InterchainqueryKeeper, app.RatelimitKeeper, diff --git a/app/upgrades/v17/upgrades.go b/app/upgrades/v17/upgrades.go index da3a879370..de4f31ad95 100644 --- a/app/upgrades/v17/upgrades.go +++ b/app/upgrades/v17/upgrades.go @@ -12,6 +12,8 @@ import ( icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types" connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + "github.com/Stride-Labs/stride/v16/utils" icqkeeper "github.com/Stride-Labs/stride/v16/x/interchainquery/keeper" ratelimitkeeper "github.com/Stride-Labs/stride/v16/x/ratelimit/keeper" @@ -58,12 +60,19 @@ var ( // Osmo transfer channel is required for new rate limits OsmosisTransferChannelId = "channel-5" + + // Constants for Prop 225 + CommunityPoolGrowthAddress = "stride1lj0m72d70qerts9ksrsphy9nmsd4h0s88ll9gfphmhemh8ewet5qj44jc9" + LiquidityReceiver = "stride1auhjs4zgp3ahvrpkspf088r2psz7wpyrypcnal" + Prop225TransferAmount = sdk.NewInt(31_572_300_000) + Ustrd = "ustrd" ) // CreateUpgradeHandler creates an SDK upgrade handler for v17 func CreateUpgradeHandler( mm *module.Manager, configurator module.Configurator, + bankKeeper bankkeeper.Keeper, distributionkeeper distributionkeeper.Keeper, icqKeeper icqkeeper.Keeper, ratelimitKeeper ratelimitkeeper.Keeper, @@ -102,6 +111,11 @@ func CreateUpgradeHandler( return vm, errorsmod.Wrapf(err, "unable to add rate limits to Osmosis") } + ctx.Logger().Info("Executing Prop 225, SHD Liquidity") + if err := ExecuteProp225(ctx, bankKeeper); err != nil { + return vm, errorsmod.Wrapf(err, "unable to execute prop 225") + } + return mm.RunMigrations(ctx, configurator, vm) } } @@ -303,3 +317,11 @@ func AddRateLimitToOsmosis(ctx sdk.Context, k ratelimitkeeper.Keeper) error { return nil } + +// Execute Prop 225, release STRD to stride1auhjs4zgp3ahvrpkspf088r2psz7wpyrypcnal +func ExecuteProp225(ctx sdk.Context, k bankkeeper.Keeper) error { + communityPoolGrowthAddress := sdk.MustAccAddressFromBech32(CommunityPoolGrowthAddress) + liquidityReceiverAddress := sdk.MustAccAddressFromBech32(LiquidityReceiver) + transferCoin := sdk.NewCoin(Ustrd, Prop225TransferAmount) + return k.SendCoins(ctx, communityPoolGrowthAddress, liquidityReceiverAddress, sdk.NewCoins(transferCoin)) +} diff --git a/app/upgrades/v17/upgrades_test.go b/app/upgrades/v17/upgrades_test.go index 44df8d61ed..c8cf11579c 100644 --- a/app/upgrades/v17/upgrades_test.go +++ b/app/upgrades/v17/upgrades_test.go @@ -74,6 +74,7 @@ func (s *UpgradeTestSuite) TestUpgrade() { checkRateLimitsAfterUpgrade := s.SetupRateLimitsBeforeUpgrade() checkCommunityPoolTaxAfterUpgrade := s.SetupCommunityPoolTaxBeforeUpgrade() checkQueriesAfterUpgrade := s.SetupQueriesBeforeUpgrade() + checkProp225AfterUpgrade := s.SetupProp225BeforeUpgrade() // Submit upgrade and confirm handler succeeds s.ConfirmUpgradeSucceededs("v17", dummyUpgradeHeight) @@ -83,6 +84,7 @@ func (s *UpgradeTestSuite) TestUpgrade() { checkRateLimitsAfterUpgrade() checkCommunityPoolTaxAfterUpgrade() checkQueriesAfterUpgrade() + checkProp225AfterUpgrade() } // Helper function to check that the community pool stake and redeem holding @@ -342,6 +344,35 @@ func (s *UpgradeTestSuite) SetupQueriesBeforeUpgrade() func() { } } +func (s *UpgradeTestSuite) SetupProp225BeforeUpgrade() func() { + // Grab the community pool growth address and balance + communityPoolGrowthAddress := sdk.MustAccAddressFromBech32(v17.CommunityPoolGrowthAddress) + // Set the balance to 3x the amount that will be transferred + newCoin := sdk.NewCoin(v17.Ustrd, v17.Prop225TransferAmount.MulRaw(3)) + s.FundAccount(communityPoolGrowthAddress, newCoin) + originalCommunityGrowthBalance := s.App.BankKeeper.GetBalance(s.Ctx, communityPoolGrowthAddress, v17.Ustrd) + + // Grab the liquidity receiver address and balance + liquidityReceiverAddress := sdk.MustAccAddressFromBech32(v17.LiquidityReceiver) + originalLiquidityReceiverBalance := s.App.BankKeeper.GetBalance(s.Ctx, liquidityReceiverAddress, v17.Ustrd) + + // grab how much we want to transfer + transferAmount := v17.Prop225TransferAmount.Int64() + + // Return callback to check store after upgrade + return func() { + // verify funds left community growth + newCommunityGrowthBalance := s.App.BankKeeper.GetBalance(s.Ctx, communityPoolGrowthAddress, v17.Ustrd) + communityGrowthBalanceChange := originalCommunityGrowthBalance.Sub(newCommunityGrowthBalance) + s.Require().Equal(transferAmount, communityGrowthBalanceChange.Amount.Int64(), "community growth decreased by correct amount") + + // verify funds entered liquidity custodian + newLiquidityCustodianBalance := s.App.BankKeeper.GetBalance(s.Ctx, liquidityReceiverAddress, v17.Ustrd) + liquidityCustodianBalanceChange := newLiquidityCustodianBalance.Sub(originalLiquidityReceiverBalance).Amount.Int64() + s.Require().Equal(transferAmount, liquidityCustodianBalanceChange, "custodian balance increased by correct amount") + } +} + func (s *UpgradeTestSuite) TestRegisterCommunityPoolAddresses() { // Create 3 host zones, with empty ICA addresses chainIds := []string{} From f65bae9b743aace16620f99425f47604811d825d Mon Sep 17 00:00:00 2001 From: ethan-stride <126913021+ethan-stride@users.noreply.github.com> Date: Wed, 10 Jan 2024 19:43:39 -0700 Subject: [PATCH 14/19] send disable tokenization tx for delegation account on hub (#1045) Co-authored-by: sampocs --- app/upgrades/v17/upgrades.go | 33 ++++ app/upgrades/v17/upgrades_test.go | 47 +++++- proto/cosmos/staking/v1beta1/lsm_tx.proto | 9 ++ x/stakeibc/types/lsm_tx.pb.go | 181 +++++++++++++++++++++- 4 files changed, 255 insertions(+), 15 deletions(-) diff --git a/app/upgrades/v17/upgrades.go b/app/upgrades/v17/upgrades.go index de4f31ad95..6143380d14 100644 --- a/app/upgrades/v17/upgrades.go +++ b/app/upgrades/v17/upgrades.go @@ -2,6 +2,7 @@ package v17 import ( "fmt" + "time" errorsmod "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" @@ -9,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/module" distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + "github.com/cosmos/gogoproto/proto" icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types" connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" @@ -32,6 +34,9 @@ var ( RedemptionRateOuterMinAdjustment = sdk.MustNewDecFromStr("0.05") RedemptionRateOuterMaxAdjustment = sdk.MustNewDecFromStr("0.10") + // Define the hub chainId for disabling tokenization + GaiaChainId = "cosmoshub-4" + // Osmosis will have a slighly larger buffer with the redemption rate // since their yield is less predictable OsmosisChainId = "osmosis-1" @@ -111,6 +116,9 @@ func CreateUpgradeHandler( return vm, errorsmod.Wrapf(err, "unable to add rate limits to Osmosis") } + ctx.Logger().Info("Disabling tokenization on the hub...") + DisableTokenization(ctx, stakeibcKeeper, GaiaChainId) + ctx.Logger().Info("Executing Prop 225, SHD Liquidity") if err := ExecuteProp225(ctx, bankKeeper); err != nil { return vm, errorsmod.Wrapf(err, "unable to execute prop 225") @@ -318,6 +326,31 @@ func AddRateLimitToOsmosis(ctx sdk.Context, k ratelimitkeeper.Keeper) error { return nil } +// Sends the ICA message which disables LSM style tokenization of shares from the delegation +// account for this chain as a security to prevent possibility of large/fast withdrawls +func DisableTokenization(ctx sdk.Context, k stakeibckeeper.Keeper, chainId string) error { + hostZone, found := k.GetHostZone(ctx, chainId) + if !found { + return errorsmod.Wrapf(stakeibctypes.ErrHostZoneNotFound, "Unable to find chainId %s to remove tokenization", chainId) + } + + // Build the msg for the disable tokenization ICA tx + var msgs []proto.Message + msgs = append(msgs, &stakeibctypes.MsgDisableTokenizeShares{ + DelegatorAddress: hostZone.DelegationIcaAddress, + }) + + // Send the ICA tx to disable tokenization + timeoutTimestamp := uint64(ctx.BlockTime().Add(24 * time.Hour).UnixNano()) + delegationOwner := stakeibctypes.FormatHostZoneICAOwner(hostZone.ChainId, stakeibctypes.ICAAccountType_DELEGATION) + err := k.SubmitICATxWithoutCallback(ctx, hostZone.ConnectionId, delegationOwner, msgs, timeoutTimestamp) + if err != nil { + return errorsmod.Wrapf(err, "Failed to submit ICA tx to disable tokenization, Messages: %+v", msgs) + } + + return nil +} + // Execute Prop 225, release STRD to stride1auhjs4zgp3ahvrpkspf088r2psz7wpyrypcnal func ExecuteProp225(ctx sdk.Context, k bankkeeper.Keeper) error { communityPoolGrowthAddress := sdk.MustAccAddressFromBech32(CommunityPoolGrowthAddress) diff --git a/app/upgrades/v17/upgrades_test.go b/app/upgrades/v17/upgrades_test.go index c8cf11579c..325a79feda 100644 --- a/app/upgrades/v17/upgrades_test.go +++ b/app/upgrades/v17/upgrades_test.go @@ -7,6 +7,7 @@ import ( sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types" + ibctesting "github.com/cosmos/ibc-go/v7/testing" "github.com/stretchr/testify/suite" icqtypes "github.com/Stride-Labs/stride/v16/x/interchainquery/types" @@ -20,7 +21,6 @@ import ( ) const ( - GaiaChainId = "cosmoshub-4" SommelierChainId = "sommelier-3" Atom = "uatom" @@ -69,11 +69,16 @@ func TestKeeperTestSuite(t *testing.T) { func (s *UpgradeTestSuite) TestUpgrade() { dummyUpgradeHeight := int64(5) + // Create the gaia delegation channel + owner := stakeibctypes.FormatHostZoneICAOwner(v17.GaiaChainId, stakeibctypes.ICAAccountType_DELEGATION) + channelId, portId := s.CreateICAChannel(owner) + // Setup store before upgrade checkHostZonesAfterUpgrade := s.SetupHostZonesBeforeUpgrade() checkRateLimitsAfterUpgrade := s.SetupRateLimitsBeforeUpgrade() checkCommunityPoolTaxAfterUpgrade := s.SetupCommunityPoolTaxBeforeUpgrade() checkQueriesAfterUpgrade := s.SetupQueriesBeforeUpgrade() + checkDisableTokenizationICASubmitted := s.SetupTestDisableTokenization(channelId, portId) checkProp225AfterUpgrade := s.SetupProp225BeforeUpgrade() // Submit upgrade and confirm handler succeeds @@ -84,6 +89,7 @@ func (s *UpgradeTestSuite) TestUpgrade() { checkRateLimitsAfterUpgrade() checkCommunityPoolTaxAfterUpgrade() checkQueriesAfterUpgrade() + checkDisableTokenizationICASubmitted() checkProp225AfterUpgrade() } @@ -126,9 +132,9 @@ func (s *UpgradeTestSuite) checkCommunityPoolICAAccountsRegistered(chainId strin func (s *UpgradeTestSuite) SetupHostZonesBeforeUpgrade() func() { hostZones := []stakeibctypes.HostZone{ { - ChainId: GaiaChainId, + ChainId: v17.GaiaChainId, HostDenom: Atom, - ConnectionId: "connection-1", + ConnectionId: ibctesting.FirstConnectionID, // must be connection-0 since an ICA will be submitted Validators: []*stakeibctypes.Validator{ {Address: "val1", SlashQueryInProgress: false}, {Address: "val2", SlashQueryInProgress: true}, @@ -169,14 +175,14 @@ func (s *UpgradeTestSuite) SetupHostZonesBeforeUpgrade() func() { // Return callback to check store after upgrade return func() { // Check that the module and ICA accounts were registered - s.checkCommunityPoolModuleAccountsRegistered(GaiaChainId) + s.checkCommunityPoolModuleAccountsRegistered(v17.GaiaChainId) s.checkCommunityPoolModuleAccountsRegistered(v17.OsmosisChainId) - s.checkCommunityPoolICAAccountsRegistered(GaiaChainId) + s.checkCommunityPoolICAAccountsRegistered(v17.GaiaChainId) s.checkCommunityPoolICAAccountsRegistered(v17.OsmosisChainId) // Check that the redemption rate bounds were set - gaiaHostZone, found := s.App.StakeibcKeeper.GetHostZone(s.Ctx, GaiaChainId) + gaiaHostZone, found := s.App.StakeibcKeeper.GetHostZone(s.Ctx, v17.GaiaChainId) s.Require().True(found) s.Require().Equal(sdk.MustNewDecFromStr("1.045"), gaiaHostZone.MinRedemptionRate, "gaia min outer") // 1.1 - 5% = 1.045 @@ -274,7 +280,7 @@ func (s *UpgradeTestSuite) SetupRateLimitsBeforeUpgrade() func() { stAtomToGaiaRateLimit, found := s.App.RatelimitKeeper.GetRateLimit(s.Ctx, StAtom, gaiaChannelId) s.Require().True(found) - atomThreshold := v17.UpdatedRateLimits[GaiaChainId] + atomThreshold := v17.UpdatedRateLimits[v17.GaiaChainId] s.Require().Equal(atomThreshold, stAtomToGaiaRateLimit.Quota.MaxPercentSend, "statom -> gaia max percent send") s.Require().Equal(atomThreshold, stAtomToGaiaRateLimit.Quota.MaxPercentRecv, "statom -> gaia max percent recv") s.Require().Equal(initialFlow, stAtomToGaiaRateLimit.Flow.Outflow, "statom -> gaia outflow") @@ -344,6 +350,17 @@ func (s *UpgradeTestSuite) SetupQueriesBeforeUpgrade() func() { } } +func (s *UpgradeTestSuite) SetupTestDisableTokenization(channelId, portId string) func() { + // Get the current sequence number (to check that it incremented) + startSequence := s.MustGetNextSequenceNumber(portId, channelId) + + // Return callback to that the ICA was submitted + return func() { + endSequence := s.MustGetNextSequenceNumber(portId, channelId) + s.Require().Equal(startSequence+1, endSequence, "sequence number should have incremented from disabling detokenization") + } +} + func (s *UpgradeTestSuite) SetupProp225BeforeUpgrade() func() { // Grab the community pool growth address and balance communityPoolGrowthAddress := sdk.MustAccAddressFromBech32(v17.CommunityPoolGrowthAddress) @@ -725,3 +742,19 @@ func (s *UpgradeTestSuite) TestAddRateLimitToOsmosis() { err = v17.AddRateLimitToOsmosis(s.Ctx, s.App.RatelimitKeeper) s.Require().ErrorContains(err, "channel value is zero") } + +func (s *UpgradeTestSuite) TestDisableTokenization() { + // Create the host zone and delegation channel + owner := stakeibctypes.FormatHostZoneICAOwner(v17.GaiaChainId, stakeibctypes.ICAAccountType_DELEGATION) + channelId, portId := s.CreateICAChannel(owner) + + s.App.StakeibcKeeper.SetHostZone(s.Ctx, stakeibctypes.HostZone{ + ChainId: v17.GaiaChainId, + ConnectionId: ibctesting.FirstConnectionID, + }) + + // Call the disable function and confirm the sequence number incremented (indicating an ICA was submitted) + s.CheckICATxSubmitted(portId, channelId, func() error { + return v17.DisableTokenization(s.Ctx, s.App.StakeibcKeeper, v17.GaiaChainId) + }) +} diff --git a/proto/cosmos/staking/v1beta1/lsm_tx.proto b/proto/cosmos/staking/v1beta1/lsm_tx.proto index 239a11ebf6..3d35408192 100644 --- a/proto/cosmos/staking/v1beta1/lsm_tx.proto +++ b/proto/cosmos/staking/v1beta1/lsm_tx.proto @@ -28,3 +28,12 @@ message MsgRedeemTokensForShares { message MsgRedeemTokensForSharesResponse { cosmos.base.v1beta1.Coin amount = 1 [ (gogoproto.nullable) = false ]; } + +// MsgDisableTokenizeShares prevents LSM tokenization of shares for address +message MsgDisableTokenizeShares { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string delegator_address = 1 + [ (gogoproto.moretags) = "yaml:\"delegator_address\"" ]; +} diff --git a/x/stakeibc/types/lsm_tx.pb.go b/x/stakeibc/types/lsm_tx.pb.go index e6c3eb3a73..f824d5172a 100644 --- a/x/stakeibc/types/lsm_tx.pb.go +++ b/x/stakeibc/types/lsm_tx.pb.go @@ -110,9 +110,48 @@ func (m *MsgRedeemTokensForSharesResponse) GetAmount() types.Coin { return types.Coin{} } +// MsgDisableTokenizeShares prevents LSM tokenization of shares for address +type MsgDisableTokenizeShares struct { + DelegatorAddress string `protobuf:"bytes,1,opt,name=delegator_address,json=delegatorAddress,proto3" json:"delegator_address,omitempty" yaml:"delegator_address"` +} + +func (m *MsgDisableTokenizeShares) Reset() { *m = MsgDisableTokenizeShares{} } +func (m *MsgDisableTokenizeShares) String() string { return proto.CompactTextString(m) } +func (*MsgDisableTokenizeShares) ProtoMessage() {} +func (*MsgDisableTokenizeShares) Descriptor() ([]byte, []int) { + return fileDescriptor_34c3b474a863e424, []int{2} +} +func (m *MsgDisableTokenizeShares) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgDisableTokenizeShares) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgDisableTokenizeShares.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgDisableTokenizeShares) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgDisableTokenizeShares.Merge(m, src) +} +func (m *MsgDisableTokenizeShares) XXX_Size() int { + return m.Size() +} +func (m *MsgDisableTokenizeShares) XXX_DiscardUnknown() { + xxx_messageInfo_MsgDisableTokenizeShares.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgDisableTokenizeShares proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgRedeemTokensForShares)(nil), "cosmos.staking.v1beta1.MsgRedeemTokensForShares") proto.RegisterType((*MsgRedeemTokensForSharesResponse)(nil), "cosmos.staking.v1beta1.MsgRedeemTokensForSharesResponse") + proto.RegisterType((*MsgDisableTokenizeShares)(nil), "cosmos.staking.v1beta1.MsgDisableTokenizeShares") } func init() { @@ -120,7 +159,7 @@ func init() { } var fileDescriptor_34c3b474a863e424 = []byte{ - // 327 bytes of a gzipped FileDescriptorProto + // 349 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4e, 0xce, 0x2f, 0xce, 0xcd, 0x2f, 0xd6, 0x2f, 0x2e, 0x49, 0xcc, 0xce, 0xcc, 0x4b, 0xd7, 0x2f, 0x33, 0x4c, 0x4a, 0x2d, 0x49, 0x34, 0xd4, 0xcf, 0x29, 0xce, 0x8d, 0x2f, 0xa9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, @@ -135,13 +174,14 @@ var fileDescriptor_34c3b474a863e424 = []byte{ 0xa4, 0xc0, 0xa8, 0xc1, 0x6d, 0x24, 0xa9, 0x07, 0xf5, 0x06, 0xc8, 0x61, 0x30, 0x3f, 0xe8, 0x39, 0xe7, 0x67, 0xe6, 0x39, 0xb1, 0x9c, 0xb8, 0x27, 0xcf, 0x10, 0x04, 0x55, 0x6e, 0xc5, 0xd1, 0xb1, 0x40, 0x9e, 0xe1, 0xc5, 0x02, 0x79, 0x06, 0xa5, 0x68, 0x2e, 0x05, 0x5c, 0x2e, 0x0d, 0x4a, 0x2d, - 0x2e, 0xc8, 0xcf, 0x2b, 0x4e, 0x45, 0xb2, 0x86, 0x91, 0x24, 0x6b, 0x9c, 0x7c, 0x4e, 0x3c, 0x92, - 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, - 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x28, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, - 0x39, 0x3f, 0x57, 0x3f, 0xb8, 0xa4, 0x28, 0x33, 0x25, 0x55, 0xd7, 0x27, 0x31, 0x09, 0x14, 0x49, - 0x20, 0xb6, 0x7e, 0x99, 0xa1, 0x99, 0x7e, 0x05, 0x38, 0xc6, 0x52, 0x33, 0x93, 0x92, 0xf5, 0x4b, - 0x2a, 0x0b, 0x52, 0x8b, 0x93, 0xd8, 0xc0, 0x81, 0x6b, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x49, - 0x5c, 0x2e, 0x7e, 0xd1, 0x01, 0x00, 0x00, + 0x2e, 0xc8, 0xcf, 0x2b, 0x4e, 0x45, 0xb2, 0x86, 0x91, 0x24, 0x6b, 0x94, 0xf2, 0xc1, 0xc1, 0xe0, + 0x92, 0x59, 0x9c, 0x98, 0x94, 0x93, 0x0a, 0x36, 0x3d, 0xb3, 0x2a, 0x95, 0xea, 0xc1, 0x80, 0xf0, + 0x8d, 0x93, 0xcf, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x38, + 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, 0x37, 0x1e, 0xcb, 0x31, 0x44, 0x19, 0xa5, 0x67, + 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7, 0xe7, 0xea, 0x07, 0x97, 0x14, 0x65, 0xa6, 0xa4, 0xea, + 0xfa, 0x24, 0x26, 0x81, 0x52, 0x05, 0x88, 0xad, 0x5f, 0x66, 0x68, 0xa6, 0x5f, 0x01, 0x4e, 0x22, + 0xa9, 0x99, 0x49, 0xc9, 0xfa, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0xe0, 0xd8, 0x34, 0x06, + 0x04, 0x00, 0x00, 0xff, 0xff, 0x8a, 0x66, 0xa5, 0x97, 0x42, 0x02, 0x00, 0x00, } func (m *MsgRedeemTokensForShares) Marshal() (dAtA []byte, err error) { @@ -217,6 +257,36 @@ func (m *MsgRedeemTokensForSharesResponse) MarshalToSizedBuffer(dAtA []byte) (in return len(dAtA) - i, nil } +func (m *MsgDisableTokenizeShares) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgDisableTokenizeShares) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgDisableTokenizeShares) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.DelegatorAddress) > 0 { + i -= len(m.DelegatorAddress) + copy(dAtA[i:], m.DelegatorAddress) + i = encodeVarintLsmTx(dAtA, i, uint64(len(m.DelegatorAddress))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintLsmTx(dAtA []byte, offset int, v uint64) int { offset -= sovLsmTx(v) base := offset @@ -254,6 +324,19 @@ func (m *MsgRedeemTokensForSharesResponse) Size() (n int) { return n } +func (m *MsgDisableTokenizeShares) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.DelegatorAddress) + if l > 0 { + n += 1 + l + sovLsmTx(uint64(l)) + } + return n +} + func sovLsmTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -458,6 +541,88 @@ func (m *MsgRedeemTokensForSharesResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgDisableTokenizeShares) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLsmTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgDisableTokenizeShares: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgDisableTokenizeShares: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DelegatorAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLsmTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLsmTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthLsmTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DelegatorAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLsmTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthLsmTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipLsmTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 From 4c199dc413967082bf27f8a344473e48f54e4dfe Mon Sep 17 00:00:00 2001 From: sampocs Date: Wed, 10 Jan 2024 20:47:02 -0600 Subject: [PATCH 15/19] fixed unit test --- app/upgrades/v17/upgrades_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/upgrades/v17/upgrades_test.go b/app/upgrades/v17/upgrades_test.go index c8cf11579c..e279cc9b1e 100644 --- a/app/upgrades/v17/upgrades_test.go +++ b/app/upgrades/v17/upgrades_test.go @@ -541,22 +541,22 @@ func (s *UpgradeTestSuite) TestUpdateRateLimitThresholds() { // Define test cases consisting of an initial redemption rates and expected bounds testCases := map[string]UpdateRateLimits{ "cosmoshub": { - // 10% threshold + // 15% threshold ChainId: "cosmoshub-4", ChannelId: "channel-0", HostDenom: "uatom", RateLimitDenom: "stuatom", Duration: 10, - Threshold: sdkmath.NewInt(10), + Threshold: sdkmath.NewInt(15), }, "osmosis": { - // 10% threshold + // 15% threshold ChainId: "osmosis-1", ChannelId: "channel-1", HostDenom: "uosmo", RateLimitDenom: "stuosmo", Duration: 20, - Threshold: sdkmath.NewInt(20), + Threshold: sdkmath.NewInt(15), }, "juno": { // No denom on matching host From 8fbd60ad67ec49212002799896edd3c5352332d8 Mon Sep 17 00:00:00 2001 From: sampocs Date: Wed, 10 Jan 2024 20:48:15 -0600 Subject: [PATCH 16/19] added pfm store key --- app/upgrades.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/upgrades.go b/app/upgrades.go index 446fd9518d..01e1e3ae3e 100644 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -10,6 +10,7 @@ import ( consensustypes "github.com/cosmos/cosmos-sdk/x/consensus/types" crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + packetforwardtypes "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7/packetforward/types" consumertypes "github.com/cosmos/interchain-security/v3/x/ccv/consumer/types" evmosvestingtypes "github.com/evmos/vesting/x/vesting/types" @@ -271,13 +272,12 @@ func (app *StrideApp) setupUpgradeHandlers(appOpts servertypes.AppOptions) { storeUpgrades = &storetypes.StoreUpgrades{ Added: []string{evmosvestingtypes.ModuleName}, } + case "v17": + storeUpgrades = &storetypes.StoreUpgrades{ + // Add PFM store key + Added: []string{packetforwardtypes.ModuleName}, + } } - // TODO: uncomment when v17 upgrade is ready - // case "v17": - // storeUpgrades = &storetypes.StoreUpgrades{ - // // Add PFM store key - // Added: []string{packetforwardtypes.ModuleName}, - // } if storeUpgrades != nil { app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, storeUpgrades)) From 6e92ff0bcbb94fc51e0dc7b385f868234002e4fe Mon Sep 17 00:00:00 2001 From: sampocs Date: Wed, 10 Jan 2024 21:08:33 -0600 Subject: [PATCH 17/19] addressed aidan PR comments --- app/upgrades/v17/upgrades.go | 10 ++++++---- app/upgrades/v17/upgrades_test.go | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/upgrades/v17/upgrades.go b/app/upgrades/v17/upgrades.go index 6143380d14..1595aad505 100644 --- a/app/upgrades/v17/upgrades.go +++ b/app/upgrades/v17/upgrades.go @@ -97,11 +97,11 @@ func CreateUpgradeHandler( ctx.Logger().Info("Deleting all pending slash queries...") DeleteAllStaleQueries(ctx, icqKeeper) - ctx.Logger().Info("Reseting slash query in progress...") + ctx.Logger().Info("Resetting slash query in progress...") ResetSlashQueryInProgress(ctx, stakeibcKeeper) ctx.Logger().Info("Updating community pool tax...") - if err := IncreaseCommunityPoolTax(ctx, distributionkeeper); err != nil { + if err := ExecuteProp223(ctx, distributionkeeper); err != nil { return vm, errorsmod.Wrapf(err, "unable to increase community pool tax") } @@ -117,7 +117,9 @@ func CreateUpgradeHandler( } ctx.Logger().Info("Disabling tokenization on the hub...") - DisableTokenization(ctx, stakeibcKeeper, GaiaChainId) + if err := DisableTokenization(ctx, stakeibcKeeper, GaiaChainId); err != nil { + return vm, errorsmod.Wrapf(err, "unable to submit disable tokenization transaction") + } ctx.Logger().Info("Executing Prop 225, SHD Liquidity") if err := ExecuteProp225(ctx, bankKeeper); err != nil { @@ -222,7 +224,7 @@ func ResetSlashQueryInProgress(ctx sdk.Context, k stakeibckeeper.Keeper) { // Increases the community pool tax from 2 to 5% // This was from prop 223 which passed, but was deleted due to an ICS blacklist -func IncreaseCommunityPoolTax(ctx sdk.Context, k distributionkeeper.Keeper) error { +func ExecuteProp223(ctx sdk.Context, k distributionkeeper.Keeper) error { params := k.GetParams(ctx) params.CommunityTax = CommunityPoolTax return k.SetParams(ctx, params) diff --git a/app/upgrades/v17/upgrades_test.go b/app/upgrades/v17/upgrades_test.go index f57a176a66..e22aaae712 100644 --- a/app/upgrades/v17/upgrades_test.go +++ b/app/upgrades/v17/upgrades_test.go @@ -488,7 +488,7 @@ func (s *UpgradeTestSuite) TestResetSlashQueryInProgress() { } } -func (s *UpgradeTestSuite) TestIncreaseCommunityPoolTax() { +func (s *UpgradeTestSuite) TestExecuteProp223() { // Set initial community pool tax to 2% initialTax := sdk.MustNewDecFromStr("0.02") params := s.App.DistrKeeper.GetParams(s.Ctx) @@ -497,7 +497,7 @@ func (s *UpgradeTestSuite) TestIncreaseCommunityPoolTax() { s.Require().NoError(err, "no error expected when setting params") // Increase the tax - err = v17.IncreaseCommunityPoolTax(s.Ctx, s.App.DistrKeeper) + err = v17.ExecuteProp223(s.Ctx, s.App.DistrKeeper) s.Require().NoError(err, "no error expected when increasing community pool tax") // Confirm it increased From 5f546ec9954824d492e82bca39e7597babd4e82d Mon Sep 17 00:00:00 2001 From: sampocs Date: Wed, 10 Jan 2024 22:55:55 -0600 Subject: [PATCH 18/19] nit --- app/upgrades/v17/upgrades_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/upgrades/v17/upgrades_test.go b/app/upgrades/v17/upgrades_test.go index e22aaae712..baf6adcc46 100644 --- a/app/upgrades/v17/upgrades_test.go +++ b/app/upgrades/v17/upgrades_test.go @@ -211,16 +211,16 @@ func (s *UpgradeTestSuite) SetupRateLimitsBeforeUpgrade() func() { initialChannelValue := sdkmath.NewInt(100) updatedChannelValue := sdkmath.NewInt(200) - ratesLimits := []AddRateLimits{ + rateLimits := []AddRateLimits{ { // Gaia rate limit - // Treshold should be updated, new gaia RL added + // Threshold should be updated, new gaia RL added Denom: StAtom, ChannelId: gaiaChannelId, }, { // Osmo rate limit - // Treshold should be updated, no new RL added + // Threshold should be updated, no new RL added Denom: StOsmo, ChannelId: v17.OsmosisTransferChannelId, }, @@ -234,7 +234,7 @@ func (s *UpgradeTestSuite) SetupRateLimitsBeforeUpgrade() func() { // Add rate limits // No need to register host zones since they're initialized in the setup host zone function - for _, rateLimit := range ratesLimits { + for _, rateLimit := range rateLimits { s.App.RatelimitKeeper.SetRateLimit(s.Ctx, ratelimittypes.RateLimit{ Path: &ratelimittypes.Path{ Denom: rateLimit.Denom, From 37251bece0d130bb76a7d941cbd4c6b4cedba988 Mon Sep 17 00:00:00 2001 From: sampocs Date: Wed, 10 Jan 2024 23:04:39 -0600 Subject: [PATCH 19/19] fixed upgrade handler unit tests --- app/apptesting/test_helpers.go | 1 + 1 file changed, 1 insertion(+) diff --git a/app/apptesting/test_helpers.go b/app/apptesting/test_helpers.go index 7076ffce33..53552290c1 100644 --- a/app/apptesting/test_helpers.go +++ b/app/apptesting/test_helpers.go @@ -200,6 +200,7 @@ func (s *AppTestHelper) SetupIBCChains(hostChainID string) { // Call InitGenesis on the consumer s.StrideChain.App.(*app.StrideApp).GetConsumerKeeper().InitGenesis(s.StrideChain.GetContext(), &strideConsumerGenesis) + s.StrideChain.NextBlock() // Update coordinator s.Coordinator.Chains = map[string]*ibctesting.TestChain{