diff --git a/app/upgrades/v24/upgrades.go b/app/upgrades/v24/upgrades.go index 747afeb991..85c5304712 100644 --- a/app/upgrades/v24/upgrades.go +++ b/app/upgrades/v24/upgrades.go @@ -14,6 +14,15 @@ import ( var ( UpgradeName = "v24" + + // 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") ) // CreateUpgradeHandler creates an SDK upgrade handler for v23 @@ -31,6 +40,7 @@ func CreateUpgradeHandler( MigrateHostZones(ctx, stakeibcKeeper) MigrateDepositRecords(ctx, recordsKeeper) MigrateEpochUnbondingRecords(ctx, recordsKeeper) + UpdateRedemptionRateBounds(ctx, stakeibcKeeper) ctx.Logger().Info("Running module migrations...") return mm.RunMigrations(ctx, configurator, vm) @@ -117,3 +127,27 @@ func MigrateEpochUnbondingRecords(ctx sdk.Context, k recordskeeper.Keeper) { k.SetEpochUnbondingRecord(ctx, epochUnbondingRecord) } } + +// Updates the outer redemption rate bounds +func UpdateRedemptionRateBounds(ctx sdk.Context, k stakeibckeeper.Keeper) { + ctx.Logger().Info("Updating redemption rate bounds...") + + 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) + 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) + } +} diff --git a/app/upgrades/v24/upgrades_test.go b/app/upgrades/v24/upgrades_test.go index 288d066bf3..faa79d2e30 100644 --- a/app/upgrades/v24/upgrades_test.go +++ b/app/upgrades/v24/upgrades_test.go @@ -3,9 +3,9 @@ package v24_test import ( "testing" - "github.com/stretchr/testify/suite" - sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" "github.com/Stride-Labs/stride/v24/app/apptesting" v24 "github.com/Stride-Labs/stride/v24/app/upgrades/v24" @@ -13,6 +13,13 @@ import ( stakeibctypes "github.com/Stride-Labs/stride/v24/x/stakeibc/types" ) +type UpdateRedemptionRateBounds struct { + ChainId string + CurrentRedemptionRate sdk.Dec + ExpectedMinOuterRedemptionRate sdk.Dec + ExpectedMaxOuterRedemptionRate sdk.Dec +} + type UpgradeTestSuite struct { apptesting.AppTestHelper } @@ -59,6 +66,8 @@ func (s *UpgradeTestSuite) TestUpgrade() { }, }) + checkRedemptionRatesAfterUpgrade := s.SetupTestUpdateRedemptionRateBounds() + // Run the upgrade s.ConfirmUpgradeSucceededs(v24.UpgradeName, upgradeHeight) @@ -79,6 +88,8 @@ func (s *UpgradeTestSuite) TestUpgrade() { s.Require().Equal(stTokens.Int64(), hostZoneUnbonding.StTokensToBurn.Int64(), "sttokens to burn") s.Require().Equal(nativeTokens.Int64(), hostZoneUnbonding.NativeTokensToUnbond.Int64(), "native to unbond") s.Require().Zero(hostZoneUnbonding.ClaimableNativeTokens.Int64(), "claimable tokens") + + checkRedemptionRatesAfterUpgrade() } func (s *UpgradeTestSuite) TestMigrateHostZones() { @@ -256,3 +267,49 @@ func (s *UpgradeTestSuite) TestMigrateEpochUnbondingRecords() { "%s undelegation txs in progress", tc.chainId) } } + +func (s *UpgradeTestSuite) SetupTestUpdateRedemptionRateBounds() func() { + // 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 + 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 + ExpectedMaxOuterRedemptionRate: sdk.MustNewDecFromStr("1.210"), // 1.1 + 10% = 1.21 + }, + { + // Max outer for osmo uses 12% instead of 10% + ChainId: v24.OsmosisChainId, + CurrentRedemptionRate: sdk.MustNewDecFromStr("1.25"), + ExpectedMinOuterRedemptionRate: sdk.MustNewDecFromStr("1.1875"), // 1.25 - 5% = 1.1875 + 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) + } + + // Return callback to check store after upgrade + return func() { + // 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.ExpectedMaxOuterRedemptionRate, hostZone.MaxRedemptionRate, "%s - max outer", tc.ChainId) + } + } +}