Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V17 upgrade handler #1029

Merged
merged 26 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d546f72
added shell for upgrade handler
sampocs Dec 14, 2023
dc60756
first pass at upgrade handler
sampocs Dec 14, 2023
9fc442b
first pass at upgrade handler
sampocs Dec 14, 2023
f2e1fff
Merge branch 'main' into v17-upgrade-handler
sampocs Dec 14, 2023
9b7bf0d
Merge branch 'v17-upgrade-handler' of github.com:Stride-Labs/stride i…
sampocs Dec 14, 2023
040d093
added unit tests
sampocs Dec 19, 2023
d5648a8
add unit test for full upgrade
sampocs Dec 19, 2023
9ced924
updated rate limits
sampocs Dec 19, 2023
e95161d
nit comments
sampocs Dec 20, 2023
70ce965
Update app/upgrades/v17/upgrades.go
sampocs Jan 9, 2024
4871a9c
updated rate limits
sampocs Jan 9, 2024
1926074
Merge branch 'v17-upgrade-handler' of github.com:Stride-Labs/stride i…
sampocs Jan 9, 2024
71e9917
removed inner RR updates
sampocs Jan 9, 2024
9424189
nit
sampocs Jan 9, 2024
d7204b8
added param migration
sampocs Jan 9, 2024
6d9b9ab
Merge branch 'main' into v17-upgrade-handler
sampocs Jan 9, 2024
e8f2d0a
Add Prop 225 to v17 Upgrade Handler (#1044)
shellvish Jan 9, 2024
f65bae9
send disable tokenization tx for delegation account on hub (#1045)
ethan-stride Jan 11, 2024
4c199dc
fixed unit test
sampocs Jan 11, 2024
ccedebe
Merge branch 'v17-upgrade-handler' of github.com:Stride-Labs/stride i…
sampocs Jan 11, 2024
d52d789
Merge branch 'main' into v17-upgrade-handler
sampocs Jan 11, 2024
8fbd60a
added pfm store key
sampocs Jan 11, 2024
6e92ff0
addressed aidan PR comments
sampocs Jan 11, 2024
5f546ec
nit
sampocs Jan 11, 2024
37251be
fixed upgrade handler unit tests
sampocs Jan 11, 2024
ba8e948
Merge branch 'main' into v17-upgrade-handler
sampocs Jan 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion app/apptesting/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
15 changes: 15 additions & 0 deletions app/upgrades.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -216,6 +217,20 @@ func (app *StrideApp) setupUpgradeHandlers(appOpts servertypes.AppOptions) {
),
)

// v17 upgrade handler
app.UpgradeKeeper.SetUpgradeHandler(
asalzmann marked this conversation as resolved.
Show resolved Hide resolved
v17.UpgradeName,
v17.CreateUpgradeHandler(
app.mm,
app.configurator,
app.BankKeeper,
app.DistrKeeper,
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))
Expand Down
327 changes: 327 additions & 0 deletions app/upgrades/v17/upgrades.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
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"

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"
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")

// Redemption rate bounds updated to give ~3 months of slack on outer bounds
RedemptionRateOuterMinAdjustment = 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
// 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: ~150k | <2.5M | No rate limit
"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.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(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
"umee-1": sdkmath.ZeroInt(), // TVL: ~150k | <2.5M | No rate limit
}

// Osmo transfer channel is required for new rate limits
OsmosisTransferChannelId = "channel-5"
sampocs marked this conversation as resolved.
Show resolved Hide resolved

// 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,
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 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")
}

ctx.Logger().Info("Deleting all pending slash queries...")
DeleteAllStaleQueries(ctx, icqKeeper)

ctx.Logger().Info("Reseting slash query in progress...")
asalzmann marked this conversation as resolved.
Show resolved Hide resolved
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 := AddRateLimitToOsmosis(ctx, ratelimitKeeper); err != nil {
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)
}
}

// 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()
sampocs marked this conversation as resolved.
Show resolved Hide resolved
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)
// This will also register the relevant community pool ICA addresses
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)
}

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 {
asalzmann marked this conversation as resolved.
Show resolved Hide resolved
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
}

// Deletes all stale queries
func DeleteAllStaleQueries(ctx sdk.Context, k icqkeeper.Keeper) {
for _, query := range k.AllQueries(ctx) {
if query.CallbackId == stakeibckeeper.ICQCallbackID_Delegation {
ethan-stride marked this conversation as resolved.
Show resolved Hide resolved
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 223 which passed, but was deleted due to an ICS blacklist
func IncreaseCommunityPoolTax(ctx sdk.Context, k distributionkeeper.Keeper) error {
asalzmann marked this conversation as resolved.
Show resolved Hide resolved
params := k.GetParams(ctx)
params.CommunityTax = CommunityPoolTax
return k.SetParams(ctx, params)
}

// 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)
asalzmann marked this conversation as resolved.
Show resolved Hide resolved
outerMaxDelta := hostZone.RedemptionRate.Mul(outerAdjustment)

outerMin := hostZone.RedemptionRate.Sub(outerMinDelta)
outerMax := hostZone.RedemptionRate.Add(outerMaxDelta)

hostZone.MinRedemptionRate = outerMin
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, hostDenom)
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 across each stToken
sampocs marked this conversation as resolved.
Show resolved Hide resolved
func AddRateLimitToOsmosis(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: &quota,
Flow: &flow,
})
}

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))
}
Loading
Loading