From 99d773f1e17873e07cf17565370538fb43b3a32e Mon Sep 17 00:00:00 2001 From: sampocs Date: Wed, 12 Jun 2024 21:15:04 -0500 Subject: [PATCH] staketia migration (#1212) --- app/app.go | 2 + app/apptesting/test_helpers.go | 2 +- proto/stride/stakeibc/host_zone.proto | 2 + proto/stride/staketia/staketia.proto | 41 +- proto/stride/staketia/tx.proto | 7 + x/autopilot/keeper/redeem_stake_test.go | 13 +- x/stakeibc/keeper/community_pool_test.go | 1 + x/stakeibc/keeper/host_zone.go | 13 + x/stakeibc/keeper/host_zone_test.go | 13 + x/stakeibc/keeper/msg_server.go | 376 +-- x/stakeibc/keeper/msg_server_test.go | 745 +----- x/stakeibc/keeper/redeem_stake.go | 153 ++ x/stakeibc/keeper/redeem_stake_test.go | 308 +++ x/stakeibc/keeper/registration.go | 258 ++ x/stakeibc/keeper/registration_test.go | 478 ++++ x/stakeibc/types/errors.go | 1 + x/stakeibc/types/host_zone.pb.go | 171 +- x/staketia/client/cli/tx.go | 43 - x/staketia/keeper/abci.go | 11 +- x/staketia/keeper/delegation.go | 106 +- x/staketia/keeper/delegation_test.go | 284 +-- x/staketia/keeper/events.go | 1 - x/staketia/keeper/hooks.go | 21 - x/staketia/keeper/host_zone_test.go | 31 +- x/staketia/keeper/invariants.go | 2 +- x/staketia/keeper/keeper.go | 6 + x/staketia/keeper/migration.go | 167 ++ x/staketia/keeper/migration_test.go | 208 ++ x/staketia/keeper/msg_server.go | 84 +- x/staketia/keeper/msg_server_test.go | 216 +- x/staketia/keeper/redemption_rate.go | 116 +- x/staketia/keeper/redemption_rate_test.go | 169 +- x/staketia/keeper/unbonding.go | 144 +- x/staketia/keeper/unbonding_test.go | 380 +-- x/staketia/legacytypes/staketia.pb.go | 2739 +++++++++++++++++++++ x/staketia/types/celestia.go | 8 +- x/staketia/types/errors.go | 1 + x/staketia/types/expected_keepers.go | 19 + x/staketia/types/genesis.go | 11 +- x/staketia/types/host_zone.go | 40 - x/staketia/types/host_zone_test.go | 133 - x/staketia/types/staketia.pb.go | 454 +--- x/staketia/types/tx.pb.go | 230 +- 43 files changed, 5087 insertions(+), 3121 deletions(-) create mode 100644 x/stakeibc/keeper/redeem_stake.go create mode 100644 x/stakeibc/keeper/redeem_stake_test.go create mode 100644 x/stakeibc/keeper/registration.go create mode 100644 x/stakeibc/keeper/registration_test.go create mode 100644 x/staketia/keeper/migration.go create mode 100644 x/staketia/keeper/migration_test.go create mode 100644 x/staketia/legacytypes/staketia.pb.go diff --git a/app/app.go b/app/app.go index e88dd86a1..edfca5a7b 100644 --- a/app/app.go +++ b/app/app.go @@ -692,6 +692,8 @@ func NewStrideApp( app.BankKeeper, app.ICAOracleKeeper, app.RatelimitKeeper, + app.RecordsKeeper, + app.StakeibcKeeper, app.TransferKeeper, ) stakeTiaModule := staketia.NewAppModule(appCodec, app.StaketiaKeeper) diff --git a/app/apptesting/test_helpers.go b/app/apptesting/test_helpers.go index bf7e31890..035a7ed65 100644 --- a/app/apptesting/test_helpers.go +++ b/app/apptesting/test_helpers.go @@ -86,7 +86,7 @@ func (s *AppTestHelper) Setup() { GRPCQueryRouter: s.App.GRPCQueryRouter(), Ctx: s.Ctx, } - s.TestAccs = CreateRandomAccounts(3) + s.TestAccs = CreateRandomAccounts(4) s.IbcEnabled = false s.IcaAddresses = make(map[string]string) diff --git a/proto/stride/stakeibc/host_zone.proto b/proto/stride/stakeibc/host_zone.proto index fff5a2c02..3438842d7 100644 --- a/proto/stride/stakeibc/host_zone.proto +++ b/proto/stride/stakeibc/host_zone.proto @@ -127,6 +127,8 @@ message HostZone { // The max number of messages that can be sent in a delegation // or undelegation ICA tx uint64 max_messages_per_ica_tx = 36; + // Indicates whether redemptions are allowed through this module + bool redemptions_enabled = 37; // An optional fee rebate // If there is no rebate for the host zone, this will be nil CommunityPoolRebate community_pool_rebate = 34; diff --git a/proto/stride/staketia/staketia.proto b/proto/stride/staketia/staketia.proto index c9171ae2e..e40d928fd 100644 --- a/proto/stride/staketia/staketia.proto +++ b/proto/stride/staketia/staketia.proto @@ -35,45 +35,8 @@ message HostZone { string safe_address_on_stride = 11 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; - // Previous redemption rate - string last_redemption_rate = 12 [ - (cosmos_proto.scalar) = "cosmos.Dec", - (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", - (gogoproto.nullable) = false - ]; - // Current redemption rate - string redemption_rate = 13 [ - (cosmos_proto.scalar) = "cosmos.Dec", - (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", - (gogoproto.nullable) = false - ]; - // Min outer redemption rate - adjusted by governance - string min_redemption_rate = 14 [ - (cosmos_proto.scalar) = "cosmos.Dec", - (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", - (gogoproto.nullable) = false - ]; - // Max outer redemption rate - adjusted by governance - string max_redemption_rate = 15 [ - (cosmos_proto.scalar) = "cosmos.Dec", - (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", - (gogoproto.nullable) = false - ]; - // Min inner redemption rate - adjusted by controller - string min_inner_redemption_rate = 16 [ - (cosmos_proto.scalar) = "cosmos.Dec", - (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", - (gogoproto.nullable) = false - ]; - // Max inner redemption rate - adjusted by controller - string max_inner_redemption_rate = 17 [ - (cosmos_proto.scalar) = "cosmos.Dec", - (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", - (gogoproto.nullable) = false - ]; - // Total delegated balance on the host zone delegation account - string delegated_balance = 18 [ + string remaining_delegated_balance = 18 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false ]; @@ -82,6 +45,8 @@ message HostZone { uint64 unbonding_period_seconds = 19; // Indicates whether the host zone has been halted bool halted = 20; + + reserved 13; } // Status fields for a delegation record diff --git a/proto/stride/staketia/tx.proto b/proto/stride/staketia/tx.proto index e26666cab..ad81aefca 100644 --- a/proto/stride/staketia/tx.proto +++ b/proto/stride/staketia/tx.proto @@ -75,10 +75,12 @@ service Msg { returns (MsgSetOperatorAddressResponse); } +// Deprecated: Liquid stakes should be handled in stakeibc // LiquidStake message MsgLiquidStake { option (cosmos.msg.v1.signer) = "staker"; option (amino.name) = "staketia/MsgLiquidStake"; + option deprecated = true; string staker = 1; string native_amount = 2 [ @@ -87,6 +89,8 @@ message MsgLiquidStake { ]; } message MsgLiquidStakeResponse { + option deprecated = true; + cosmos.base.v1beta1.Coin st_token = 1 [ (gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" @@ -103,6 +107,9 @@ message MsgRedeemStake { (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false ]; + // The receiver field is a celestia address + // It is only used in the case where the redemption spills over to stakeibc + string receiver = 3; } message MsgRedeemStakeResponse { cosmos.base.v1beta1.Coin native_token = 1 [ diff --git a/x/autopilot/keeper/redeem_stake_test.go b/x/autopilot/keeper/redeem_stake_test.go index 3ab8c020c..3e7111364 100644 --- a/x/autopilot/keeper/redeem_stake_test.go +++ b/x/autopilot/keeper/redeem_stake_test.go @@ -57,12 +57,13 @@ func (s *KeeperTestSuite) SetupAutopilotRedeemStake(featureEnabled bool, redeemA // store the host zone s.App.StakeibcKeeper.SetHostZone(s.Ctx, stakeibctypes.HostZone{ - ChainId: HostChainId, - Bech32Prefix: HostBechPrefix, // required to validate claim receiver - HostDenom: HostDenom, - RedemptionRate: sdk.NewDec(1), // used to determine native token amount - DepositAddress: depositAddress.String(), - TotalDelegations: redeemAmount, // there must be enough stake to cover the redemption + ChainId: HostChainId, + Bech32Prefix: HostBechPrefix, // required to validate claim receiver + HostDenom: HostDenom, + RedemptionRate: sdk.NewDec(1), // used to determine native token amount + DepositAddress: depositAddress.String(), + TotalDelegations: redeemAmount, // there must be enough stake to cover the redemption + RedemptionsEnabled: true, }) // fund the user with sttokens so they can redeem diff --git a/x/stakeibc/keeper/community_pool_test.go b/x/stakeibc/keeper/community_pool_test.go index e576f4d76..1628bdf7b 100644 --- a/x/stakeibc/keeper/community_pool_test.go +++ b/x/stakeibc/keeper/community_pool_test.go @@ -352,6 +352,7 @@ func (s *KeeperTestSuite) SetupRedeemCommunityPoolTokens() RedeemCommunityPoolTo DepositAddress: depositAddress.String(), TotalDelegations: initialStTokens, // at least as much as we are trying to redeem RedemptionRate: sdk.OneDec(), + RedemptionsEnabled: true, } s.App.StakeibcKeeper.SetHostZone(s.Ctx, hostZone) diff --git a/x/stakeibc/keeper/host_zone.go b/x/stakeibc/keeper/host_zone.go index 4ab254a84..d59c88d7d 100644 --- a/x/stakeibc/keeper/host_zone.go +++ b/x/stakeibc/keeper/host_zone.go @@ -288,3 +288,16 @@ func (k Keeper) GetTargetValAmtsForHostZone(ctx sdk.Context, hostZone types.Host return targetUnbondingsByValidator, nil } + +// Enables redemptions by setting the parameter on the host zone to true +// This is used during the staketia/stakedym migrations +func (k Keeper) EnableRedemptions(ctx sdk.Context, chainId string) error { + hostZone, found := k.GetHostZone(ctx, chainId) + if !found { + return types.ErrHostZoneNotFound.Wrapf(chainId) + } + + hostZone.RedemptionsEnabled = true + k.SetHostZone(ctx, hostZone) + return nil +} diff --git a/x/stakeibc/keeper/host_zone_test.go b/x/stakeibc/keeper/host_zone_test.go index 28598cf07..ce7d4ea61 100644 --- a/x/stakeibc/keeper/host_zone_test.go +++ b/x/stakeibc/keeper/host_zone_test.go @@ -573,3 +573,16 @@ func (s *KeeperTestSuite) TestGetTargetValAmtsForHostZone() { _, err = s.App.StakeibcKeeper.GetTargetValAmtsForHostZone(s.Ctx, types.HostZone{}, sdkmath.NewInt(1)) s.Require().ErrorContains(err, "No non-zero validators found for host zone") } + +func (s *KeeperTestSuite) TestEnableRedemptions() { + s.App.StakeibcKeeper.SetHostZone(s.Ctx, types.HostZone{ + ChainId: HostChainId, + RedemptionsEnabled: false, + }) + + err := s.App.StakeibcKeeper.EnableRedemptions(s.Ctx, HostChainId) + s.Require().NoError(err) + + hostZone := s.MustGetHostZone(HostChainId) + s.Require().True(hostZone.RedemptionsEnabled, "redemptions should have been enabled") +} diff --git a/x/stakeibc/keeper/msg_server.go b/x/stakeibc/keeper/msg_server.go index 75475d2a3..5ae2f871c 100644 --- a/x/stakeibc/keeper/msg_server.go +++ b/x/stakeibc/keeper/msg_server.go @@ -6,7 +6,6 @@ import ( "time" errorsmod "cosmossdk.io/errors" - sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" @@ -23,13 +22,6 @@ import ( "github.com/Stride-Labs/stride/v22/x/stakeibc/types" ) -var ( - CommunityPoolStakeHoldingAddressKey = "community-pool-stake" - CommunityPoolRedeemHoldingAddressKey = "community-pool-redeem" - - DefaultMaxMessagesPerIcaTx = uint64(32) -) - type msgServer struct { Keeper } @@ -44,239 +36,7 @@ var _ types.MsgServer = msgServer{} func (k msgServer) RegisterHostZone(goCtx context.Context, msg *types.MsgRegisterHostZone) (*types.MsgRegisterHostZoneResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - - // Get ConnectionEnd (for counterparty connection) - connectionEnd, found := k.IBCKeeper.ConnectionKeeper.GetConnection(ctx, msg.ConnectionId) - if !found { - errMsg := fmt.Sprintf("invalid connection id, %s not found", msg.ConnectionId) - k.Logger(ctx).Error(errMsg) - return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) - } - counterpartyConnection := connectionEnd.Counterparty - - // Get chain id from connection - chainId, err := k.GetChainIdFromConnectionId(ctx, msg.ConnectionId) - if err != nil { - errMsg := fmt.Sprintf("unable to obtain chain id from connection %s, err: %s", msg.ConnectionId, err.Error()) - k.Logger(ctx).Error(errMsg) - return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) - } - - // get zone - _, found = k.GetHostZone(ctx, chainId) - if found { - errMsg := fmt.Sprintf("invalid chain id, zone for %s already registered", chainId) - k.Logger(ctx).Error(errMsg) - return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) - } - - // check the denom is not already registered - hostZones := k.GetAllHostZone(ctx) - for _, hostZone := range hostZones { - if hostZone.HostDenom == msg.HostDenom { - errMsg := fmt.Sprintf("host denom %s already registered", msg.HostDenom) - k.Logger(ctx).Error(errMsg) - return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) - } - if hostZone.ConnectionId == msg.ConnectionId { - errMsg := fmt.Sprintf("connectionId %s already registered", msg.ConnectionId) - k.Logger(ctx).Error(errMsg) - return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) - } - if hostZone.TransferChannelId == msg.TransferChannelId { - errMsg := fmt.Sprintf("transfer channel %s already registered", msg.TransferChannelId) - k.Logger(ctx).Error(errMsg) - return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) - } - if hostZone.Bech32Prefix == msg.Bech32Prefix { - errMsg := fmt.Sprintf("bech32prefix %s already registered", msg.Bech32Prefix) - k.Logger(ctx).Error(errMsg) - return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) - } - } - - // create and save the zones's module account - depositAddress := types.NewHostZoneDepositAddress(chainId) - if err := utils.CreateModuleAccount(ctx, k.AccountKeeper, depositAddress); err != nil { - return nil, errorsmod.Wrapf(err, "unable to create deposit account for host zone %s", chainId) - } - - // Create the host zone's community pool holding accounts - 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) - } - if err := utils.CreateModuleAccount(ctx, k.AccountKeeper, communityPoolRedeemAddress); err != nil { - return nil, errorsmod.Wrapf(err, "unable to create community pool redeem account for host zone %s", chainId) - } - - // Validate the community pool treasury address if it's non-empty - if msg.CommunityPoolTreasuryAddress != "" { - _, err := utils.AccAddressFromBech32(msg.CommunityPoolTreasuryAddress, msg.Bech32Prefix) - if err != nil { - return nil, errorsmod.Wrapf(err, "invalid community pool treasury address (%s)", msg.CommunityPoolTreasuryAddress) - } - } - - params := k.GetParams(ctx) - if msg.MinRedemptionRate.IsNil() || msg.MinRedemptionRate.IsZero() { - msg.MinRedemptionRate = sdk.NewDecWithPrec(int64(params.DefaultMinRedemptionRateThreshold), 2) - } - if msg.MaxRedemptionRate.IsNil() || msg.MaxRedemptionRate.IsZero() { - msg.MaxRedemptionRate = sdk.NewDecWithPrec(int64(params.DefaultMaxRedemptionRateThreshold), 2) - } - - // Set the max messages per ICA tx to the default value if it's not specified - maxMessagesPerIcaTx := msg.MaxMessagesPerIcaTx - if maxMessagesPerIcaTx == 0 { - maxMessagesPerIcaTx = DefaultMaxMessagesPerIcaTx - } - - // set the zone - zone := types.HostZone{ - ChainId: chainId, - ConnectionId: msg.ConnectionId, - Bech32Prefix: msg.Bech32Prefix, - IbcDenom: msg.IbcDenom, - HostDenom: msg.HostDenom, - TransferChannelId: msg.TransferChannelId, - // Start sharesToTokens rate at 1 upon registration - RedemptionRate: sdk.NewDec(1), - LastRedemptionRate: sdk.NewDec(1), - UnbondingPeriod: msg.UnbondingPeriod, - DepositAddress: depositAddress.String(), - CommunityPoolStakeHoldingAddress: communityPoolStakeAddress.String(), - CommunityPoolRedeemHoldingAddress: communityPoolRedeemAddress.String(), - MinRedemptionRate: msg.MinRedemptionRate, - MaxRedemptionRate: msg.MaxRedemptionRate, - // Default the inner bounds to the outer bounds - MinInnerRedemptionRate: msg.MinRedemptionRate, - MaxInnerRedemptionRate: msg.MaxRedemptionRate, - LsmLiquidStakeEnabled: msg.LsmLiquidStakeEnabled, - CommunityPoolTreasuryAddress: msg.CommunityPoolTreasuryAddress, - MaxMessagesPerIcaTx: maxMessagesPerIcaTx, - } - // write the zone back to the store - k.SetHostZone(ctx, zone) - - appVersion := string(icatypes.ModuleCdc.MustMarshalJSON(&icatypes.Metadata{ - Version: icatypes.Version, - ControllerConnectionId: zone.ConnectionId, - HostConnectionId: counterpartyConnection.ConnectionId, - Encoding: icatypes.EncodingProtobuf, - TxType: icatypes.TxTypeSDKMultiMsg, - })) - - // generate delegate account - // NOTE: in the future, if we implement proxy governance, we'll need many more delegate accounts - delegateAccount := types.FormatHostZoneICAOwner(chainId, types.ICAAccountType_DELEGATION) - if err := k.ICAControllerKeeper.RegisterInterchainAccount(ctx, zone.ConnectionId, delegateAccount, appVersion); err != nil { - errMsg := fmt.Sprintf("unable to register delegation account, err: %s", err.Error()) - k.Logger(ctx).Error(errMsg) - return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) - } - - // generate fee account - feeAccount := types.FormatHostZoneICAOwner(chainId, types.ICAAccountType_FEE) - if err := k.ICAControllerKeeper.RegisterInterchainAccount(ctx, zone.ConnectionId, feeAccount, appVersion); err != nil { - errMsg := fmt.Sprintf("unable to register fee account, err: %s", err.Error()) - k.Logger(ctx).Error(errMsg) - return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) - } - - // generate withdrawal account - withdrawalAccount := types.FormatHostZoneICAOwner(chainId, types.ICAAccountType_WITHDRAWAL) - if err := k.ICAControllerKeeper.RegisterInterchainAccount(ctx, zone.ConnectionId, withdrawalAccount, appVersion); err != nil { - errMsg := fmt.Sprintf("unable to register withdrawal account, err: %s", err.Error()) - k.Logger(ctx).Error(errMsg) - return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) - } - - // generate redemption account - redemptionAccount := types.FormatHostZoneICAOwner(chainId, types.ICAAccountType_REDEMPTION) - if err := k.ICAControllerKeeper.RegisterInterchainAccount(ctx, zone.ConnectionId, redemptionAccount, appVersion); err != nil { - errMsg := fmt.Sprintf("unable to register redemption account, err: %s", err.Error()) - k.Logger(ctx).Error(errMsg) - return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) - } - - // create community pool deposit account - communityPoolDepositAccount := types.FormatHostZoneICAOwner(chainId, types.ICAAccountType_COMMUNITY_POOL_DEPOSIT) - if err := k.ICAControllerKeeper.RegisterInterchainAccount(ctx, zone.ConnectionId, communityPoolDepositAccount, appVersion); err != nil { - return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, "failed to register community pool deposit ICA") - } - - // create community pool return account - communityPoolReturnAccount := types.FormatHostZoneICAOwner(chainId, types.ICAAccountType_COMMUNITY_POOL_RETURN) - if err := k.ICAControllerKeeper.RegisterInterchainAccount(ctx, zone.ConnectionId, communityPoolReturnAccount, appVersion); err != nil { - return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, "failed to register community pool return ICA") - } - - // add this host zone to unbonding hostZones, otherwise users won't be able to unbond - // for this host zone until the following day - dayEpochTracker, found := k.GetEpochTracker(ctx, epochtypes.DAY_EPOCH) - if !found { - return nil, errorsmod.Wrapf(types.ErrEpochNotFound, "epoch tracker (%s) not found", epochtypes.DAY_EPOCH) - } - epochUnbondingRecord, found := k.RecordsKeeper.GetEpochUnbondingRecord(ctx, dayEpochTracker.EpochNumber) - if !found { - errMsg := "unable to find latest epoch unbonding record" - k.Logger(ctx).Error(errMsg) - return nil, errorsmod.Wrapf(recordstypes.ErrEpochUnbondingRecordNotFound, errMsg) - } - hostZoneUnbonding := recordstypes.HostZoneUnbonding{ - NativeTokenAmount: sdkmath.ZeroInt(), - StTokenAmount: sdkmath.ZeroInt(), - Denom: zone.HostDenom, - HostZoneId: zone.ChainId, - Status: recordstypes.HostZoneUnbonding_UNBONDING_QUEUE, - } - err = k.RecordsKeeper.SetHostZoneUnbondingRecord(ctx, epochUnbondingRecord.EpochNumber, chainId, hostZoneUnbonding) - if err != nil { - return nil, err - } - - // create an empty deposit record for the host zone - strideEpochTracker, found := k.GetEpochTracker(ctx, epochtypes.STRIDE_EPOCH) - if !found { - return nil, errorsmod.Wrapf(types.ErrEpochNotFound, "epoch tracker (%s) not found", epochtypes.STRIDE_EPOCH) - } - depositRecord := recordstypes.DepositRecord{ - Id: 0, - Amount: sdkmath.ZeroInt(), - Denom: zone.HostDenom, - HostZoneId: zone.ChainId, - Status: recordstypes.DepositRecord_TRANSFER_QUEUE, - DepositEpochNumber: strideEpochTracker.EpochNumber, - } - k.RecordsKeeper.AppendDepositRecord(ctx, depositRecord) - - // register stToken to consumer reward denom whitelist so that - // stToken rewards can be distributed to provider validators - err = k.RegisterStTokenDenomsToWhitelist(ctx, []string{types.StAssetDenomFromHostZoneDenom(zone.HostDenom)}) - if err != nil { - errMsg := fmt.Sprintf("unable to register reward denom, err: %s", err.Error()) - k.Logger(ctx).Error(errMsg) - return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) - } - - // emit events - ctx.EventManager().EmitEvent( - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), - ), - ) - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypeRegisterZone, - sdk.NewAttribute(types.AttributeKeyConnectionId, msg.ConnectionId), - sdk.NewAttribute(types.AttributeKeyRecipientChain, chainId), - ), - ) - - return &types.MsgRegisterHostZoneResponse{}, nil + return k.Keeper.RegisterHostZone(ctx, msg) } func (ms msgServer) UpdateHostZoneParams(goCtx context.Context, msg *types.MsgUpdateHostZoneParams) (*types.MsgUpdateHostZoneParamsResponse, error) { @@ -525,139 +285,7 @@ func (k msgServer) LiquidStake(goCtx context.Context, msg *types.MsgLiquidStake) func (k msgServer) RedeemStake(goCtx context.Context, msg *types.MsgRedeemStake) (*types.MsgRedeemStakeResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - k.Logger(ctx).Info(fmt.Sprintf("redeem stake: %s", msg.String())) - - // ----------------- PRELIMINARY CHECKS ----------------- - // get our addresses, make sure they're valid - sender, err := sdk.AccAddressFromBech32(msg.Creator) - if err != nil { - return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "creator address is invalid: %s. err: %s", msg.Creator, err.Error()) - } - // then make sure host zone is valid - hostZone, found := k.GetHostZone(ctx, msg.HostZone) - if !found { - return nil, errorsmod.Wrapf(types.ErrInvalidHostZone, "host zone is invalid: %s", msg.HostZone) - } - - if hostZone.Halted { - k.Logger(ctx).Error(fmt.Sprintf("Host Zone halted for zone (%s)", msg.HostZone)) - return nil, errorsmod.Wrapf(types.ErrHaltedHostZone, "halted host zone found for zone (%s)", msg.HostZone) - } - - // first construct a user redemption record - epochTracker, found := k.GetEpochTracker(ctx, "day") - if !found { - return nil, errorsmod.Wrapf(types.ErrEpochNotFound, "epoch tracker found: %s", "day") - } - - // ensure the recipient address is a valid bech32 address on the hostZone - _, err = utils.AccAddressFromBech32(msg.Receiver, hostZone.Bech32Prefix) - if err != nil { - return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid receiver address (%s)", err) - } - - // construct desired unstaking amount from host zone - stDenom := types.StAssetDenomFromHostZoneDenom(hostZone.HostDenom) - nativeAmount := sdk.NewDecFromInt(msg.Amount).Mul(hostZone.RedemptionRate).TruncateInt() - - if nativeAmount.GT(hostZone.TotalDelegations) { - return nil, errorsmod.Wrapf(types.ErrInvalidAmount, "cannot unstake an amount g.t. staked balance on host zone: %v", msg.Amount) - } - - // safety check: redemption rate must be within safety bounds - rateIsSafe, err := k.IsRedemptionRateWithinSafetyBounds(ctx, hostZone) - if !rateIsSafe || (err != nil) { - errMsg := fmt.Sprintf("IsRedemptionRateWithinSafetyBounds check failed. hostZone: %s, err: %s", hostZone.String(), err.Error()) - return nil, errorsmod.Wrapf(types.ErrRedemptionRateOutsideSafetyBounds, errMsg) - } - - // safety checks on the coin - // - Redemption amount must be positive - if !nativeAmount.IsPositive() { - return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "amount must be greater than 0. found: %v", msg.Amount) - } - // - Creator owns at least "amount" stAssets - balance := k.bankKeeper.GetBalance(ctx, sender, stDenom) - if balance.Amount.LT(msg.Amount) { - return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "balance is lower than redemption amount. redemption amount: %v, balance %v: ", msg.Amount, balance.Amount) - } - - // ----------------- UNBONDING RECORD KEEPING ----------------- - // Fetch the record - redemptionId := recordstypes.UserRedemptionRecordKeyFormatter(hostZone.ChainId, epochTracker.EpochNumber, msg.Receiver) - userRedemptionRecord, userHasRedeemedThisEpoch := k.RecordsKeeper.GetUserRedemptionRecord(ctx, redemptionId) - if userHasRedeemedThisEpoch { - k.Logger(ctx).Info(fmt.Sprintf("UserRedemptionRecord found for %s", redemptionId)) - // Add the unbonded amount to the UserRedemptionRecord - // The record is set below - userRedemptionRecord.StTokenAmount = userRedemptionRecord.StTokenAmount.Add(msg.Amount) - userRedemptionRecord.NativeTokenAmount = userRedemptionRecord.NativeTokenAmount.Add(nativeAmount) - } else { - // First time a user is redeeming this epoch - userRedemptionRecord = recordstypes.UserRedemptionRecord{ - Id: redemptionId, - Receiver: msg.Receiver, - NativeTokenAmount: nativeAmount, - Denom: hostZone.HostDenom, - HostZoneId: hostZone.ChainId, - EpochNumber: epochTracker.EpochNumber, - StTokenAmount: msg.Amount, - // claimIsPending represents whether a redemption is currently being claimed, - // contingent on the host zone unbonding having status CLAIMABLE - ClaimIsPending: false, - } - k.Logger(ctx).Info(fmt.Sprintf("UserRedemptionRecord not found - creating for %s", redemptionId)) - } - - // then add undelegation amount to epoch unbonding records - epochUnbondingRecord, found := k.RecordsKeeper.GetEpochUnbondingRecord(ctx, epochTracker.EpochNumber) - if !found { - k.Logger(ctx).Error("latest epoch unbonding record not found") - return nil, errorsmod.Wrapf(recordstypes.ErrEpochUnbondingRecordNotFound, "latest epoch unbonding record not found") - } - // get relevant host zone on this epoch unbonding record - hostZoneUnbonding, found := k.RecordsKeeper.GetHostZoneUnbondingByChainId(ctx, epochUnbondingRecord.EpochNumber, hostZone.ChainId) - if !found { - return nil, errorsmod.Wrapf(types.ErrInvalidHostZone, "host zone not found in unbondings: %s", hostZone.ChainId) - } - hostZoneUnbonding.NativeTokenAmount = hostZoneUnbonding.NativeTokenAmount.Add(nativeAmount) - if !userHasRedeemedThisEpoch { - // Only append a UserRedemptionRecord to the HZU if it wasn't previously appended - hostZoneUnbonding.UserRedemptionRecords = append(hostZoneUnbonding.UserRedemptionRecords, userRedemptionRecord.Id) - } - - // Escrow user's balance - redeemCoin := sdk.NewCoins(sdk.NewCoin(stDenom, msg.Amount)) - depositAddress, err := sdk.AccAddressFromBech32(hostZone.DepositAddress) - if err != nil { - return nil, fmt.Errorf("could not bech32 decode address %s of zone with id: %s", hostZone.DepositAddress, hostZone.ChainId) - } - err = k.bankKeeper.SendCoins(ctx, sender, depositAddress, redeemCoin) - if err != nil { - k.Logger(ctx).Error("Failed to send sdk.NewCoins(inCoins) from account to module") - return nil, errorsmod.Wrapf(types.ErrInsufficientFunds, "couldn't send %v derivative %s tokens to module account. err: %s", msg.Amount, hostZone.HostDenom, err.Error()) - } - - // record the number of stAssets that should be burned after unbonding - hostZoneUnbonding.StTokenAmount = hostZoneUnbonding.StTokenAmount.Add(msg.Amount) - - // Actually set the records, we wait until now to prevent any errors - k.RecordsKeeper.SetUserRedemptionRecord(ctx, userRedemptionRecord) - - // Set the UserUnbondingRecords on the proper HostZoneUnbondingRecord - hostZoneUnbondings := epochUnbondingRecord.GetHostZoneUnbondings() - if hostZoneUnbondings == nil { - hostZoneUnbondings = []*recordstypes.HostZoneUnbonding{} - epochUnbondingRecord.HostZoneUnbondings = hostZoneUnbondings - } - if err := k.RecordsKeeper.SetHostZoneUnbondingRecord(ctx, epochUnbondingRecord.EpochNumber, hostZone.ChainId, *hostZoneUnbonding); err != nil { - return nil, err - } - - k.Logger(ctx).Info(fmt.Sprintf("executed redeem stake: %s", msg.String())) - EmitSuccessfulRedeemStakeEvent(ctx, msg, hostZone, nativeAmount, msg.Amount) - - return &types.MsgRedeemStakeResponse{}, nil + return k.Keeper.RedeemStake(ctx, msg) } // Exchanges a user's LSM tokenized shares for stTokens using the current redemption rate diff --git a/x/stakeibc/keeper/msg_server_test.go b/x/stakeibc/keeper/msg_server_test.go index 708dd16ab..b98a7735e 100644 --- a/x/stakeibc/keeper/msg_server_test.go +++ b/x/stakeibc/keeper/msg_server_test.go @@ -24,466 +24,6 @@ import ( stakeibctypes "github.com/Stride-Labs/stride/v22/x/stakeibc/types" ) -// ---------------------------------------------------- -// RegisterHostZone -// ---------------------------------------------------- - -type RegisterHostZoneTestCase struct { - validMsg stakeibctypes.MsgRegisterHostZone - epochUnbondingRecordNumber uint64 - strideEpochNumber uint64 - unbondingPeriod uint64 - defaultRedemptionRate sdk.Dec - atomHostZoneChainId string -} - -func (s *KeeperTestSuite) SetupRegisterHostZone() RegisterHostZoneTestCase { - epochUnbondingRecordNumber := uint64(3) - strideEpochNumber := uint64(4) - unbondingPeriod := uint64(14) - defaultRedemptionRate := sdk.NewDec(1) - atomHostZoneChainId := "GAIA" - - s.CreateTransferChannel(HostChainId) - - s.App.StakeibcKeeper.SetEpochTracker(s.Ctx, stakeibctypes.EpochTracker{ - EpochIdentifier: epochtypes.DAY_EPOCH, - EpochNumber: epochUnbondingRecordNumber, - }) - - s.App.StakeibcKeeper.SetEpochTracker(s.Ctx, stakeibctypes.EpochTracker{ - EpochIdentifier: epochtypes.STRIDE_EPOCH, - EpochNumber: strideEpochNumber, - }) - - epochUnbondingRecord := recordtypes.EpochUnbondingRecord{ - EpochNumber: epochUnbondingRecordNumber, - HostZoneUnbondings: []*recordtypes.HostZoneUnbonding{}, - } - s.App.RecordsKeeper.SetEpochUnbondingRecord(s.Ctx, epochUnbondingRecord) - - defaultMsg := stakeibctypes.MsgRegisterHostZone{ - ConnectionId: ibctesting.FirstConnectionID, - Bech32Prefix: GaiaPrefix, - HostDenom: Atom, - IbcDenom: IbcAtom, - TransferChannelId: ibctesting.FirstChannelID, - UnbondingPeriod: unbondingPeriod, - MinRedemptionRate: sdk.NewDec(0), - MaxRedemptionRate: sdk.NewDec(0), - } - - return RegisterHostZoneTestCase{ - validMsg: defaultMsg, - epochUnbondingRecordNumber: epochUnbondingRecordNumber, - strideEpochNumber: strideEpochNumber, - unbondingPeriod: unbondingPeriod, - defaultRedemptionRate: defaultRedemptionRate, - atomHostZoneChainId: atomHostZoneChainId, - } -} - -// Helper function to test registering a duplicate host zone -// If there's a duplicate connection ID, register_host_zone will error before checking other fields for duplicates -// In order to test those cases, we need to first create a new host zone, -// -// and then attempt to register with duplicate fields in the message -// -// This function 1) creates a new host zone and 2) returns what would be a successful register message -func (s *KeeperTestSuite) createNewHostZoneMessage(chainID string, denom string, prefix string) stakeibctypes.MsgRegisterHostZone { - // Create a new test chain and connection ID - ibctesting.DefaultTestingAppInit = ibctesting.SetupTestingApp - osmoChain := ibctesting.NewTestChain(s.T(), s.Coordinator, chainID) - path := ibctesting.NewPath(s.StrideChain, osmoChain) - s.Coordinator.SetupConnections(path) - connectionId := path.EndpointA.ConnectionID - - // Build what would be a successful message to register the host zone - // Note: this is purposefully missing fields because it is used in failure cases that short circuit - return stakeibctypes.MsgRegisterHostZone{ - ConnectionId: connectionId, - Bech32Prefix: prefix, - HostDenom: denom, - } -} - -// Helper function to assist in testing a failure to create an ICA account -// This function will occupy one of the specified port with the specified channel -// -// so that the registration fails -func (s *KeeperTestSuite) createActiveChannelOnICAPort(accountName string, channelID string) { - portID := fmt.Sprintf("%s%s.%s", icatypes.ControllerPortPrefix, HostChainId, accountName) - openChannel := channeltypes.Channel{State: channeltypes.OPEN} - - // The channel ID doesn't matter here - all that matters is that theres an open channel on the port - s.App.IBCKeeper.ChannelKeeper.SetChannel(s.Ctx, portID, channelID, openChannel) - s.App.ICAControllerKeeper.SetActiveChannelID(s.Ctx, ibctesting.FirstConnectionID, portID, channelID) -} - -func (s *KeeperTestSuite) TestRegisterHostZone_Success() { - tc := s.SetupRegisterHostZone() - msg := tc.validMsg - - // Register host zone - _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) - s.Require().NoError(err, "able to successfully register host zone") - - // Confirm host zone unbonding was added - hostZone, found := s.App.StakeibcKeeper.GetHostZone(s.Ctx, HostChainId) - s.Require().True(found, "host zone found") - s.Require().Equal(tc.defaultRedemptionRate, hostZone.RedemptionRate, "redemption rate set to default: 1") - s.Require().Equal(tc.defaultRedemptionRate, hostZone.LastRedemptionRate, "last redemption rate set to default: 1") - defaultMinThreshold := sdk.NewDec(int64(stakeibctypes.DefaultMinRedemptionRateThreshold)).Quo(sdk.NewDec(100)) - defaultMaxThreshold := sdk.NewDec(int64(stakeibctypes.DefaultMaxRedemptionRateThreshold)).Quo(sdk.NewDec(100)) - s.Require().Equal(defaultMinThreshold, hostZone.MinRedemptionRate, "min redemption rate set to default") - s.Require().Equal(defaultMaxThreshold, hostZone.MaxRedemptionRate, "max redemption rate set to default") - s.Require().Equal(tc.unbondingPeriod, hostZone.UnbondingPeriod, "unbonding period") - - // Confirm host zone unbonding record was created - epochUnbondingRecord, found := s.App.RecordsKeeper.GetEpochUnbondingRecord(s.Ctx, tc.epochUnbondingRecordNumber) - s.Require().True(found, "epoch unbonding record found") - s.Require().Len(epochUnbondingRecord.HostZoneUnbondings, 1, "host zone unbonding record has one entry") - - // Confirm host zone unbonding was added - hostZoneUnbonding := epochUnbondingRecord.HostZoneUnbondings[0] - s.Require().Equal(HostChainId, hostZoneUnbonding.HostZoneId, "host zone unbonding set for this host zone") - s.Require().Equal(sdkmath.ZeroInt(), hostZoneUnbonding.NativeTokenAmount, "host zone unbonding set to 0 tokens") - s.Require().Equal(recordstypes.HostZoneUnbonding_UNBONDING_QUEUE, hostZoneUnbonding.Status, "host zone unbonding set to bonded") - - // Confirm a module account was created - hostZoneModuleAccount, err := sdk.AccAddressFromBech32(hostZone.DepositAddress) - s.Require().NoError(err, "converting module address to account") - acc := s.App.AccountKeeper.GetAccount(s.Ctx, hostZoneModuleAccount) - s.Require().NotNil(acc, "host zone module account found in account keeper") - - // Confirm an empty deposit record was created - expectedDepositRecord := recordstypes.DepositRecord{ - Id: uint64(0), - Amount: sdkmath.ZeroInt(), - HostZoneId: hostZone.ChainId, - Denom: hostZone.HostDenom, - Status: recordstypes.DepositRecord_TRANSFER_QUEUE, - DepositEpochNumber: tc.strideEpochNumber, - } - - depositRecords := s.App.RecordsKeeper.GetAllDepositRecord(s.Ctx) - s.Require().Len(depositRecords, 1, "number of deposit records") - s.Require().Equal(expectedDepositRecord, depositRecords[0], "deposit record") - - // Confirm max ICA messages was set to default - s.Require().Equal(keeper.DefaultMaxMessagesPerIcaTx, hostZone.MaxMessagesPerIcaTx, "max messages per ica tx") -} - -func (s *KeeperTestSuite) TestRegisterHostZone_Success_SetCommunityPoolTreasuryAddress() { - tc := s.SetupRegisterHostZone() - - // Sets the community pool treasury address to a valid address - msg := tc.validMsg - msg.CommunityPoolTreasuryAddress = ValidHostAddress - - _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) - s.Require().NoError(err, "no error expected when registering host with valid treasury address") - - // Confirm treasury address was set - hostZone := s.MustGetHostZone(HostChainId) - s.Require().Equal(ValidHostAddress, hostZone.CommunityPoolTreasuryAddress, "treasury address") -} - -func (s *KeeperTestSuite) TestRegisterHostZone_Success_SetMaxIcaMessagesPerTx() { - tc := s.SetupRegisterHostZone() - - // Set the max number of ICA messages - maxMessages := uint64(100) - msg := tc.validMsg - msg.MaxMessagesPerIcaTx = maxMessages - - _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) - s.Require().NoError(err, "no error expected when registering host with max messages") - - // Confirm max number of messages was set - hostZone := s.MustGetHostZone(HostChainId) - s.Require().Equal(maxMessages, hostZone.MaxMessagesPerIcaTx, "max messages per ica tx") -} - -func (s *KeeperTestSuite) TestRegisterHostZone_Success_Unregister() { - tc := s.SetupRegisterHostZone() - msg := tc.validMsg - - // Register the host zone with the valid message - _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) - s.Require().NoError(err, "no error expected when registering host") - - // Confirm accounts were created - depositAddress := types.NewHostZoneDepositAddress(chainId) - communityPoolStakeAddress := types.NewHostZoneModuleAddress(chainId, keeper.CommunityPoolStakeHoldingAddressKey) - communityPoolRedeemAddress := types.NewHostZoneModuleAddress(chainId, keeper.CommunityPoolRedeemHoldingAddressKey) - - depositAccount := s.App.AccountKeeper.GetAccount(s.Ctx, depositAddress) - communityPoolStakeAccount := s.App.AccountKeeper.GetAccount(s.Ctx, communityPoolStakeAddress) - communityPoolRedeemAccount := s.App.AccountKeeper.GetAccount(s.Ctx, communityPoolRedeemAddress) - - s.Require().NotNil(depositAccount, "deposit account should exist") - s.Require().NotNil(communityPoolStakeAccount, "community pool stake account should exist") - s.Require().NotNil(communityPoolRedeemAccount, "community pool redeem account should exist") - - // Confirm records were created - depositRecords := s.App.RecordsKeeper.GetAllDepositRecord(s.Ctx) - s.Require().Len(depositRecords, 1, "there should be one deposit record") - - epochUnbondingRecords := s.App.RecordsKeeper.GetAllEpochUnbondingRecord(s.Ctx) - s.Require().Len(epochUnbondingRecords, 1, "there should be one epoch unbonding record") - s.Require().Len(epochUnbondingRecords[0].HostZoneUnbondings, 1, "there should be one host zone unbonding record") - - // Unregister the host zone - err = s.App.StakeibcKeeper.UnregisterHostZone(s.Ctx, HostChainId) - s.Require().NoError(err, "no error expected when unregistering host zone") - - // Confirm accounts were deleted - depositAccount = s.App.AccountKeeper.GetAccount(s.Ctx, depositAddress) - communityPoolStakeAccount = s.App.AccountKeeper.GetAccount(s.Ctx, communityPoolStakeAddress) - communityPoolRedeemAccount = s.App.AccountKeeper.GetAccount(s.Ctx, communityPoolRedeemAddress) - - s.Require().Nil(depositAccount, "deposit account should have been deleted") - s.Require().Nil(communityPoolStakeAccount, "community pool stake account should have been deleted") - s.Require().Nil(communityPoolRedeemAccount, "community pool redeem account should have been deleted") - - // Confirm records were deleted - depositRecords = s.App.RecordsKeeper.GetAllDepositRecord(s.Ctx) - s.Require().Empty(depositRecords, "deposit records should have been deleted") - - epochUnbondingRecords = s.App.RecordsKeeper.GetAllEpochUnbondingRecord(s.Ctx) - s.Require().Empty(epochUnbondingRecords[0].HostZoneUnbondings, "host zone unbonding record should have been deleted") - - // Attempt to re-register, it should succeed - _, err = s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) - s.Require().NoError(err, "no error expected when re-registering host") -} - -func (s *KeeperTestSuite) TestRegisterHostZone_InvalidConnectionId() { - tc := s.SetupRegisterHostZone() - msg := tc.validMsg - msg.ConnectionId = "connection-10" // an invalid connection ID - - _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) - s.Require().EqualError(err, "invalid connection id, connection-10 not found: failed to register host zone") -} - -func (s *KeeperTestSuite) TestRegisterHostZone_DuplicateConnectionIdInIBCState() { - // tests for a failure if we register the same host zone twice - // (with a duplicate connectionId stored in the IBCKeeper's state) - tc := s.SetupRegisterHostZone() - msg := tc.validMsg - - _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) - s.Require().NoError(err, "able to successfully register host zone once") - - // now all attributes are different, EXCEPT the connection ID - msg.Bech32Prefix = "cosmos-different" // a different Bech32 prefix - msg.HostDenom = "atom-different" // a different host denom - msg.IbcDenom = "ibc-atom-different" // a different IBC denom - - _, err = s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) - expectedErrMsg := "invalid chain id, zone for GAIA already registered: " - expectedErrMsg += "failed to register host zone" - s.Require().EqualError(err, expectedErrMsg, "registering host zone with duplicate connection ID should fail") -} - -func (s *KeeperTestSuite) TestRegisterHostZone_DuplicateConnectionIdInStakeibcState() { - // tests for a failure if we register the same host zone twice - // (with a duplicate connectionId stored in a different host zone in stakeibc) - tc := s.SetupRegisterHostZone() - msg := tc.validMsg - - _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) - s.Require().NoError(err, "able to successfully register host zone once") - - // Create the message for a brand new host zone - // (without modifications, you would expect this to be successful) - newHostZoneMsg := s.createNewHostZoneMessage("OSMO", "osmo", "osmo") - - // Add a different host zone with the same connection Id as OSMO - newHostZone := stakeibctypes.HostZone{ - ChainId: "JUNO", - ConnectionId: newHostZoneMsg.ConnectionId, - } - s.App.StakeibcKeeper.SetHostZone(s.Ctx, newHostZone) - - // Registering should fail with a duplicate connection ID - _, err = s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &newHostZoneMsg) - expectedErrMsg := "connectionId connection-1 already registered: " - expectedErrMsg += "failed to register host zone" - s.Require().EqualError(err, expectedErrMsg, "registering host zone with duplicate connection ID should fail") -} - -func (s *KeeperTestSuite) TestRegisterHostZone_DuplicateHostDenom() { - // tests for a failure if we register the same host zone twice (with a duplicate host denom) - tc := s.SetupRegisterHostZone() - - // Register host zones successfully - _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) - s.Require().NoError(err, "able to successfully register host zone once") - - // Create the message for a brand new host zone - // (without modifications, you would expect this to be successful) - newHostZoneMsg := s.createNewHostZoneMessage("OSMO", "osmo", "osmo") - - // Try to register with a duplicate host denom - it should fail - invalidMsg := newHostZoneMsg - invalidMsg.HostDenom = tc.validMsg.HostDenom - - _, err = s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &invalidMsg) - expectedErrMsg := "host denom uatom already registered: failed to register host zone" - s.Require().EqualError(err, expectedErrMsg, "registering host zone with duplicate host denom should fail") -} - -func (s *KeeperTestSuite) TestRegisterHostZone_DuplicateTransferChannel() { - // tests for a failure if we register the same host zone twice (with a duplicate transfer) - tc := s.SetupRegisterHostZone() - - // Register host zones successfully - _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) - s.Require().NoError(err, "able to successfully register host zone once") - - // Create the message for a brand new host zone - // (without modifications, you would expect this to be successful) - newHostZoneMsg := s.createNewHostZoneMessage("OSMO", "osmo", "osmo") - - // Try to register with a duplicate transfer channel - it should fail - invalidMsg := newHostZoneMsg - invalidMsg.TransferChannelId = tc.validMsg.TransferChannelId - - _, err = s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &invalidMsg) - expectedErrMsg := "transfer channel channel-0 already registered: failed to register host zone" - s.Require().EqualError(err, expectedErrMsg, "registering host zone with duplicate host denom should fail") -} - -func (s *KeeperTestSuite) TestRegisterHostZone_DuplicateBech32Prefix() { - // tests for a failure if we register the same host zone twice (with a duplicate bech32 prefix) - tc := s.SetupRegisterHostZone() - - // Register host zones successfully - _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) - s.Require().NoError(err, "able to successfully register host zone once") - - // Create the message for a brand new host zone - // (without modifications, you would expect this to be successful) - newHostZoneMsg := s.createNewHostZoneMessage("OSMO", "osmo", "osmo") - - // Try to register with a duplicate bech32prefix - it should fail - invalidMsg := newHostZoneMsg - invalidMsg.Bech32Prefix = tc.validMsg.Bech32Prefix - - _, err = s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &invalidMsg) - expectedErrMsg := "bech32prefix cosmos already registered: failed to register host zone" - s.Require().EqualError(err, expectedErrMsg, "registering host zone with duplicate bech32 prefix should fail") -} - -func (s *KeeperTestSuite) TestRegisterHostZone_CannotFindDayEpochTracker() { - // tests for a failure if the epoch tracker cannot be found - tc := s.SetupRegisterHostZone() - msg := tc.validMsg - - // delete the epoch tracker - s.App.StakeibcKeeper.RemoveEpochTracker(s.Ctx, epochtypes.DAY_EPOCH) - - _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) - expectedErrMsg := "epoch tracker (day) not found: epoch not found" - s.Require().EqualError(err, expectedErrMsg, "day epoch tracker not found") -} - -func (s *KeeperTestSuite) TestRegisterHostZone_CannotFindStrideEpochTracker() { - // tests for a failure if the epoch tracker cannot be found - tc := s.SetupRegisterHostZone() - msg := tc.validMsg - - // delete the epoch tracker - s.App.StakeibcKeeper.RemoveEpochTracker(s.Ctx, epochtypes.STRIDE_EPOCH) - - _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) - expectedErrMsg := "epoch tracker (stride_epoch) not found: epoch not found" - s.Require().EqualError(err, expectedErrMsg, "stride epoch tracker not found") -} - -func (s *KeeperTestSuite) TestRegisterHostZone_CannotFindEpochUnbondingRecord() { - // tests for a failure if the epoch unbonding record cannot be found - tc := s.SetupRegisterHostZone() - msg := tc.validMsg - - // delete the epoch unbonding record - s.App.RecordsKeeper.RemoveEpochUnbondingRecord(s.Ctx, tc.epochUnbondingRecordNumber) - - _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) - expectedErrMsg := "unable to find latest epoch unbonding record: epoch unbonding record not found" - s.Require().EqualError(err, expectedErrMsg, " epoch unbonding record not found") -} - -func (s *KeeperTestSuite) TestRegisterHostZone_CannotRegisterDelegationAccount() { - // tests for a failure if the epoch unbonding record cannot be found - tc := s.SetupRegisterHostZone() - - // Create channel on delegation port - s.createActiveChannelOnICAPort("DELEGATION", "channel-1") - - _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) - expectedErrMsg := "unable to register delegation account, err: existing active channel channel-1 for portID icacontroller-GAIA.DELEGATION " - expectedErrMsg += "on connection connection-0: active channel already set for this owner: " - expectedErrMsg += "failed to register host zone" - s.Require().EqualError(err, expectedErrMsg, "can't register delegation account") -} - -func (s *KeeperTestSuite) TestRegisterHostZone_CannotRegisterFeeAccount() { - // tests for a failure if the epoch unbonding record cannot be found - tc := s.SetupRegisterHostZone() - - // Create channel on fee port - s.createActiveChannelOnICAPort("FEE", "channel-1") - - _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) - expectedErrMsg := "unable to register fee account, err: existing active channel channel-1 for portID icacontroller-GAIA.FEE " - expectedErrMsg += "on connection connection-0: active channel already set for this owner: " - expectedErrMsg += "failed to register host zone" - s.Require().EqualError(err, expectedErrMsg, "can't register redemption account") -} - -func (s *KeeperTestSuite) TestRegisterHostZone_CannotRegisterWithdrawalAccount() { - // tests for a failure if the epoch unbonding record cannot be found - tc := s.SetupRegisterHostZone() - - // Create channel on withdrawal port - s.createActiveChannelOnICAPort("WITHDRAWAL", "channel-1") - - _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) - expectedErrMsg := "unable to register withdrawal account, err: existing active channel channel-1 for portID icacontroller-GAIA.WITHDRAWAL " - expectedErrMsg += "on connection connection-0: active channel already set for this owner: " - expectedErrMsg += "failed to register host zone" - s.Require().EqualError(err, expectedErrMsg, "can't register redemption account") -} - -func (s *KeeperTestSuite) TestRegisterHostZone_CannotRegisterRedemptionAccount() { - // tests for a failure if the epoch unbonding record cannot be found - tc := s.SetupRegisterHostZone() - - // Create channel on redemption port - s.createActiveChannelOnICAPort("REDEMPTION", "channel-1") - - _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) - expectedErrMsg := "unable to register redemption account, err: existing active channel channel-1 for portID icacontroller-GAIA.REDEMPTION " - expectedErrMsg += "on connection connection-0: active channel already set for this owner: " - expectedErrMsg += "failed to register host zone" - s.Require().EqualError(err, expectedErrMsg, "can't register redemption account") -} - -func (s *KeeperTestSuite) TestRegisterHostZone_InvalidCommunityPoolTreasuryAddress() { - // tests for a failure if the community pool treasury address is invalid - tc := s.SetupRegisterHostZone() - - invalidMsg := tc.validMsg - invalidMsg.CommunityPoolTreasuryAddress = "invalid_address" - - _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &invalidMsg) - s.Require().ErrorContains(err, "invalid community pool treasury address") -} - // ---------------------------------------------------- // UpdateHostZoneParams // ---------------------------------------------------- @@ -1314,292 +854,9 @@ func (s *KeeperTestSuite) TestLiquidStake_HaltedZone() { } // ---------------------------------------------------- -// RedeemStake +// LSMLiquidStake // ---------------------------------------------------- -type RedeemStakeState struct { - epochNumber uint64 - initialNativeEpochUnbondingAmount sdkmath.Int - initialStTokenEpochUnbondingAmount sdkmath.Int -} -type RedeemStakeTestCase struct { - user Account - hostZone stakeibctypes.HostZone - zoneAccount Account - initialState RedeemStakeState - validMsg stakeibctypes.MsgRedeemStake - expectedNativeAmount sdkmath.Int -} - -func (s *KeeperTestSuite) SetupRedeemStake() RedeemStakeTestCase { - redeemAmount := sdkmath.NewInt(1_000_000) - redemptionRate := sdk.MustNewDecFromStr("1.5") - expectedNativeAmount := sdkmath.NewInt(1_500_000) - - user := Account{ - acc: s.TestAccs[0], - atomBalance: sdk.NewInt64Coin("ibc/uatom", 10_000_000), - stAtomBalance: sdk.NewInt64Coin("stuatom", 10_000_000), - } - s.FundAccount(user.acc, user.atomBalance) - s.FundAccount(user.acc, user.stAtomBalance) - - depositAddress := stakeibctypes.NewHostZoneDepositAddress(HostChainId) - - zoneAccount := Account{ - acc: depositAddress, - atomBalance: sdk.NewInt64Coin("ibc/uatom", 10_000_000), - stAtomBalance: sdk.NewInt64Coin("stuatom", 10_000_000), - } - s.FundAccount(zoneAccount.acc, zoneAccount.atomBalance) - s.FundAccount(zoneAccount.acc, zoneAccount.stAtomBalance) - - // TODO define the host zone with total delegation and validators with staked amounts - hostZone := stakeibctypes.HostZone{ - ChainId: HostChainId, - HostDenom: "uatom", - Bech32Prefix: "cosmos", - RedemptionRate: redemptionRate, - TotalDelegations: sdkmath.NewInt(1234567890), - DepositAddress: depositAddress.String(), - } - - epochTrackerDay := stakeibctypes.EpochTracker{ - EpochIdentifier: epochtypes.DAY_EPOCH, - EpochNumber: 1, - } - - epochUnbondingRecord := recordtypes.EpochUnbondingRecord{ - EpochNumber: 1, - HostZoneUnbondings: []*recordtypes.HostZoneUnbonding{}, - } - - hostZoneUnbonding := &recordtypes.HostZoneUnbonding{ - NativeTokenAmount: sdkmath.ZeroInt(), - Denom: "uatom", - HostZoneId: HostChainId, - Status: recordtypes.HostZoneUnbonding_UNBONDING_QUEUE, - } - epochUnbondingRecord.HostZoneUnbondings = append(epochUnbondingRecord.HostZoneUnbondings, hostZoneUnbonding) - - s.App.StakeibcKeeper.SetHostZone(s.Ctx, hostZone) - s.App.StakeibcKeeper.SetEpochTracker(s.Ctx, epochTrackerDay) - s.App.RecordsKeeper.SetEpochUnbondingRecord(s.Ctx, epochUnbondingRecord) - - return RedeemStakeTestCase{ - user: user, - hostZone: hostZone, - zoneAccount: zoneAccount, - expectedNativeAmount: expectedNativeAmount, - initialState: RedeemStakeState{ - epochNumber: epochTrackerDay.EpochNumber, - initialNativeEpochUnbondingAmount: sdkmath.ZeroInt(), - initialStTokenEpochUnbondingAmount: sdkmath.ZeroInt(), - }, - validMsg: stakeibctypes.MsgRedeemStake{ - Creator: user.acc.String(), - Amount: redeemAmount, - HostZone: HostChainId, - // TODO set this dynamically through test helpers for host zone - Receiver: "cosmos1g6qdx6kdhpf000afvvpte7hp0vnpzapuyxp8uf", - }, - } -} - -func (s *KeeperTestSuite) TestRedeemStake_Successful() { - tc := s.SetupRedeemStake() - initialState := tc.initialState - - msg := tc.validMsg - user := tc.user - redeemAmount := msg.Amount - - // Split the message amount in 2, and call redeem stake twice (each with half the amount) - // This will check that the same user can redeem multiple times - msg1 := msg - msg1.Amount = msg1.Amount.Quo(sdkmath.NewInt(2)) // half the amount - - msg2 := msg - msg2.Amount = msg.Amount.Sub(msg1.Amount) // remaining half - - _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &msg1) - s.Require().NoError(err, "no error expected during first redemption") - - _, err = s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &msg2) - s.Require().NoError(err, "no error expected during second redemption") - - // User STUATOM balance should have DECREASED by the amount to be redeemed - expectedUserStAtomBalance := user.stAtomBalance.SubAmount(redeemAmount) - actualUserStAtomBalance := s.App.BankKeeper.GetBalance(s.Ctx, user.acc, "stuatom") - s.CompareCoins(expectedUserStAtomBalance, actualUserStAtomBalance, "user stuatom balance") - - // Gaia's hostZoneUnbonding NATIVE TOKEN amount should have INCREASED from 0 to the amount redeemed multiplied by the redemption rate - // Gaia's hostZoneUnbonding STTOKEN amount should have INCREASED from 0 to be amount redeemed - epochTracker, found := s.App.StakeibcKeeper.GetEpochTracker(s.Ctx, "day") - s.Require().True(found, "epoch tracker") - epochUnbondingRecord, found := s.App.RecordsKeeper.GetEpochUnbondingRecord(s.Ctx, epochTracker.EpochNumber) - s.Require().True(found, "epoch unbonding record") - hostZoneUnbonding, found := s.App.RecordsKeeper.GetHostZoneUnbondingByChainId(s.Ctx, epochUnbondingRecord.EpochNumber, HostChainId) - s.Require().True(found, "host zone unbondings by chain ID") - - expectedHostZoneUnbondingNativeAmount := initialState.initialNativeEpochUnbondingAmount.Add(tc.expectedNativeAmount) - expectedHostZoneUnbondingStTokenAmount := initialState.initialStTokenEpochUnbondingAmount.Add(redeemAmount) - - s.Require().Equal(expectedHostZoneUnbondingNativeAmount, hostZoneUnbonding.NativeTokenAmount, "host zone native unbonding amount") - s.Require().Equal(expectedHostZoneUnbondingStTokenAmount, hostZoneUnbonding.StTokenAmount, "host zone stToken burn amount") - - // UserRedemptionRecord should have been created with correct amount, sender, receiver, host zone, claimIsPending - userRedemptionRecords := hostZoneUnbonding.UserRedemptionRecords - s.Require().Equal(len(userRedemptionRecords), 1) - userRedemptionRecordId := userRedemptionRecords[0] - userRedemptionRecord, found := s.App.RecordsKeeper.GetUserRedemptionRecord(s.Ctx, userRedemptionRecordId) - s.Require().True(found) - - s.Require().Equal(msg.Amount, userRedemptionRecord.StTokenAmount, "redemption record sttoken amount") - s.Require().Equal(tc.expectedNativeAmount, userRedemptionRecord.NativeTokenAmount, "redemption record native amount") - s.Require().Equal(msg.Receiver, userRedemptionRecord.Receiver, "redemption record receiver") - s.Require().Equal(msg.HostZone, userRedemptionRecord.HostZoneId, "redemption record host zone") - s.Require().False(userRedemptionRecord.ClaimIsPending, "redemption record is not claimable") - s.Require().NotEqual(hostZoneUnbonding.Status, recordtypes.HostZoneUnbonding_CLAIMABLE, "host zone unbonding should NOT be marked as CLAIMABLE") -} - -func (s *KeeperTestSuite) TestRedeemStake_InvalidCreatorAddress() { - tc := s.SetupRedeemStake() - invalidMsg := tc.validMsg - - // cosmos instead of stride address - invalidMsg.Creator = "cosmos1g6qdx6kdhpf000afvvpte7hp0vnpzapuyxp8uf" - _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) - s.Require().EqualError(err, fmt.Sprintf("creator address is invalid: %s. err: invalid Bech32 prefix; expected stride, got cosmos: invalid address", invalidMsg.Creator)) - - // invalid stride address - invalidMsg.Creator = "stride1g6qdx6kdhpf000afvvpte7hp0vnpzapuyxp8uf" - _, err = s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) - s.Require().EqualError(err, fmt.Sprintf("creator address is invalid: %s. err: decoding bech32 failed: invalid checksum (expected 8dpmg9 got yxp8uf): invalid address", invalidMsg.Creator)) - - // empty address - invalidMsg.Creator = "" - _, err = s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) - s.Require().EqualError(err, fmt.Sprintf("creator address is invalid: %s. err: empty address string is not allowed: invalid address", invalidMsg.Creator)) - - // wrong len address - invalidMsg.Creator = "stride1g6qdx6kdhpf000afvvpte7hp0vnpzapuyxp8ufabc" - _, err = s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) - s.Require().EqualError(err, fmt.Sprintf("creator address is invalid: %s. err: decoding bech32 failed: invalid character not part of charset: 98: invalid address", invalidMsg.Creator)) -} - -func (s *KeeperTestSuite) TestRedeemStake_HostZoneNotFound() { - tc := s.SetupRedeemStake() - - invalidMsg := tc.validMsg - invalidMsg.HostZone = "fake_host_zone" - _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) - - s.Require().EqualError(err, "host zone is invalid: fake_host_zone: host zone not registered") -} - -func (s *KeeperTestSuite) TestRedeemStake_RateAboveMaxThreshold() { - tc := s.SetupRedeemStake() - - hz := tc.hostZone - hz.RedemptionRate = sdk.NewDec(100) - s.App.StakeibcKeeper.SetHostZone(s.Ctx, hz) - - _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) - s.Require().Error(err) -} - -func (s *KeeperTestSuite) TestRedeemStake_InvalidReceiverAddress() { - tc := s.SetupRedeemStake() - - invalidMsg := tc.validMsg - - // stride instead of cosmos address - invalidMsg.Receiver = "stride159atdlc3ksl50g0659w5tq42wwer334ajl7xnq" - _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) - s.Require().EqualError(err, "invalid receiver address (invalid Bech32 prefix; expected cosmos, got stride): invalid address") - - // invalid cosmos address - invalidMsg.Receiver = "cosmos1g6qdx6kdhpf000afvvpte7hp0vnpzapuyxp8ua" - _, err = s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) - s.Require().EqualError(err, "invalid receiver address (decoding bech32 failed: invalid checksum (expected yxp8uf got yxp8ua)): invalid address") - - // empty address - invalidMsg.Receiver = "" - _, err = s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) - s.Require().EqualError(err, "invalid receiver address (empty address string is not allowed): invalid address") - - // wrong len address - invalidMsg.Receiver = "cosmos1g6qdx6kdhpf000afvvpte7hp0vnpzapuyxp8ufa" - _, err = s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) - s.Require().EqualError(err, "invalid receiver address (decoding bech32 failed: invalid checksum (expected xp8ugp got xp8ufa)): invalid address") -} - -func (s *KeeperTestSuite) TestRedeemStake_RedeemMoreThanStaked() { - tc := s.SetupRedeemStake() - - invalidMsg := tc.validMsg - invalidMsg.Amount = sdkmath.NewInt(1_000_000_000_000_000) - _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) - - s.Require().EqualError(err, fmt.Sprintf("cannot unstake an amount g.t. staked balance on host zone: %v: invalid amount", invalidMsg.Amount)) -} - -func (s *KeeperTestSuite) TestRedeemStake_NoEpochTrackerDay() { - tc := s.SetupRedeemStake() - - invalidMsg := tc.validMsg - s.App.RecordsKeeper.RemoveEpochUnbondingRecord(s.Ctx, tc.initialState.epochNumber) - _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) - - s.Require().EqualError(err, "latest epoch unbonding record not found: epoch unbonding record not found") -} - -func (s *KeeperTestSuite) TestRedeemStake_HostZoneNoUnbondings() { - tc := s.SetupRedeemStake() - - invalidMsg := tc.validMsg - epochUnbondingRecord := recordtypes.EpochUnbondingRecord{ - EpochNumber: 1, - HostZoneUnbondings: []*recordtypes.HostZoneUnbonding{}, - } - hostZoneUnbonding := &recordtypes.HostZoneUnbonding{ - NativeTokenAmount: sdkmath.ZeroInt(), - Denom: "uatom", - HostZoneId: "NOT_GAIA", - } - epochUnbondingRecord.HostZoneUnbondings = append(epochUnbondingRecord.HostZoneUnbondings, hostZoneUnbonding) - - s.App.RecordsKeeper.SetEpochUnbondingRecord(s.Ctx, epochUnbondingRecord) - _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) - - s.Require().EqualError(err, "host zone not found in unbondings: GAIA: host zone not registered") -} - -func (s *KeeperTestSuite) TestRedeemStake_InvalidHostAddress() { - tc := s.SetupRedeemStake() - - // Update hostzone with invalid address - badHostZone, _ := s.App.StakeibcKeeper.GetHostZone(s.Ctx, tc.validMsg.HostZone) - badHostZone.DepositAddress = "cosmosXXX" - s.App.StakeibcKeeper.SetHostZone(s.Ctx, badHostZone) - - _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) - s.Require().EqualError(err, "could not bech32 decode address cosmosXXX of zone with id: GAIA") -} - -func (s *KeeperTestSuite) TestRedeemStake_HaltedZone() { - tc := s.SetupRedeemStake() - - // Update hostzone with halted - haltedHostZone, _ := s.App.StakeibcKeeper.GetHostZone(s.Ctx, tc.validMsg.HostZone) - haltedHostZone.Halted = true - s.App.StakeibcKeeper.SetHostZone(s.Ctx, haltedHostZone) - - _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) - s.Require().EqualError(err, "halted host zone found for zone (GAIA): Halted host zone found") -} - type LSMLiquidStakeTestCase struct { hostZone types.HostZone liquidStakerAddress sdk.AccAddress diff --git a/x/stakeibc/keeper/redeem_stake.go b/x/stakeibc/keeper/redeem_stake.go new file mode 100644 index 000000000..babe5e303 --- /dev/null +++ b/x/stakeibc/keeper/redeem_stake.go @@ -0,0 +1,153 @@ +package keeper + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "github.com/Stride-Labs/stride/v22/utils" + recordstypes "github.com/Stride-Labs/stride/v22/x/records/types" + "github.com/Stride-Labs/stride/v22/x/stakeibc/types" +) + +func (k Keeper) RedeemStake(ctx sdk.Context, msg *types.MsgRedeemStake) (*types.MsgRedeemStakeResponse, error) { + k.Logger(ctx).Info(fmt.Sprintf("redeem stake: %s", msg.String())) + + // ----------------- PRELIMINARY CHECKS ----------------- + // get our addresses, make sure they're valid + sender, err := sdk.AccAddressFromBech32(msg.Creator) + if err != nil { + return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "creator address is invalid: %s. err: %s", msg.Creator, err.Error()) + } + // then make sure host zone is valid + hostZone, found := k.GetHostZone(ctx, msg.HostZone) + if !found { + return nil, errorsmod.Wrapf(types.ErrInvalidHostZone, "host zone is invalid: %s", msg.HostZone) + } + + if hostZone.Halted { + k.Logger(ctx).Error(fmt.Sprintf("Host Zone halted for zone (%s)", msg.HostZone)) + return nil, errorsmod.Wrapf(types.ErrHaltedHostZone, "halted host zone found for zone (%s)", msg.HostZone) + } + + if !hostZone.RedemptionsEnabled { + return nil, errorsmod.Wrapf(types.ErrRedemptionsDisabled, "redemptions disabled for %s", msg.HostZone) + } + + // first construct a user redemption record + epochTracker, found := k.GetEpochTracker(ctx, "day") + if !found { + return nil, errorsmod.Wrapf(types.ErrEpochNotFound, "epoch tracker found: %s", "day") + } + + // ensure the recipient address is a valid bech32 address on the hostZone + _, err = utils.AccAddressFromBech32(msg.Receiver, hostZone.Bech32Prefix) + if err != nil { + return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid receiver address (%s)", err) + } + + // construct desired unstaking amount from host zone + stDenom := types.StAssetDenomFromHostZoneDenom(hostZone.HostDenom) + nativeAmount := sdk.NewDecFromInt(msg.Amount).Mul(hostZone.RedemptionRate).TruncateInt() + + if nativeAmount.GT(hostZone.TotalDelegations) { + return nil, errorsmod.Wrapf(types.ErrInvalidAmount, "cannot unstake an amount g.t. staked balance on host zone: %v", msg.Amount) + } + + // safety check: redemption rate must be within safety bounds + rateIsSafe, err := k.IsRedemptionRateWithinSafetyBounds(ctx, hostZone) + if !rateIsSafe || (err != nil) { + errMsg := fmt.Sprintf("IsRedemptionRateWithinSafetyBounds check failed. hostZone: %s, err: %s", hostZone.String(), err.Error()) + return nil, errorsmod.Wrapf(types.ErrRedemptionRateOutsideSafetyBounds, errMsg) + } + + // safety checks on the coin + // - Redemption amount must be positive + if !nativeAmount.IsPositive() { + return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "amount must be greater than 0. found: %v", msg.Amount) + } + // - Creator owns at least "amount" stAssets + balance := k.bankKeeper.GetBalance(ctx, sender, stDenom) + if balance.Amount.LT(msg.Amount) { + return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "balance is lower than redemption amount. redemption amount: %v, balance %v: ", msg.Amount, balance.Amount) + } + + // ----------------- UNBONDING RECORD KEEPING ----------------- + // Fetch the record + redemptionId := recordstypes.UserRedemptionRecordKeyFormatter(hostZone.ChainId, epochTracker.EpochNumber, msg.Receiver) + userRedemptionRecord, userHasRedeemedThisEpoch := k.RecordsKeeper.GetUserRedemptionRecord(ctx, redemptionId) + if userHasRedeemedThisEpoch { + k.Logger(ctx).Info(fmt.Sprintf("UserRedemptionRecord found for %s", redemptionId)) + // Add the unbonded amount to the UserRedemptionRecord + // The record is set below + userRedemptionRecord.StTokenAmount = userRedemptionRecord.StTokenAmount.Add(msg.Amount) + userRedemptionRecord.NativeTokenAmount = userRedemptionRecord.NativeTokenAmount.Add(nativeAmount) + } else { + // First time a user is redeeming this epoch + userRedemptionRecord = recordstypes.UserRedemptionRecord{ + Id: redemptionId, + Receiver: msg.Receiver, + NativeTokenAmount: nativeAmount, + Denom: hostZone.HostDenom, + HostZoneId: hostZone.ChainId, + EpochNumber: epochTracker.EpochNumber, + StTokenAmount: msg.Amount, + // claimIsPending represents whether a redemption is currently being claimed, + // contingent on the host zone unbonding having status CLAIMABLE + ClaimIsPending: false, + } + k.Logger(ctx).Info(fmt.Sprintf("UserRedemptionRecord not found - creating for %s", redemptionId)) + } + + // then add undelegation amount to epoch unbonding records + epochUnbondingRecord, found := k.RecordsKeeper.GetEpochUnbondingRecord(ctx, epochTracker.EpochNumber) + if !found { + k.Logger(ctx).Error("latest epoch unbonding record not found") + return nil, errorsmod.Wrapf(recordstypes.ErrEpochUnbondingRecordNotFound, "latest epoch unbonding record not found") + } + // get relevant host zone on this epoch unbonding record + hostZoneUnbonding, found := k.RecordsKeeper.GetHostZoneUnbondingByChainId(ctx, epochUnbondingRecord.EpochNumber, hostZone.ChainId) + if !found { + return nil, errorsmod.Wrapf(types.ErrInvalidHostZone, "host zone not found in unbondings: %s", hostZone.ChainId) + } + hostZoneUnbonding.NativeTokenAmount = hostZoneUnbonding.NativeTokenAmount.Add(nativeAmount) + if !userHasRedeemedThisEpoch { + // Only append a UserRedemptionRecord to the HZU if it wasn't previously appended + hostZoneUnbonding.UserRedemptionRecords = append(hostZoneUnbonding.UserRedemptionRecords, userRedemptionRecord.Id) + } + + // Escrow user's balance + redeemCoin := sdk.NewCoins(sdk.NewCoin(stDenom, msg.Amount)) + depositAddress, err := sdk.AccAddressFromBech32(hostZone.DepositAddress) + if err != nil { + return nil, fmt.Errorf("could not bech32 decode address %s of zone with id: %s", hostZone.DepositAddress, hostZone.ChainId) + } + err = k.bankKeeper.SendCoins(ctx, sender, depositAddress, redeemCoin) + if err != nil { + k.Logger(ctx).Error("Failed to send sdk.NewCoins(inCoins) from account to module") + return nil, errorsmod.Wrapf(types.ErrInsufficientFunds, "couldn't send %v derivative %s tokens to module account. err: %s", msg.Amount, hostZone.HostDenom, err.Error()) + } + + // record the number of stAssets that should be burned after unbonding + hostZoneUnbonding.StTokenAmount = hostZoneUnbonding.StTokenAmount.Add(msg.Amount) + + // Actually set the records, we wait until now to prevent any errors + k.RecordsKeeper.SetUserRedemptionRecord(ctx, userRedemptionRecord) + + // Set the UserUnbondingRecords on the proper HostZoneUnbondingRecord + hostZoneUnbondings := epochUnbondingRecord.GetHostZoneUnbondings() + if hostZoneUnbondings == nil { + hostZoneUnbondings = []*recordstypes.HostZoneUnbonding{} + epochUnbondingRecord.HostZoneUnbondings = hostZoneUnbondings + } + if err := k.RecordsKeeper.SetHostZoneUnbondingRecord(ctx, epochUnbondingRecord.EpochNumber, hostZone.ChainId, *hostZoneUnbonding); err != nil { + return nil, err + } + + k.Logger(ctx).Info(fmt.Sprintf("executed redeem stake: %s", msg.String())) + EmitSuccessfulRedeemStakeEvent(ctx, msg, hostZone, nativeAmount, msg.Amount) + + return &types.MsgRedeemStakeResponse{}, nil +} diff --git a/x/stakeibc/keeper/redeem_stake_test.go b/x/stakeibc/keeper/redeem_stake_test.go new file mode 100644 index 000000000..db55e3ae3 --- /dev/null +++ b/x/stakeibc/keeper/redeem_stake_test.go @@ -0,0 +1,308 @@ +package keeper_test + +import ( + "fmt" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + + epochtypes "github.com/Stride-Labs/stride/v22/x/epochs/types" + recordtypes "github.com/Stride-Labs/stride/v22/x/records/types" + stakeibctypes "github.com/Stride-Labs/stride/v22/x/stakeibc/types" +) + +type RedeemStakeState struct { + epochNumber uint64 + initialNativeEpochUnbondingAmount sdkmath.Int + initialStTokenEpochUnbondingAmount sdkmath.Int +} +type RedeemStakeTestCase struct { + user Account + hostZone stakeibctypes.HostZone + zoneAccount Account + initialState RedeemStakeState + validMsg stakeibctypes.MsgRedeemStake + expectedNativeAmount sdkmath.Int +} + +func (s *KeeperTestSuite) SetupRedeemStake() RedeemStakeTestCase { + redeemAmount := sdkmath.NewInt(1_000_000) + redemptionRate := sdk.MustNewDecFromStr("1.5") + expectedNativeAmount := sdkmath.NewInt(1_500_000) + + user := Account{ + acc: s.TestAccs[0], + atomBalance: sdk.NewInt64Coin("ibc/uatom", 10_000_000), + stAtomBalance: sdk.NewInt64Coin("stuatom", 10_000_000), + } + s.FundAccount(user.acc, user.atomBalance) + s.FundAccount(user.acc, user.stAtomBalance) + + depositAddress := stakeibctypes.NewHostZoneDepositAddress(HostChainId) + + zoneAccount := Account{ + acc: depositAddress, + atomBalance: sdk.NewInt64Coin("ibc/uatom", 10_000_000), + stAtomBalance: sdk.NewInt64Coin("stuatom", 10_000_000), + } + s.FundAccount(zoneAccount.acc, zoneAccount.atomBalance) + s.FundAccount(zoneAccount.acc, zoneAccount.stAtomBalance) + + // TODO define the host zone with total delegation and validators with staked amounts + hostZone := stakeibctypes.HostZone{ + ChainId: HostChainId, + HostDenom: "uatom", + Bech32Prefix: "cosmos", + RedemptionRate: redemptionRate, + TotalDelegations: sdkmath.NewInt(1234567890), + DepositAddress: depositAddress.String(), + RedemptionsEnabled: true, + } + + epochTrackerDay := stakeibctypes.EpochTracker{ + EpochIdentifier: epochtypes.DAY_EPOCH, + EpochNumber: 1, + } + + epochUnbondingRecord := recordtypes.EpochUnbondingRecord{ + EpochNumber: 1, + HostZoneUnbondings: []*recordtypes.HostZoneUnbonding{}, + } + + hostZoneUnbonding := &recordtypes.HostZoneUnbonding{ + NativeTokenAmount: sdkmath.ZeroInt(), + Denom: "uatom", + HostZoneId: HostChainId, + Status: recordtypes.HostZoneUnbonding_UNBONDING_QUEUE, + } + epochUnbondingRecord.HostZoneUnbondings = append(epochUnbondingRecord.HostZoneUnbondings, hostZoneUnbonding) + + s.App.StakeibcKeeper.SetHostZone(s.Ctx, hostZone) + s.App.StakeibcKeeper.SetEpochTracker(s.Ctx, epochTrackerDay) + s.App.RecordsKeeper.SetEpochUnbondingRecord(s.Ctx, epochUnbondingRecord) + + return RedeemStakeTestCase{ + user: user, + hostZone: hostZone, + zoneAccount: zoneAccount, + expectedNativeAmount: expectedNativeAmount, + initialState: RedeemStakeState{ + epochNumber: epochTrackerDay.EpochNumber, + initialNativeEpochUnbondingAmount: sdkmath.ZeroInt(), + initialStTokenEpochUnbondingAmount: sdkmath.ZeroInt(), + }, + validMsg: stakeibctypes.MsgRedeemStake{ + Creator: user.acc.String(), + Amount: redeemAmount, + HostZone: HostChainId, + // TODO set this dynamically through test helpers for host zone + Receiver: "cosmos1g6qdx6kdhpf000afvvpte7hp0vnpzapuyxp8uf", + }, + } +} + +func (s *KeeperTestSuite) TestRedeemStake_Successful() { + tc := s.SetupRedeemStake() + initialState := tc.initialState + + msg := tc.validMsg + user := tc.user + redeemAmount := msg.Amount + + // Split the message amount in 2, and call redeem stake twice (each with half the amount) + // This will check that the same user can redeem multiple times + msg1 := msg + msg1.Amount = msg1.Amount.Quo(sdkmath.NewInt(2)) // half the amount + + msg2 := msg + msg2.Amount = msg.Amount.Sub(msg1.Amount) // remaining half + + _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &msg1) + s.Require().NoError(err, "no error expected during first redemption") + + _, err = s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &msg2) + s.Require().NoError(err, "no error expected during second redemption") + + // User STUATOM balance should have DECREASED by the amount to be redeemed + expectedUserStAtomBalance := user.stAtomBalance.SubAmount(redeemAmount) + actualUserStAtomBalance := s.App.BankKeeper.GetBalance(s.Ctx, user.acc, "stuatom") + s.CompareCoins(expectedUserStAtomBalance, actualUserStAtomBalance, "user stuatom balance") + + // Gaia's hostZoneUnbonding NATIVE TOKEN amount should have INCREASED from 0 to the amount redeemed multiplied by the redemption rate + // Gaia's hostZoneUnbonding STTOKEN amount should have INCREASED from 0 to be amount redeemed + epochTracker, found := s.App.StakeibcKeeper.GetEpochTracker(s.Ctx, "day") + s.Require().True(found, "epoch tracker") + epochUnbondingRecord, found := s.App.RecordsKeeper.GetEpochUnbondingRecord(s.Ctx, epochTracker.EpochNumber) + s.Require().True(found, "epoch unbonding record") + hostZoneUnbonding, found := s.App.RecordsKeeper.GetHostZoneUnbondingByChainId(s.Ctx, epochUnbondingRecord.EpochNumber, HostChainId) + s.Require().True(found, "host zone unbondings by chain ID") + + expectedHostZoneUnbondingNativeAmount := initialState.initialNativeEpochUnbondingAmount.Add(tc.expectedNativeAmount) + expectedHostZoneUnbondingStTokenAmount := initialState.initialStTokenEpochUnbondingAmount.Add(redeemAmount) + + s.Require().Equal(expectedHostZoneUnbondingNativeAmount, hostZoneUnbonding.NativeTokenAmount, "host zone native unbonding amount") + s.Require().Equal(expectedHostZoneUnbondingStTokenAmount, hostZoneUnbonding.StTokenAmount, "host zone stToken burn amount") + + // UserRedemptionRecord should have been created with correct amount, sender, receiver, host zone, claimIsPending + userRedemptionRecords := hostZoneUnbonding.UserRedemptionRecords + s.Require().Equal(len(userRedemptionRecords), 1) + userRedemptionRecordId := userRedemptionRecords[0] + userRedemptionRecord, found := s.App.RecordsKeeper.GetUserRedemptionRecord(s.Ctx, userRedemptionRecordId) + s.Require().True(found) + + s.Require().Equal(msg.Amount, userRedemptionRecord.StTokenAmount, "redemption record sttoken amount") + s.Require().Equal(tc.expectedNativeAmount, userRedemptionRecord.NativeTokenAmount, "redemption record native amount") + s.Require().Equal(msg.Receiver, userRedemptionRecord.Receiver, "redemption record receiver") + s.Require().Equal(msg.HostZone, userRedemptionRecord.HostZoneId, "redemption record host zone") + s.Require().False(userRedemptionRecord.ClaimIsPending, "redemption record is not claimable") + s.Require().NotEqual(hostZoneUnbonding.Status, recordtypes.HostZoneUnbonding_CLAIMABLE, "host zone unbonding should NOT be marked as CLAIMABLE") +} + +func (s *KeeperTestSuite) TestRedeemStake_InvalidCreatorAddress() { + tc := s.SetupRedeemStake() + invalidMsg := tc.validMsg + + // cosmos instead of stride address + invalidMsg.Creator = "cosmos1g6qdx6kdhpf000afvvpte7hp0vnpzapuyxp8uf" + _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) + s.Require().EqualError(err, fmt.Sprintf("creator address is invalid: %s. err: invalid Bech32 prefix; expected stride, got cosmos: invalid address", invalidMsg.Creator)) + + // invalid stride address + invalidMsg.Creator = "stride1g6qdx6kdhpf000afvvpte7hp0vnpzapuyxp8uf" + _, err = s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) + s.Require().EqualError(err, fmt.Sprintf("creator address is invalid: %s. err: decoding bech32 failed: invalid checksum (expected 8dpmg9 got yxp8uf): invalid address", invalidMsg.Creator)) + + // empty address + invalidMsg.Creator = "" + _, err = s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) + s.Require().EqualError(err, fmt.Sprintf("creator address is invalid: %s. err: empty address string is not allowed: invalid address", invalidMsg.Creator)) + + // wrong len address + invalidMsg.Creator = "stride1g6qdx6kdhpf000afvvpte7hp0vnpzapuyxp8ufabc" + _, err = s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) + s.Require().EqualError(err, fmt.Sprintf("creator address is invalid: %s. err: decoding bech32 failed: invalid character not part of charset: 98: invalid address", invalidMsg.Creator)) +} + +func (s *KeeperTestSuite) TestRedeemStake_HostZoneNotFound() { + tc := s.SetupRedeemStake() + + invalidMsg := tc.validMsg + invalidMsg.HostZone = "fake_host_zone" + _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) + + s.Require().EqualError(err, "host zone is invalid: fake_host_zone: host zone not registered") +} + +func (s *KeeperTestSuite) TestRedeemStake_RateAboveMaxThreshold() { + tc := s.SetupRedeemStake() + + hz := tc.hostZone + hz.RedemptionRate = sdk.NewDec(100) + s.App.StakeibcKeeper.SetHostZone(s.Ctx, hz) + + _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) + s.Require().Error(err) +} + +func (s *KeeperTestSuite) TestRedeemStake_InvalidReceiverAddress() { + tc := s.SetupRedeemStake() + + invalidMsg := tc.validMsg + + // stride instead of cosmos address + invalidMsg.Receiver = "stride159atdlc3ksl50g0659w5tq42wwer334ajl7xnq" + _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) + s.Require().EqualError(err, "invalid receiver address (invalid Bech32 prefix; expected cosmos, got stride): invalid address") + + // invalid cosmos address + invalidMsg.Receiver = "cosmos1g6qdx6kdhpf000afvvpte7hp0vnpzapuyxp8ua" + _, err = s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) + s.Require().EqualError(err, "invalid receiver address (decoding bech32 failed: invalid checksum (expected yxp8uf got yxp8ua)): invalid address") + + // empty address + invalidMsg.Receiver = "" + _, err = s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) + s.Require().EqualError(err, "invalid receiver address (empty address string is not allowed): invalid address") + + // wrong len address + invalidMsg.Receiver = "cosmos1g6qdx6kdhpf000afvvpte7hp0vnpzapuyxp8ufa" + _, err = s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) + s.Require().EqualError(err, "invalid receiver address (decoding bech32 failed: invalid checksum (expected xp8ugp got xp8ufa)): invalid address") +} + +func (s *KeeperTestSuite) TestRedeemStake_RedeemMoreThanStaked() { + tc := s.SetupRedeemStake() + + invalidMsg := tc.validMsg + invalidMsg.Amount = sdkmath.NewInt(1_000_000_000_000_000) + _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) + + s.Require().EqualError(err, fmt.Sprintf("cannot unstake an amount g.t. staked balance on host zone: %v: invalid amount", invalidMsg.Amount)) +} + +func (s *KeeperTestSuite) TestRedeemStake_NoEpochTrackerDay() { + tc := s.SetupRedeemStake() + + invalidMsg := tc.validMsg + s.App.RecordsKeeper.RemoveEpochUnbondingRecord(s.Ctx, tc.initialState.epochNumber) + _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) + + s.Require().EqualError(err, "latest epoch unbonding record not found: epoch unbonding record not found") +} + +func (s *KeeperTestSuite) TestRedeemStake_HostZoneNoUnbondings() { + tc := s.SetupRedeemStake() + + invalidMsg := tc.validMsg + epochUnbondingRecord := recordtypes.EpochUnbondingRecord{ + EpochNumber: 1, + HostZoneUnbondings: []*recordtypes.HostZoneUnbonding{}, + } + hostZoneUnbonding := &recordtypes.HostZoneUnbonding{ + NativeTokenAmount: sdkmath.ZeroInt(), + Denom: "uatom", + HostZoneId: "NOT_GAIA", + } + epochUnbondingRecord.HostZoneUnbondings = append(epochUnbondingRecord.HostZoneUnbondings, hostZoneUnbonding) + + s.App.RecordsKeeper.SetEpochUnbondingRecord(s.Ctx, epochUnbondingRecord) + _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &invalidMsg) + + s.Require().EqualError(err, "host zone not found in unbondings: GAIA: host zone not registered") +} + +func (s *KeeperTestSuite) TestRedeemStake_InvalidHostAddress() { + tc := s.SetupRedeemStake() + + // Update hostzone with invalid address + badHostZone, _ := s.App.StakeibcKeeper.GetHostZone(s.Ctx, tc.validMsg.HostZone) + badHostZone.DepositAddress = "cosmosXXX" + s.App.StakeibcKeeper.SetHostZone(s.Ctx, badHostZone) + + _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) + s.Require().EqualError(err, "could not bech32 decode address cosmosXXX of zone with id: GAIA") +} + +func (s *KeeperTestSuite) TestRedeemStake_HaltedZone() { + tc := s.SetupRedeemStake() + + // Update hostzone with halted + haltedHostZone, _ := s.App.StakeibcKeeper.GetHostZone(s.Ctx, tc.validMsg.HostZone) + haltedHostZone.Halted = true + s.App.StakeibcKeeper.SetHostZone(s.Ctx, haltedHostZone) + + _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) + s.Require().EqualError(err, "halted host zone found for zone (GAIA): Halted host zone found") +} + +func (s *KeeperTestSuite) TestRedeemStake_RedemptionsDisabled() { + tc := s.SetupRedeemStake() + + // Update hostzone with halted + haltedHostZone, _ := s.App.StakeibcKeeper.GetHostZone(s.Ctx, tc.validMsg.HostZone) + haltedHostZone.RedemptionsEnabled = false + s.App.StakeibcKeeper.SetHostZone(s.Ctx, haltedHostZone) + + _, err := s.GetMsgServer().RedeemStake(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) + s.Require().ErrorContains(err, "redemptions disabled") +} diff --git a/x/stakeibc/keeper/registration.go b/x/stakeibc/keeper/registration.go new file mode 100644 index 000000000..9224de8a2 --- /dev/null +++ b/x/stakeibc/keeper/registration.go @@ -0,0 +1,258 @@ +package keeper + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types" + + "github.com/Stride-Labs/stride/v22/utils" + epochtypes "github.com/Stride-Labs/stride/v22/x/epochs/types" + recordstypes "github.com/Stride-Labs/stride/v22/x/records/types" + "github.com/Stride-Labs/stride/v22/x/stakeibc/types" +) + +var ( + CommunityPoolStakeHoldingAddressKey = "community-pool-stake" + CommunityPoolRedeemHoldingAddressKey = "community-pool-redeem" + + DefaultMaxMessagesPerIcaTx = uint64(32) +) + +func (k Keeper) RegisterHostZone(ctx sdk.Context, msg *types.MsgRegisterHostZone) (*types.MsgRegisterHostZoneResponse, error) { + // Get ConnectionEnd (for counterparty connection) + connectionEnd, found := k.IBCKeeper.ConnectionKeeper.GetConnection(ctx, msg.ConnectionId) + if !found { + errMsg := fmt.Sprintf("invalid connection id, %s not found", msg.ConnectionId) + k.Logger(ctx).Error(errMsg) + return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) + } + counterpartyConnection := connectionEnd.Counterparty + + // Get chain id from connection + chainId, err := k.GetChainIdFromConnectionId(ctx, msg.ConnectionId) + if err != nil { + errMsg := fmt.Sprintf("unable to obtain chain id from connection %s, err: %s", msg.ConnectionId, err.Error()) + k.Logger(ctx).Error(errMsg) + return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) + } + + // get zone + _, found = k.GetHostZone(ctx, chainId) + if found { + errMsg := fmt.Sprintf("invalid chain id, zone for %s already registered", chainId) + k.Logger(ctx).Error(errMsg) + return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) + } + + // check the denom is not already registered + hostZones := k.GetAllHostZone(ctx) + for _, hostZone := range hostZones { + if hostZone.HostDenom == msg.HostDenom { + errMsg := fmt.Sprintf("host denom %s already registered", msg.HostDenom) + k.Logger(ctx).Error(errMsg) + return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) + } + if hostZone.ConnectionId == msg.ConnectionId { + errMsg := fmt.Sprintf("connectionId %s already registered", msg.ConnectionId) + k.Logger(ctx).Error(errMsg) + return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) + } + if hostZone.TransferChannelId == msg.TransferChannelId { + errMsg := fmt.Sprintf("transfer channel %s already registered", msg.TransferChannelId) + k.Logger(ctx).Error(errMsg) + return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) + } + if hostZone.Bech32Prefix == msg.Bech32Prefix { + errMsg := fmt.Sprintf("bech32prefix %s already registered", msg.Bech32Prefix) + k.Logger(ctx).Error(errMsg) + return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) + } + } + + // create and save the zones's module account + depositAddress := types.NewHostZoneDepositAddress(chainId) + if err := utils.CreateModuleAccount(ctx, k.AccountKeeper, depositAddress); err != nil { + return nil, errorsmod.Wrapf(err, "unable to create deposit account for host zone %s", chainId) + } + + // Create the host zone's community pool holding accounts + 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) + } + if err := utils.CreateModuleAccount(ctx, k.AccountKeeper, communityPoolRedeemAddress); err != nil { + return nil, errorsmod.Wrapf(err, "unable to create community pool redeem account for host zone %s", chainId) + } + + // Validate the community pool treasury address if it's non-empty + if msg.CommunityPoolTreasuryAddress != "" { + _, err := utils.AccAddressFromBech32(msg.CommunityPoolTreasuryAddress, msg.Bech32Prefix) + if err != nil { + return nil, errorsmod.Wrapf(err, "invalid community pool treasury address (%s)", msg.CommunityPoolTreasuryAddress) + } + } + + params := k.GetParams(ctx) + if msg.MinRedemptionRate.IsNil() || msg.MinRedemptionRate.IsZero() { + msg.MinRedemptionRate = sdk.NewDecWithPrec(int64(params.DefaultMinRedemptionRateThreshold), 2) + } + if msg.MaxRedemptionRate.IsNil() || msg.MaxRedemptionRate.IsZero() { + msg.MaxRedemptionRate = sdk.NewDecWithPrec(int64(params.DefaultMaxRedemptionRateThreshold), 2) + } + + // Set the max messages per ICA tx to the default value if it's not specified + maxMessagesPerIcaTx := msg.MaxMessagesPerIcaTx + if maxMessagesPerIcaTx == 0 { + maxMessagesPerIcaTx = DefaultMaxMessagesPerIcaTx + } + + // set the zone + zone := types.HostZone{ + ChainId: chainId, + ConnectionId: msg.ConnectionId, + Bech32Prefix: msg.Bech32Prefix, + IbcDenom: msg.IbcDenom, + HostDenom: msg.HostDenom, + TransferChannelId: msg.TransferChannelId, + // Start sharesToTokens rate at 1 upon registration + RedemptionRate: sdk.NewDec(1), + LastRedemptionRate: sdk.NewDec(1), + UnbondingPeriod: msg.UnbondingPeriod, + DepositAddress: depositAddress.String(), + CommunityPoolStakeHoldingAddress: communityPoolStakeAddress.String(), + CommunityPoolRedeemHoldingAddress: communityPoolRedeemAddress.String(), + MinRedemptionRate: msg.MinRedemptionRate, + MaxRedemptionRate: msg.MaxRedemptionRate, + // Default the inner bounds to the outer bounds + MinInnerRedemptionRate: msg.MinRedemptionRate, + MaxInnerRedemptionRate: msg.MaxRedemptionRate, + LsmLiquidStakeEnabled: msg.LsmLiquidStakeEnabled, + CommunityPoolTreasuryAddress: msg.CommunityPoolTreasuryAddress, + MaxMessagesPerIcaTx: maxMessagesPerIcaTx, + RedemptionsEnabled: true, + } + // write the zone back to the store + k.SetHostZone(ctx, zone) + + appVersion := string(icatypes.ModuleCdc.MustMarshalJSON(&icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: zone.ConnectionId, + HostConnectionId: counterpartyConnection.ConnectionId, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, + })) + + // generate delegate account + // NOTE: in the future, if we implement proxy governance, we'll need many more delegate accounts + delegateAccount := types.FormatHostZoneICAOwner(chainId, types.ICAAccountType_DELEGATION) + if err := k.ICAControllerKeeper.RegisterInterchainAccount(ctx, zone.ConnectionId, delegateAccount, appVersion); err != nil { + errMsg := fmt.Sprintf("unable to register delegation account, err: %s", err.Error()) + k.Logger(ctx).Error(errMsg) + return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) + } + + // generate fee account + feeAccount := types.FormatHostZoneICAOwner(chainId, types.ICAAccountType_FEE) + if err := k.ICAControllerKeeper.RegisterInterchainAccount(ctx, zone.ConnectionId, feeAccount, appVersion); err != nil { + errMsg := fmt.Sprintf("unable to register fee account, err: %s", err.Error()) + k.Logger(ctx).Error(errMsg) + return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) + } + + // generate withdrawal account + withdrawalAccount := types.FormatHostZoneICAOwner(chainId, types.ICAAccountType_WITHDRAWAL) + if err := k.ICAControllerKeeper.RegisterInterchainAccount(ctx, zone.ConnectionId, withdrawalAccount, appVersion); err != nil { + errMsg := fmt.Sprintf("unable to register withdrawal account, err: %s", err.Error()) + k.Logger(ctx).Error(errMsg) + return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) + } + + // generate redemption account + redemptionAccount := types.FormatHostZoneICAOwner(chainId, types.ICAAccountType_REDEMPTION) + if err := k.ICAControllerKeeper.RegisterInterchainAccount(ctx, zone.ConnectionId, redemptionAccount, appVersion); err != nil { + errMsg := fmt.Sprintf("unable to register redemption account, err: %s", err.Error()) + k.Logger(ctx).Error(errMsg) + return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) + } + + // create community pool deposit account + communityPoolDepositAccount := types.FormatHostZoneICAOwner(chainId, types.ICAAccountType_COMMUNITY_POOL_DEPOSIT) + if err := k.ICAControllerKeeper.RegisterInterchainAccount(ctx, zone.ConnectionId, communityPoolDepositAccount, appVersion); err != nil { + return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, "failed to register community pool deposit ICA") + } + + // create community pool return account + communityPoolReturnAccount := types.FormatHostZoneICAOwner(chainId, types.ICAAccountType_COMMUNITY_POOL_RETURN) + if err := k.ICAControllerKeeper.RegisterInterchainAccount(ctx, zone.ConnectionId, communityPoolReturnAccount, appVersion); err != nil { + return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, "failed to register community pool return ICA") + } + + // add this host zone to unbonding hostZones, otherwise users won't be able to unbond + // for this host zone until the following day + dayEpochTracker, found := k.GetEpochTracker(ctx, epochtypes.DAY_EPOCH) + if !found { + return nil, errorsmod.Wrapf(types.ErrEpochNotFound, "epoch tracker (%s) not found", epochtypes.DAY_EPOCH) + } + epochUnbondingRecord, found := k.RecordsKeeper.GetEpochUnbondingRecord(ctx, dayEpochTracker.EpochNumber) + if !found { + errMsg := "unable to find latest epoch unbonding record" + k.Logger(ctx).Error(errMsg) + return nil, errorsmod.Wrapf(recordstypes.ErrEpochUnbondingRecordNotFound, errMsg) + } + hostZoneUnbonding := recordstypes.HostZoneUnbonding{ + NativeTokenAmount: sdkmath.ZeroInt(), + StTokenAmount: sdkmath.ZeroInt(), + Denom: zone.HostDenom, + HostZoneId: zone.ChainId, + Status: recordstypes.HostZoneUnbonding_UNBONDING_QUEUE, + } + err = k.RecordsKeeper.SetHostZoneUnbondingRecord(ctx, epochUnbondingRecord.EpochNumber, chainId, hostZoneUnbonding) + if err != nil { + return nil, err + } + + // create an empty deposit record for the host zone + strideEpochTracker, found := k.GetEpochTracker(ctx, epochtypes.STRIDE_EPOCH) + if !found { + return nil, errorsmod.Wrapf(types.ErrEpochNotFound, "epoch tracker (%s) not found", epochtypes.STRIDE_EPOCH) + } + depositRecord := recordstypes.DepositRecord{ + Id: 0, + Amount: sdkmath.ZeroInt(), + Denom: zone.HostDenom, + HostZoneId: zone.ChainId, + Status: recordstypes.DepositRecord_TRANSFER_QUEUE, + DepositEpochNumber: strideEpochTracker.EpochNumber, + } + k.RecordsKeeper.AppendDepositRecord(ctx, depositRecord) + + // register stToken to consumer reward denom whitelist so that + // stToken rewards can be distributed to provider validators + err = k.RegisterStTokenDenomsToWhitelist(ctx, []string{types.StAssetDenomFromHostZoneDenom(zone.HostDenom)}) + if err != nil { + errMsg := fmt.Sprintf("unable to register reward denom, err: %s", err.Error()) + k.Logger(ctx).Error(errMsg) + return nil, errorsmod.Wrapf(types.ErrFailedToRegisterHostZone, errMsg) + } + + // emit events + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + ) + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeRegisterZone, + sdk.NewAttribute(types.AttributeKeyConnectionId, msg.ConnectionId), + sdk.NewAttribute(types.AttributeKeyRecipientChain, chainId), + ), + ) + + return &types.MsgRegisterHostZoneResponse{}, nil +} diff --git a/x/stakeibc/keeper/registration_test.go b/x/stakeibc/keeper/registration_test.go new file mode 100644 index 000000000..32f4a888a --- /dev/null +++ b/x/stakeibc/keeper/registration_test.go @@ -0,0 +1,478 @@ +package keeper_test + +import ( + "fmt" + + 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" + ibctesting "github.com/cosmos/ibc-go/v7/testing" + + epochtypes "github.com/Stride-Labs/stride/v22/x/epochs/types" + recordstypes "github.com/Stride-Labs/stride/v22/x/records/types" + recordtypes "github.com/Stride-Labs/stride/v22/x/records/types" + "github.com/Stride-Labs/stride/v22/x/stakeibc/keeper" + "github.com/Stride-Labs/stride/v22/x/stakeibc/types" + stakeibctypes "github.com/Stride-Labs/stride/v22/x/stakeibc/types" +) + +// ---------------------------------------------------- +// RegisterHostZone +// ---------------------------------------------------- + +type RegisterHostZoneTestCase struct { + validMsg stakeibctypes.MsgRegisterHostZone + epochUnbondingRecordNumber uint64 + strideEpochNumber uint64 + unbondingPeriod uint64 + defaultRedemptionRate sdk.Dec + atomHostZoneChainId string +} + +func (s *KeeperTestSuite) SetupRegisterHostZone() RegisterHostZoneTestCase { + epochUnbondingRecordNumber := uint64(3) + strideEpochNumber := uint64(4) + unbondingPeriod := uint64(14) + defaultRedemptionRate := sdk.NewDec(1) + atomHostZoneChainId := "GAIA" + + s.CreateTransferChannel(HostChainId) + + s.App.StakeibcKeeper.SetEpochTracker(s.Ctx, stakeibctypes.EpochTracker{ + EpochIdentifier: epochtypes.DAY_EPOCH, + EpochNumber: epochUnbondingRecordNumber, + }) + + s.App.StakeibcKeeper.SetEpochTracker(s.Ctx, stakeibctypes.EpochTracker{ + EpochIdentifier: epochtypes.STRIDE_EPOCH, + EpochNumber: strideEpochNumber, + }) + + epochUnbondingRecord := recordtypes.EpochUnbondingRecord{ + EpochNumber: epochUnbondingRecordNumber, + HostZoneUnbondings: []*recordtypes.HostZoneUnbonding{}, + } + s.App.RecordsKeeper.SetEpochUnbondingRecord(s.Ctx, epochUnbondingRecord) + + defaultMsg := stakeibctypes.MsgRegisterHostZone{ + ConnectionId: ibctesting.FirstConnectionID, + Bech32Prefix: GaiaPrefix, + HostDenom: Atom, + IbcDenom: IbcAtom, + TransferChannelId: ibctesting.FirstChannelID, + UnbondingPeriod: unbondingPeriod, + MinRedemptionRate: sdk.NewDec(0), + MaxRedemptionRate: sdk.NewDec(0), + } + + return RegisterHostZoneTestCase{ + validMsg: defaultMsg, + epochUnbondingRecordNumber: epochUnbondingRecordNumber, + strideEpochNumber: strideEpochNumber, + unbondingPeriod: unbondingPeriod, + defaultRedemptionRate: defaultRedemptionRate, + atomHostZoneChainId: atomHostZoneChainId, + } +} + +// Helper function to test registering a duplicate host zone +// If there's a duplicate connection ID, register_host_zone will error before checking other fields for duplicates +// In order to test those cases, we need to first create a new host zone, +// +// and then attempt to register with duplicate fields in the message +// +// This function 1) creates a new host zone and 2) returns what would be a successful register message +func (s *KeeperTestSuite) createNewHostZoneMessage(chainID string, denom string, prefix string) stakeibctypes.MsgRegisterHostZone { + // Create a new test chain and connection ID + ibctesting.DefaultTestingAppInit = ibctesting.SetupTestingApp + osmoChain := ibctesting.NewTestChain(s.T(), s.Coordinator, chainID) + path := ibctesting.NewPath(s.StrideChain, osmoChain) + s.Coordinator.SetupConnections(path) + connectionId := path.EndpointA.ConnectionID + + // Build what would be a successful message to register the host zone + // Note: this is purposefully missing fields because it is used in failure cases that short circuit + return stakeibctypes.MsgRegisterHostZone{ + ConnectionId: connectionId, + Bech32Prefix: prefix, + HostDenom: denom, + } +} + +// Helper function to assist in testing a failure to create an ICA account +// This function will occupy one of the specified port with the specified channel +// +// so that the registration fails +func (s *KeeperTestSuite) createActiveChannelOnICAPort(accountName string, channelID string) { + portID := fmt.Sprintf("%s%s.%s", icatypes.ControllerPortPrefix, HostChainId, accountName) + openChannel := channeltypes.Channel{State: channeltypes.OPEN} + + // The channel ID doesn't matter here - all that matters is that theres an open channel on the port + s.App.IBCKeeper.ChannelKeeper.SetChannel(s.Ctx, portID, channelID, openChannel) + s.App.ICAControllerKeeper.SetActiveChannelID(s.Ctx, ibctesting.FirstConnectionID, portID, channelID) +} + +func (s *KeeperTestSuite) TestRegisterHostZone_Success() { + tc := s.SetupRegisterHostZone() + msg := tc.validMsg + + // Register host zone + _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) + s.Require().NoError(err, "able to successfully register host zone") + + // Confirm host zone unbonding was added + hostZone, found := s.App.StakeibcKeeper.GetHostZone(s.Ctx, HostChainId) + s.Require().True(found, "host zone found") + s.Require().Equal(tc.defaultRedemptionRate, hostZone.RedemptionRate, "redemption rate set to default: 1") + s.Require().Equal(tc.defaultRedemptionRate, hostZone.LastRedemptionRate, "last redemption rate set to default: 1") + defaultMinThreshold := sdk.NewDec(int64(stakeibctypes.DefaultMinRedemptionRateThreshold)).Quo(sdk.NewDec(100)) + defaultMaxThreshold := sdk.NewDec(int64(stakeibctypes.DefaultMaxRedemptionRateThreshold)).Quo(sdk.NewDec(100)) + s.Require().Equal(defaultMinThreshold, hostZone.MinRedemptionRate, "min redemption rate set to default") + s.Require().Equal(defaultMaxThreshold, hostZone.MaxRedemptionRate, "max redemption rate set to default") + s.Require().Equal(tc.unbondingPeriod, hostZone.UnbondingPeriod, "unbonding period") + + // Confirm host zone unbonding record was created + epochUnbondingRecord, found := s.App.RecordsKeeper.GetEpochUnbondingRecord(s.Ctx, tc.epochUnbondingRecordNumber) + s.Require().True(found, "epoch unbonding record found") + s.Require().Len(epochUnbondingRecord.HostZoneUnbondings, 1, "host zone unbonding record has one entry") + + // Confirm host zone unbonding was added + hostZoneUnbonding := epochUnbondingRecord.HostZoneUnbondings[0] + s.Require().Equal(HostChainId, hostZoneUnbonding.HostZoneId, "host zone unbonding set for this host zone") + s.Require().Equal(sdkmath.ZeroInt(), hostZoneUnbonding.NativeTokenAmount, "host zone unbonding set to 0 tokens") + s.Require().Equal(recordstypes.HostZoneUnbonding_UNBONDING_QUEUE, hostZoneUnbonding.Status, "host zone unbonding set to bonded") + + // Confirm a module account was created + hostZoneModuleAccount, err := sdk.AccAddressFromBech32(hostZone.DepositAddress) + s.Require().NoError(err, "converting module address to account") + acc := s.App.AccountKeeper.GetAccount(s.Ctx, hostZoneModuleAccount) + s.Require().NotNil(acc, "host zone module account found in account keeper") + + // Confirm an empty deposit record was created + expectedDepositRecord := recordstypes.DepositRecord{ + Id: uint64(0), + Amount: sdkmath.ZeroInt(), + HostZoneId: hostZone.ChainId, + Denom: hostZone.HostDenom, + Status: recordstypes.DepositRecord_TRANSFER_QUEUE, + DepositEpochNumber: tc.strideEpochNumber, + } + + depositRecords := s.App.RecordsKeeper.GetAllDepositRecord(s.Ctx) + s.Require().Len(depositRecords, 1, "number of deposit records") + s.Require().Equal(expectedDepositRecord, depositRecords[0], "deposit record") + + // Confirm max ICA messages was set to default + s.Require().Equal(keeper.DefaultMaxMessagesPerIcaTx, hostZone.MaxMessagesPerIcaTx, "max messages per ica tx") +} + +func (s *KeeperTestSuite) TestRegisterHostZone_Success_SetCommunityPoolTreasuryAddress() { + tc := s.SetupRegisterHostZone() + + // Sets the community pool treasury address to a valid address + msg := tc.validMsg + msg.CommunityPoolTreasuryAddress = ValidHostAddress + + _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) + s.Require().NoError(err, "no error expected when registering host with valid treasury address") + + // Confirm treasury address was set + hostZone := s.MustGetHostZone(HostChainId) + s.Require().Equal(ValidHostAddress, hostZone.CommunityPoolTreasuryAddress, "treasury address") +} + +func (s *KeeperTestSuite) TestRegisterHostZone_Success_SetMaxIcaMessagesPerTx() { + tc := s.SetupRegisterHostZone() + + // Set the max number of ICA messages + maxMessages := uint64(100) + msg := tc.validMsg + msg.MaxMessagesPerIcaTx = maxMessages + + _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) + s.Require().NoError(err, "no error expected when registering host with max messages") + + // Confirm max number of messages was set + hostZone := s.MustGetHostZone(HostChainId) + s.Require().Equal(maxMessages, hostZone.MaxMessagesPerIcaTx, "max messages per ica tx") +} + +func (s *KeeperTestSuite) TestRegisterHostZone_Success_Unregister() { + tc := s.SetupRegisterHostZone() + msg := tc.validMsg + + // Register the host zone with the valid message + _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) + s.Require().NoError(err, "no error expected when registering host") + + // Confirm accounts were created + depositAddress := types.NewHostZoneDepositAddress(chainId) + communityPoolStakeAddress := types.NewHostZoneModuleAddress(chainId, keeper.CommunityPoolStakeHoldingAddressKey) + communityPoolRedeemAddress := types.NewHostZoneModuleAddress(chainId, keeper.CommunityPoolRedeemHoldingAddressKey) + + depositAccount := s.App.AccountKeeper.GetAccount(s.Ctx, depositAddress) + communityPoolStakeAccount := s.App.AccountKeeper.GetAccount(s.Ctx, communityPoolStakeAddress) + communityPoolRedeemAccount := s.App.AccountKeeper.GetAccount(s.Ctx, communityPoolRedeemAddress) + + s.Require().NotNil(depositAccount, "deposit account should exist") + s.Require().NotNil(communityPoolStakeAccount, "community pool stake account should exist") + s.Require().NotNil(communityPoolRedeemAccount, "community pool redeem account should exist") + + // Confirm records were created + depositRecords := s.App.RecordsKeeper.GetAllDepositRecord(s.Ctx) + s.Require().Len(depositRecords, 1, "there should be one deposit record") + + epochUnbondingRecords := s.App.RecordsKeeper.GetAllEpochUnbondingRecord(s.Ctx) + s.Require().Len(epochUnbondingRecords, 1, "there should be one epoch unbonding record") + s.Require().Len(epochUnbondingRecords[0].HostZoneUnbondings, 1, "there should be one host zone unbonding record") + + // Unregister the host zone + err = s.App.StakeibcKeeper.UnregisterHostZone(s.Ctx, HostChainId) + s.Require().NoError(err, "no error expected when unregistering host zone") + + // Confirm accounts were deleted + depositAccount = s.App.AccountKeeper.GetAccount(s.Ctx, depositAddress) + communityPoolStakeAccount = s.App.AccountKeeper.GetAccount(s.Ctx, communityPoolStakeAddress) + communityPoolRedeemAccount = s.App.AccountKeeper.GetAccount(s.Ctx, communityPoolRedeemAddress) + + s.Require().Nil(depositAccount, "deposit account should have been deleted") + s.Require().Nil(communityPoolStakeAccount, "community pool stake account should have been deleted") + s.Require().Nil(communityPoolRedeemAccount, "community pool redeem account should have been deleted") + + // Confirm records were deleted + depositRecords = s.App.RecordsKeeper.GetAllDepositRecord(s.Ctx) + s.Require().Empty(depositRecords, "deposit records should have been deleted") + + epochUnbondingRecords = s.App.RecordsKeeper.GetAllEpochUnbondingRecord(s.Ctx) + s.Require().Empty(epochUnbondingRecords[0].HostZoneUnbondings, "host zone unbonding record should have been deleted") + + // Attempt to re-register, it should succeed + _, err = s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) + s.Require().NoError(err, "no error expected when re-registering host") +} + +func (s *KeeperTestSuite) TestRegisterHostZone_InvalidConnectionId() { + tc := s.SetupRegisterHostZone() + msg := tc.validMsg + msg.ConnectionId = "connection-10" // an invalid connection ID + + _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) + s.Require().EqualError(err, "invalid connection id, connection-10 not found: failed to register host zone") +} + +func (s *KeeperTestSuite) TestRegisterHostZone_DuplicateConnectionIdInIBCState() { + // tests for a failure if we register the same host zone twice + // (with a duplicate connectionId stored in the IBCKeeper's state) + tc := s.SetupRegisterHostZone() + msg := tc.validMsg + + _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) + s.Require().NoError(err, "able to successfully register host zone once") + + // now all attributes are different, EXCEPT the connection ID + msg.Bech32Prefix = "cosmos-different" // a different Bech32 prefix + msg.HostDenom = "atom-different" // a different host denom + msg.IbcDenom = "ibc-atom-different" // a different IBC denom + + _, err = s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) + expectedErrMsg := "invalid chain id, zone for GAIA already registered: " + expectedErrMsg += "failed to register host zone" + s.Require().EqualError(err, expectedErrMsg, "registering host zone with duplicate connection ID should fail") +} + +func (s *KeeperTestSuite) TestRegisterHostZone_DuplicateConnectionIdInStakeibcState() { + // tests for a failure if we register the same host zone twice + // (with a duplicate connectionId stored in a different host zone in stakeibc) + tc := s.SetupRegisterHostZone() + msg := tc.validMsg + + _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) + s.Require().NoError(err, "able to successfully register host zone once") + + // Create the message for a brand new host zone + // (without modifications, you would expect this to be successful) + newHostZoneMsg := s.createNewHostZoneMessage("OSMO", "osmo", "osmo") + + // Add a different host zone with the same connection Id as OSMO + newHostZone := stakeibctypes.HostZone{ + ChainId: "JUNO", + ConnectionId: newHostZoneMsg.ConnectionId, + } + s.App.StakeibcKeeper.SetHostZone(s.Ctx, newHostZone) + + // Registering should fail with a duplicate connection ID + _, err = s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &newHostZoneMsg) + expectedErrMsg := "connectionId connection-1 already registered: " + expectedErrMsg += "failed to register host zone" + s.Require().EqualError(err, expectedErrMsg, "registering host zone with duplicate connection ID should fail") +} + +func (s *KeeperTestSuite) TestRegisterHostZone_DuplicateHostDenom() { + // tests for a failure if we register the same host zone twice (with a duplicate host denom) + tc := s.SetupRegisterHostZone() + + // Register host zones successfully + _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) + s.Require().NoError(err, "able to successfully register host zone once") + + // Create the message for a brand new host zone + // (without modifications, you would expect this to be successful) + newHostZoneMsg := s.createNewHostZoneMessage("OSMO", "osmo", "osmo") + + // Try to register with a duplicate host denom - it should fail + invalidMsg := newHostZoneMsg + invalidMsg.HostDenom = tc.validMsg.HostDenom + + _, err = s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &invalidMsg) + expectedErrMsg := "host denom uatom already registered: failed to register host zone" + s.Require().EqualError(err, expectedErrMsg, "registering host zone with duplicate host denom should fail") +} + +func (s *KeeperTestSuite) TestRegisterHostZone_DuplicateTransferChannel() { + // tests for a failure if we register the same host zone twice (with a duplicate transfer) + tc := s.SetupRegisterHostZone() + + // Register host zones successfully + _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) + s.Require().NoError(err, "able to successfully register host zone once") + + // Create the message for a brand new host zone + // (without modifications, you would expect this to be successful) + newHostZoneMsg := s.createNewHostZoneMessage("OSMO", "osmo", "osmo") + + // Try to register with a duplicate transfer channel - it should fail + invalidMsg := newHostZoneMsg + invalidMsg.TransferChannelId = tc.validMsg.TransferChannelId + + _, err = s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &invalidMsg) + expectedErrMsg := "transfer channel channel-0 already registered: failed to register host zone" + s.Require().EqualError(err, expectedErrMsg, "registering host zone with duplicate host denom should fail") +} + +func (s *KeeperTestSuite) TestRegisterHostZone_DuplicateBech32Prefix() { + // tests for a failure if we register the same host zone twice (with a duplicate bech32 prefix) + tc := s.SetupRegisterHostZone() + + // Register host zones successfully + _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) + s.Require().NoError(err, "able to successfully register host zone once") + + // Create the message for a brand new host zone + // (without modifications, you would expect this to be successful) + newHostZoneMsg := s.createNewHostZoneMessage("OSMO", "osmo", "osmo") + + // Try to register with a duplicate bech32prefix - it should fail + invalidMsg := newHostZoneMsg + invalidMsg.Bech32Prefix = tc.validMsg.Bech32Prefix + + _, err = s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &invalidMsg) + expectedErrMsg := "bech32prefix cosmos already registered: failed to register host zone" + s.Require().EqualError(err, expectedErrMsg, "registering host zone with duplicate bech32 prefix should fail") +} + +func (s *KeeperTestSuite) TestRegisterHostZone_CannotFindDayEpochTracker() { + // tests for a failure if the epoch tracker cannot be found + tc := s.SetupRegisterHostZone() + msg := tc.validMsg + + // delete the epoch tracker + s.App.StakeibcKeeper.RemoveEpochTracker(s.Ctx, epochtypes.DAY_EPOCH) + + _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) + expectedErrMsg := "epoch tracker (day) not found: epoch not found" + s.Require().EqualError(err, expectedErrMsg, "day epoch tracker not found") +} + +func (s *KeeperTestSuite) TestRegisterHostZone_CannotFindStrideEpochTracker() { + // tests for a failure if the epoch tracker cannot be found + tc := s.SetupRegisterHostZone() + msg := tc.validMsg + + // delete the epoch tracker + s.App.StakeibcKeeper.RemoveEpochTracker(s.Ctx, epochtypes.STRIDE_EPOCH) + + _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) + expectedErrMsg := "epoch tracker (stride_epoch) not found: epoch not found" + s.Require().EqualError(err, expectedErrMsg, "stride epoch tracker not found") +} + +func (s *KeeperTestSuite) TestRegisterHostZone_CannotFindEpochUnbondingRecord() { + // tests for a failure if the epoch unbonding record cannot be found + tc := s.SetupRegisterHostZone() + msg := tc.validMsg + + // delete the epoch unbonding record + s.App.RecordsKeeper.RemoveEpochUnbondingRecord(s.Ctx, tc.epochUnbondingRecordNumber) + + _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &msg) + expectedErrMsg := "unable to find latest epoch unbonding record: epoch unbonding record not found" + s.Require().EqualError(err, expectedErrMsg, " epoch unbonding record not found") +} + +func (s *KeeperTestSuite) TestRegisterHostZone_CannotRegisterDelegationAccount() { + // tests for a failure if the epoch unbonding record cannot be found + tc := s.SetupRegisterHostZone() + + // Create channel on delegation port + s.createActiveChannelOnICAPort("DELEGATION", "channel-1") + + _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) + expectedErrMsg := "unable to register delegation account, err: existing active channel channel-1 for portID icacontroller-GAIA.DELEGATION " + expectedErrMsg += "on connection connection-0: active channel already set for this owner: " + expectedErrMsg += "failed to register host zone" + s.Require().EqualError(err, expectedErrMsg, "can't register delegation account") +} + +func (s *KeeperTestSuite) TestRegisterHostZone_CannotRegisterFeeAccount() { + // tests for a failure if the epoch unbonding record cannot be found + tc := s.SetupRegisterHostZone() + + // Create channel on fee port + s.createActiveChannelOnICAPort("FEE", "channel-1") + + _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) + expectedErrMsg := "unable to register fee account, err: existing active channel channel-1 for portID icacontroller-GAIA.FEE " + expectedErrMsg += "on connection connection-0: active channel already set for this owner: " + expectedErrMsg += "failed to register host zone" + s.Require().EqualError(err, expectedErrMsg, "can't register redemption account") +} + +func (s *KeeperTestSuite) TestRegisterHostZone_CannotRegisterWithdrawalAccount() { + // tests for a failure if the epoch unbonding record cannot be found + tc := s.SetupRegisterHostZone() + + // Create channel on withdrawal port + s.createActiveChannelOnICAPort("WITHDRAWAL", "channel-1") + + _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) + expectedErrMsg := "unable to register withdrawal account, err: existing active channel channel-1 for portID icacontroller-GAIA.WITHDRAWAL " + expectedErrMsg += "on connection connection-0: active channel already set for this owner: " + expectedErrMsg += "failed to register host zone" + s.Require().EqualError(err, expectedErrMsg, "can't register redemption account") +} + +func (s *KeeperTestSuite) TestRegisterHostZone_CannotRegisterRedemptionAccount() { + // tests for a failure if the epoch unbonding record cannot be found + tc := s.SetupRegisterHostZone() + + // Create channel on redemption port + s.createActiveChannelOnICAPort("REDEMPTION", "channel-1") + + _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &tc.validMsg) + expectedErrMsg := "unable to register redemption account, err: existing active channel channel-1 for portID icacontroller-GAIA.REDEMPTION " + expectedErrMsg += "on connection connection-0: active channel already set for this owner: " + expectedErrMsg += "failed to register host zone" + s.Require().EqualError(err, expectedErrMsg, "can't register redemption account") +} + +func (s *KeeperTestSuite) TestRegisterHostZone_InvalidCommunityPoolTreasuryAddress() { + // tests for a failure if the community pool treasury address is invalid + tc := s.SetupRegisterHostZone() + + invalidMsg := tc.validMsg + invalidMsg.CommunityPoolTreasuryAddress = "invalid_address" + + _, err := s.GetMsgServer().RegisterHostZone(sdk.WrapSDKContext(s.Ctx), &invalidMsg) + s.Require().ErrorContains(err, "invalid community pool treasury address") +} diff --git a/x/stakeibc/types/errors.go b/x/stakeibc/types/errors.go index feae9ce28..ae38ce7c7 100644 --- a/x/stakeibc/types/errors.go +++ b/x/stakeibc/types/errors.go @@ -69,4 +69,5 @@ var ( ErrFailedToRegisterRebate = errorsmod.Register(ModuleName, 1562, "failed to register rebate") ErrInvalidDelegationsInProgress = errorsmod.Register(ModuleName, 1563, "invalid delegation changes in progress") ErrInvalidUndelegationsInProgress = errorsmod.Register(ModuleName, 1564, "invalid undelegation changes in progress") + ErrRedemptionsDisabled = errorsmod.Register(ModuleName, 1565, "redemptions disabled") ) diff --git a/x/stakeibc/types/host_zone.pb.go b/x/stakeibc/types/host_zone.pb.go index b4059bd69..5182f75c0 100644 --- a/x/stakeibc/types/host_zone.pb.go +++ b/x/stakeibc/types/host_zone.pb.go @@ -135,6 +135,8 @@ type HostZone struct { // The max number of messages that can be sent in a delegation // or undelegation ICA tx MaxMessagesPerIcaTx uint64 `protobuf:"varint,36,opt,name=max_messages_per_ica_tx,json=maxMessagesPerIcaTx,proto3" json:"max_messages_per_ica_tx,omitempty"` + // Indicates whether redemptions are allowed through this module + RedemptionsEnabled bool `protobuf:"varint,37,opt,name=redemptions_enabled,json=redemptionsEnabled,proto3" json:"redemptions_enabled,omitempty"` // An optional fee rebate // If there is no rebate for the host zone, this will be nil CommunityPoolRebate *CommunityPoolRebate `protobuf:"bytes,34,opt,name=community_pool_rebate,json=communityPoolRebate,proto3" json:"community_pool_rebate,omitempty"` @@ -310,6 +312,13 @@ func (m *HostZone) GetMaxMessagesPerIcaTx() uint64 { return 0 } +func (m *HostZone) GetRedemptionsEnabled() bool { + if m != nil { + return m.RedemptionsEnabled + } + return false +} + func (m *HostZone) GetCommunityPoolRebate() *CommunityPoolRebate { if m != nil { return m.CommunityPoolRebate @@ -339,69 +348,70 @@ func init() { func init() { proto.RegisterFile("stride/stakeibc/host_zone.proto", fileDescriptor_f81bf5b42c61245a) } var fileDescriptor_f81bf5b42c61245a = []byte{ - // 983 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x96, 0x4f, 0x6f, 0xdb, 0x36, - 0x14, 0xc0, 0xe3, 0xd6, 0x4d, 0x14, 0xa6, 0x89, 0x65, 0xe5, 0x4f, 0x95, 0xb4, 0x71, 0x1c, 0x37, - 0x1b, 0xbc, 0x43, 0x6c, 0xc0, 0x1d, 0x30, 0x60, 0xd8, 0x61, 0x69, 0x33, 0xa0, 0x36, 0xb2, 0x2e, - 0x50, 0x82, 0x61, 0xe8, 0x0e, 0x02, 0x25, 0xbe, 0xd8, 0x5c, 0x24, 0xd2, 0x23, 0xe9, 0xc6, 0xd9, - 0xa7, 0xd8, 0x87, 0xe9, 0x47, 0xd8, 0xa1, 0xc7, 0xa2, 0xa7, 0x62, 0x87, 0x62, 0x48, 0xbe, 0xc8, - 0x20, 0x4a, 0xb2, 0x65, 0x2b, 0x83, 0xd1, 0xc1, 0x27, 0x4b, 0x7c, 0x8f, 0xbf, 0x9f, 0x29, 0x52, - 0x4f, 0x0f, 0xed, 0x49, 0x25, 0x28, 0x81, 0xa6, 0x54, 0xf8, 0x12, 0xa8, 0xe7, 0x37, 0x7b, 0x5c, - 0x2a, 0xf7, 0x0f, 0xce, 0xa0, 0xd1, 0x17, 0x5c, 0x71, 0xab, 0x14, 0x27, 0x34, 0xd2, 0x84, 0x9d, - 0xdc, 0x8c, 0x37, 0x38, 0xa0, 0x04, 0x2b, 0x2e, 0xe2, 0x19, 0x3b, 0x1b, 0x5d, 0xde, 0xe5, 0xfa, - 0xb2, 0x19, 0x5d, 0x25, 0xa3, 0xdb, 0x3e, 0x97, 0x21, 0x97, 0x6e, 0x1c, 0x88, 0x6f, 0xe2, 0x50, - 0xed, 0x63, 0x01, 0xad, 0xbf, 0xe0, 0x61, 0x38, 0x60, 0x54, 0x5d, 0x9f, 0x72, 0x1e, 0x38, 0xe0, - 0x61, 0x05, 0xd6, 0x4f, 0x68, 0x45, 0xe8, 0x2b, 0x57, 0x60, 0x05, 0x76, 0xa1, 0x5a, 0xa8, 0x2f, - 0x3f, 0x6f, 0xbc, 0xfb, 0xb4, 0xb7, 0xf0, 0xf7, 0xa7, 0xbd, 0x2f, 0xbb, 0x54, 0xf5, 0x06, 0x5e, - 0xc3, 0xe7, 0x61, 0x42, 0x4b, 0x7e, 0x0e, 0x25, 0xb9, 0x6c, 0xaa, 0xeb, 0x3e, 0xc8, 0xc6, 0x31, - 0xf8, 0x0e, 0x8a, 0x11, 0x4e, 0x04, 0xec, 0xa3, 0xdd, 0x80, 0xfe, 0x3e, 0xa0, 0xc4, 0xd5, 0x7f, - 0x3e, 0xfa, 0x71, 0x15, 0xbf, 0x04, 0xe6, 0xe2, 0x90, 0x0f, 0x98, 0xb2, 0xef, 0x7d, 0xb6, 0xa2, - 0xcd, 0x94, 0xb3, 0x1d, 0x43, 0xcf, 0x34, 0xf3, 0x4c, 0x9d, 0x47, 0xc4, 0x23, 0x0d, 0xac, 0xfd, - 0x55, 0x46, 0xc6, 0x4b, 0x2e, 0xd5, 0x6b, 0xce, 0xc0, 0xda, 0x46, 0x86, 0xdf, 0xc3, 0x94, 0xb9, - 0x94, 0xc4, 0x8b, 0x71, 0x96, 0xf4, 0x7d, 0x9b, 0x58, 0x35, 0xf4, 0xd0, 0x03, 0xbf, 0xf7, 0xac, - 0xd5, 0x17, 0x70, 0x41, 0x87, 0x76, 0x59, 0x87, 0x27, 0xc6, 0xac, 0xa7, 0x68, 0xd5, 0xe7, 0x8c, - 0x81, 0xaf, 0x28, 0xd7, 0x8c, 0x7b, 0x71, 0xd2, 0x78, 0xb0, 0x4d, 0xac, 0x06, 0x5a, 0x57, 0x02, - 0x33, 0x79, 0x01, 0xc2, 0xf5, 0x7b, 0x98, 0x31, 0x08, 0xa2, 0xd4, 0x87, 0x3a, 0xb5, 0x9c, 0x86, - 0x5e, 0xc4, 0x91, 0x36, 0xb1, 0x1e, 0xa3, 0x65, 0xea, 0xf9, 0x2e, 0x01, 0xc6, 0x43, 0xdb, 0xd0, - 0x59, 0x06, 0xf5, 0xfc, 0xe3, 0xe8, 0xde, 0xda, 0x45, 0x48, 0x1f, 0x87, 0x38, 0xba, 0xac, 0xa3, - 0xcb, 0xd1, 0x48, 0x1c, 0xfe, 0x0a, 0x99, 0x03, 0xe6, 0x71, 0x46, 0x28, 0xeb, 0xba, 0x7d, 0x10, - 0x94, 0x13, 0x7b, 0xa7, 0x5a, 0xa8, 0x17, 0x9d, 0xd2, 0x68, 0xfc, 0x54, 0x0f, 0x5b, 0xdf, 0x22, - 0x34, 0x3a, 0x26, 0xd2, 0xbe, 0x5f, 0xbd, 0x5f, 0x5f, 0x69, 0xed, 0x34, 0xa6, 0x8e, 0x56, 0xe3, - 0xe7, 0x34, 0xc5, 0xc9, 0x64, 0x5b, 0x47, 0xa8, 0x44, 0xa0, 0xcf, 0x25, 0x55, 0x2e, 0x26, 0x44, - 0x80, 0x94, 0xb6, 0xa5, 0xf7, 0xc9, 0xfe, 0xf0, 0xf6, 0x70, 0x23, 0x39, 0x49, 0x47, 0x71, 0xe4, - 0x4c, 0x09, 0xca, 0xba, 0xce, 0x5a, 0x32, 0x21, 0x19, 0xb5, 0x5e, 0xa1, 0xad, 0x2b, 0xaa, 0x7a, - 0x44, 0xe0, 0x2b, 0x1c, 0xb8, 0xd4, 0xc7, 0x23, 0xd2, 0xd6, 0x0c, 0xd2, 0xc6, 0x78, 0x5e, 0xdb, - 0xc7, 0x29, 0xef, 0x7b, 0x54, 0xba, 0x00, 0x98, 0x00, 0x3d, 0x9a, 0x01, 0x5a, 0xbd, 0x00, 0xc8, - 0x10, 0x5e, 0xa1, 0x2d, 0x02, 0x01, 0x74, 0x71, 0xbc, 0x99, 0x19, 0x90, 0x3d, 0xeb, 0x1f, 0x8d, - 0xe7, 0x4d, 0xf2, 0x04, 0x10, 0x08, 0xfb, 0x39, 0xde, 0xf6, 0x2c, 0xde, 0x78, 0x5e, 0x86, 0x47, - 0x50, 0xcd, 0x4f, 0x5f, 0x49, 0xb7, 0xcf, 0x79, 0xe0, 0xa6, 0x7b, 0x90, 0x65, 0x57, 0x66, 0xb0, - 0x2b, 0x7e, 0xf6, 0xb5, 0x3e, 0x8e, 0x09, 0x19, 0x8b, 0x87, 0xf6, 0xa7, 0x2c, 0x02, 0xd4, 0x40, - 0x4c, 0x2e, 0x60, 0x6f, 0x86, 0x64, 0xd7, 0x9f, 0xac, 0x1d, 0x11, 0x20, 0xe3, 0xe8, 0xa1, 0x83, - 0x29, 0x87, 0x3e, 0x6f, 0x6e, 0x8f, 0x07, 0xfa, 0xe0, 0xa6, 0x9a, 0xea, 0x0c, 0x4d, 0x75, 0x42, - 0xa3, 0x5f, 0xf6, 0x97, 0x31, 0x22, 0x35, 0xfd, 0x86, 0xbe, 0xc8, 0xad, 0x86, 0x00, 0x84, 0x39, - 0xd5, 0xfe, 0x0c, 0xd5, 0xfe, 0xd4, 0x8a, 0x22, 0xc8, 0x94, 0xcb, 0x45, 0x7b, 0x53, 0x2e, 0x25, - 0x00, 0xcb, 0x81, 0xb8, 0x1e, 0x59, 0x9e, 0xce, 0xb0, 0x3c, 0x99, 0xb0, 0x9c, 0x27, 0xd3, 0x53, - 0xc1, 0xaf, 0xa8, 0xac, 0xb8, 0xc2, 0xd1, 0xbe, 0xa7, 0xc7, 0x4d, 0xda, 0xab, 0xff, 0xab, 0x3e, - 0x9a, 0x1a, 0x74, 0x3c, 0xe6, 0x58, 0x0c, 0x6d, 0x04, 0x58, 0x2a, 0x37, 0x73, 0x64, 0x75, 0x89, - 0x47, 0x9a, 0xff, 0xdd, 0xe7, 0x95, 0xf8, 0x0f, 0x6f, 0x0f, 0x51, 0xb2, 0xc0, 0xa8, 0xe0, 0x5b, - 0x11, 0xd9, 0x19, 0x81, 0x75, 0xe1, 0x07, 0x54, 0x9a, 0x56, 0xad, 0xcc, 0x41, 0xb5, 0x26, 0x26, - 0x35, 0x01, 0x5a, 0x0f, 0x29, 0xcb, 0xad, 0x6a, 0x63, 0x0e, 0xaa, 0x72, 0x48, 0x99, 0x93, 0xb7, - 0xe1, 0x61, 0xce, 0xb6, 0x39, 0x17, 0x1b, 0x1e, 0x4e, 0xd9, 0xae, 0xd0, 0x76, 0xb4, 0x36, 0xca, - 0x18, 0x88, 0x9c, 0xf3, 0xc9, 0x1c, 0x9c, 0x5b, 0x21, 0x65, 0xed, 0x88, 0x7e, 0x87, 0x18, 0x0f, - 0xff, 0x43, 0xbc, 0x3b, 0x17, 0x31, 0x1e, 0xde, 0x25, 0xfe, 0x1a, 0x3d, 0x8a, 0xc4, 0x21, 0x48, - 0x89, 0xbb, 0x20, 0xa3, 0x2f, 0x9c, 0xae, 0x4b, 0x6a, 0x68, 0x1f, 0xe8, 0xaf, 0x5c, 0xf4, 0xf8, - 0x7f, 0x4c, 0xa2, 0xa7, 0x20, 0xda, 0x3e, 0x3e, 0x1f, 0x5a, 0xbf, 0xa0, 0xcd, 0x5c, 0x11, 0x88, - 0x1a, 0x10, 0xbb, 0x56, 0x2d, 0xd4, 0x57, 0x5a, 0x07, 0xb9, 0x8f, 0xde, 0x1d, 0x9d, 0x8f, 0xb3, - 0xee, 0xdf, 0xd1, 0x0e, 0x7d, 0x83, 0xec, 0x40, 0x86, 0x6e, 0xb6, 0x83, 0x71, 0x81, 0x61, 0x2f, - 0x00, 0x62, 0x3f, 0xae, 0x16, 0xea, 0x86, 0xb3, 0x19, 0xc8, 0xf0, 0x64, 0xdc, 0x8b, 0xfc, 0x10, - 0x07, 0xad, 0x2d, 0xb4, 0xd8, 0xc3, 0x81, 0x02, 0x62, 0xaf, 0xeb, 0xb4, 0xe4, 0xae, 0x53, 0x34, - 0x8a, 0xe6, 0x83, 0x4e, 0xd1, 0x78, 0x60, 0x2e, 0x76, 0x8a, 0xc6, 0xa2, 0xb9, 0xd4, 0x29, 0x1a, - 0x4b, 0xa6, 0xd1, 0x29, 0x1a, 0x6b, 0x66, 0xa9, 0x53, 0x34, 0x4a, 0xa6, 0xd9, 0x29, 0x1a, 0xa6, - 0x59, 0x7e, 0x7e, 0xf2, 0xee, 0xa6, 0x52, 0x78, 0x7f, 0x53, 0x29, 0xfc, 0x73, 0x53, 0x29, 0xfc, - 0x79, 0x5b, 0x59, 0x78, 0x7f, 0x5b, 0x59, 0xf8, 0x78, 0x5b, 0x59, 0x78, 0xdd, 0xca, 0x3c, 0xf2, - 0x33, 0xbd, 0xb2, 0xc3, 0x13, 0xec, 0xc9, 0x66, 0xd2, 0x24, 0xbe, 0x69, 0xb5, 0x9a, 0xc3, 0x71, - 0xab, 0xa8, 0xb7, 0xc0, 0x5b, 0xd4, 0x6d, 0xdf, 0xb3, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0xce, - 0xb7, 0xab, 0x42, 0x7c, 0x0a, 0x00, 0x00, + // 999 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x96, 0xc1, 0x6e, 0xdb, 0xb6, + 0x1b, 0xc0, 0xe3, 0xd6, 0x4d, 0x14, 0xa6, 0x89, 0x65, 0x39, 0x49, 0x95, 0xb4, 0x71, 0x9c, 0x34, + 0xfd, 0xc3, 0xff, 0x43, 0x6c, 0xc0, 0x1d, 0x30, 0x60, 0xd8, 0x61, 0x69, 0x33, 0xa0, 0x36, 0xb2, + 0x2e, 0x50, 0x82, 0x61, 0xe8, 0x0e, 0x02, 0x25, 0x7e, 0xb1, 0xb9, 0x48, 0xa4, 0x47, 0xd2, 0x8d, + 0xb3, 0xa7, 0xd8, 0xc3, 0xf4, 0x21, 0x7a, 0x2c, 0x7a, 0x2a, 0x76, 0x28, 0x86, 0xe4, 0x19, 0x76, + 0x1f, 0x44, 0x49, 0xb6, 0x6c, 0x65, 0x30, 0x3a, 0xf8, 0x64, 0x91, 0xdf, 0xc7, 0xdf, 0x8f, 0x34, + 0x29, 0xea, 0x43, 0xbb, 0x52, 0x09, 0x4a, 0xa0, 0x29, 0x15, 0xbe, 0x04, 0xea, 0xf9, 0xcd, 0x1e, + 0x97, 0xca, 0xfd, 0x9d, 0x33, 0x68, 0xf4, 0x05, 0x57, 0xdc, 0x2a, 0xc5, 0x09, 0x8d, 0x34, 0x61, + 0x3b, 0x37, 0xe2, 0x2d, 0x0e, 0x28, 0xc1, 0x8a, 0x8b, 0x78, 0xc4, 0xf6, 0x7a, 0x97, 0x77, 0xb9, + 0x7e, 0x6c, 0x46, 0x4f, 0x49, 0xef, 0x96, 0xcf, 0x65, 0xc8, 0xa5, 0x1b, 0x07, 0xe2, 0x46, 0x1c, + 0xda, 0xff, 0x54, 0x40, 0x95, 0x97, 0x3c, 0x0c, 0x07, 0x8c, 0xaa, 0xeb, 0x53, 0xce, 0x03, 0x07, + 0x3c, 0xac, 0xc0, 0xfa, 0x11, 0xad, 0x08, 0xfd, 0xe4, 0x0a, 0xac, 0xc0, 0x2e, 0xd4, 0x0a, 0xf5, + 0xe5, 0x17, 0x8d, 0xf7, 0x9f, 0x77, 0x17, 0xfe, 0xfc, 0xbc, 0xfb, 0xbf, 0x2e, 0x55, 0xbd, 0x81, + 0xd7, 0xf0, 0x79, 0x98, 0xd0, 0x92, 0x9f, 0x43, 0x49, 0x2e, 0x9b, 0xea, 0xba, 0x0f, 0xb2, 0x71, + 0x0c, 0xbe, 0x83, 0x62, 0x84, 0x13, 0x01, 0xfb, 0x68, 0x27, 0xa0, 0xbf, 0x0d, 0x28, 0x71, 0xf5, + 0xe4, 0xa3, 0x1f, 0x57, 0xf1, 0x4b, 0x60, 0x2e, 0x0e, 0xf9, 0x80, 0x29, 0xfb, 0xde, 0x17, 0x2b, + 0xda, 0x4c, 0x39, 0x5b, 0x31, 0xf4, 0x4c, 0x33, 0xcf, 0xd4, 0x79, 0x44, 0x3c, 0xd2, 0xc0, 0xfd, + 0xbf, 0xcb, 0xc8, 0x78, 0xc5, 0xa5, 0x7a, 0xc3, 0x19, 0x58, 0x5b, 0xc8, 0xf0, 0x7b, 0x98, 0x32, + 0x97, 0x92, 0x78, 0x31, 0xce, 0x92, 0x6e, 0xb7, 0x89, 0xb5, 0x8f, 0x1e, 0x7a, 0xe0, 0xf7, 0x9e, + 0xb7, 0xfa, 0x02, 0x2e, 0xe8, 0xd0, 0x2e, 0xeb, 0xf0, 0x44, 0x9f, 0xf5, 0x14, 0xad, 0xfa, 0x9c, + 0x31, 0xf0, 0x15, 0xe5, 0x9a, 0x71, 0x2f, 0x4e, 0x1a, 0x77, 0xb6, 0x89, 0xd5, 0x40, 0x15, 0x25, + 0x30, 0x93, 0x17, 0x20, 0x5c, 0xbf, 0x87, 0x19, 0x83, 0x20, 0x4a, 0x7d, 0xa8, 0x53, 0xcb, 0x69, + 0xe8, 0x65, 0x1c, 0x69, 0x13, 0xeb, 0x31, 0x5a, 0xa6, 0x9e, 0xef, 0x12, 0x60, 0x3c, 0xb4, 0x0d, + 0x9d, 0x65, 0x50, 0xcf, 0x3f, 0x8e, 0xda, 0xd6, 0x0e, 0x42, 0xfa, 0x38, 0xc4, 0xd1, 0x65, 0x1d, + 0x5d, 0x8e, 0x7a, 0xe2, 0xf0, 0xff, 0x91, 0x39, 0x60, 0x1e, 0x67, 0x84, 0xb2, 0xae, 0xdb, 0x07, + 0x41, 0x39, 0xb1, 0xb7, 0x6b, 0x85, 0x7a, 0xd1, 0x29, 0x8d, 0xfa, 0x4f, 0x75, 0xb7, 0xf5, 0x0d, + 0x42, 0xa3, 0x63, 0x22, 0xed, 0xfb, 0xb5, 0xfb, 0xf5, 0x95, 0xd6, 0x76, 0x63, 0xea, 0x68, 0x35, + 0x7e, 0x4a, 0x53, 0x9c, 0x4c, 0xb6, 0x75, 0x84, 0x4a, 0x04, 0xfa, 0x5c, 0x52, 0xe5, 0x62, 0x42, + 0x04, 0x48, 0x69, 0x5b, 0x7a, 0x9f, 0xec, 0x8f, 0xef, 0x0e, 0xd7, 0x93, 0x93, 0x74, 0x14, 0x47, + 0xce, 0x94, 0xa0, 0xac, 0xeb, 0xac, 0x25, 0x03, 0x92, 0x5e, 0xeb, 0x35, 0xda, 0xbc, 0xa2, 0xaa, + 0x47, 0x04, 0xbe, 0xc2, 0x81, 0x4b, 0x7d, 0x3c, 0x22, 0x6d, 0xce, 0x20, 0xad, 0x8f, 0xc7, 0xb5, + 0x7d, 0x9c, 0xf2, 0xbe, 0x43, 0xa5, 0x0b, 0x80, 0x09, 0xd0, 0xa3, 0x19, 0xa0, 0xd5, 0x0b, 0x80, + 0x0c, 0xe1, 0x35, 0xda, 0x24, 0x10, 0x40, 0x17, 0xc7, 0x9b, 0x99, 0x01, 0xd9, 0xb3, 0x66, 0x34, + 0x1e, 0x37, 0xc9, 0x13, 0x40, 0x20, 0xec, 0xe7, 0x78, 0x5b, 0xb3, 0x78, 0xe3, 0x71, 0x19, 0x1e, + 0x41, 0xfb, 0x7e, 0xfa, 0x4a, 0xba, 0x7d, 0xce, 0x03, 0x37, 0xdd, 0x83, 0x2c, 0xbb, 0x3a, 0x83, + 0x5d, 0xf5, 0xb3, 0xaf, 0xf5, 0x71, 0x4c, 0xc8, 0x58, 0x3c, 0xb4, 0x37, 0x65, 0x11, 0xa0, 0x06, + 0x62, 0x72, 0x01, 0xbb, 0x33, 0x24, 0x3b, 0xfe, 0xe4, 0xdd, 0x11, 0x01, 0x32, 0x8e, 0x1e, 0x3a, + 0x98, 0x72, 0xe8, 0xf3, 0xe6, 0xf6, 0x78, 0xa0, 0x0f, 0x6e, 0xaa, 0xa9, 0xcd, 0xd0, 0xd4, 0x26, + 0x34, 0xfa, 0x65, 0x7f, 0x15, 0x23, 0x52, 0xd3, 0xaf, 0xe8, 0x59, 0x6e, 0x35, 0x04, 0x20, 0xcc, + 0xa9, 0xf6, 0x66, 0xa8, 0xf6, 0xa6, 0x56, 0x14, 0x41, 0xa6, 0x5c, 0x2e, 0xda, 0x9d, 0x72, 0x29, + 0x01, 0x58, 0x0e, 0xc4, 0xf5, 0xc8, 0xf2, 0x74, 0x86, 0xe5, 0xc9, 0x84, 0xe5, 0x3c, 0x19, 0x9e, + 0x0a, 0x7e, 0x41, 0x65, 0xc5, 0x15, 0x8e, 0xf6, 0x3d, 0x3d, 0x6e, 0xd2, 0x5e, 0xfd, 0x4f, 0xf7, + 0xa3, 0xa9, 0x41, 0xc7, 0x63, 0x8e, 0xc5, 0xd0, 0x7a, 0x80, 0xa5, 0x72, 0x33, 0x47, 0x56, 0x5f, + 0xf1, 0x48, 0xf3, 0xbf, 0xfd, 0xb2, 0x2b, 0xfe, 0xe3, 0xbb, 0x43, 0x94, 0x2c, 0x30, 0xba, 0xf0, + 0xad, 0x88, 0xec, 0x8c, 0xc0, 0xfa, 0xe2, 0x07, 0x54, 0x9a, 0x56, 0xad, 0xcc, 0x41, 0xb5, 0x26, + 0x26, 0x35, 0x01, 0xaa, 0x84, 0x94, 0xe5, 0x56, 0xb5, 0x3e, 0x07, 0x55, 0x39, 0xa4, 0xcc, 0xc9, + 0xdb, 0xf0, 0x30, 0x67, 0xdb, 0x98, 0x8b, 0x0d, 0x0f, 0xa7, 0x6c, 0x57, 0x68, 0x2b, 0x5a, 0x1b, + 0x65, 0x0c, 0x44, 0xce, 0xf9, 0x64, 0x0e, 0xce, 0xcd, 0x90, 0xb2, 0x76, 0x44, 0xbf, 0x43, 0x8c, + 0x87, 0xff, 0x22, 0xde, 0x99, 0x8b, 0x18, 0x0f, 0xef, 0x12, 0x7f, 0x85, 0x1e, 0x45, 0xe2, 0x10, + 0xa4, 0xc4, 0x5d, 0x90, 0xd1, 0x17, 0x4e, 0xdf, 0x4b, 0x6a, 0x68, 0x1f, 0xe8, 0xaf, 0x5c, 0xf4, + 0xf7, 0xff, 0x90, 0x44, 0x4f, 0x41, 0xb4, 0x7d, 0x7c, 0x3e, 0xb4, 0x9a, 0xa8, 0x32, 0x9e, 0xa4, + 0x74, 0x81, 0x61, 0x2f, 0x00, 0x62, 0x3f, 0xab, 0x15, 0xea, 0x86, 0x63, 0x65, 0x42, 0xdf, 0xc7, + 0x11, 0xeb, 0x67, 0xb4, 0x91, 0xbb, 0x35, 0xa2, 0x8a, 0xc5, 0xde, 0xaf, 0x15, 0xea, 0x2b, 0xad, + 0x83, 0xdc, 0x57, 0xf2, 0x8e, 0x52, 0xc9, 0xa9, 0xf8, 0x77, 0xd4, 0x4f, 0x5f, 0x23, 0x3b, 0x90, + 0xa1, 0x9b, 0x2d, 0x79, 0x46, 0xf3, 0x79, 0xac, 0xe7, 0xb3, 0x11, 0xc8, 0xf0, 0x64, 0x5c, 0xbc, + 0xa4, 0x53, 0xda, 0x44, 0x8b, 0x3d, 0x1c, 0x28, 0x20, 0x76, 0x45, 0xa7, 0x25, 0xad, 0x4e, 0xd1, + 0x28, 0x9a, 0x0f, 0x3a, 0x45, 0xe3, 0x81, 0xb9, 0xd8, 0x29, 0x1a, 0x8b, 0xe6, 0x52, 0xa7, 0x68, + 0x2c, 0x99, 0x46, 0xa7, 0x68, 0xac, 0x99, 0xa5, 0x4e, 0xd1, 0x28, 0x99, 0x66, 0xa7, 0x68, 0x98, + 0x66, 0xf9, 0xc5, 0xc9, 0xfb, 0x9b, 0x6a, 0xe1, 0xc3, 0x4d, 0xb5, 0xf0, 0xd7, 0x4d, 0xb5, 0xf0, + 0xc7, 0x6d, 0x75, 0xe1, 0xc3, 0x6d, 0x75, 0xe1, 0xd3, 0x6d, 0x75, 0xe1, 0x4d, 0x2b, 0xb3, 0x47, + 0x67, 0x7a, 0x65, 0x87, 0x27, 0xd8, 0x93, 0xcd, 0xa4, 0xaa, 0x7c, 0xdb, 0x6a, 0x35, 0x87, 0xe3, + 0xda, 0x52, 0xef, 0x99, 0xb7, 0xa8, 0xeb, 0xc4, 0xe7, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x02, + 0x50, 0x71, 0xce, 0xad, 0x0a, 0x00, 0x00, } func (m *CommunityPoolRebate) Marshal() (dAtA []byte, err error) { @@ -467,6 +477,18 @@ func (m *HostZone) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.RedemptionsEnabled { + i-- + if m.RedemptionsEnabled { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x2 + i-- + dAtA[i] = 0xa8 + } if m.MaxMessagesPerIcaTx != 0 { i = encodeVarintHostZone(dAtA, i, uint64(m.MaxMessagesPerIcaTx)) i-- @@ -878,6 +900,9 @@ func (m *HostZone) Size() (n int) { if m.MaxMessagesPerIcaTx != 0 { n += 2 + sovHostZone(uint64(m.MaxMessagesPerIcaTx)) } + if m.RedemptionsEnabled { + n += 3 + } return n } @@ -1932,6 +1957,26 @@ func (m *HostZone) Unmarshal(dAtA []byte) error { break } } + case 37: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RedemptionsEnabled", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHostZone + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.RedemptionsEnabled = bool(v != 0) default: iNdEx = preIndex skippy, err := skipHostZone(dAtA[iNdEx:]) diff --git a/x/staketia/client/cli/tx.go b/x/staketia/client/cli/tx.go index 52bee7cdc..25d7a70b5 100644 --- a/x/staketia/client/cli/tx.go +++ b/x/staketia/client/cli/tx.go @@ -36,7 +36,6 @@ func GetTxCmd() *cobra.Command { } cmd.AddCommand( - CmdLiquidStake(), CmdRedeemStake(), CmdConfirmDelegation(), CmdConfirmUndelegation(), @@ -52,48 +51,6 @@ func GetTxCmd() *cobra.Command { return cmd } -// User transaction to liquid stake native tokens into stTokens -func CmdLiquidStake() *cobra.Command { - cmd := &cobra.Command{ - Use: "liquid-stake [amount]", - Short: "Liquid stakes native tokens and receives stTokens", - Long: strings.TrimSpace( - fmt.Sprintf(`Liquid stakes native tokens and receives stTokens - -Example: - $ %[1]s tx %[2]s liquid-stake 10000 -`, version.AppName, types.ModuleName), - ), - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - amount, ok := sdkmath.NewIntFromString(args[0]) - if !ok { - return errors.New("unable to parse amount") - } - - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - msg := types.NewMsgLiquidStake( - clientCtx.GetFromAddress().String(), - amount, - ) - - if err := msg.ValidateBasic(); err != nil { - return err - } - - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) - }, - } - - flags.AddTxFlagsToCmd(cmd) - - return cmd -} - // User transaction to redeem stake stTokens into native tokens func CmdRedeemStake() *cobra.Command { cmd := &cobra.Command{ diff --git a/x/staketia/keeper/abci.go b/x/staketia/keeper/abci.go index 6e55a665c..600ad6d01 100644 --- a/x/staketia/keeper/abci.go +++ b/x/staketia/keeper/abci.go @@ -4,13 +4,4 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) BeginBlocker(ctx sdk.Context) { - // Check invariants - - // Check redemption rate is within safety bounds - if err := k.CheckRedemptionRateExceedsBounds(ctx); err != nil { - k.Logger(ctx).Error(err.Error()) - // If not, halt the zone - k.HaltZone(ctx) - } -} +func (k Keeper) BeginBlocker(ctx sdk.Context) {} diff --git a/x/staketia/keeper/delegation.go b/x/staketia/keeper/delegation.go index 69f841fdd..18e19f803 100644 --- a/x/staketia/keeper/delegation.go +++ b/x/staketia/keeper/delegation.go @@ -1,81 +1,16 @@ package keeper import ( - "fmt" "time" errorsmod "cosmossdk.io/errors" - sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" "github.com/Stride-Labs/stride/v22/utils" - stakeibctypes "github.com/Stride-Labs/stride/v22/x/stakeibc/types" "github.com/Stride-Labs/stride/v22/x/staketia/types" ) -// Liquid stakes native tokens and returns stTokens to the user -// The staker's native tokens (which exist as an IBC denom on stride) are escrowed -// in the deposit account -// StTokens are minted at the current redemption rate -func (k Keeper) LiquidStake(ctx sdk.Context, liquidStaker string, nativeAmount sdkmath.Int) (stToken sdk.Coin, err error) { - // Get the host zone and verify it's unhalted - hostZone, err := k.GetUnhaltedHostZone(ctx) - if err != nil { - return stToken, err - } - - // Get user and deposit account addresses - liquidStakerAddress, err := sdk.AccAddressFromBech32(liquidStaker) - if err != nil { - return stToken, errorsmod.Wrapf(err, "user's address is invalid") - } - hostZoneDepositAddress, err := sdk.AccAddressFromBech32(hostZone.DepositAddress) - if err != nil { - return stToken, errorsmod.Wrapf(err, "host zone deposit address is invalid") - } - - // Check redemption rates are within safety bounds - if err := k.CheckRedemptionRateExceedsBounds(ctx); err != nil { - return stToken, err - } - - // The tokens that are sent to the protocol are denominated in the ibc hash of the native token on stride (e.g. ibc/xxx) - nativeToken := sdk.NewCoin(hostZone.NativeTokenIbcDenom, nativeAmount) - if !utils.IsIBCToken(hostZone.NativeTokenIbcDenom) { - return stToken, errorsmod.Wrapf(stakeibctypes.ErrInvalidToken, - "denom is not an IBC token (%s)", hostZone.NativeTokenIbcDenom) - } - - // Determine the amount of stTokens to mint using the redemption rate - stAmount := (sdk.NewDecFromInt(nativeAmount).Quo(hostZone.RedemptionRate)).TruncateInt() - if stAmount.IsZero() { - return stToken, errorsmod.Wrapf(stakeibctypes.ErrInsufficientLiquidStake, - "Liquid stake of %s%s would return 0 stTokens", nativeAmount.String(), hostZone.NativeTokenDenom) - } - - // Transfer the native tokens from the user to module account - if err := k.bankKeeper.SendCoins(ctx, liquidStakerAddress, hostZoneDepositAddress, sdk.NewCoins(nativeToken)); err != nil { - return stToken, errorsmod.Wrapf(err, "failed to send tokens from liquid staker %s to deposit address", liquidStaker) - } - - // Mint the stTokens and transfer them to the user - stDenom := utils.StAssetDenomFromHostZoneDenom(hostZone.NativeTokenDenom) - stToken = sdk.NewCoin(stDenom, stAmount) - if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(stToken)); err != nil { - return stToken, errorsmod.Wrapf(err, "Failed to mint stTokens") - } - if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, liquidStakerAddress, sdk.NewCoins(stToken)); err != nil { - return stToken, errorsmod.Wrapf(err, "Failed to send %s from deposit address to liquid staker", stToken.String()) - } - - // Emit liquid stake event with the same schema as stakeibc - EmitSuccessfulLiquidStakeEvent(ctx, liquidStaker, hostZone, nativeAmount, stAmount) - - return stToken, nil -} - // IBC transfers all TIA in the deposit account and sends it to the delegation account func (k Keeper) PrepareDelegation(ctx sdk.Context, epochNumber uint64, epochDuration time.Duration) error { k.Logger(ctx).Info(utils.LogWithHostZone(types.CelestiaChainId, "Preparing delegation for epoch %d", epochNumber)) @@ -171,55 +106,16 @@ func (k Keeper) ConfirmDelegation(ctx sdk.Context, recordId uint64, txHash strin k.ArchiveDelegationRecord(ctx, delegationRecord) // increment delegation on Host Zone - hostZone.DelegatedBalance = hostZone.DelegatedBalance.Add(delegationRecord.NativeAmount) + hostZone.RemainingDelegatedBalance = hostZone.RemainingDelegatedBalance.Add(delegationRecord.NativeAmount) k.SetHostZone(ctx, hostZone) EmitSuccessfulConfirmDelegationEvent(ctx, recordId, delegationRecord.NativeAmount, txHash, sender) return nil } -// Liquid stakes tokens in the fee account and distributes them to the fee collector -func (k Keeper) LiquidStakeAndDistributeFees(ctx sdk.Context) error { - // Get the fee address from the host zone - hostZone, err := k.GetUnhaltedHostZone(ctx) - if err != nil { - return err - } - - // Get the balance of native tokens in the fee address, if there are no tokens, no action is necessary - feeAddress := k.accountKeeper.GetModuleAddress(types.FeeAddress) - feesBalance := k.bankKeeper.GetBalance(ctx, feeAddress, hostZone.NativeTokenIbcDenom) - if feesBalance.IsZero() { - k.Logger(ctx).Info("No fees generated this epoch") - return nil - } - - // Liquid stake those native tokens - stTokens, err := k.LiquidStake(ctx, feeAddress.String(), feesBalance.Amount) - if err != nil { - return errorsmod.Wrapf(err, "unable to liquid stake fees") - } - - // Send the stTokens to the fee collector - err = k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.FeeAddress, authtypes.FeeCollectorName, sdk.NewCoins(stTokens)) - if err != nil { - return errorsmod.Wrapf(err, "unable to send liquid staked tokens to fee collector") - } - k.Logger(ctx).Info(fmt.Sprintf("Liquid staked and sent %v to fee collector", stTokens)) - - return nil -} - // Runs prepare delegations with a cache context wrapper so revert any partial state changes func (k Keeper) SafelyPrepareDelegation(ctx sdk.Context, epochNumber uint64, epochDuration time.Duration) error { return utils.ApplyFuncIfNoError(ctx, func(ctx sdk.Context) error { return k.PrepareDelegation(ctx, epochNumber, epochDuration) }) } - -// Liquid stakes fees with a cache context wrapper so revert any partial state changes -func (k Keeper) SafelyLiquidStakeAndDistributeFees(ctx sdk.Context) error { - return utils.ApplyFuncIfNoError(ctx, func(ctx sdk.Context) error { - return k.LiquidStakeAndDistributeFees(ctx) - }) -} diff --git a/x/staketia/keeper/delegation_test.go b/x/staketia/keeper/delegation_test.go index 2ad2fc330..0c396f58c 100644 --- a/x/staketia/keeper/delegation_test.go +++ b/x/staketia/keeper/delegation_test.go @@ -5,7 +5,6 @@ import ( sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" ibctesting "github.com/cosmos/ibc-go/v7/testing" @@ -14,224 +13,6 @@ import ( var InitialDelegation = sdkmath.NewInt(1_000_000) -type LiquidStakeTestCase struct { - liquidStakeAmount sdkmath.Int - expectedStAmount sdkmath.Int - stakerAddress sdk.AccAddress - depositAddress sdk.AccAddress -} - -// ---------------------------------------------------- -// LiquidStake -// ---------------------------------------------------- - -// Helper function to mock relevant state before testing a liquid stake -func (s *KeeperTestSuite) SetupTestLiquidStake( - redemptionRate sdk.Dec, - liquidStakeAmount, - expectedStAmount sdkmath.Int, -) LiquidStakeTestCase { - // Create relevant addresses - stakerAddress := s.TestAccs[0] - depositAddress := s.TestAccs[1] - - // Create a host zone with relevant denom's and addresses - s.App.StaketiaKeeper.SetHostZone(s.Ctx, types.HostZone{ - ChainId: HostChainId, - NativeTokenDenom: HostNativeDenom, - NativeTokenIbcDenom: HostIBCDenom, - DepositAddress: depositAddress.String(), - RedemptionRate: redemptionRate, - MinRedemptionRate: redemptionRate.Sub(sdk.MustNewDecFromStr("0.2")), - MinInnerRedemptionRate: redemptionRate.Sub(sdk.MustNewDecFromStr("0.1")), - MaxInnerRedemptionRate: redemptionRate.Add(sdk.MustNewDecFromStr("0.1")), - MaxRedemptionRate: redemptionRate.Add(sdk.MustNewDecFromStr("0.2")), - }) - - // Fund the staker - liquidStakeToken := sdk.NewCoin(HostIBCDenom, liquidStakeAmount) - s.FundAccount(stakerAddress, liquidStakeToken) - - return LiquidStakeTestCase{ - liquidStakeAmount: liquidStakeAmount, - expectedStAmount: expectedStAmount, - stakerAddress: stakerAddress, - depositAddress: depositAddress, - } -} - -// Helper function to setup the state with default values -// (useful when testing error cases) -func (s *KeeperTestSuite) DefaultSetupTestLiquidStake() LiquidStakeTestCase { - redemptionRate := sdk.MustNewDecFromStr("1.0") - liquidStakeAmount := sdkmath.NewInt(1000) - stAmount := sdkmath.NewInt(1000) - return s.SetupTestLiquidStake(redemptionRate, liquidStakeAmount, stAmount) -} - -// Helper function to confirm balances after a successful liquid stake -func (s *KeeperTestSuite) ConfirmLiquidStakeTokenTransfer(tc LiquidStakeTestCase) { - zeroNativeTokens := sdk.NewCoin(HostIBCDenom, sdk.ZeroInt()) - liquidStakedNativeTokens := sdk.NewCoin(HostIBCDenom, tc.liquidStakeAmount) - - zeroStTokens := sdk.NewCoin(StDenom, sdk.ZeroInt()) - liquidStakedStTokens := sdk.NewCoin(StDenom, tc.expectedStAmount) - - // Confirm native tokens were escrowed - // Staker balance should have decreased to zero - // Deposit balance should have increased by liquid stake amount - stakerNativeBalance := s.App.BankKeeper.GetBalance(s.Ctx, tc.stakerAddress, HostIBCDenom) - s.CompareCoins(zeroNativeTokens, stakerNativeBalance, "staker native balance") - - depositNativeBalance := s.App.BankKeeper.GetBalance(s.Ctx, tc.depositAddress, HostIBCDenom) - s.CompareCoins(liquidStakedNativeTokens, depositNativeBalance, "deposit native balance") - - // Confirm stTokens were minted to the user - // Staker balance should increase by the liquid stake amount - // Deposit balance should still be zero - stakerStBalance := s.App.BankKeeper.GetBalance(s.Ctx, tc.stakerAddress, StDenom) - s.CompareCoins(liquidStakedStTokens, stakerStBalance, "staker stToken balance") - - depositStBalance := s.App.BankKeeper.GetBalance(s.Ctx, tc.depositAddress, StDenom) - s.CompareCoins(zeroStTokens, depositStBalance, "deposit native balance") -} - -func (s *KeeperTestSuite) TestLiquidStake_Successful() { - // Test liquid stake across different redemption rates - testCases := []struct { - name string - redemptionRate sdk.Dec - liquidStakeAmount sdkmath.Int - expectedStAmount sdkmath.Int - }{ - { - // Redemption Rate of 1: - // 1000 native -> 1000 stTokens - name: "redemption rate of 1", - redemptionRate: sdk.MustNewDecFromStr("1.0"), - liquidStakeAmount: sdkmath.NewInt(1000), - expectedStAmount: sdkmath.NewInt(1000), - }, - { - // Redemption Rate of 2: - // 1000 native -> 500 stTokens - name: "redemption rate of 2", - redemptionRate: sdk.MustNewDecFromStr("2.0"), - liquidStakeAmount: sdkmath.NewInt(1000), - expectedStAmount: sdkmath.NewInt(500), - }, - { - // Redemption Rate of 0.5: - // 1000 native -> 2000 stTokens - name: "redemption rate of 0.5", - redemptionRate: sdk.MustNewDecFromStr("0.5"), - liquidStakeAmount: sdkmath.NewInt(1000), - expectedStAmount: sdkmath.NewInt(2000), - }, - { - // Redemption Rate of 1.1: - // 333 native -> 302.72 (302) stTokens - name: "int truncation", - redemptionRate: sdk.MustNewDecFromStr("1.1"), - liquidStakeAmount: sdkmath.NewInt(333), - expectedStAmount: sdkmath.NewInt(302), - }, - } - - for _, testCase := range testCases { - s.Run(testCase.name, func() { - s.SetupTest() // reset state - tc := s.SetupTestLiquidStake(testCase.redemptionRate, testCase.liquidStakeAmount, testCase.expectedStAmount) - - // Confirm liquid stake succeeded - stTokenResponse, err := s.App.StaketiaKeeper.LiquidStake(s.Ctx, tc.stakerAddress.String(), tc.liquidStakeAmount) - s.Require().NoError(err, "no error expected during liquid stake") - - // Confirm the stToken from the response matches expectations - s.Require().Equal(StDenom, stTokenResponse.Denom, "st token denom in liquid stake response") - s.Require().Equal(tc.expectedStAmount.Int64(), stTokenResponse.Amount.Int64(), - "st token amount in liquid stake response") - - // Confirm the native token escrow and stToken mint succeeded - s.ConfirmLiquidStakeTokenTransfer(tc) - }) - } -} - -func (s *KeeperTestSuite) TestLiquidStake_HostZoneHalted() { - tc := s.DefaultSetupTestLiquidStake() - - // Halt the host zone so the liquid stake fails - hostZone := s.MustGetHostZone() - hostZone.Halted = true - s.App.StaketiaKeeper.SetHostZone(s.Ctx, hostZone) - - _, err := s.App.StaketiaKeeper.LiquidStake(s.Ctx, tc.stakerAddress.String(), tc.liquidStakeAmount) - s.Require().ErrorContains(err, "host zone is halted") -} - -func (s *KeeperTestSuite) TestLiquidStake_InvalidAddresse() { - tc := s.DefaultSetupTestLiquidStake() - - // Pass an invalid staker address and confirm it fails - _, err := s.App.StaketiaKeeper.LiquidStake(s.Ctx, "invalid_address", tc.liquidStakeAmount) - s.Require().ErrorContains(err, "user's address is invalid") - - // Set an invalid deposit address and confirm it fails - hostZone := s.MustGetHostZone() - hostZone.DepositAddress = "invalid_address" - s.App.StaketiaKeeper.SetHostZone(s.Ctx, hostZone) - - _, err = s.App.StaketiaKeeper.LiquidStake(s.Ctx, tc.stakerAddress.String(), tc.liquidStakeAmount) - s.Require().ErrorContains(err, "host zone deposit address is invalid") -} - -func (s *KeeperTestSuite) TestLiquidStake_InvalidRedemptionRate() { - tc := s.DefaultSetupTestLiquidStake() - - // Update the redemption rate so it exceeds the bounds - hostZone := s.MustGetHostZone() - hostZone.RedemptionRate = hostZone.MaxInnerRedemptionRate.Add(sdk.MustNewDecFromStr("0.01")) - s.App.StaketiaKeeper.SetHostZone(s.Ctx, hostZone) - - _, err := s.App.StaketiaKeeper.LiquidStake(s.Ctx, tc.stakerAddress.String(), tc.liquidStakeAmount) - s.Require().ErrorContains(err, "redemption rate outside inner safety bounds") -} - -func (s *KeeperTestSuite) TestLiquidStake_InvalidIBCDenom() { - tc := s.DefaultSetupTestLiquidStake() - - // Set an invalid IBC denom on the host so the liquid stake fails - hostZone := s.MustGetHostZone() - hostZone.NativeTokenIbcDenom = "non-ibc-denom" - s.App.StaketiaKeeper.SetHostZone(s.Ctx, hostZone) - - _, err := s.App.StaketiaKeeper.LiquidStake(s.Ctx, tc.stakerAddress.String(), tc.liquidStakeAmount) - s.Require().ErrorContains(err, "denom is not an IBC token") -} - -func (s *KeeperTestSuite) TestLiquidStake_InsufficientLiquidStake() { - // Adjust redemption rate so that a small liquid stake will result in 0 stTokens - // stTokens = 1(amount) / 1.1(RR) = rounds down to 0 - redemptionRate := sdk.MustNewDecFromStr("1.1") - liquidStakeAmount := sdkmath.NewInt(1) - expectedStAmount := sdkmath.ZeroInt() - tc := s.SetupTestLiquidStake(redemptionRate, liquidStakeAmount, expectedStAmount) - - _, err := s.App.StaketiaKeeper.LiquidStake(s.Ctx, tc.stakerAddress.String(), tc.liquidStakeAmount) - s.Require().ErrorContains(err, "Liquid staked amount is too small") -} - -func (s *KeeperTestSuite) TestLiquidStake_InsufficientFunds() { - // Attempt to liquid stake more tokens than the staker has available - tc := s.DefaultSetupTestLiquidStake() - - excessiveLiquidStakeAmount := sdkmath.NewInt(10000000000) - _, err := s.App.StaketiaKeeper.LiquidStake(s.Ctx, tc.stakerAddress.String(), excessiveLiquidStakeAmount) - s.Require().ErrorContains(err, "failed to send tokens from liquid staker") - s.Require().ErrorContains(err, "insufficient funds") -} - // ---------------------------------------------------- // PrepareDelegation // ---------------------------------------------------- @@ -372,7 +153,7 @@ func (s *KeeperTestSuite) SetupDelegationRecords() { // Set HostZone hostZone := s.initializeHostZone() - hostZone.DelegatedBalance = InitialDelegation + hostZone.RemainingDelegatedBalance = InitialDelegation s.App.StaketiaKeeper.SetHostZone(s.Ctx, hostZone) } @@ -415,7 +196,7 @@ func (s *KeeperTestSuite) VerifyDelegationRecords(verifyIdentical bool, archiveI // if nothing should have changed, verify that host zone balance is unmodified if verifyIdentical { // verify hostZone delegated balance is same as initial delegation - s.Require().Equal(InitialDelegation.Int64(), hostZone.DelegatedBalance.Int64(), "hostZone delegated balance should not have changed") + s.Require().Equal(InitialDelegation.Int64(), hostZone.RemainingDelegatedBalance.Int64(), "hostZone delegated balance should not have changed") } } } @@ -439,7 +220,7 @@ func (s *KeeperTestSuite) TestConfirmDelegation_Successful() { // verify hostZone delegated balance is same as initial delegation + 6000 hostZone := s.MustGetHostZone() - s.Require().Equal(InitialDelegation.Int64()+6000, hostZone.DelegatedBalance.Int64(), "hostZone delegated balance should have increased by 6000") + s.Require().Equal(InitialDelegation.Int64()+6000, hostZone.RemainingDelegatedBalance.Int64(), "hostZone delegated balance should have increased by 6000") } func (s *KeeperTestSuite) TestConfirmDelegation_DelegationZero() { @@ -489,62 +270,3 @@ func (s *KeeperTestSuite) TestConfirmDelegation_RecordIncorrectState() { s.VerifyDelegationRecords(true) } } - -// ---------------------------------------------------- -// LiquidStakeAndDistributeFees -// ---------------------------------------------------- - -func (s *KeeperTestSuite) TestLiquidStakeAndDistributeFees() { - // Create relevant addresses - depositAddress := s.TestAccs[0] - feeAddress := s.App.AccountKeeper.GetModuleAddress(types.FeeAddress) - - // Liquid stake 1000 with a RR of 2, should return 500 tokens - liquidStakeAmount := sdkmath.NewInt(1000) - redemptionRate := sdk.NewDec(2) - expectedStTokens := sdkmath.NewInt(500) - - // Create a host zone with relevant denom's and addresses - hostZone := types.HostZone{ - ChainId: HostChainId, - NativeTokenDenom: HostNativeDenom, - NativeTokenIbcDenom: HostIBCDenom, - DepositAddress: depositAddress.String(), - RedemptionRate: redemptionRate, - MinRedemptionRate: redemptionRate.Sub(sdk.MustNewDecFromStr("0.2")), - MinInnerRedemptionRate: redemptionRate.Sub(sdk.MustNewDecFromStr("0.1")), - MaxInnerRedemptionRate: redemptionRate.Add(sdk.MustNewDecFromStr("0.1")), - MaxRedemptionRate: redemptionRate.Add(sdk.MustNewDecFromStr("0.2")), - } - s.App.StaketiaKeeper.SetHostZone(s.Ctx, hostZone) - - // Fund the fee address with native tokens - liquidStakeToken := sdk.NewCoin(HostIBCDenom, liquidStakeAmount) - s.FundAccount(feeAddress, liquidStakeToken) - - // Call liquid stake and distribute - err := s.App.StaketiaKeeper.LiquidStakeAndDistributeFees(s.Ctx) - s.Require().NoError(err, "no error expected when liquid staking fee tokens") - - // Confirm stTokens were sent to the fee collector - feeCollectorAddress := s.App.AccountKeeper.GetModuleAddress(authtypes.FeeCollectorName) - feeCollectorBalance := s.App.BankKeeper.GetBalance(s.Ctx, feeCollectorAddress, StDenom) - s.Require().Equal(expectedStTokens.Int64(), feeCollectorBalance.Amount.Int64(), - "fee collector should have received sttokens") - - // Attempt to liquid stake again when there are no more rewards, it should succeed but do nothing - err = s.App.StaketiaKeeper.LiquidStakeAndDistributeFees(s.Ctx) - s.Require().NoError(err, "no error expected when liquid staking again") - - feeCollectorBalance = s.App.BankKeeper.GetBalance(s.Ctx, feeCollectorAddress, StDenom) - s.Require().Equal(expectedStTokens.Int64(), feeCollectorBalance.Amount.Int64(), - "fee collector should not have changed") - - // Test that if the host zone is halted, it will error - haltedHostZone := hostZone - haltedHostZone.Halted = true - s.App.StaketiaKeeper.SetHostZone(s.Ctx, haltedHostZone) - - err = s.App.StaketiaKeeper.LiquidStakeAndDistributeFees(s.Ctx) - s.Require().ErrorContains(err, "host zone is halted") -} diff --git a/x/staketia/keeper/events.go b/x/staketia/keeper/events.go index 674745a1c..5f88955ff 100644 --- a/x/staketia/keeper/events.go +++ b/x/staketia/keeper/events.go @@ -90,7 +90,6 @@ func EmitHaltZoneEvent(ctx sdk.Context, hostZone types.HostZone) { sdk.NewEvent( types.EventTypeHostZoneHalt, sdk.NewAttribute(types.AttributeKeyHostZone, hostZone.ChainId), - sdk.NewAttribute(types.AttributeKeyRedemptionRate, hostZone.RedemptionRate.String()), ), ) } diff --git a/x/staketia/keeper/hooks.go b/x/staketia/keeper/hooks.go index b91efc6ee..c2e1ed9b2 100644 --- a/x/staketia/keeper/hooks.go +++ b/x/staketia/keeper/hooks.go @@ -23,20 +23,6 @@ func (k Keeper) BeforeEpochStart(ctx sdk.Context, epochInfo epochstypes.EpochInf // Every day, refresh the redemption rate and prepare delegations // Every 4 days, prepare undelegations if epochInfo.Identifier == epochstypes.DAY_EPOCH { - // Update the redemption rate - // If this fails, do not proceed to the delegation or undelegation step - // Note: This must be run first because it is used when refreshing the native token - // balance in prepare undelegation - if err := k.UpdateRedemptionRate(ctx); err != nil { - k.Logger(ctx).Error(fmt.Sprintf("Unable update redemption rate: %s", err.Error())) - return - } - - // Post the redemption rate to the oracle (if it doesn't exceed the bounds) - if err := k.PostRedemptionRateToOracles(ctx); err != nil { - k.Logger(ctx).Error(fmt.Sprintf("Unable to post redemption rate to oracle: %s", err.Error())) - } - // Prepare delegations by transferring the deposited tokens to the host zone if err := k.SafelyPrepareDelegation(ctx, epochNumber, epochInfo.Duration); err != nil { k.Logger(ctx).Error(fmt.Sprintf("Unable to prepare delegation for epoch %d: %s", epochNumber, err.Error())) @@ -62,13 +48,6 @@ func (k Keeper) BeforeEpochStart(ctx sdk.Context, epochInfo epochstypes.EpochInf k.Logger(ctx).Error(fmt.Sprintf("Unable to distribute claims for epoch %d: %s", epochNumber, err.Error())) } } - - // Every mint epoch, liquid stake fees and distribute to fee collector - if epochInfo.Identifier == epochstypes.MINT_EPOCH { - if err := k.SafelyLiquidStakeAndDistributeFees(ctx); err != nil { - k.Logger(ctx).Error(fmt.Sprintf("Unable to liquid stake and distribute fees this epoch %d: %s", epochNumber, err.Error())) - } - } } type Hooks struct { diff --git a/x/staketia/keeper/host_zone_test.go b/x/staketia/keeper/host_zone_test.go index 0141c6345..1a47b6fe9 100644 --- a/x/staketia/keeper/host_zone_test.go +++ b/x/staketia/keeper/host_zone_test.go @@ -9,23 +9,17 @@ import ( // Helper function to create the singleton HostZone with attributes func (s *KeeperTestSuite) initializeHostZone() types.HostZone { hostZone := types.HostZone{ - ChainId: "CELESTIA", - NativeTokenDenom: "utia", - NativeTokenIbcDenom: "ibc/utia", - TransferChannelId: "channel-05", - DelegationAddress: "tia0384a", - RewardAddress: "tia144f42e9", - DepositAddress: "stride8abb3e", - RedemptionAddress: "stride3400de1", - ClaimAddress: "stride00b1a83", - LastRedemptionRate: sdk.MustNewDecFromStr("1.0"), - RedemptionRate: sdk.MustNewDecFromStr("1.0"), - MinRedemptionRate: sdk.MustNewDecFromStr("0.95"), - MaxRedemptionRate: sdk.MustNewDecFromStr("1.10"), - MinInnerRedemptionRate: sdk.MustNewDecFromStr("0.97"), - MaxInnerRedemptionRate: sdk.MustNewDecFromStr("1.07"), - DelegatedBalance: sdk.NewInt(1_000_000), - Halted: false, + ChainId: "CELESTIA", + NativeTokenDenom: "utia", + NativeTokenIbcDenom: "ibc/utia", + TransferChannelId: "channel-05", + DelegationAddress: "tia0384a", + RewardAddress: "tia144f42e9", + DepositAddress: "stride8abb3e", + RedemptionAddress: "stride3400de1", + ClaimAddress: "stride00b1a83", + RemainingDelegatedBalance: sdk.NewInt(1_000_000), + Halted: false, } s.App.StaketiaKeeper.SetHostZone(s.Ctx, hostZone) return hostZone @@ -47,8 +41,7 @@ func (s *KeeperTestSuite) TestRemoveHostZone() { func (s *KeeperTestSuite) TestSetHostZone() { hostZone := s.initializeHostZone() - hostZone.RedemptionRate = hostZone.RedemptionRate.Add(sdk.MustNewDecFromStr("0.1")) - hostZone.DelegatedBalance = hostZone.DelegatedBalance.Add(sdk.NewInt(100_000)) + hostZone.RemainingDelegatedBalance = hostZone.RemainingDelegatedBalance.Add(sdk.NewInt(100_000)) s.App.StaketiaKeeper.SetHostZone(s.Ctx, hostZone) loadedHostZone := s.MustGetHostZone() diff --git a/x/staketia/keeper/invariants.go b/x/staketia/keeper/invariants.go index 562caa9af..012890e8c 100644 --- a/x/staketia/keeper/invariants.go +++ b/x/staketia/keeper/invariants.go @@ -24,7 +24,7 @@ func (k Keeper) HaltZone(ctx sdk.Context) { stDenom := utils.StAssetDenomFromHostZoneDenom(hostZone.NativeTokenDenom) k.ratelimitKeeper.AddDenomToBlacklist(ctx, stDenom) - k.Logger(ctx).Error(fmt.Sprintf("[INVARIANT BROKEN!!!] %s's RR is %s.", hostZone.GetChainId(), hostZone.RedemptionRate.String())) + k.Logger(ctx).Error("[INVARIANT BROKEN!!!] %s's RR is %s.", hostZone.GetChainId()) EmitHaltZoneEvent(ctx, hostZone) } diff --git a/x/staketia/keeper/keeper.go b/x/staketia/keeper/keeper.go index eca6796d6..d155b6bb7 100644 --- a/x/staketia/keeper/keeper.go +++ b/x/staketia/keeper/keeper.go @@ -18,6 +18,8 @@ type Keeper struct { bankKeeper types.BankKeeper icaOracleKeeper types.ICAOracleKeeper ratelimitKeeper types.RatelimitKeeper + recordsKeeper types.RecordsKeeper + stakeibcKeeper types.StakeibcKeeper transferKeeper types.TransferKeeper } @@ -28,6 +30,8 @@ func NewKeeper( bankKeeper types.BankKeeper, icaOracleKeeper types.ICAOracleKeeper, ratelimitKeeper types.RatelimitKeeper, + recordsKeeper types.RecordsKeeper, + stakeibcKeeper types.StakeibcKeeper, transferKeeper types.TransferKeeper, ) *Keeper { return &Keeper{ @@ -37,6 +41,8 @@ func NewKeeper( bankKeeper: bankKeeper, icaOracleKeeper: icaOracleKeeper, ratelimitKeeper: ratelimitKeeper, + recordsKeeper: recordsKeeper, + stakeibcKeeper: stakeibcKeeper, transferKeeper: transferKeeper, } } diff --git a/x/staketia/keeper/migration.go b/x/staketia/keeper/migration.go new file mode 100644 index 000000000..d82113b48 --- /dev/null +++ b/x/staketia/keeper/migration.go @@ -0,0 +1,167 @@ +package keeper + +import ( + "errors" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + + recordtypes "github.com/Stride-Labs/stride/v22/x/records/types" + stakeibctypes "github.com/Stride-Labs/stride/v22/x/stakeibc/types" + oldtypes "github.com/Stride-Labs/stride/v22/x/staketia/legacytypes" + "github.com/Stride-Labs/stride/v22/x/staketia/types" +) + +// TODO [UPGRADE HANDLER]: Migrate stakeibc host zone (set redemptions enabled to true on each host zone) + +// Helper to deserialize the host zone with the old types +func (k Keeper) GetLegacyHostZone(ctx sdk.Context) (hostZone oldtypes.HostZone, err error) { + store := ctx.KVStore(k.storeKey) + hostZoneBz := store.Get(types.HostZoneKey) + + if len(hostZoneBz) == 0 { + return hostZone, types.ErrHostZoneNotFound.Wrapf("No HostZone found, there must be exactly one HostZone!") + } + + k.cdc.MustUnmarshal(hostZoneBz, &hostZone) + return hostZone, nil +} + +// Helper to deserialize store a host zone with the old type definition +// (only used for tests) +func (k Keeper) SetLegacyHostZone(ctx sdk.Context, hostZone oldtypes.HostZone) { + store := ctx.KVStore(k.storeKey) + hostZoneBz := k.cdc.MustMarshal(&hostZone) + store.Set(types.HostZoneKey, hostZoneBz) +} + +// Update the newly created stakeibc host zone with the accounting values from staketia +func (k Keeper) UpdateStakeibcHostZone(ctx sdk.Context, legacyHostZone oldtypes.HostZone) (stakeibctypes.HostZone, error) { + // Grab the newly created stakeibc host zone + stakeibcHostZone, found := k.stakeibcKeeper.GetHostZone(ctx, types.CelestiaChainId) + if !found { + return stakeibctypes.HostZone{}, errors.New("celestia host zone not found in stakeibc after registration") + } + + // Disable redemptions and set the redemption rate to the one from stakeibc + stakeibcHostZone.RedemptionsEnabled = false + stakeibcHostZone.RedemptionRate = legacyHostZone.RedemptionRate + + // Set the total delegations to the sum of the staketia total, plus any delegation records + // This is so we don't have to trigger any stakeibc account changes when delegations are + // confirmed from staketia + // In practice, if timed right, there should be no delegation records + pendingDelegations := sdkmath.ZeroInt() + for _, delegationRecord := range k.GetAllActiveDelegationRecords(ctx) { + pendingDelegations = pendingDelegations.Add(delegationRecord.NativeAmount) + } + stakeibcHostZone.TotalDelegations = legacyHostZone.DelegatedBalance.Add(pendingDelegations) + k.stakeibcKeeper.SetHostZone(ctx, stakeibcHostZone) + + return stakeibcHostZone, nil +} + +// Migrates the protocol owned accounts (deposit and fee) to their stakeibc counterparts +func (k Keeper) MigrateProtocolOwnedAccounts( + ctx sdk.Context, + legacyHostZone oldtypes.HostZone, + stakeibcHostZone stakeibctypes.HostZone, +) error { + // Transfer tokens from the staketia deposit account to the stakeibc deposit account + ctx.Logger().Info("Migrating the deposit account...") + staketiaDepositAddress, err := sdk.AccAddressFromBech32(legacyHostZone.DepositAddress) + if err != nil { + return errorsmod.Wrapf(err, "invalid staketia deposit address") + } + stakeibcDepositAddress, err := sdk.AccAddressFromBech32(stakeibcHostZone.DepositAddress) + if err != nil { + return errorsmod.Wrapf(err, "invalid stakeibc deposit address") + } + + depositBalance := k.bankKeeper.GetBalance(ctx, staketiaDepositAddress, legacyHostZone.NativeTokenIbcDenom) + err = k.bankKeeper.SendCoins(ctx, staketiaDepositAddress, stakeibcDepositAddress, sdk.NewCoins(depositBalance)) + if err != nil { + return errorsmod.Wrapf(err, "unable to transfer deposit accounts") + } + + // Add that deposit amount to the new stakeibc deposit record (in status TRANSFER_QUEUE) + celestiaDepositRecords := []recordtypes.DepositRecord{} + for _, depositRecord := range k.recordsKeeper.GetAllDepositRecord(ctx) { + if depositRecord.HostZoneId == types.CelestiaChainId { + celestiaDepositRecords = append(celestiaDepositRecords, depositRecord) + } + } + + if len(celestiaDepositRecords) != 1 || celestiaDepositRecords[0].Status != recordtypes.DepositRecord_TRANSFER_QUEUE { + return errors.New("there should only be one celestia deposit record in status TRANSFER_QUEUE") + } + + depositRecord := celestiaDepositRecords[0] + depositRecord.Amount = depositBalance.Amount + k.recordsKeeper.SetDepositRecord(ctx, depositRecord) + + // Transfer tokens from the staketia fee account to the stakeibc reward collector + ctx.Logger().Info("Migrating the fee account...") + staketiaFeeAddress := k.accountKeeper.GetModuleAddress(types.FeeAddress) + stakeibcFeeAddress := stakeibctypes.RewardCollectorName + + feesBalance := k.bankKeeper.GetBalance(ctx, staketiaFeeAddress, legacyHostZone.NativeTokenIbcDenom) + if feesBalance.IsZero() { + ctx.Logger().Info("No fees to migrate") + return nil + } + + err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, staketiaFeeAddress, stakeibcFeeAddress, sdk.NewCoins(feesBalance)) + if err != nil { + return errorsmod.Wrapf(err, "unable to transfer fee accounts") + } + + return nil +} + +// Initiates the migration to stakeibc by registering the host zone +// and transferring funds to the new stakeibc accounts +// This will be called from the upgrade handler +func InitiateMigration(k Keeper, ctx sdk.Context) error { + ctx.Logger().Info("Initiating staketia to stakeibc migration...") + + // Deserialize the staketia host zone with the old types (to recover the redemption rates) + legacyHostZone, err := k.GetLegacyHostZone(ctx) + if err != nil { + return err + } + + registerMsg := stakeibctypes.MsgRegisterHostZone{ + ConnectionId: types.CelestiaConnectionId, + Bech32Prefix: types.CelestiaBechPrefix, + HostDenom: legacyHostZone.NativeTokenDenom, + IbcDenom: legacyHostZone.NativeTokenIbcDenom, + TransferChannelId: legacyHostZone.TransferChannelId, + UnbondingPeriod: types.CelestiaUnbondingPeriodDays, + MinRedemptionRate: legacyHostZone.MinRedemptionRate, + MaxRedemptionRate: legacyHostZone.MaxRedemptionRate, + LsmLiquidStakeEnabled: false, + CommunityPoolTreasuryAddress: "", + MaxMessagesPerIcaTx: 32, + } + + ctx.Logger().Info("Registering the stakeibc host zone...") + if _, err := k.stakeibcKeeper.RegisterHostZone(ctx, ®isterMsg); err != nil { + return errorsmod.Wrapf(err, "unable to register host zone with stakeibc") + } + + ctx.Logger().Info("Updating the stakeibc host zone...") + stakeibcHostZone, err := k.UpdateStakeibcHostZone(ctx, legacyHostZone) + if err != nil { + return errorsmod.Wrapf(err, "unable to update the new stakeibc host zone") + } + + ctx.Logger().Info("Migrating protocol owned accounts...") + if err := k.MigrateProtocolOwnedAccounts(ctx, legacyHostZone, stakeibcHostZone); err != nil { + return errorsmod.Wrapf(err, "unable to migrate protocol owned accounts") + } + + ctx.Logger().Info("Done with staketia migration") + return nil +} diff --git a/x/staketia/keeper/migration_test.go b/x/staketia/keeper/migration_test.go new file mode 100644 index 000000000..0fe8baa27 --- /dev/null +++ b/x/staketia/keeper/migration_test.go @@ -0,0 +1,208 @@ +package keeper_test + +import ( + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + ibctesting "github.com/cosmos/ibc-go/v7/testing" + + epochtypes "github.com/Stride-Labs/stride/v22/x/epochs/types" + recordtypes "github.com/Stride-Labs/stride/v22/x/records/types" + stakeibctypes "github.com/Stride-Labs/stride/v22/x/stakeibc/types" + "github.com/Stride-Labs/stride/v22/x/staketia/keeper" + oldtypes "github.com/Stride-Labs/stride/v22/x/staketia/legacytypes" + "github.com/Stride-Labs/stride/v22/x/staketia/types" +) + +func (s *KeeperTestSuite) TestUpdateStakeibcHostZone() { + // Create deposit records with amounts 100 and 200 respectively + delegationRecords := []types.DelegationRecord{ + {Id: 1, Status: types.TRANSFER_IN_PROGRESS, NativeAmount: sdk.NewInt(100)}, + {Id: 2, Status: types.DELEGATION_QUEUE, NativeAmount: sdk.NewInt(200)}, + } + for _, delegationRecord := range delegationRecords { + s.App.StaketiaKeeper.SetDelegationRecord(s.Ctx, delegationRecord) + } + + // Create a host zone with a delegated balance of 1000 + redemptionRate := sdk.NewDec(2) + legacyHostZone := oldtypes.HostZone{ + RedemptionRate: redemptionRate, + DelegatedBalance: sdk.NewInt(1_000), + } + stakeibcHostZone := stakeibctypes.HostZone{ + ChainId: types.CelestiaChainId, + } + s.App.StaketiaKeeper.SetLegacyHostZone(s.Ctx, legacyHostZone) + s.App.StakeibcKeeper.SetHostZone(s.Ctx, stakeibcHostZone) + + // The expected stakeibc host zone should have total delegations + // equal to 1000 + 100 + 200 = 1300 + expectedStakeibcTotalDelegations := sdkmath.NewInt(1_000 + 100 + 200) + + // Call the update host zone function and confirm against expectations + actualStakeibcHostZone, err := s.App.StaketiaKeeper.UpdateStakeibcHostZone(s.Ctx, legacyHostZone) + s.Require().NoError(err, "no error expected when updating host zone") + + s.Require().Equal(types.CelestiaChainId, actualStakeibcHostZone.ChainId, "chain ID") + s.Require().Equal(expectedStakeibcTotalDelegations, actualStakeibcHostZone.TotalDelegations, "total delegations") + s.Require().Equal(redemptionRate, actualStakeibcHostZone.RedemptionRate, "redemption rate") + + // Remove the host zone and try again, it should fail + s.App.StakeibcKeeper.RemoveHostZone(s.Ctx, types.CelestiaChainId) + _, err = s.App.StaketiaKeeper.UpdateStakeibcHostZone(s.Ctx, legacyHostZone) + s.Require().ErrorContains(err, "celestia host zone not found") +} + +func (s *KeeperTestSuite) TestMigrateProtocolOwnedAccounts() { + // Create deposit accounts across both modules + staketiaDepositAccount := s.TestAccs[0] + stakeibcDepositAccount := s.TestAccs[1] + + // Get the respective fee module accounts for both modules + staketiaFeeModuleName := types.FeeAddress + stakeibcFeeAddress := s.App.AccountKeeper.GetModuleAddress(stakeibctypes.RewardCollectorName) + + // Set the addresses on the respective host zones + staketiaHostZone := oldtypes.HostZone{ + DepositAddress: staketiaDepositAccount.String(), + NativeTokenIbcDenom: HostIBCDenom, + } + stakeibcHostZone := stakeibctypes.HostZone{ + ChainId: types.CelestiaChainId, + DepositAddress: stakeibcDepositAccount.String(), + } + + // Create a deposit record that will be modified + s.App.RecordsKeeper.SetDepositRecord(s.Ctx, recordtypes.DepositRecord{ + Id: 1, + Amount: sdkmath.ZeroInt(), + HostZoneId: types.CelestiaChainId, + }) + + // Fund the deposit and fee account on staketia + denom := staketiaHostZone.NativeTokenIbcDenom + expectedDepositBalance := sdkmath.NewInt(1000) + expectedFeeBalance := sdkmath.NewInt(2000) + + s.FundAccount(staketiaDepositAccount, sdk.NewCoin(denom, expectedDepositBalance)) + s.FundModuleAccount(staketiaFeeModuleName, sdk.NewCoin(denom, expectedFeeBalance)) + + // Call the migration function to transfer to stakeibc + err := s.App.StaketiaKeeper.MigrateProtocolOwnedAccounts(s.Ctx, staketiaHostZone, stakeibcHostZone) + s.Require().NoError(err, "no error expected when migrating accounts") + + // Check that the stakeibc accounts are now funded + actualDepositBalance := s.App.BankKeeper.GetBalance(s.Ctx, stakeibcDepositAccount, denom) + s.Require().Equal(expectedDepositBalance.Int64(), actualDepositBalance.Amount.Int64(), "deposit balance") + + actualFeeBalance := s.App.BankKeeper.GetBalance(s.Ctx, stakeibcFeeAddress, denom) + s.Require().Equal(expectedFeeBalance.Int64(), actualFeeBalance.Amount.Int64(), "fee balance") + + // Confirm that the deposit record was incremented + depositRecords := s.App.RecordsKeeper.GetAllDepositRecord(s.Ctx) + s.Require().Len(depositRecords, 1, "deposit record should have been created") + s.Require().Equal(expectedDepositBalance.Int64(), depositRecords[0].Amount.Int64(), "deposit record") + + // Create a second deposit record and try to call the migration again, it should fail + s.App.RecordsKeeper.SetDepositRecord(s.Ctx, recordtypes.DepositRecord{ + Id: 2, + HostZoneId: types.CelestiaChainId, + }) + err = s.App.StaketiaKeeper.MigrateProtocolOwnedAccounts(s.Ctx, staketiaHostZone, stakeibcHostZone) + s.Require().ErrorContains(err, "there should only be one celestia deposit record") +} + +func (s *KeeperTestSuite) TestInitiateMigration() { + // Create a transfer channel (which will create a connection) + s.CreateTransferChannel(types.CelestiaChainId) + + staketiaDepositAccount := s.TestAccs[0] + staketiaFeeModuleName := types.FeeAddress + + // Fund the staketia deposit and fee accounts + depositBalance := sdkmath.NewInt(1000) + feeBalance := sdkmath.NewInt(2000) + s.FundAccount(staketiaDepositAccount, sdk.NewCoin(HostIBCDenom, depositBalance)) + s.FundModuleAccount(staketiaFeeModuleName, sdk.NewCoin(HostIBCDenom, feeBalance)) + + // Store the legacy host zone + legacyHostZone := oldtypes.HostZone{ + ChainId: types.CelestiaChainId, + DepositAddress: staketiaDepositAccount.String(), + NativeTokenDenom: HostNativeDenom, + NativeTokenIbcDenom: HostIBCDenom, + TransferChannelId: ibctesting.FirstChannelID, + MinRedemptionRate: sdk.MustNewDecFromStr("0.90"), + MaxRedemptionRate: sdk.MustNewDecFromStr("1.5"), + RedemptionRate: sdk.MustNewDecFromStr("1.2"), + DelegatedBalance: sdk.NewInt(1000), + } + s.App.StaketiaKeeper.SetLegacyHostZone(s.Ctx, legacyHostZone) + + // Create a delegation record that will be used in the delegated balance migration + delegationRecord := types.DelegationRecord{ + Id: 1, + Status: types.DELEGATION_QUEUE, + NativeAmount: sdk.NewInt(100), + } + s.App.StaketiaKeeper.SetDelegationRecord(s.Ctx, delegationRecord) + expectedTotalDelegations := legacyHostZone.DelegatedBalance.Add(delegationRecord.NativeAmount) + + // Create epoch trackers and EURs which are needed for the stakeibc registration + s.App.StakeibcKeeper.SetEpochTracker(s.Ctx, stakeibctypes.EpochTracker{ + EpochIdentifier: epochtypes.DAY_EPOCH, + EpochNumber: uint64(1), + }) + s.App.StakeibcKeeper.SetEpochTracker(s.Ctx, stakeibctypes.EpochTracker{ + EpochIdentifier: epochtypes.STRIDE_EPOCH, + EpochNumber: uint64(1), + }) + epochUnbondingRecord := recordtypes.EpochUnbondingRecord{ + EpochNumber: uint64(1), + HostZoneUnbondings: []*recordtypes.HostZoneUnbonding{}, + } + s.App.RecordsKeeper.SetEpochUnbondingRecord(s.Ctx, epochUnbondingRecord) + + // Call the migration function to register with stakeibc + // Before we call it, temporarily update the variable to be connection-0 to match the above + // and then set it back after the function call for other tests that use it + mainnetConnectionId := types.CelestiaConnectionId + types.CelestiaConnectionId = ibctesting.FirstConnectionID + + err := keeper.InitiateMigration(s.App.StaketiaKeeper, s.Ctx) + types.CelestiaConnectionId = mainnetConnectionId + s.Require().NoError(err, "no error expected during migration") + + // Confirm the new host zone + hostZone, found := s.App.StakeibcKeeper.GetHostZone(s.Ctx, types.CelestiaChainId) + s.Require().True(found, "stakeibc host zone should have been created") + + s.Require().Equal(legacyHostZone.TransferChannelId, hostZone.TransferChannelId, "transfer channel ID") + s.Require().Equal(legacyHostZone.NativeTokenDenom, hostZone.HostDenom, "native denom") + s.Require().Equal(legacyHostZone.NativeTokenIbcDenom, hostZone.IbcDenom, "ibc denom") + + s.Require().Equal(legacyHostZone.RedemptionRate, hostZone.RedemptionRate, "redemption rate") + s.Require().Equal(legacyHostZone.MinRedemptionRate, hostZone.MinRedemptionRate, "min redemption rate") + s.Require().Equal(legacyHostZone.MaxRedemptionRate, hostZone.MaxRedemptionRate, "max redemption rate") + + s.Require().Equal(ibctesting.FirstConnectionID, hostZone.ConnectionId, "connection ID") + s.Require().Equal(types.CelestiaBechPrefix, hostZone.Bech32Prefix, "bech prefix") + s.Require().Equal(uint64(types.CelestiaUnbondingPeriodDays), hostZone.UnbondingPeriod, "unbonding period") + + s.Require().False(hostZone.RedemptionsEnabled, "redemptions enabled") + s.Require().Equal(expectedTotalDelegations, hostZone.TotalDelegations, "total delegations") + + // Confirm balances were transferred + stakeibcDepositAccount := sdk.MustAccAddressFromBech32(hostZone.DepositAddress) + actualDepositBalance := s.App.BankKeeper.GetBalance(s.Ctx, stakeibcDepositAccount, HostIBCDenom) + s.Require().Equal(depositBalance.Int64(), actualDepositBalance.Amount.Int64(), "deposit balance transfer") + + stakeibcFeeAddress := s.App.AccountKeeper.GetModuleAddress(stakeibctypes.RewardCollectorName) + actualFeeBalance := s.App.BankKeeper.GetBalance(s.Ctx, stakeibcFeeAddress, HostIBCDenom) + s.Require().Equal(feeBalance.Int64(), actualFeeBalance.Amount.Int64(), "fee balance transfer") + + // Confirm a deposit record was created with the deposit amount + depositRecords := s.App.RecordsKeeper.GetAllDepositRecord(s.Ctx) + s.Require().Len(depositRecords, 1, "deposit record should have been created") + s.Require().Equal(depositBalance.Int64(), depositRecords[0].Amount.Int64(), "deposit record") +} diff --git a/x/staketia/keeper/msg_server.go b/x/staketia/keeper/msg_server.go index d5be19939..a68c3897e 100644 --- a/x/staketia/keeper/msg_server.go +++ b/x/staketia/keeper/msg_server.go @@ -2,12 +2,10 @@ package keeper import ( "context" + "errors" - errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/Stride-Labs/stride/v22/utils" - "github.com/Stride-Labs/stride/v22/x/staketia/types" ) @@ -24,19 +22,14 @@ func NewMsgServerImpl(keeper Keeper) types.MsgServer { var _ types.MsgServer = msgServer{} // User transaction to liquid stake native tokens into stTokens -func (k msgServer) LiquidStake(goCtx context.Context, msg *types.MsgLiquidStake) (*types.MsgLiquidStakeResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - stToken, err := k.Keeper.LiquidStake(ctx, msg.Staker, msg.NativeAmount) - if err != nil { - return nil, err - } - return &types.MsgLiquidStakeResponse{StToken: stToken}, nil +func (k msgServer) LiquidStake(goCtx context.Context, msg *types.MsgLiquidStake) (*types.MsgLiquidStakeResponse, error) { //nolint:staticcheck + return nil, errors.New("Liquid staking is no longer enabled in staketia") } // User transaction to redeem stake stTokens into native tokens func (k msgServer) RedeemStake(goCtx context.Context, msg *types.MsgRedeemStake) (*types.MsgRedeemStakeResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - nativeToken, err := k.Keeper.RedeemStake(ctx, msg.Redeemer, msg.StTokenAmount) + nativeToken, err := k.Keeper.RedeemStake(ctx, msg.Redeemer, msg.Receiver, msg.StTokenAmount) if err != nil { return nil, err } @@ -111,10 +104,10 @@ func (k msgServer) AdjustDelegatedBalance(goCtx context.Context, msg *types.MsgA if err != nil { return nil, err } - hostZone.DelegatedBalance = hostZone.DelegatedBalance.Add(msg.DelegationOffset) + hostZone.RemainingDelegatedBalance = hostZone.RemainingDelegatedBalance.Add(msg.DelegationOffset) // safety check that this will not cause the delegated balance to be negative - if hostZone.DelegatedBalance.IsNegative() { + if hostZone.RemainingDelegatedBalance.IsNegative() { return nil, types.ErrNegativeNotAllowed.Wrapf("offset would cause the delegated balance to be negative") } k.SetHostZone(ctx, hostZone) @@ -134,71 +127,14 @@ func (k msgServer) AdjustDelegatedBalance(goCtx context.Context, msg *types.MsgA // Adjusts the inner redemption rate bounds on the host zone func (k msgServer) UpdateInnerRedemptionRateBounds(goCtx context.Context, msg *types.MsgUpdateInnerRedemptionRateBounds) (*types.MsgUpdateInnerRedemptionRateBoundsResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - - // gate this transaction to the BOUNDS address - if err := utils.ValidateAdminAddress(msg.Creator); err != nil { - return nil, types.ErrInvalidAdmin - } - - // Fetch the zone - zone, err := k.GetHostZone(ctx) - if err != nil { - return nil, err - } - - // Get the outer bounds - maxOuterBound := zone.MaxRedemptionRate - minOuterBound := zone.MinRedemptionRate - - // Confirm the inner bounds are within the outer bounds - maxInnerBound := msg.MaxInnerRedemptionRate - minInnerBound := msg.MinInnerRedemptionRate - if maxInnerBound.GT(maxOuterBound) { - return nil, types.ErrInvalidRedemptionRateBounds - } - if minInnerBound.LT(minOuterBound) { - return nil, types.ErrInvalidRedemptionRateBounds - } - - // Set the inner bounds on the host zone - zone.MinInnerRedemptionRate = minInnerBound - zone.MaxInnerRedemptionRate = maxInnerBound - - // Update the host zone - k.SetHostZone(ctx, zone) - + _ = sdk.UnwrapSDKContext(goCtx) return &types.MsgUpdateInnerRedemptionRateBoundsResponse{}, nil } // Unhalts the host zone if redemption rates were exceeded // BOUNDS: verified in ValidateBasic func (k msgServer) ResumeHostZone(goCtx context.Context, msg *types.MsgResumeHostZone) (*types.MsgResumeHostZoneResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - - // gate this transaction to the BOUNDS address - if err := utils.ValidateAdminAddress(msg.Creator); err != nil { - return nil, types.ErrInvalidAdmin - } - - // Note: of course we don't want to fail this if the zone is halted! - zone, err := k.GetHostZone(ctx) - if err != nil { - return nil, err - } - - // Check the zone is halted - if !zone.Halted { - return nil, errorsmod.Wrapf(types.ErrHostZoneNotHalted, "zone is not halted") - } - - stDenom := utils.StAssetDenomFromHostZoneDenom(zone.NativeTokenDenom) - k.ratelimitKeeper.RemoveDenomFromBlacklist(ctx, stDenom) - - // Resume zone - zone.Halted = false - k.SetHostZone(ctx, zone) - + _ = sdk.UnwrapSDKContext(goCtx) return &types.MsgResumeHostZoneResponse{}, nil } @@ -211,9 +147,7 @@ func (k msgServer) RefreshRedemptionRate(goCtx context.Context, msgTriggerRedemp return nil, err } - err := k.UpdateRedemptionRate(ctx) - - return &types.MsgRefreshRedemptionRateResponse{}, err + return &types.MsgRefreshRedemptionRateResponse{}, nil } // overwrite a delegation record diff --git a/x/staketia/keeper/msg_server_test.go b/x/staketia/keeper/msg_server_test.go index 699f36b46..2a7c380a3 100644 --- a/x/staketia/keeper/msg_server_test.go +++ b/x/staketia/keeper/msg_server_test.go @@ -4,35 +4,9 @@ import ( sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/Stride-Labs/stride/v22/app/apptesting" "github.com/Stride-Labs/stride/v22/x/staketia/types" ) -// ---------------------------------------------- -// MsgLiquidStake -// ---------------------------------------------- - -// More granular testing of liquid stake is done in the keeper function -// This just tests the msg server wrapper -func (s *KeeperTestSuite) TestMsgServerLiquidStake() { - tc := s.DefaultSetupTestLiquidStake() - - // Attempt a successful liquid stake - validMsg := types.MsgLiquidStake{ - Staker: tc.stakerAddress.String(), - NativeAmount: tc.liquidStakeAmount, - } - resp, err := s.GetMsgServer().LiquidStake(sdk.UnwrapSDKContext(s.Ctx), &validMsg) - s.Require().NoError(err, "no error expected during liquid stake") - s.Require().Equal(tc.expectedStAmount.Int64(), resp.StToken.Amount.Int64(), "stToken amount") - - s.ConfirmLiquidStakeTokenTransfer(tc) - - // Attempt a liquid stake again, it should fail now that the staker is out of funds - _, err = s.GetMsgServer().LiquidStake(sdk.UnwrapSDKContext(s.Ctx), &validMsg) - s.Require().ErrorContains(err, "insufficient funds") -} - // ---------------------------------------------- // MsgConfirmDelegation // ---------------------------------------------- @@ -184,8 +158,8 @@ func (s *KeeperTestSuite) TestAdjustDelegatedBalance() { // Create the host zone s.App.StaketiaKeeper.SetHostZone(s.Ctx, types.HostZone{ - SafeAddressOnStride: safeAddress, - DelegatedBalance: sdk.NewInt(0), + SafeAddressOnStride: safeAddress, + RemainingDelegatedBalance: sdk.NewInt(0), }) // we're halting the zone to test that the tx works even when the host zone is halted @@ -213,7 +187,7 @@ func (s *KeeperTestSuite) TestAdjustDelegatedBalance() { s.Require().NoError(err, "no error expected when adjusting delegated bal properly for %s", tc.address) hostZone := s.MustGetHostZone() - s.Require().Equal(tc.endDelegation, hostZone.DelegatedBalance, "delegation after change for %s", tc.address) + s.Require().Equal(tc.endDelegation, hostZone.RemainingDelegatedBalance, "delegation after change for %s", tc.address) } // Attempt to call it with an amount that would make it negative, it should fail @@ -233,190 +207,6 @@ func (s *KeeperTestSuite) TestAdjustDelegatedBalance() { s.App.StaketiaKeeper.RemoveHostZone(s.Ctx) _, err = s.GetMsgServer().AdjustDelegatedBalance(s.Ctx, &types.MsgAdjustDelegatedBalance{}) s.Require().ErrorContains(err, "host zone not found") - -} - -// ---------------------------------------------- -// MsgUpdateInnerRedemptionRateBounds -// ---------------------------------------------- - -func (s *KeeperTestSuite) TestUpdateInnerRedemptionRateBounds() { - adminAddress, ok := apptesting.GetAdminAddress() - s.Require().True(ok) - - // Register a host zone - zone := types.HostZone{ - ChainId: HostChainId, - // Upper bound 1.5 - MaxRedemptionRate: sdk.NewDec(3).Quo(sdk.NewDec(2)), - // Lower bound 0.9 - MinRedemptionRate: sdk.NewDec(9).Quo(sdk.NewDec(10)), - } - - s.App.StaketiaKeeper.SetHostZone(s.Ctx, zone) - // we're halting the zone to test that the tx works even when the host zone is halted - s.App.StaketiaKeeper.HaltZone(s.Ctx) - - initialMsg := types.MsgUpdateInnerRedemptionRateBounds{ - Creator: adminAddress, - MinInnerRedemptionRate: sdk.NewDec(90).Quo(sdk.NewDec(100)), - MaxInnerRedemptionRate: sdk.NewDec(105).Quo(sdk.NewDec(100)), - } - - updateMsg := types.MsgUpdateInnerRedemptionRateBounds{ - Creator: adminAddress, - MinInnerRedemptionRate: sdk.NewDec(95).Quo(sdk.NewDec(100)), - MaxInnerRedemptionRate: sdk.NewDec(11).Quo(sdk.NewDec(10)), - } - - invalidMsg := types.MsgUpdateInnerRedemptionRateBounds{ - Creator: adminAddress, - MinInnerRedemptionRate: sdk.NewDec(0), - MaxInnerRedemptionRate: sdk.NewDec(2), - } - - nonAdminMsg := types.MsgUpdateInnerRedemptionRateBounds{ - Creator: "non-admin", - MinInnerRedemptionRate: sdk.NewDec(0), - MaxInnerRedemptionRate: sdk.NewDec(2), - } - - // Set the inner bounds on the host zone for the first time - _, err := s.GetMsgServer().UpdateInnerRedemptionRateBounds(s.Ctx, &initialMsg) - s.Require().NoError(err, "should not throw an error") - - // Confirm the inner bounds were set - zone = s.MustGetHostZone() - s.Require().Equal(initialMsg.MinInnerRedemptionRate, zone.MinInnerRedemptionRate, "min inner redemption rate should be set") - s.Require().Equal(initialMsg.MaxInnerRedemptionRate, zone.MaxInnerRedemptionRate, "max inner redemption rate should be set") - - // Update the inner bounds on the host zone - _, err = s.GetMsgServer().UpdateInnerRedemptionRateBounds(s.Ctx, &updateMsg) - s.Require().NoError(err, "should not throw an error") - - // Confirm the inner bounds were set - zone = s.MustGetHostZone() - s.Require().Equal(updateMsg.MinInnerRedemptionRate, zone.MinInnerRedemptionRate, "min inner redemption rate should be set") - s.Require().Equal(updateMsg.MaxInnerRedemptionRate, zone.MaxInnerRedemptionRate, "max inner redemption rate should be set") - - // Set the inner bounds on the host zone for the first time - _, err = s.GetMsgServer().UpdateInnerRedemptionRateBounds(s.Ctx, &invalidMsg) - s.Require().ErrorContains(err, "invalid host zone redemption rate inner bounds") - - // Attempt to update bounds with a non-admin address, it should fail - _, err = s.GetMsgServer().UpdateInnerRedemptionRateBounds(s.Ctx, &nonAdminMsg) - s.Require().ErrorContains(err, "signer is not an admin") -} - -// ---------------------------------------------- -// MsgResumeHostZone -// ---------------------------------------------- - -// Test cases -// - Zone is not halted -// - Zone is halted - unhalt it -func (s *KeeperTestSuite) TestResumeHostZone() { - // TODO [sttia]: verify denom blacklisting removal works - - adminAddress, ok := apptesting.GetAdminAddress() - s.Require().True(ok) - - zone := types.HostZone{ - ChainId: HostChainId, - RedemptionRate: sdk.NewDec(1), - Halted: false, - NativeTokenDenom: HostNativeDenom, - } - s.App.StaketiaKeeper.SetHostZone(s.Ctx, zone) - - msg := types.MsgResumeHostZone{ - Creator: adminAddress, - } - - // TEST 1: Zone is not halted - // Try to unhalt the unhalted zone - _, err := s.GetMsgServer().ResumeHostZone(s.Ctx, &msg) - s.Require().ErrorContains(err, "zone is not halted") - - // Verify the denom is not in the blacklist - blacklist := s.App.RatelimitKeeper.GetAllBlacklistedDenoms(s.Ctx) - s.Require().NotContains(blacklist, StDenom, "denom should not be blacklisted") - - // Confirm the zone is not halted - zone, err = s.App.StaketiaKeeper.GetHostZone(s.Ctx) - s.Require().NoError(err, "should not throw an error") - s.Require().False(zone.Halted, "zone should not be halted") - - // TEST 2: Zone is halted - // Halt the zone - s.App.StaketiaKeeper.HaltZone(s.Ctx) - - // Verify the denom is in the blacklist - blacklist = s.App.RatelimitKeeper.GetAllBlacklistedDenoms(s.Ctx) - s.Require().Contains(blacklist, StDenom, "denom should be blacklisted") - - // Try to unhalt the halted zone - _, err = s.GetMsgServer().ResumeHostZone(s.Ctx, &msg) - s.Require().NoError(err, "should not throw an error") - - // Confirm the zone is not halted - zone, err = s.App.StaketiaKeeper.GetHostZone(s.Ctx) - s.Require().NoError(err, "should not throw an error") - s.Require().False(zone.Halted, "zone should not be halted") - - // Verify the denom is not in the blacklist - blacklist = s.App.RatelimitKeeper.GetAllBlacklistedDenoms(s.Ctx) - s.Require().NotContains(blacklist, StDenom, "denom should not be blacklisted") - - // Attempt to resume with a non-admin address, it should fail - _, err = s.GetMsgServer().ResumeHostZone(s.Ctx, &types.MsgResumeHostZone{ - Creator: "non-admin", - }) - s.Require().ErrorContains(err, "signer is not an admin") -} - -// ---------------------------------------------- -// MsgRefreshRedemptionRate -// ---------------------------------------------- - -func (s *KeeperTestSuite) TestRefreshRedemptionRate() { - safeAddress := "safe" - depositAddress := s.TestAccs[0] - redemptionAddress := s.TestAccs[1] - - // Create host zone with initial redemption rate of 1 - // There will be 1000 delegated tokens, and 500 stTokens - // implying an updated redemption rate of 2 - initialRedemptionRate := sdk.OneDec() - expectedRedemptionRate := sdk.NewDec(2) - - s.App.StaketiaKeeper.SetHostZone(s.Ctx, types.HostZone{ - DelegatedBalance: sdkmath.NewInt(1000), - RedemptionRate: initialRedemptionRate, - NativeTokenDenom: HostNativeDenom, - NativeTokenIbcDenom: HostIBCDenom, - SafeAddressOnStride: safeAddress, - DepositAddress: depositAddress.String(), - }) - - // Mint 500 stTokens (implying a redemption rate of 2) - s.FundAccount(redemptionAddress, sdk.NewCoin(StDenom, sdkmath.NewInt(500))) - - // Attempt to refresh the rate with a non-safe address, it should fail - _, err := s.GetMsgServer().RefreshRedemptionRate(s.Ctx, &types.MsgRefreshRedemptionRate{ - Creator: "non-admin", - }) - s.Require().ErrorContains(err, "signer is not an admin") - - // Attempt to refresh the rate with the safe address, it should succeed - _, err = s.GetMsgServer().RefreshRedemptionRate(s.Ctx, &types.MsgRefreshRedemptionRate{ - Creator: safeAddress, - }) - s.Require().NoError(err, "no error expected when using safe address") - - // Confirm the redemption rate was updated - hostZone := s.MustGetHostZone() - s.Require().Equal(expectedRedemptionRate, hostZone.RedemptionRate) } // ---------------------------------------------- diff --git a/x/staketia/keeper/redemption_rate.go b/x/staketia/keeper/redemption_rate.go index 2de6398dd..b93143138 100644 --- a/x/staketia/keeper/redemption_rate.go +++ b/x/staketia/keeper/redemption_rate.go @@ -1,101 +1,20 @@ package keeper import ( - "encoding/json" - "errors" - "fmt" - - errorsmod "cosmossdk.io/errors" - sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/Stride-Labs/stride/v22/utils" - icaoracletypes "github.com/Stride-Labs/stride/v22/x/icaoracle/types" "github.com/Stride-Labs/stride/v22/x/staketia/types" ) -// Updates the redemption rate for each host zone -// At a high level, the redemption rate is equal to the amount of native tokens locked divided by the stTokens in existence. -// The equation is broken down further into the following sub-components: -// -// Native Tokens Locked: -// 1. Deposit Account Balance: tokens deposited from liquid stakes, that are still living on Stride -// 2. Undelegated Balance: tokens that are ready to be staked -// (they're either currently in the delegation account or currently being transferred there) -// 3. Delegated Balance: Delegations on the host zone -// -// StToken Amount: -// 1. Total Supply of the stToken -// -// Redemption Rate = (Deposit Account Balance + Undelegated Balance + Delegated Balance) / (stToken Supply) -// -// Note: Reinvested tokens are sent to the deposit account and are automatically included in this formula -func (k Keeper) UpdateRedemptionRate(ctx sdk.Context) error { - k.Logger(ctx).Info(utils.LogWithHostZone(types.CelestiaChainId, "Updating redemption rate")) - - hostZone, err := k.GetHostZone(ctx) - if err != nil { - return err - } - - // Get the number of stTokens from the supply - stTokenSupply := k.bankKeeper.GetSupply(ctx, utils.StAssetDenomFromHostZoneDenom(hostZone.NativeTokenDenom)).Amount - if stTokenSupply.IsZero() { - k.Logger(ctx).Info(utils.LogWithHostZone(hostZone.ChainId, - "No st%s in circulation - redemption rate is unchanged", hostZone.NativeTokenDenom)) - return nil - } - - // Get the balance of the deposit address - depositAddress, err := sdk.AccAddressFromBech32(hostZone.DepositAddress) - if err != nil { - return errorsmod.Wrapf(err, "invalid deposit address") - } - depositAccountBalance := k.bankKeeper.GetBalance(ctx, depositAddress, hostZone.NativeTokenIbcDenom) - - // Then add that to the sum of the delegation records to get the undelegated balance - // Delegation records are only created once the tokens leave the deposit address - // and the record is deleted once the tokens are delegated - undelegatedBalance := sdkmath.ZeroInt() - for _, delegationRecord := range k.GetAllActiveDelegationRecords(ctx) { - undelegatedBalance = undelegatedBalance.Add(delegationRecord.NativeAmount) - } - - // Finally, calculated the redemption rate as the native tokens locked divided by the stTokens - nativeTokensLocked := depositAccountBalance.Amount.Add(undelegatedBalance).Add(hostZone.DelegatedBalance) - if !nativeTokensLocked.IsPositive() { - return errors.New("Non-zero stToken supply, yet the zero delegated and undelegated balance") - } - redemptionRate := sdk.NewDecFromInt(nativeTokensLocked).Quo(sdk.NewDecFromInt(stTokenSupply)) - - // Set the old and update redemption rate on the host - hostZone.LastRedemptionRate = hostZone.RedemptionRate - hostZone.RedemptionRate = redemptionRate - k.SetHostZone(ctx, hostZone) - - k.Logger(ctx).Info(utils.LogWithHostZone(types.CelestiaChainId, "Redemption rate updated from %v to %v", - hostZone.LastRedemptionRate, hostZone.RedemptionRate)) - k.Logger(ctx).Info(utils.LogWithHostZone(types.CelestiaChainId, - "Deposit Account Balance: %v, Undelegated Balance: %v, Delegated Balance: %v, StToken Supply: %v", - depositAccountBalance.Amount, undelegatedBalance, hostZone.DelegatedBalance, stTokenSupply)) - - return nil -} - // Checks whether the redemption rate has exceeded the inner or outer safety bounds // and returns an error if so func (k Keeper) CheckRedemptionRateExceedsBounds(ctx sdk.Context) error { - hostZone, err := k.GetHostZone(ctx) - if err != nil { - return err + hostZone, found := k.stakeibcKeeper.GetHostZone(ctx, types.CelestiaChainId) + if !found { + return types.ErrHostZoneNotFound } redemptionRate := hostZone.RedemptionRate - // Validate the safety bounds (e.g. that the inner is inside the outer) - if err := hostZone.ValidateRedemptionRateBoundsInitalized(); err != nil { - return err - } - // Check if the redemption rate is outside the outer bounds if redemptionRate.LT(hostZone.MinRedemptionRate) || redemptionRate.GT(hostZone.MaxRedemptionRate) { return types.ErrRedemptionRateOutsideSafetyBounds.Wrapf("redemption rate outside outer safety bounds") @@ -108,32 +27,3 @@ func (k Keeper) CheckRedemptionRateExceedsBounds(ctx sdk.Context) error { return nil } - -// Pushes a redemption rate update to the ICA oracle -func (k Keeper) PostRedemptionRateToOracles(ctx sdk.Context) error { - if err := k.CheckRedemptionRateExceedsBounds(ctx); err != nil { - return errorsmod.Wrapf(err, "preventing oracle update since redemption rate exceeded bounds") - } - - hostZone, err := k.GetHostZone(ctx) - if err != nil { - return err - } - redemptionRate := hostZone.RedemptionRate - - stDenom := utils.StAssetDenomFromHostZoneDenom(hostZone.NativeTokenDenom) - attributes, err := json.Marshal(icaoracletypes.RedemptionRateAttributes{ - SttokenDenom: stDenom, - }) - if err != nil { - return err - } - - // Metric Key is of format: {stToken}_redemption_rate - metricKey := fmt.Sprintf("%s_%s", stDenom, icaoracletypes.MetricType_RedemptionRate) - metricValue := redemptionRate.String() - metricType := icaoracletypes.MetricType_RedemptionRate - k.icaOracleKeeper.QueueMetricUpdate(ctx, metricKey, metricValue, metricType, string(attributes)) - - return nil -} diff --git a/x/staketia/keeper/redemption_rate_test.go b/x/staketia/keeper/redemption_rate_test.go index d4a771755..81c755196 100644 --- a/x/staketia/keeper/redemption_rate_test.go +++ b/x/staketia/keeper/redemption_rate_test.go @@ -1,171 +1,21 @@ package keeper_test import ( - "fmt" - - sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + stakeibctypes "github.com/Stride-Labs/stride/v22/x/stakeibc/types" "github.com/Stride-Labs/stride/v22/x/staketia/types" ) -func (s *KeeperTestSuite) TestUpdateRedemptionRate() { - depositAddress := s.TestAccs[0] - - testCases := []struct { - expectedRedemptionRate sdk.Dec - depositBalance sdkmath.Int - delegatedBalance sdkmath.Int - stTokenSupply sdkmath.Int - delegationRecords []types.DelegationRecord - }{ - { - // Deposit: 250, Undelegated: 500, Delegated: 250, StTokens: 1000 - // (250 + 500 + 250 / 1000) = 1000 / 1000 = 1.0 - expectedRedemptionRate: sdk.MustNewDecFromStr("1.0"), - depositBalance: sdkmath.NewInt(250), - delegatedBalance: sdkmath.NewInt(250), - delegationRecords: []types.DelegationRecord{ - {Id: 1, NativeAmount: sdkmath.NewInt(250), Status: types.TRANSFER_IN_PROGRESS}, - {Id: 2, NativeAmount: sdkmath.NewInt(250), Status: types.DELEGATION_QUEUE}, - }, - stTokenSupply: sdkmath.NewInt(1000), - }, - { - // Deposit: 500, Undelegated: 500, Delegated: 250, StTokens: 1000 - // (500 + 500 + 250 / 1000) = 1250 / 1000 = 1.25 - expectedRedemptionRate: sdk.MustNewDecFromStr("1.25"), - depositBalance: sdkmath.NewInt(500), - delegatedBalance: sdkmath.NewInt(250), - delegationRecords: []types.DelegationRecord{ - {Id: 1, NativeAmount: sdkmath.NewInt(250), Status: types.TRANSFER_IN_PROGRESS}, - {Id: 2, NativeAmount: sdkmath.NewInt(250), Status: types.DELEGATION_QUEUE}, - }, - stTokenSupply: sdkmath.NewInt(1000), - }, - { - // Deposit: 250, Undelegated: 500, Delegated: 500, StTokens: 1000 - // (500 + 500 + 250 / 1000) = 1250 / 1000 = 1.250 - expectedRedemptionRate: sdk.MustNewDecFromStr("1.25"), - depositBalance: sdkmath.NewInt(250), - delegatedBalance: sdkmath.NewInt(500), - delegationRecords: []types.DelegationRecord{ - {Id: 2, NativeAmount: sdkmath.NewInt(250), Status: types.TRANSFER_IN_PROGRESS}, - {Id: 3, NativeAmount: sdkmath.NewInt(250), Status: types.DELEGATION_QUEUE}, - }, - stTokenSupply: sdkmath.NewInt(1000), - }, - { - // Deposit: 250, Undelegated: 1000, Delegated: 250, StTokens: 1000 - // (250 + 1000 + 250 / 1000) = 1500 / 1000 = 1.5 - expectedRedemptionRate: sdk.MustNewDecFromStr("1.5"), - depositBalance: sdkmath.NewInt(250), - delegatedBalance: sdkmath.NewInt(250), - delegationRecords: []types.DelegationRecord{ - {Id: 1, NativeAmount: sdkmath.NewInt(250), Status: types.TRANSFER_IN_PROGRESS}, - {Id: 2, NativeAmount: sdkmath.NewInt(250), Status: types.DELEGATION_QUEUE}, - {Id: 4, NativeAmount: sdkmath.NewInt(250), Status: types.TRANSFER_IN_PROGRESS}, - {Id: 6, NativeAmount: sdkmath.NewInt(250), Status: types.DELEGATION_QUEUE}, - }, - stTokenSupply: sdkmath.NewInt(1000), - }, - { - // Deposit: 250, Undelegated: 500, Delegated: 250, StTokens: 2000 - // (250 + 500 + 250 / 2000) = 1000 / 2000 = 0.5 - expectedRedemptionRate: sdk.MustNewDecFromStr("0.5"), - depositBalance: sdkmath.NewInt(250), - delegatedBalance: sdkmath.NewInt(250), - delegationRecords: []types.DelegationRecord{ - {Id: 1, NativeAmount: sdkmath.NewInt(250), Status: types.TRANSFER_IN_PROGRESS}, - {Id: 2, NativeAmount: sdkmath.NewInt(250), Status: types.DELEGATION_QUEUE}, - }, - stTokenSupply: sdkmath.NewInt(2000), - }, - } - - for i, tc := range testCases { - s.Run(fmt.Sprintf("test-%d", i), func() { - s.SetupTest() // reset state - - // Fund the deposit balance - s.FundAccount(depositAddress, sdk.NewCoin(HostIBCDenom, tc.depositBalance)) - - // Create the host zone with the delegated balance and deposit address - initialRedemptionRate := sdk.MustNewDecFromStr("0.999") - s.App.StaketiaKeeper.SetHostZone(s.Ctx, types.HostZone{ - NativeTokenDenom: HostNativeDenom, - NativeTokenIbcDenom: HostIBCDenom, - DepositAddress: depositAddress.String(), - DelegatedBalance: tc.delegatedBalance, - RedemptionRate: initialRedemptionRate, - }) - - // Set each delegation record - for _, delegationRecord := range tc.delegationRecords { - s.App.StaketiaKeeper.SetDelegationRecord(s.Ctx, delegationRecord) - } - - // Add some archive delegation records that should be excluded - // We'll create these by first creating normal records and then removing them - for i := 0; i <= 5; i++ { - id := uint64(i * 1000) - s.App.StaketiaKeeper.SetArchivedDelegationRecord(s.Ctx, types.DelegationRecord{Id: id}) - } - - // Mint sttokens for the supply (fund account calls mint) - s.FundAccount(s.TestAccs[1], sdk.NewCoin(StDenom, tc.stTokenSupply)) - - // Update the redemption rate and check that it matches - err := s.App.StaketiaKeeper.UpdateRedemptionRate(s.Ctx) - s.Require().NoError(err, "no error expected when calculating redemption rate") - - hostZone := s.MustGetHostZone() - s.Require().Equal(tc.expectedRedemptionRate, hostZone.RedemptionRate, "redemption rate") - - // Check that the last redemption rate was set - s.Require().Equal(initialRedemptionRate, hostZone.LastRedemptionRate, "redemption rate") - }) - - } -} - -func (s *KeeperTestSuite) TestUpdateRedemptionRate_NoTokens() { - depositAddress := s.TestAccs[0] - - // Create the host zone with no delegated balance - s.App.StaketiaKeeper.SetHostZone(s.Ctx, types.HostZone{ - NativeTokenDenom: HostNativeDenom, - NativeTokenIbcDenom: HostIBCDenom, - DepositAddress: depositAddress.String(), - DelegatedBalance: sdkmath.ZeroInt(), - RedemptionRate: sdk.OneDec(), - }) - - // Check that the update funtion returns nil, since there are no stTokens - err := s.App.StaketiaKeeper.UpdateRedemptionRate(s.Ctx) - s.Require().NoError(err, "no error when there are no stTokens") - - // Check that the redemption rate was not updated - hostZone := s.MustGetHostZone() - s.Require().Equal(sdk.OneDec(), hostZone.RedemptionRate, "redemption rate should not have been updated") - - // Mint stTokens - s.FundAccount(s.TestAccs[1], sdk.NewCoin(StDenom, sdkmath.NewInt(1000))) - - // Try to update again, now it should error since there's stTokens but no native tokens - err = s.App.StaketiaKeeper.UpdateRedemptionRate(s.Ctx) - s.Require().ErrorContains(err, "Non-zero stToken supply, yet the zero delegated and undelegated balance") -} - func (s *KeeperTestSuite) TestCheckRedemptionRateExceedsBounds() { testCases := []struct { name string - hostZone types.HostZone + hostZone stakeibctypes.HostZone exceedsBounds bool }{ { name: "valid bounds", - hostZone: types.HostZone{ + hostZone: stakeibctypes.HostZone{ MinRedemptionRate: sdk.MustNewDecFromStr("0.8"), MinInnerRedemptionRate: sdk.MustNewDecFromStr("0.9"), RedemptionRate: sdk.MustNewDecFromStr("1.0"), // <-- @@ -176,7 +26,7 @@ func (s *KeeperTestSuite) TestCheckRedemptionRateExceedsBounds() { }, { name: "outside min inner", - hostZone: types.HostZone{ + hostZone: stakeibctypes.HostZone{ MinRedemptionRate: sdk.MustNewDecFromStr("0.8"), RedemptionRate: sdk.MustNewDecFromStr("0.9"), // <-- MinInnerRedemptionRate: sdk.MustNewDecFromStr("1.0"), @@ -187,7 +37,7 @@ func (s *KeeperTestSuite) TestCheckRedemptionRateExceedsBounds() { }, { name: "outside max inner", - hostZone: types.HostZone{ + hostZone: stakeibctypes.HostZone{ MinRedemptionRate: sdk.MustNewDecFromStr("0.8"), MinInnerRedemptionRate: sdk.MustNewDecFromStr("0.9"), MaxInnerRedemptionRate: sdk.MustNewDecFromStr("1.0"), @@ -198,7 +48,7 @@ func (s *KeeperTestSuite) TestCheckRedemptionRateExceedsBounds() { }, { name: "outside min outer", - hostZone: types.HostZone{ + hostZone: stakeibctypes.HostZone{ RedemptionRate: sdk.MustNewDecFromStr("0.8"), // <-- MinRedemptionRate: sdk.MustNewDecFromStr("0.9"), MinInnerRedemptionRate: sdk.MustNewDecFromStr("1.0"), @@ -209,7 +59,7 @@ func (s *KeeperTestSuite) TestCheckRedemptionRateExceedsBounds() { }, { name: "outside max outer", - hostZone: types.HostZone{ + hostZone: stakeibctypes.HostZone{ MinRedemptionRate: sdk.MustNewDecFromStr("0.8"), MinInnerRedemptionRate: sdk.MustNewDecFromStr("0.9"), MaxInnerRedemptionRate: sdk.MustNewDecFromStr("1.0"), @@ -222,7 +72,10 @@ func (s *KeeperTestSuite) TestCheckRedemptionRateExceedsBounds() { for _, tc := range testCases { s.Run(tc.name, func() { - s.App.StaketiaKeeper.SetHostZone(s.Ctx, tc.hostZone) + hostZone := tc.hostZone + hostZone.ChainId = types.CelestiaChainId + s.App.StakeibcKeeper.SetHostZone(s.Ctx, hostZone) + err := s.App.StaketiaKeeper.CheckRedemptionRateExceedsBounds(s.Ctx) if tc.exceedsBounds { s.Require().ErrorIs(err, types.ErrRedemptionRateOutsideSafetyBounds) diff --git a/x/staketia/keeper/unbonding.go b/x/staketia/keeper/unbonding.go index fb2359936..6f70ac7e9 100644 --- a/x/staketia/keeper/unbonding.go +++ b/x/staketia/keeper/unbonding.go @@ -10,13 +10,19 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/Stride-Labs/stride/v22/utils" + stakeibctypes "github.com/Stride-Labs/stride/v22/x/stakeibc/types" "github.com/Stride-Labs/stride/v22/x/staketia/types" ) // Takes custody of staked tokens in an escrow account, updates the current // accumulating UnbondingRecord with the amount taken, and creates or updates // the RedemptionRecord for this user -func (k Keeper) RedeemStake(ctx sdk.Context, redeemer string, stTokenAmount sdkmath.Int) (nativeToken sdk.Coin, err error) { +func (k Keeper) RedeemStake( + ctx sdk.Context, + redeemer string, + receiver string, + stTokenAmount sdkmath.Int, +) (nativeToken sdk.Coin, err error) { // Validate Basic already has ensured redeemer is legal address, stTokenAmount is above min threshold // Check HostZone exists, has legal redemption address for escrow, is not halted, has RR in bounds @@ -24,6 +30,16 @@ func (k Keeper) RedeemStake(ctx sdk.Context, redeemer string, stTokenAmount sdkm if err != nil { return nativeToken, err } + stakeibcHostZone, err := k.stakeibcKeeper.GetActiveHostZone(ctx, types.CelestiaChainId) + if err != nil { + return nativeToken, err + } + + // If the remaining delegated balance for staketia is 0, that means we've undelegated + // all the stake in the MS account and the redemptions should be switched over to stakeibc + if hostZone.RemainingDelegatedBalance.LTE(sdkmath.ZeroInt()) { + return nativeToken, types.ErrRedemptionsDisabled + } escrowAccount, err := sdk.AccAddressFromBech32(hostZone.RedemptionAddress) if err != nil { @@ -56,10 +72,36 @@ func (k Keeper) RedeemStake(ctx sdk.Context, redeemer string, stTokenAmount sdkm // Estimate a placeholder native amount with current RedemptionRate // this estimate will be updated when the Undelegation record is finalized - nativeAmount := sdk.NewDecFromInt(stTokenAmount).Mul(hostZone.RedemptionRate).TruncateInt() - if nativeAmount.GT(hostZone.DelegatedBalance) { + nativeAmount := sdk.NewDecFromInt(stTokenAmount).Mul(stakeibcHostZone.RedemptionRate).TruncateInt() + + // When checking if there's enough delegated TIA to handle the request, + // use the value from stakeibc instead of staketia + if nativeAmount.GT(stakeibcHostZone.TotalDelegations) { return nativeToken, errorsmod.Wrapf(types.ErrUnbondAmountToLarge, - "cannot unstake an amount g.t. total staked balance: %v > %v", nativeAmount, hostZone.DelegatedBalance) + "cannot unstake an amount g.t. total staked balance: %v > %v", nativeAmount, stakeibcHostZone.TotalDelegations) + } + + // Check if the requested unbonding is greater than what's in the multisig account + // If so, handle the spillover via stakeibc + if nativeAmount.GT(hostZone.RemainingDelegatedBalance) { + // First, eable redemptions in stakeibc + if err := k.stakeibcKeeper.EnableRedemptions(ctx, types.CelestiaChainId); err != nil { + return nativeToken, errorsmod.Wrapf(err, "unable to enable redemptions") + } + + // Then pass the spillover to stakeibc, returning the remaining amount back to be processed in staketia + nativeAmount, stTokenAmount, err = k.HandleRedemptionSpillover( + ctx, + redeemer, + receiver, + nativeAmount, + stTokenAmount, + hostZone.RemainingDelegatedBalance, + stakeibcHostZone.RedemptionRate, + ) + if err != nil { + return nativeToken, err + } } // Update the accumulating UnbondingRecord with the undelegation amounts @@ -98,17 +140,49 @@ func (k Keeper) RedeemStake(ctx sdk.Context, redeemer string, stTokenAmount sdkm return nativeToken, nil } +// Calls redeem stake for any requested redemption amount that exceeds what's in the staketia account +// Returns the updated native and stTokens amounts that should be used in staketia +func (k Keeper) HandleRedemptionSpillover( + ctx sdk.Context, + redeemer string, + receiver string, + requestedNativeAmount sdkmath.Int, + requestedStTokenAmount sdkmath.Int, + remainingDelegatedBalance sdkmath.Int, + redemptionRate sdk.Dec, +) (staketiaNativeAmount, staketiaStTokenAmount sdkmath.Int, err error) { + // Converts the spillover amount so that it's denominated in stTokens + stakeibcNativeAmount := requestedNativeAmount.Sub(remainingDelegatedBalance) + stakeibcStTokenAmount := sdk.NewDecFromInt(stakeibcNativeAmount).Quo(redemptionRate).TruncateInt() + + // Call stakeibc's redeem stake for the excess + stakeibcRedeemMessage := stakeibctypes.MsgRedeemStake{ + Creator: redeemer, + Amount: stakeibcStTokenAmount, + HostZone: types.CelestiaChainId, + Receiver: receiver, + } + if _, err = k.stakeibcKeeper.RedeemStake(ctx, &stakeibcRedeemMessage); err != nil { + return sdkmath.ZeroInt(), sdkmath.ZeroInt(), errorsmod.Wrapf(err, "unable to execute stakeibc redeem stake") + } + + // Return the updated staketia portion back to the staketia redeem stake + staketiaNativeAmount = requestedNativeAmount.Sub(stakeibcNativeAmount) + staketiaStTokenAmount = requestedStTokenAmount.Sub(stakeibcStTokenAmount) + return staketiaNativeAmount, staketiaStTokenAmount, nil +} + // Freezes the ACCUMULATING record by changing the status to UNBONDING_QUEUE // and updating the native token amounts on the unbonding and redemption records func (k Keeper) PrepareUndelegation(ctx sdk.Context, epochNumber uint64) error { k.Logger(ctx).Info(utils.LogWithHostZone(types.CelestiaChainId, "Preparing undelegation for epoch %d", epochNumber)) // Get the redemption record from the host zone (to calculate the native tokens) - hostZone, err := k.GetUnhaltedHostZone(ctx) + stakeibcHostZone, err := k.stakeibcKeeper.GetActiveHostZone(ctx, types.CelestiaChainId) if err != nil { return err } - redemptionRate := hostZone.RedemptionRate + redemptionRate := stakeibcHostZone.RedemptionRate // Get the one accumulating record that has the redemptions for the past epoch unbondingRecord, err := k.GetAccumulatingUnbondingRecord(ctx) @@ -176,19 +250,21 @@ func (k Keeper) ConfirmUndelegation(ctx sdk.Context, recordId uint64, txHash str } // Note: we're intentionally not checking that the host zone is halted, because we still want to process this tx in that case - hostZone, err := k.GetHostZone(ctx) + staketiaHostZone, err := k.GetHostZone(ctx) + if err != nil { + return err + } + stakeibcHostZone, err := k.stakeibcKeeper.GetActiveHostZone(ctx, types.CelestiaChainId) if err != nil { return err } // sanity check: store down the stToken supply and DelegatedBalance for checking against after burn - stDenom := utils.StAssetDenomFromHostZoneDenom(hostZone.NativeTokenDenom) - stTokenSupplyBefore := k.bankKeeper.GetSupply(ctx, stDenom).Amount - delegatedBalanceBefore := hostZone.DelegatedBalance + stDenom := utils.StAssetDenomFromHostZoneDenom(staketiaHostZone.NativeTokenDenom) // update the record's txhash, status, and unbonding completion time - unbondingLength := time.Duration(hostZone.UnbondingPeriodSeconds) * time.Second // 21 days - unbondingCompletionTime := uint64(ctx.BlockTime().Add(unbondingLength).Unix()) // now + 21 days + unbondingLength := time.Duration(staketiaHostZone.UnbondingPeriodSeconds) * time.Second // 21 days + unbondingCompletionTime := uint64(ctx.BlockTime().Add(unbondingLength).Unix()) // now + 21 days record.UndelegationTxHash = txHash record.Status = types.UNBONDING_IN_PROGRESS @@ -197,26 +273,21 @@ func (k Keeper) ConfirmUndelegation(ctx sdk.Context, recordId uint64, txHash str // update host zone struct's delegated balance amountAddedToDelegation := record.NativeAmount - newDelegatedBalance := hostZone.DelegatedBalance.Sub(amountAddedToDelegation) + newDelegatedBalance := stakeibcHostZone.TotalDelegations.Sub(amountAddedToDelegation) // sanity check: if the new balance is negative, throw an error if newDelegatedBalance.IsNegative() { return errorsmod.Wrapf(types.ErrNegativeNotAllowed, "host zone's delegated balance would be negative after undelegation") } - hostZone.DelegatedBalance = newDelegatedBalance - k.SetHostZone(ctx, hostZone) + stakeibcHostZone.TotalDelegations = newDelegatedBalance + k.stakeibcKeeper.SetHostZone(ctx, stakeibcHostZone) // burn the corresponding stTokens from the redemptionAddress stTokensToBurn := sdk.NewCoins(sdk.NewCoin(stDenom, record.StTokenAmount)) - if err := k.BurnRedeemedStTokens(ctx, stTokensToBurn, hostZone.RedemptionAddress); err != nil { + if err := k.BurnRedeemedStTokens(ctx, stTokensToBurn, staketiaHostZone.RedemptionAddress); err != nil { return errorsmod.Wrapf(err, "unable to burn stTokens in ConfirmUndelegation") } - // sanity check: check that (DelegatedBalance increment / stToken supply decrement) is within outer bounds - if err := k.VerifyImpliedRedemptionRateFromUnbonding(ctx, stTokenSupplyBefore, delegatedBalanceBefore); err != nil { - return errorsmod.Wrap(err, "ratio of delegation change to burned tokens exceeds redemption rate bounds") - } - EmitSuccessfulConfirmUndelegationEvent(ctx, recordId, record.NativeAmount, txHash, sender) return nil } @@ -245,37 +316,6 @@ func (k Keeper) BurnRedeemedStTokens(ctx sdk.Context, stTokensToBurn sdk.Coins, return nil } -// Sanity check helper for checking diffs on delegated balance and stToken supply are within outer RR bounds -func (k Keeper) VerifyImpliedRedemptionRateFromUnbonding(ctx sdk.Context, stTokenSupplyBefore sdkmath.Int, delegatedBalanceBefore sdkmath.Int) error { - hostZoneAfter, err := k.GetHostZone(ctx) - if err != nil { - return types.ErrHostZoneNotFound - } - stDenom := utils.StAssetDenomFromHostZoneDenom(hostZoneAfter.NativeTokenDenom) - - // grab the delegated balance and token supply after the burn - delegatedBalanceAfter := hostZoneAfter.DelegatedBalance - stTokenSupplyAfter := k.bankKeeper.GetSupply(ctx, stDenom).Amount - - // calculate the delta for both the delegated balance and stToken burn - delegatedBalanceDecremented := delegatedBalanceBefore.Sub(delegatedBalanceAfter) - stTokenSupplyBurned := stTokenSupplyBefore.Sub(stTokenSupplyAfter) - - // It shouldn't be possible for this to be zero, but this will prevent a division by zero error - if stTokenSupplyBurned.IsZero() { - return types.ErrDivisionByZero - } - - // calculate the ratio of delegated balance change to stToken burn - it should be close to the redemption rate - ratio := sdk.NewDecFromInt(delegatedBalanceDecremented).Quo(sdk.NewDecFromInt(stTokenSupplyBurned)) - - // check ratio against bounds - if ratio.LT(hostZoneAfter.MinRedemptionRate) || ratio.GT(hostZoneAfter.MaxRedemptionRate) { - return types.ErrRedemptionRateOutsideSafetyBounds - } - return nil -} - // Checks for any unbonding records that have finished unbonding, // identified by having status UNBONDING_IN_PROGRESS and an // unbonding that's older than the current time. diff --git a/x/staketia/keeper/unbonding_test.go b/x/staketia/keeper/unbonding_test.go index b05c37aba..80ce564ae 100644 --- a/x/staketia/keeper/unbonding_test.go +++ b/x/staketia/keeper/unbonding_test.go @@ -9,6 +9,9 @@ import ( "github.com/Stride-Labs/stride/v22/app/apptesting" "github.com/Stride-Labs/stride/v22/utils" + epochtypes "github.com/Stride-Labs/stride/v22/x/epochs/types" + recordtypes "github.com/Stride-Labs/stride/v22/x/records/types" + stakeibctypes "github.com/Stride-Labs/stride/v22/x/stakeibc/types" "github.com/Stride-Labs/stride/v22/x/staketia/types" ) @@ -29,19 +32,22 @@ type RedeemStakeTestCase struct { userAccount Account hostZone *types.HostZone + stakeibcHostZone *stakeibctypes.HostZone accUnbondRecord *types.UnbondingRecord redemptionRecord *types.RedemptionRecord redeemMsg types.MsgRedeemStake - expectedUnbondingRecord *types.UnbondingRecord - expectedRedemptionRecord *types.RedemptionRecord - expectedErrorContains string + expectedUnbondingRecord *types.UnbondingRecord + expectedRedemptionRecord *types.RedemptionRecord + expectedStakeibcRedemptionAmount sdkmath.Int + expectedErrorContains string } // Create the correct amounts in accounts, setup the records in store func (s *KeeperTestSuite) SetupTestRedeemStake( userAccount Account, hostZone *types.HostZone, + stakeibcHostZone *stakeibctypes.HostZone, accUnbondRecord *types.UnbondingRecord, redemptionRecord *types.RedemptionRecord, ) { @@ -52,6 +58,10 @@ func (s *KeeperTestSuite) SetupTestRedeemStake( s.App.StaketiaKeeper.SetHostZone(s.Ctx, *hostZone) } + if stakeibcHostZone != nil { + s.App.StakeibcKeeper.SetHostZone(s.Ctx, *stakeibcHostZone) + } + if accUnbondRecord != nil { s.App.StaketiaKeeper.SetUnbondingRecord(s.Ctx, *accUnbondRecord) } @@ -68,18 +78,39 @@ func (s *KeeperTestSuite) SetupTestRedeemStake( if redemptionRecord != nil { s.App.StaketiaKeeper.SetRedemptionRecord(s.Ctx, *redemptionRecord) } + + // Prepare stakeibc for a redemption + epochNumber := uint64(1) + s.App.StakeibcKeeper.SetEpochTracker(s.Ctx, stakeibctypes.EpochTracker{ + EpochIdentifier: epochtypes.DAY_EPOCH, + EpochNumber: epochNumber, + }) + + s.App.RecordsKeeper.SetEpochUnbondingRecord(s.Ctx, recordtypes.EpochUnbondingRecord{ + EpochNumber: epochNumber, + HostZoneUnbondings: []*recordtypes.HostZoneUnbonding{ + { + HostZoneId: types.CelestiaChainId, + StTokenAmount: sdkmath.ZeroInt(), + NativeTokenAmount: sdkmath.ZeroInt(), + }, + }, + }) } // Default values for key variables, different tests will change 1-2 fields for setup func (s *KeeperTestSuite) getDefaultTestInputs() ( *Account, *types.HostZone, + *stakeibctypes.HostZone, *types.UnbondingRecord, *types.RedemptionRecord, *types.MsgRedeemStake, ) { redeemerAccount := s.TestAccs[0] redemptionAccount := s.TestAccs[1] + receiverAddress := s.TestAccs[2].String() + depositAddress := s.TestAccs[3].String() defaultUserAccount := Account{ account: redeemerAccount, @@ -88,16 +119,23 @@ func (s *KeeperTestSuite) getDefaultTestInputs() ( } redemptionRate := sdk.MustNewDecFromStr("1.1") - defaultHostZone := types.HostZone{ - NativeTokenDenom: HostNativeDenom, - RedemptionAddress: redemptionAccount.String(), + defaultStaketiaHostZone := types.HostZone{ + NativeTokenDenom: HostNativeDenom, + RedemptionAddress: redemptionAccount.String(), + RemainingDelegatedBalance: sdkmath.NewInt(1_000_000_000), + Halted: false, + } + defaultStakeibcHostZone := stakeibctypes.HostZone{ + ChainId: types.CelestiaChainId, + HostDenom: HostNativeDenom, + Bech32Prefix: "stride", + DepositAddress: depositAddress, RedemptionRate: redemptionRate, MinRedemptionRate: redemptionRate.Sub(sdk.MustNewDecFromStr("0.2")), MinInnerRedemptionRate: redemptionRate.Sub(sdk.MustNewDecFromStr("0.1")), MaxInnerRedemptionRate: redemptionRate.Add(sdk.MustNewDecFromStr("0.1")), MaxRedemptionRate: redemptionRate.Add(sdk.MustNewDecFromStr("0.2")), - DelegatedBalance: sdkmath.NewInt(1_000_000_000), - Halted: false, + TotalDelegations: sdkmath.NewInt(1_000_000_000), } defaultAccUnbondingRecord := types.UnbondingRecord{ @@ -119,21 +157,32 @@ func (s *KeeperTestSuite) getDefaultTestInputs() ( defaultMsg := types.MsgRedeemStake{ Redeemer: redeemerAccount.String(), StTokenAmount: sdk.NewInt(1_000_000), + Receiver: receiverAddress, } - return &defaultUserAccount, &defaultHostZone, &defaultAccUnbondingRecord, - &defaultRedemptionRecord, &defaultMsg + return &defaultUserAccount, &defaultStaketiaHostZone, &defaultStakeibcHostZone, + &defaultAccUnbondingRecord, &defaultRedemptionRecord, &defaultMsg } func (s *KeeperTestSuite) TestRedeemStake() { - defaultUA, defaultHZ, defaultUR, defaultRR, defaultMsg := s.getDefaultTestInputs() + defaultUA, defaultMsHZ, defaultIcaHZ, defaultUR, defaultRR, defaultMsg := s.getDefaultTestInputs() testCases := []RedeemStakeTestCase{ { testName: "[Error] Can't find the HostZone", - userAccount: *defaultUA, - hostZone: nil, + userAccount: *defaultUA, + hostZone: nil, + stakeibcHostZone: defaultIcaHZ, + + expectedErrorContains: types.ErrHostZoneNotFound.Error(), + }, + { + testName: "[Error] Can't find the stakeibc HostZone", + + userAccount: *defaultUA, + hostZone: defaultMsHZ, + stakeibcHostZone: nil, expectedErrorContains: types.ErrHostZoneNotFound.Error(), }, @@ -142,10 +191,11 @@ func (s *KeeperTestSuite) TestRedeemStake() { userAccount: *defaultUA, hostZone: func() *types.HostZone { - _, hz, _, _, _ := s.getDefaultTestInputs() + _, hz, _, _, _, _ := s.getDefaultTestInputs() hz.RedemptionAddress = "nonparsable-address" return hz }(), + stakeibcHostZone: defaultIcaHZ, expectedErrorContains: "could not bech32 decode redemption address", }, @@ -154,10 +204,11 @@ func (s *KeeperTestSuite) TestRedeemStake() { userAccount: *defaultUA, hostZone: func() *types.HostZone { - _, hz, _, _, _ := s.getDefaultTestInputs() + _, hz, _, _, _, _ := s.getDefaultTestInputs() hz.Halted = true return hz }(), + stakeibcHostZone: defaultIcaHZ, expectedErrorContains: types.ErrHostZoneHalted.Error(), }, @@ -165,8 +216,9 @@ func (s *KeeperTestSuite) TestRedeemStake() { testName: "[Error] RedemptionRate outside of bounds", userAccount: *defaultUA, - hostZone: func() *types.HostZone { - _, hz, _, _, _ := s.getDefaultTestInputs() + hostZone: defaultMsHZ, + stakeibcHostZone: func() *stakeibctypes.HostZone { + _, _, hz, _, _, _ := s.getDefaultTestInputs() hz.RedemptionRate = sdk.MustNewDecFromStr("5.2") return hz }(), @@ -176,9 +228,10 @@ func (s *KeeperTestSuite) TestRedeemStake() { { testName: "[Error] No Accumulating UndondingRecord", - userAccount: *defaultUA, - hostZone: defaultHZ, - accUnbondRecord: nil, + userAccount: *defaultUA, + hostZone: defaultMsHZ, + stakeibcHostZone: defaultIcaHZ, + accUnbondRecord: nil, expectedErrorContains: types.ErrBrokenUnbondingRecordInvariant.Error(), }, @@ -186,13 +239,14 @@ func (s *KeeperTestSuite) TestRedeemStake() { testName: "[Error] Not enough tokens in wallet", userAccount: func() Account { - acc, _, _, _, _ := s.getDefaultTestInputs() + acc, _, _, _, _, _ := s.getDefaultTestInputs() acc.stTokens.Amount = sdk.NewInt(500_000) return *acc }(), - hostZone: defaultHZ, - accUnbondRecord: defaultUR, - redeemMsg: *defaultMsg, // attempt to redeem 1_000_000 stTokens + hostZone: defaultMsHZ, + stakeibcHostZone: defaultIcaHZ, + accUnbondRecord: defaultUR, + redeemMsg: *defaultMsg, // attempt to redeem 1_000_000 stTokens expectedErrorContains: sdkerrors.ErrInsufficientFunds.Error(), }, @@ -200,31 +254,72 @@ func (s *KeeperTestSuite) TestRedeemStake() { testName: "[Error] Redeeming more than HostZone delegation total", userAccount: func() Account { - acc, _, _, _, _ := s.getDefaultTestInputs() + acc, _, _, _, _, _ := s.getDefaultTestInputs() acc.stTokens.Amount = sdk.NewInt(5_000_000_000) return *acc }(), - hostZone: defaultHZ, // 1_000_000_000 total delegation - accUnbondRecord: defaultUR, + hostZone: defaultMsHZ, // 1_000_000_000 total delegation + stakeibcHostZone: defaultIcaHZ, + accUnbondRecord: defaultUR, redeemMsg: func() types.MsgRedeemStake { - _, _, _, _, msg := s.getDefaultTestInputs() + _, _, _, _, _, msg := s.getDefaultTestInputs() msg.StTokenAmount = sdk.NewInt(5_000_000_000) return *msg }(), expectedErrorContains: types.ErrUnbondAmountToLarge.Error(), }, + { + testName: "[Error] Redeeming is disabled", + + userAccount: *defaultUA, + hostZone: func() *types.HostZone { + _, hz, _, _, _, _ := s.getDefaultTestInputs() + hz.RemainingDelegatedBalance = sdkmath.ZeroInt() + return hz + }(), + stakeibcHostZone: defaultIcaHZ, + accUnbondRecord: defaultUR, + redeemMsg: *defaultMsg, + + expectedErrorContains: types.ErrRedemptionsDisabled.Error(), + }, + { + testName: "[Error] Spillover fails in stakeibc", + + userAccount: *defaultUA, + hostZone: func() *types.HostZone { + _, hz, _, _, _, msg := s.getDefaultTestInputs() + hz.RemainingDelegatedBalance = msg.StTokenAmount.Sub(sdkmath.OneInt()) // subtract 1 so there's excess + return hz + }(), + stakeibcHostZone: func() *stakeibctypes.HostZone { + _, _, hz, _, _, _ := s.getDefaultTestInputs() + hz.RedemptionRate = sdk.OneDec() + return hz + }(), + accUnbondRecord: defaultUR, + redeemMsg: func() types.MsgRedeemStake { + // Make receiver address invalid so stakeibc fails + _, _, _, _, _, msg := s.getDefaultTestInputs() + msg.Receiver = "" + return *msg + }(), + + expectedErrorContains: "unable to execute stakeibc redeem stake", + }, { testName: "[Success] No RR exists yet, RedeemStake tx creates one", userAccount: *defaultUA, - hostZone: defaultHZ, + hostZone: defaultMsHZ, + stakeibcHostZone: defaultIcaHZ, accUnbondRecord: defaultUR, redemptionRecord: nil, redeemMsg: *defaultMsg, // redeem 1_000_000 stTokens expectedUnbondingRecord: func() *types.UnbondingRecord { - _, hz, ur, _, msg := s.getDefaultTestInputs() + _, _, hz, ur, _, msg := s.getDefaultTestInputs() ur.StTokenAmount = ur.StTokenAmount.Add(msg.StTokenAmount) nativeDiff := sdk.NewDecFromInt(msg.StTokenAmount).Mul(hz.RedemptionRate).TruncateInt() ur.NativeAmount = ur.NativeAmount.Add(nativeDiff) @@ -234,33 +329,93 @@ func (s *KeeperTestSuite) TestRedeemStake() { UnbondingRecordId: defaultUR.Id, Redeemer: defaultMsg.Redeemer, StTokenAmount: defaultMsg.StTokenAmount, - NativeAmount: sdk.NewDecFromInt(defaultMsg.StTokenAmount).Mul(defaultHZ.RedemptionRate).TruncateInt(), + NativeAmount: sdk.NewDecFromInt(defaultMsg.StTokenAmount).Mul(defaultIcaHZ.RedemptionRate).TruncateInt(), }, }, { testName: "[Success] RR exists already for redeemer, RedeemStake tx updates", userAccount: *defaultUA, - hostZone: defaultHZ, + hostZone: defaultMsHZ, + stakeibcHostZone: defaultIcaHZ, accUnbondRecord: defaultUR, redemptionRecord: defaultRR, // previous redeemption of 400_000 redeemMsg: *defaultMsg, // redeem 1_000_000 stTokens expectedUnbondingRecord: func() *types.UnbondingRecord { - _, hz, ur, _, msg := s.getDefaultTestInputs() + _, _, hz, ur, _, msg := s.getDefaultTestInputs() ur.StTokenAmount = ur.StTokenAmount.Add(msg.StTokenAmount) nativeDiff := sdk.NewDecFromInt(msg.StTokenAmount).Mul(hz.RedemptionRate).TruncateInt() ur.NativeAmount = ur.NativeAmount.Add(nativeDiff) return ur }(), expectedRedemptionRecord: func() *types.RedemptionRecord { - _, hz, _, rr, msg := s.getDefaultTestInputs() + _, _, hz, _, rr, msg := s.getDefaultTestInputs() rr.StTokenAmount = rr.StTokenAmount.Add(msg.StTokenAmount) nativeDiff := sdk.NewDecFromInt(msg.StTokenAmount).Mul(hz.RedemptionRate).TruncateInt() rr.NativeAmount = rr.NativeAmount.Add(nativeDiff) return rr }(), }, + { + testName: "[Success] Redeems all remaining balance", + + userAccount: *defaultUA, + hostZone: func() *types.HostZone { + _, msHz, icaHz, _, _, msg := s.getDefaultTestInputs() + nativeRedeemAmount := sdk.NewDecFromInt(msg.StTokenAmount).Mul(icaHz.RedemptionRate).TruncateInt() + msHz.RemainingDelegatedBalance = nativeRedeemAmount + return msHz + }(), + stakeibcHostZone: defaultIcaHZ, + accUnbondRecord: defaultUR, + redeemMsg: *defaultMsg, + + expectedUnbondingRecord: func() *types.UnbondingRecord { + _, _, hz, ur, _, msg := s.getDefaultTestInputs() + ur.StTokenAmount = ur.StTokenAmount.Add(msg.StTokenAmount) + nativeDiff := sdk.NewDecFromInt(msg.StTokenAmount).Mul(hz.RedemptionRate).TruncateInt() + ur.NativeAmount = ur.NativeAmount.Add(nativeDiff) + return ur + }(), + expectedRedemptionRecord: &types.RedemptionRecord{ + UnbondingRecordId: defaultUR.Id, + Redeemer: defaultMsg.Redeemer, + StTokenAmount: defaultMsg.StTokenAmount, + NativeAmount: sdk.NewDecFromInt(defaultMsg.StTokenAmount).Mul(defaultIcaHZ.RedemptionRate).TruncateInt(), + }, + }, + { + testName: "[Success] Redeems with stakeibc spillover", + + userAccount: *defaultUA, + hostZone: func() *types.HostZone { + _, hz, _, _, _, msg := s.getDefaultTestInputs() + hz.RemainingDelegatedBalance = msg.StTokenAmount.Sub(sdkmath.OneInt()) // subtract 1 so there's excess + return hz + }(), + stakeibcHostZone: func() *stakeibctypes.HostZone { + _, _, hz, _, _, _ := s.getDefaultTestInputs() + hz.RedemptionRate = sdk.OneDec() + return hz + }(), + accUnbondRecord: defaultUR, + redeemMsg: *defaultMsg, + + expectedUnbondingRecord: func() *types.UnbondingRecord { + _, _, _, ur, _, msg := s.getDefaultTestInputs() + ur.StTokenAmount = ur.StTokenAmount.Add(msg.StTokenAmount).Sub(sdkmath.OneInt()) + ur.NativeAmount = ur.NativeAmount.Add(msg.StTokenAmount).Sub(sdkmath.OneInt()) + return ur + }(), + expectedRedemptionRecord: &types.RedemptionRecord{ + UnbondingRecordId: defaultUR.Id, + Redeemer: defaultMsg.Redeemer, + StTokenAmount: defaultMsg.StTokenAmount.Sub(sdkmath.OneInt()), + NativeAmount: defaultMsg.StTokenAmount.Sub(sdkmath.OneInt()), + }, + expectedStakeibcRedemptionAmount: sdkmath.OneInt(), + }, } for _, tc := range testCases { @@ -273,7 +428,7 @@ func (s *KeeperTestSuite) TestRedeemStake() { func (s *KeeperTestSuite) checkRedeemStakeTestCase(tc RedeemStakeTestCase) { s.SetupTest() // reset state - s.SetupTestRedeemStake(tc.userAccount, tc.hostZone, tc.accUnbondRecord, tc.redemptionRecord) + s.SetupTestRedeemStake(tc.userAccount, tc.hostZone, tc.stakeibcHostZone, tc.accUnbondRecord, tc.redemptionRecord) startingStEscrowBalance := sdk.NewInt64Coin(StDenom, 0) if tc.hostZone != nil { @@ -284,7 +439,7 @@ func (s *KeeperTestSuite) checkRedeemStakeTestCase(tc RedeemStakeTestCase) { } // Run the RedeemStake, verify expected errors returned or no errors with expected updates to records - _, err := s.App.StaketiaKeeper.RedeemStake(s.Ctx, tc.redeemMsg.Redeemer, tc.redeemMsg.StTokenAmount) + _, err := s.App.StaketiaKeeper.RedeemStake(s.Ctx, tc.redeemMsg.Redeemer, tc.redeemMsg.Receiver, tc.redeemMsg.StTokenAmount) if tc.expectedErrorContains == "" { // Successful Run Test Case s.Require().NoError(err, "No error expected during redeem stake execution") @@ -306,6 +461,14 @@ func (s *KeeperTestSuite) checkRedeemStakeTestCase(tc RedeemStakeTestCase) { currentStEscrowBalance := s.App.BankKeeper.GetBalance(s.Ctx, escrowAccount, StDenom) s.Require().NotEqual(startingStEscrowBalance, currentStEscrowBalance, "Escrowed balance should have changed") s.Require().Equal(currentStEscrowBalance.Amount, currentAUR.StTokenAmount, "Escrowed balance does not match the UnbondingRecord") + + // If there's stakeibc spillover, check that the amount was stored in the user redemption record + if !tc.expectedStakeibcRedemptionAmount.IsNil() { + userRedemptionRecords := s.App.RecordsKeeper.GetAllUserRedemptionRecord(s.Ctx) + s.Require().Len(userRedemptionRecords, 1, "there should be a stakeibc user redemption record") + s.Require().Equal(tc.expectedStakeibcRedemptionAmount.Int64(), userRedemptionRecords[0].StTokenAmount.Int64(), + "stakeibc user redemption record amount") + } } else { // Expected Error Test Case s.Require().Error(err, "Error expected to be returned but none found") @@ -331,7 +494,8 @@ func (s *KeeperTestSuite) TestPrepareUndelegation() { // (an uneven number is used to test rounding/truncation) oldRedemptionRate := sdk.MustNewDecFromStr("1.9") redemptionRate := sdk.MustNewDecFromStr("1.999") - s.App.StaketiaKeeper.SetHostZone(s.Ctx, types.HostZone{ + s.App.StakeibcKeeper.SetHostZone(s.Ctx, stakeibctypes.HostZone{ + ChainId: types.CelestiaChainId, RedemptionRate: redemptionRate, }) @@ -418,12 +582,13 @@ func (s *KeeperTestSuite) TestPrepareUndelegation() { s.Require().ErrorContains(err, "more than one record in status ACCUMULATING") // Halt the host zone and try again - it should fail - hostZone := s.MustGetHostZone() + hostZone, found := s.App.StakeibcKeeper.GetHostZone(s.Ctx, types.CelestiaChainId) + s.Require().True(found) hostZone.Halted = true - s.App.StaketiaKeeper.SetHostZone(s.Ctx, hostZone) + s.App.StakeibcKeeper.SetHostZone(s.Ctx, hostZone) err = s.App.StaketiaKeeper.PrepareUndelegation(s.Ctx, epochNumber) - s.Require().ErrorContains(err, "host zone is halted") + s.Require().ErrorContains(err, "host zone celestia is halted") } // ---------------------------------------------- @@ -438,7 +603,8 @@ type ConfirmUndelegationTestCase struct { amountToUndelegate sdkmath.Int delegatedBalance sdkmath.Int redemptionAccountBalance sdkmath.Int - hostZone types.HostZone + staketiaHostZone types.HostZone + stakeibcHostZone stakeibctypes.HostZone expectedUnbondingTime uint64 } @@ -453,16 +619,19 @@ func (s *KeeperTestSuite) SetupTestConfirmUndelegation(amountToUndelegate sdkmat expectedUnbondingTime := uint64(s.Ctx.BlockTime().Add(time.Minute * 2).Unix()) // Create a host zone with delegatedBalance and RedemptionAddresses - hostZone := types.HostZone{ - DelegatedBalance: delegatedBalance, + staketiaHostZone := types.HostZone{ RedemptionAddress: redemptionAddress.String(), NativeTokenDenom: HostNativeDenom, UnbondingPeriodSeconds: unbondingPeriodSeconds, - MinRedemptionRate: sdk.MustNewDecFromStr("0.9"), - MaxRedemptionRate: sdk.MustNewDecFromStr("1.2"), - RedemptionRate: sdk.MustNewDecFromStr("1.1"), } - s.App.StaketiaKeeper.SetHostZone(s.Ctx, hostZone) + s.App.StaketiaKeeper.SetHostZone(s.Ctx, staketiaHostZone) + + stakeibcHostZone := stakeibctypes.HostZone{ + ChainId: types.CelestiaChainId, + RedemptionRate: sdk.MustNewDecFromStr("1.1"), + TotalDelegations: delegatedBalance, + } + s.App.StakeibcKeeper.SetHostZone(s.Ctx, stakeibcHostZone) // Fund the redemption account with tokens that will be burned stTokensInRedemption := sdk.NewCoin(StDenom, redemptionAccountBalance) @@ -470,7 +639,7 @@ func (s *KeeperTestSuite) SetupTestConfirmUndelegation(amountToUndelegate sdkmat // create an unbonding record in status UNBONDING_QUEUE // - stToken amount to burn as if the RR is 1.1 - stTokenAmountToBurn := sdk.NewDecFromInt(amountToUndelegate).Mul(hostZone.RedemptionRate).TruncateInt() + stTokenAmountToBurn := sdk.NewDecFromInt(amountToUndelegate).Mul(stakeibcHostZone.RedemptionRate).TruncateInt() unbondingRecord := types.UnbondingRecord{ Id: 1, Status: types.UNBONDING_QUEUE, @@ -487,7 +656,8 @@ func (s *KeeperTestSuite) SetupTestConfirmUndelegation(amountToUndelegate sdkmat amountToUndelegate: amountToUndelegate, delegatedBalance: delegatedBalance, redemptionAccountBalance: redemptionAccountBalance, - hostZone: hostZone, + stakeibcHostZone: stakeibcHostZone, + staketiaHostZone: staketiaHostZone, expectedUnbondingTime: expectedUnbondingTime, } return tc @@ -517,8 +687,9 @@ func (s *KeeperTestSuite) TestConfirmUndelegation_Success() { s.Require().Equal(expectedRedemptionAccountBalance, actualRedemptionAccountBalance, "redemption account balance") // check that delegated balance was updated - hostZone := s.MustGetHostZone() - s.Require().Equal(tc.delegatedBalance.Sub(tc.amountToUndelegate), hostZone.DelegatedBalance, "delegated balance") + stakeibcHostZone, found := s.App.StakeibcKeeper.GetHostZone(s.Ctx, types.CelestiaChainId) + s.Require().True(found) + s.Require().Equal(tc.delegatedBalance.Sub(tc.amountToUndelegate), stakeibcHostZone.TotalDelegations, "delegated balance") } // unit test ConfirmUndelegation with nothing to unbond @@ -642,113 +813,6 @@ func (s *KeeperTestSuite) TestBurnRedeemedStTokens_BadRedemptionAddress() { s.Require().Error(err, "could not bech32 decode addres") } -func (s *KeeperTestSuite) TestVerifyImpliedRedemptionRateFromUnbonding() { - minRRBound := sdk.MustNewDecFromStr("0.9") - maxRRBound := sdk.MustNewDecFromStr("1.1") - - testCases := []struct { - name string - delegatedBalanceBefore sdkmath.Int - delegatedBalanceAfter sdkmath.Int - stTokenSupplyBefore sdkmath.Int - stTokenSupplyAfter sdkmath.Int - expectedError string - }{ - { - // Undelegated: 1000, Burned: 1000, Implied Rate: 1.0 - name: "valid implied rate - 1", - delegatedBalanceBefore: sdkmath.NewInt(5000), - delegatedBalanceAfter: sdkmath.NewInt(4000), // 5000 - 4000 = 1000 undelegated - stTokenSupplyBefore: sdkmath.NewInt(5000), - stTokenSupplyAfter: sdkmath.NewInt(4000), // 5000 - 4000 = 1000 burned - }, - { - // Undelegated: 950, Burned: 1000, Implied Rate: 0.95 - name: "valid implied rate below 1", - delegatedBalanceBefore: sdkmath.NewInt(5000), - delegatedBalanceAfter: sdkmath.NewInt(4050), // 5000 - 4050 = 950 undelegated - stTokenSupplyBefore: sdkmath.NewInt(5000), - stTokenSupplyAfter: sdkmath.NewInt(4000), // 5000 - 4000 = 1000 burned - }, - { - // Undelegated: 1050, Burned: 1000, Implied Rate: 1.05 - name: "valid implied rate above 1", - delegatedBalanceBefore: sdkmath.NewInt(5000), - delegatedBalanceAfter: sdkmath.NewInt(3950), // 5000 - 3950 = 1050 undelegated - stTokenSupplyBefore: sdkmath.NewInt(5000), - stTokenSupplyAfter: sdkmath.NewInt(4000), // 5000 - 4000 = 1000 burned - }, - { - // Undelegated: 900, Burned: 1000, Implied Rate: 0.9 - name: "valid implied rate at min", - delegatedBalanceBefore: sdkmath.NewInt(5000), - delegatedBalanceAfter: sdkmath.NewInt(4000), // 5000 - 4000 = 900 undelegated - stTokenSupplyBefore: sdkmath.NewInt(5000), - stTokenSupplyAfter: sdkmath.NewInt(4000), // 5000 - 4000 = 1000 burned - }, - { - // Undelegated: 1100, Burned: 1000, Implied Rate: 1.1 - name: "valid implied rate at max", - delegatedBalanceBefore: sdkmath.NewInt(5000), - delegatedBalanceAfter: sdkmath.NewInt(3900), // 5000 - 3900 = 1100 undelegated - stTokenSupplyBefore: sdkmath.NewInt(5000), - stTokenSupplyAfter: sdkmath.NewInt(4000), // 5000 - 4000 = 1000 burned - }, - { - // Undelegated: 899, Burned: 1000, Implied Rate: 0.899 - name: "valid implied rate below min", - delegatedBalanceBefore: sdkmath.NewInt(5000), - delegatedBalanceAfter: sdkmath.NewInt(4101), // 5000 - 4101 = 899 undelegated - stTokenSupplyBefore: sdkmath.NewInt(5000), - stTokenSupplyAfter: sdkmath.NewInt(4000), // 5000 - 4000 = 1000 burned - expectedError: "redemption rate outside safety bounds", - }, - { - // Undelegated: 1101, Burned: 1000, Implied Rate: 1.101 - name: "valid implied rate above max", - delegatedBalanceBefore: sdkmath.NewInt(5000), - delegatedBalanceAfter: sdkmath.NewInt(3899), // 5000 - 3899 = 1101 undelegated - stTokenSupplyBefore: sdkmath.NewInt(5000), - stTokenSupplyAfter: sdkmath.NewInt(4000), // 5000 - 4000 = 1000 burned - expectedError: "redemption rate outside safety bounds", - }, - { - name: "division by zero", - delegatedBalanceBefore: sdkmath.NewInt(1000), - delegatedBalanceAfter: sdkmath.NewInt(2000), - stTokenSupplyBefore: sdkmath.NewInt(4000), - stTokenSupplyAfter: sdkmath.NewInt(4000), // same as before -> supply change is 0 - expectedError: "division by zero", - }, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - s.SetupTest() // reset state - - // Mint stTokens to a random account to create supply - s.FundAccount(s.TestAccs[0], sdk.NewCoin(StDenom, tc.stTokenSupplyAfter)) - - // Set the delegated balance on the host zone - s.App.StaketiaKeeper.SetHostZone(s.Ctx, types.HostZone{ - NativeTokenDenom: HostNativeDenom, - DelegatedBalance: tc.delegatedBalanceAfter, - MinRedemptionRate: minRRBound, - MaxRedemptionRate: maxRRBound, - }) - - // verify that the implied redemption rate is between the bounds - err := s.App.StaketiaKeeper.VerifyImpliedRedemptionRateFromUnbonding(s.Ctx, tc.stTokenSupplyBefore, tc.delegatedBalanceBefore) - - if tc.expectedError == "" { - s.Require().NoError(err) - } else { - s.Require().ErrorContains(err, tc.expectedError) - } - }) - } -} - // ---------------------------------------------- // ConfirmUnbondedTokensSwept // ---------------------------------------------- diff --git a/x/staketia/legacytypes/staketia.pb.go b/x/staketia/legacytypes/staketia.pb.go new file mode 100644 index 000000000..e1fbbc100 --- /dev/null +++ b/x/staketia/legacytypes/staketia.pb.go @@ -0,0 +1,2739 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: stride/staketia/staketia.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Status fields for a delegation record +// Note: There is an important assumption here that tokens in the deposit +// account should not be tracked by these records. The record is created as soon +// as the tokens leave stride +// Additionally, the GetActiveDelegationRecords query filters for records that +// are either TRANSFER_IN_PROGERSS or DELEGATION_QUEUE. If a new active status +// is added, the keeper must be modified +type DelegationRecordStatus int32 + +const ( + // TRANSFER_IN_PROGRESS indicates the native tokens are being sent from the + // deposit account to the delegation account + TRANSFER_IN_PROGRESS DelegationRecordStatus = 0 + // TRANSFER_FAILED indicates that the transfer either timed out or was an ack + // failure + TRANSFER_FAILED DelegationRecordStatus = 1 + // DELEGATION_QUEUE indicates the tokens have landed on the host zone and are + // ready to be delegated + DELEGATION_QUEUE DelegationRecordStatus = 2 + // DELEGATION_COMPLETE indicates the delegation has been completed + DELEGATION_COMPLETE DelegationRecordStatus = 3 +) + +var DelegationRecordStatus_name = map[int32]string{ + 0: "TRANSFER_IN_PROGRESS", + 1: "TRANSFER_FAILED", + 2: "DELEGATION_QUEUE", + 3: "DELEGATION_COMPLETE", +} + +var DelegationRecordStatus_value = map[string]int32{ + "TRANSFER_IN_PROGRESS": 0, + "TRANSFER_FAILED": 1, + "DELEGATION_QUEUE": 2, + "DELEGATION_COMPLETE": 3, +} + +func (x DelegationRecordStatus) String() string { + return proto.EnumName(DelegationRecordStatus_name, int32(x)) +} + +func (DelegationRecordStatus) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_d306d9365b78f7b2, []int{0} +} + +// Status fields for an unbonding record +type UnbondingRecordStatus int32 + +const ( + // ACCUMULATING_REDEMPTIONS indicates redemptions are still being accumulated + // on this record + ACCUMULATING_REDEMPTIONS UnbondingRecordStatus = 0 + // UNBONDING_QUEUE indicates the unbond amount for this epoch has been froze + // and the tokens are ready to be unbonded on the host zone + UNBONDING_QUEUE UnbondingRecordStatus = 1 + // UNBONDING_IN_PROGRESS indicates the unbonding is currently in progress on + // the host zone + UNBONDING_IN_PROGRESS UnbondingRecordStatus = 2 + // UNBONDED indicates the unbonding is finished on the host zone and the + // tokens are still in the delegation account + UNBONDED UnbondingRecordStatus = 3 + // CLAIMABLE indicates the unbonded tokens have been swept to stride and are + // ready to be distributed to users + CLAIMABLE UnbondingRecordStatus = 4 + // CLAIMED indicates the full unbonding cycle has been completed + CLAIMED UnbondingRecordStatus = 5 +) + +var UnbondingRecordStatus_name = map[int32]string{ + 0: "ACCUMULATING_REDEMPTIONS", + 1: "UNBONDING_QUEUE", + 2: "UNBONDING_IN_PROGRESS", + 3: "UNBONDED", + 4: "CLAIMABLE", + 5: "CLAIMED", +} + +var UnbondingRecordStatus_value = map[string]int32{ + "ACCUMULATING_REDEMPTIONS": 0, + "UNBONDING_QUEUE": 1, + "UNBONDING_IN_PROGRESS": 2, + "UNBONDED": 3, + "CLAIMABLE": 4, + "CLAIMED": 5, +} + +func (x UnbondingRecordStatus) String() string { + return proto.EnumName(UnbondingRecordStatus_name, int32(x)) +} + +func (UnbondingRecordStatus) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_d306d9365b78f7b2, []int{1} +} + +type HostZone struct { + // Chain ID + ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + // Native token denom on the host zone (e.g. utia) + NativeTokenDenom string `protobuf:"bytes,2,opt,name=native_token_denom,json=nativeTokenDenom,proto3" json:"native_token_denom,omitempty"` + // IBC denom of the native token as it lives on stride (e.g. ibc/...) + NativeTokenIbcDenom string `protobuf:"bytes,3,opt,name=native_token_ibc_denom,json=nativeTokenIbcDenom,proto3" json:"native_token_ibc_denom,omitempty"` + // Transfer channel ID from stride to the host zone + TransferChannelId string `protobuf:"bytes,4,opt,name=transfer_channel_id,json=transferChannelId,proto3" json:"transfer_channel_id,omitempty"` + // Operator controlled delegation address on the host zone + DelegationAddress string `protobuf:"bytes,5,opt,name=delegation_address,json=delegationAddress,proto3" json:"delegation_address,omitempty"` + // Operator controlled reward address on the host zone + RewardAddress string `protobuf:"bytes,6,opt,name=reward_address,json=rewardAddress,proto3" json:"reward_address,omitempty"` + // Deposit address on stride + DepositAddress string `protobuf:"bytes,7,opt,name=deposit_address,json=depositAddress,proto3" json:"deposit_address,omitempty"` + // Redemption address on stride + RedemptionAddress string `protobuf:"bytes,8,opt,name=redemption_address,json=redemptionAddress,proto3" json:"redemption_address,omitempty"` + // Claim address on stride + ClaimAddress string `protobuf:"bytes,9,opt,name=claim_address,json=claimAddress,proto3" json:"claim_address,omitempty"` + // operator address set by safe, on stride + OperatorAddressOnStride string `protobuf:"bytes,10,opt,name=operator_address_on_stride,json=operatorAddressOnStride,proto3" json:"operator_address_on_stride,omitempty"` + // admin address set upon host zone creation, on stride + SafeAddressOnStride string `protobuf:"bytes,11,opt,name=safe_address_on_stride,json=safeAddressOnStride,proto3" json:"safe_address_on_stride,omitempty"` + // Previous redemption rate + LastRedemptionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,12,opt,name=last_redemption_rate,json=lastRedemptionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"last_redemption_rate"` + // Current redemption rate + RedemptionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,13,opt,name=redemption_rate,json=redemptionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"redemption_rate"` + // Min outer redemption rate - adjusted by governance + MinRedemptionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,14,opt,name=min_redemption_rate,json=minRedemptionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"min_redemption_rate"` + // Max outer redemption rate - adjusted by governance + MaxRedemptionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,15,opt,name=max_redemption_rate,json=maxRedemptionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"max_redemption_rate"` + // Min inner redemption rate - adjusted by controller + MinInnerRedemptionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,16,opt,name=min_inner_redemption_rate,json=minInnerRedemptionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"min_inner_redemption_rate"` + // Max inner redemption rate - adjusted by controller + MaxInnerRedemptionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,17,opt,name=max_inner_redemption_rate,json=maxInnerRedemptionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"max_inner_redemption_rate"` + // Total delegated balance on the host zone delegation account + DelegatedBalance github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,18,opt,name=delegated_balance,json=delegatedBalance,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"delegated_balance"` + // The undelegation period for Celestia in days + UnbondingPeriodSeconds uint64 `protobuf:"varint,19,opt,name=unbonding_period_seconds,json=unbondingPeriodSeconds,proto3" json:"unbonding_period_seconds,omitempty"` + // Indicates whether the host zone has been halted + Halted bool `protobuf:"varint,20,opt,name=halted,proto3" json:"halted,omitempty"` +} + +func (m *HostZone) Reset() { *m = HostZone{} } +func (m *HostZone) String() string { return proto.CompactTextString(m) } +func (*HostZone) ProtoMessage() {} +func (*HostZone) Descriptor() ([]byte, []int) { + return fileDescriptor_d306d9365b78f7b2, []int{0} +} +func (m *HostZone) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *HostZone) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_HostZone.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 *HostZone) XXX_Merge(src proto.Message) { + xxx_messageInfo_HostZone.Merge(m, src) +} +func (m *HostZone) XXX_Size() int { + return m.Size() +} +func (m *HostZone) XXX_DiscardUnknown() { + xxx_messageInfo_HostZone.DiscardUnknown(m) +} + +var xxx_messageInfo_HostZone proto.InternalMessageInfo + +func (m *HostZone) GetChainId() string { + if m != nil { + return m.ChainId + } + return "" +} + +func (m *HostZone) GetNativeTokenDenom() string { + if m != nil { + return m.NativeTokenDenom + } + return "" +} + +func (m *HostZone) GetNativeTokenIbcDenom() string { + if m != nil { + return m.NativeTokenIbcDenom + } + return "" +} + +func (m *HostZone) GetTransferChannelId() string { + if m != nil { + return m.TransferChannelId + } + return "" +} + +func (m *HostZone) GetDelegationAddress() string { + if m != nil { + return m.DelegationAddress + } + return "" +} + +func (m *HostZone) GetRewardAddress() string { + if m != nil { + return m.RewardAddress + } + return "" +} + +func (m *HostZone) GetDepositAddress() string { + if m != nil { + return m.DepositAddress + } + return "" +} + +func (m *HostZone) GetRedemptionAddress() string { + if m != nil { + return m.RedemptionAddress + } + return "" +} + +func (m *HostZone) GetClaimAddress() string { + if m != nil { + return m.ClaimAddress + } + return "" +} + +func (m *HostZone) GetOperatorAddressOnStride() string { + if m != nil { + return m.OperatorAddressOnStride + } + return "" +} + +func (m *HostZone) GetSafeAddressOnStride() string { + if m != nil { + return m.SafeAddressOnStride + } + return "" +} + +func (m *HostZone) GetUnbondingPeriodSeconds() uint64 { + if m != nil { + return m.UnbondingPeriodSeconds + } + return 0 +} + +func (m *HostZone) GetHalted() bool { + if m != nil { + return m.Halted + } + return false +} + +// DelegationRecords track the aggregate liquid stakes and delegations +// for a given epoch +// Note: There is an important assumption here that tokens in the deposit +// account should not be tracked by these records. The record is created as soon +// as the tokens leave stride +type DelegationRecord struct { + // Deposit record unique ID + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + // The amount of native tokens that should be delegated + NativeAmount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,2,opt,name=native_amount,json=nativeAmount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"native_amount"` + // The status indicating the point in the delegation's lifecycle + Status DelegationRecordStatus `protobuf:"varint,3,opt,name=status,proto3,enum=stride.staketia.DelegationRecordStatus" json:"status,omitempty"` + // The tx hash of the delegation on the host zone + TxHash string `protobuf:"bytes,4,opt,name=tx_hash,json=txHash,proto3" json:"tx_hash,omitempty"` +} + +func (m *DelegationRecord) Reset() { *m = DelegationRecord{} } +func (m *DelegationRecord) String() string { return proto.CompactTextString(m) } +func (*DelegationRecord) ProtoMessage() {} +func (*DelegationRecord) Descriptor() ([]byte, []int) { + return fileDescriptor_d306d9365b78f7b2, []int{1} +} +func (m *DelegationRecord) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DelegationRecord) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DelegationRecord.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 *DelegationRecord) XXX_Merge(src proto.Message) { + xxx_messageInfo_DelegationRecord.Merge(m, src) +} +func (m *DelegationRecord) XXX_Size() int { + return m.Size() +} +func (m *DelegationRecord) XXX_DiscardUnknown() { + xxx_messageInfo_DelegationRecord.DiscardUnknown(m) +} + +var xxx_messageInfo_DelegationRecord proto.InternalMessageInfo + +func (m *DelegationRecord) GetId() uint64 { + if m != nil { + return m.Id + } + return 0 +} + +func (m *DelegationRecord) GetStatus() DelegationRecordStatus { + if m != nil { + return m.Status + } + return TRANSFER_IN_PROGRESS +} + +func (m *DelegationRecord) GetTxHash() string { + if m != nil { + return m.TxHash + } + return "" +} + +// UnbondingRecords track the aggregate unbondings across an epoch +type UnbondingRecord struct { + // Unbonding record ID + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + // The status indicating the point in the delegation's lifecycle + Status UnbondingRecordStatus `protobuf:"varint,2,opt,name=status,proto3,enum=stride.staketia.UnbondingRecordStatus" json:"status,omitempty"` + // The amount of stTokens that were redeemed + StTokenAmount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,3,opt,name=st_token_amount,json=stTokenAmount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"st_token_amount"` + // The corresponding amount of native tokens that should be unbonded + NativeAmount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,4,opt,name=native_amount,json=nativeAmount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"native_amount"` + // The Unix timestamp (in seconds) at which the unbonding completes + UnbondingCompletionTimeSeconds uint64 `protobuf:"varint,5,opt,name=unbonding_completion_time_seconds,json=unbondingCompletionTimeSeconds,proto3" json:"unbonding_completion_time_seconds,omitempty"` + // The tx hash of the undelegation on the host zone + UndelegationTxHash string `protobuf:"bytes,6,opt,name=undelegation_tx_hash,json=undelegationTxHash,proto3" json:"undelegation_tx_hash,omitempty"` + // The tx hash of the unbonded token sweep on the host zone + UnbondedTokenSweepTxHash string `protobuf:"bytes,7,opt,name=unbonded_token_sweep_tx_hash,json=unbondedTokenSweepTxHash,proto3" json:"unbonded_token_sweep_tx_hash,omitempty"` +} + +func (m *UnbondingRecord) Reset() { *m = UnbondingRecord{} } +func (m *UnbondingRecord) String() string { return proto.CompactTextString(m) } +func (*UnbondingRecord) ProtoMessage() {} +func (*UnbondingRecord) Descriptor() ([]byte, []int) { + return fileDescriptor_d306d9365b78f7b2, []int{2} +} +func (m *UnbondingRecord) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *UnbondingRecord) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_UnbondingRecord.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 *UnbondingRecord) XXX_Merge(src proto.Message) { + xxx_messageInfo_UnbondingRecord.Merge(m, src) +} +func (m *UnbondingRecord) XXX_Size() int { + return m.Size() +} +func (m *UnbondingRecord) XXX_DiscardUnknown() { + xxx_messageInfo_UnbondingRecord.DiscardUnknown(m) +} + +var xxx_messageInfo_UnbondingRecord proto.InternalMessageInfo + +func (m *UnbondingRecord) GetId() uint64 { + if m != nil { + return m.Id + } + return 0 +} + +func (m *UnbondingRecord) GetStatus() UnbondingRecordStatus { + if m != nil { + return m.Status + } + return ACCUMULATING_REDEMPTIONS +} + +func (m *UnbondingRecord) GetUnbondingCompletionTimeSeconds() uint64 { + if m != nil { + return m.UnbondingCompletionTimeSeconds + } + return 0 +} + +func (m *UnbondingRecord) GetUndelegationTxHash() string { + if m != nil { + return m.UndelegationTxHash + } + return "" +} + +func (m *UnbondingRecord) GetUnbondedTokenSweepTxHash() string { + if m != nil { + return m.UnbondedTokenSweepTxHash + } + return "" +} + +// RedemptionRecords track an individual user's redemption claims +type RedemptionRecord struct { + // Unbonding record ID + UnbondingRecordId uint64 `protobuf:"varint,1,opt,name=unbonding_record_id,json=unbondingRecordId,proto3" json:"unbonding_record_id,omitempty"` + // Redeemer + Redeemer string `protobuf:"bytes,2,opt,name=redeemer,proto3" json:"redeemer,omitempty"` + // The amount of stTokens that were redeemed + StTokenAmount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,3,opt,name=st_token_amount,json=stTokenAmount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"st_token_amount"` + // The corresponding amount of native tokens that should be unbonded + NativeAmount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,4,opt,name=native_amount,json=nativeAmount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"native_amount"` +} + +func (m *RedemptionRecord) Reset() { *m = RedemptionRecord{} } +func (m *RedemptionRecord) String() string { return proto.CompactTextString(m) } +func (*RedemptionRecord) ProtoMessage() {} +func (*RedemptionRecord) Descriptor() ([]byte, []int) { + return fileDescriptor_d306d9365b78f7b2, []int{3} +} +func (m *RedemptionRecord) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RedemptionRecord) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RedemptionRecord.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 *RedemptionRecord) XXX_Merge(src proto.Message) { + xxx_messageInfo_RedemptionRecord.Merge(m, src) +} +func (m *RedemptionRecord) XXX_Size() int { + return m.Size() +} +func (m *RedemptionRecord) XXX_DiscardUnknown() { + xxx_messageInfo_RedemptionRecord.DiscardUnknown(m) +} + +var xxx_messageInfo_RedemptionRecord proto.InternalMessageInfo + +func (m *RedemptionRecord) GetUnbondingRecordId() uint64 { + if m != nil { + return m.UnbondingRecordId + } + return 0 +} + +func (m *RedemptionRecord) GetRedeemer() string { + if m != nil { + return m.Redeemer + } + return "" +} + +// SlashRecords log adjustments to the delegated balance +type SlashRecord struct { + // The slash record monotonically increasing ID + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + // The Unix timestamp (in seconds) when the slash adjustment was processed on + // stride + Time uint64 `protobuf:"varint,2,opt,name=time,proto3" json:"time,omitempty"` + // The delta by which the total delegated amount changed from slash + NativeAmount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,3,opt,name=native_amount,json=nativeAmount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"native_amount"` + // The address (or addresses) of the validator that was slashed + ValidatorAddress string `protobuf:"bytes,4,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` +} + +func (m *SlashRecord) Reset() { *m = SlashRecord{} } +func (m *SlashRecord) String() string { return proto.CompactTextString(m) } +func (*SlashRecord) ProtoMessage() {} +func (*SlashRecord) Descriptor() ([]byte, []int) { + return fileDescriptor_d306d9365b78f7b2, []int{4} +} +func (m *SlashRecord) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *SlashRecord) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_SlashRecord.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 *SlashRecord) XXX_Merge(src proto.Message) { + xxx_messageInfo_SlashRecord.Merge(m, src) +} +func (m *SlashRecord) XXX_Size() int { + return m.Size() +} +func (m *SlashRecord) XXX_DiscardUnknown() { + xxx_messageInfo_SlashRecord.DiscardUnknown(m) +} + +var xxx_messageInfo_SlashRecord proto.InternalMessageInfo + +func (m *SlashRecord) GetId() uint64 { + if m != nil { + return m.Id + } + return 0 +} + +func (m *SlashRecord) GetTime() uint64 { + if m != nil { + return m.Time + } + return 0 +} + +func (m *SlashRecord) GetValidatorAddress() string { + if m != nil { + return m.ValidatorAddress + } + return "" +} + +func init() { + proto.RegisterEnum("stride.staketia.DelegationRecordStatus", DelegationRecordStatus_name, DelegationRecordStatus_value) + proto.RegisterEnum("stride.staketia.UnbondingRecordStatus", UnbondingRecordStatus_name, UnbondingRecordStatus_value) + proto.RegisterType((*HostZone)(nil), "stride.staketia.HostZone") + proto.RegisterType((*DelegationRecord)(nil), "stride.staketia.DelegationRecord") + proto.RegisterType((*UnbondingRecord)(nil), "stride.staketia.UnbondingRecord") + proto.RegisterType((*RedemptionRecord)(nil), "stride.staketia.RedemptionRecord") + proto.RegisterType((*SlashRecord)(nil), "stride.staketia.SlashRecord") +} + +func init() { proto.RegisterFile("stride/staketia/staketia.proto", fileDescriptor_d306d9365b78f7b2) } + +var fileDescriptor_d306d9365b78f7b2 = []byte{ + // 1119 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x57, 0x4f, 0x6f, 0x1a, 0x47, + 0x14, 0x67, 0x81, 0xd8, 0xe4, 0xc5, 0xc0, 0x32, 0x10, 0xb2, 0xb6, 0x22, 0xe2, 0xfa, 0x90, 0x5a, + 0x69, 0x8d, 0x2b, 0xe7, 0xd2, 0x43, 0x9b, 0x08, 0xc3, 0xc6, 0x59, 0x09, 0x63, 0x77, 0x81, 0x1e, + 0xd2, 0xc3, 0x6a, 0xd8, 0x9d, 0xc0, 0xca, 0xec, 0x2c, 0xda, 0x19, 0x6c, 0x2a, 0xf5, 0xde, 0x4a, + 0xbd, 0xf4, 0xd2, 0x4f, 0xd0, 0x8f, 0xd0, 0x7c, 0x88, 0x48, 0xbd, 0xa4, 0x39, 0x55, 0x3d, 0x44, + 0x95, 0xfd, 0x45, 0xaa, 0x9d, 0xfd, 0x03, 0x06, 0x5b, 0x28, 0x15, 0x97, 0x9e, 0xd8, 0x79, 0xbf, + 0xf7, 0x7e, 0xbf, 0x79, 0xef, 0xcd, 0xec, 0x5b, 0xa0, 0xc2, 0xb8, 0x67, 0x5b, 0x64, 0x9f, 0x71, + 0x7c, 0x46, 0xb8, 0x8d, 0xe3, 0x87, 0xea, 0xc8, 0x73, 0xb9, 0x8b, 0xf2, 0x01, 0x5e, 0x8d, 0xcc, + 0x5b, 0x9b, 0xa6, 0xcb, 0x1c, 0x97, 0x19, 0x02, 0xde, 0x0f, 0x16, 0x81, 0xef, 0x56, 0xa9, 0xef, + 0xf6, 0xdd, 0xc0, 0xee, 0x3f, 0x05, 0xd6, 0x9d, 0x1f, 0x37, 0x20, 0xf3, 0xd2, 0x65, 0xfc, 0x95, + 0x4b, 0x09, 0xda, 0x84, 0x8c, 0x39, 0xc0, 0x36, 0x35, 0x6c, 0x4b, 0x91, 0xb6, 0xa5, 0xdd, 0xbb, + 0xfa, 0xba, 0x58, 0x6b, 0x16, 0xfa, 0x1c, 0x10, 0xc5, 0xdc, 0x3e, 0x27, 0x06, 0x77, 0xcf, 0x08, + 0x35, 0x2c, 0x42, 0x5d, 0x47, 0x49, 0x0a, 0x27, 0x39, 0x40, 0x3a, 0x3e, 0xd0, 0xf0, 0xed, 0xe8, + 0x29, 0x94, 0xaf, 0x79, 0xdb, 0x3d, 0x33, 0x8c, 0x48, 0x89, 0x88, 0xe2, 0x4c, 0x84, 0xd6, 0x33, + 0x83, 0xa0, 0x2a, 0x14, 0xb9, 0x87, 0x29, 0x7b, 0x4d, 0x3c, 0xc3, 0x1c, 0x60, 0x4a, 0xc9, 0xd0, + 0xdf, 0x48, 0x5a, 0x44, 0x14, 0x22, 0xa8, 0x1e, 0x20, 0x9a, 0x85, 0x8e, 0x00, 0x59, 0x64, 0x48, + 0xfa, 0x98, 0xdb, 0x2e, 0x35, 0xb0, 0x65, 0x79, 0x84, 0x31, 0xe5, 0x8e, 0xef, 0x7e, 0xa8, 0xbc, + 0x7f, 0xb3, 0x57, 0x0a, 0xd3, 0xaf, 0x05, 0x48, 0x9b, 0x7b, 0x36, 0xed, 0xeb, 0x85, 0x69, 0x4c, + 0x08, 0xa0, 0xe7, 0x90, 0xf3, 0xc8, 0x05, 0xf6, 0xac, 0x98, 0x64, 0x6d, 0x09, 0x49, 0x36, 0xf0, + 0x8f, 0x08, 0x6a, 0x90, 0xb7, 0xc8, 0xc8, 0x65, 0x36, 0x8f, 0x19, 0xd6, 0x97, 0x30, 0xe4, 0xc2, + 0x80, 0x88, 0xe2, 0x08, 0x90, 0x47, 0x2c, 0xe2, 0x8c, 0xae, 0x25, 0x93, 0x59, 0x96, 0xcc, 0x34, + 0x26, 0x22, 0xfa, 0x1a, 0xb2, 0xe6, 0x10, 0xdb, 0x4e, 0xcc, 0x71, 0x77, 0x09, 0xc7, 0x86, 0x70, + 0x8f, 0xc2, 0xbb, 0xb0, 0xe5, 0x8e, 0x88, 0x87, 0xb9, 0xeb, 0x45, 0x0c, 0x86, 0x4b, 0x8d, 0xe0, + 0x9c, 0x29, 0xb0, 0x84, 0xeb, 0x41, 0x14, 0x1b, 0x9a, 0x4f, 0x68, 0x5b, 0x04, 0xa2, 0x63, 0x28, + 0x33, 0xfc, 0x9a, 0xdc, 0x40, 0x79, 0x6f, 0x09, 0x65, 0xd1, 0x8f, 0x9b, 0xa7, 0xa3, 0x50, 0x1a, + 0x62, 0xc6, 0x8d, 0x99, 0x92, 0x79, 0x98, 0x13, 0x65, 0x43, 0x90, 0x7d, 0xf5, 0xf6, 0xc3, 0xa3, + 0xc4, 0xdf, 0x1f, 0x1e, 0x3d, 0xee, 0xdb, 0x7c, 0x30, 0xee, 0x55, 0x4d, 0xd7, 0x09, 0xaf, 0x42, + 0xf8, 0xb3, 0xc7, 0xac, 0xb3, 0x7d, 0xfe, 0xfd, 0x88, 0xb0, 0x6a, 0x83, 0x98, 0xef, 0xdf, 0xec, + 0x41, 0x28, 0xdd, 0x20, 0xa6, 0x8e, 0x7c, 0x66, 0x3d, 0x26, 0xd6, 0x31, 0x27, 0x88, 0x40, 0x7e, + 0x5e, 0x2a, 0xbb, 0x02, 0xa9, 0x9c, 0x77, 0x5d, 0x66, 0x08, 0x45, 0xc7, 0xa6, 0x0b, 0x59, 0xe5, + 0x56, 0x20, 0x55, 0x70, 0x6c, 0xaa, 0x2f, 0xaa, 0xe1, 0xc9, 0x82, 0x5a, 0x7e, 0x25, 0x6a, 0x78, + 0x32, 0xa7, 0x76, 0x01, 0x9b, 0x7e, 0x6e, 0x36, 0xa5, 0xc4, 0x5b, 0xd0, 0x94, 0x57, 0xa0, 0x59, + 0x76, 0x6c, 0xaa, 0xf9, 0xec, 0x37, 0x08, 0xe3, 0xc9, 0x2d, 0xc2, 0x85, 0x95, 0x08, 0xe3, 0xc9, + 0x4d, 0xc2, 0xdf, 0x41, 0xf4, 0xae, 0x21, 0x96, 0xd1, 0xc3, 0x43, 0x4c, 0x4d, 0xa2, 0x20, 0x21, + 0x58, 0xfd, 0x08, 0x41, 0x8d, 0x72, 0x5d, 0x8e, 0x89, 0x0e, 0x03, 0x1e, 0xf4, 0x25, 0x28, 0x63, + 0xda, 0x73, 0xa9, 0x65, 0xd3, 0xbe, 0x31, 0x22, 0x9e, 0xed, 0x5a, 0x06, 0x23, 0xa6, 0x4b, 0x2d, + 0xa6, 0x14, 0xb7, 0xa5, 0xdd, 0xb4, 0x5e, 0x8e, 0xf1, 0x53, 0x01, 0xb7, 0x03, 0x14, 0x95, 0x61, + 0x6d, 0x80, 0x87, 0x9c, 0x58, 0x4a, 0x69, 0x5b, 0xda, 0xcd, 0xe8, 0xe1, 0x6a, 0xe7, 0x4f, 0x09, + 0xe4, 0x46, 0xfc, 0x6e, 0xd4, 0x89, 0xe9, 0x7a, 0x16, 0xca, 0x41, 0x32, 0x9c, 0x05, 0x69, 0x3d, + 0x69, 0x5b, 0xa8, 0x0d, 0xd9, 0xf0, 0xc5, 0x8e, 0x1d, 0x77, 0x4c, 0x79, 0x30, 0x01, 0x3e, 0x3a, + 0x9f, 0x8d, 0x80, 0xa4, 0x26, 0x38, 0xd0, 0x73, 0x58, 0x63, 0x1c, 0xf3, 0x31, 0x13, 0xd3, 0x21, + 0x77, 0xf0, 0x69, 0x75, 0x6e, 0xac, 0x55, 0xe7, 0xf7, 0xd5, 0x16, 0xee, 0x7a, 0x18, 0x86, 0x1e, + 0xc0, 0x3a, 0x9f, 0x18, 0x03, 0xcc, 0x06, 0xe1, 0xb4, 0x58, 0xe3, 0x93, 0x97, 0x98, 0x0d, 0x76, + 0xfe, 0x48, 0x41, 0xbe, 0x1b, 0x95, 0xe1, 0x96, 0x94, 0x9e, 0xc5, 0xea, 0x49, 0xa1, 0xfe, 0x78, + 0x41, 0x7d, 0x8e, 0x61, 0x4e, 0xfc, 0x5b, 0xc8, 0x33, 0x1e, 0xce, 0xb9, 0xb0, 0x28, 0xa9, 0xff, + 0x54, 0x94, 0x2c, 0xe3, 0x62, 0x20, 0x86, 0x55, 0x59, 0x28, 0x75, 0x7a, 0x05, 0xa5, 0xd6, 0xe0, + 0x93, 0xe9, 0xb1, 0x31, 0x5d, 0x67, 0x34, 0x24, 0xe2, 0x32, 0x70, 0xdb, 0x21, 0xf1, 0xf9, 0xb9, + 0x23, 0x6a, 0x53, 0x89, 0x1d, 0xeb, 0xb1, 0x5f, 0xc7, 0x76, 0x48, 0x74, 0x8e, 0xbe, 0x80, 0xd2, + 0x98, 0xce, 0x0c, 0xe0, 0xa8, 0x03, 0x62, 0x76, 0xea, 0x68, 0x16, 0xeb, 0x88, 0x6e, 0xa0, 0x67, + 0xf0, 0x30, 0xe0, 0x24, 0x56, 0x58, 0x2f, 0x76, 0x41, 0xc8, 0x28, 0x8e, 0x14, 0x33, 0x53, 0x57, + 0x22, 0x1f, 0x51, 0x8c, 0xb6, 0xef, 0x11, 0xc4, 0xef, 0xfc, 0x9c, 0x04, 0x79, 0xe6, 0x8e, 0x05, + 0xed, 0xac, 0x42, 0x71, 0x9a, 0x91, 0x27, 0x6c, 0x46, 0xdc, 0xdf, 0xc2, 0xf8, 0x7a, 0xeb, 0x34, + 0x0b, 0x6d, 0x41, 0xc6, 0x7f, 0x09, 0x10, 0x87, 0x78, 0xe1, 0xe7, 0x4b, 0xbc, 0xfe, 0x5f, 0xb5, + 0x72, 0xe7, 0x77, 0x09, 0xee, 0xb5, 0x87, 0x98, 0x0d, 0x6e, 0x39, 0xd7, 0x08, 0xd2, 0x7e, 0x57, + 0x45, 0x92, 0x69, 0x5d, 0x3c, 0x2f, 0x6e, 0x24, 0xb5, 0x82, 0x33, 0xf5, 0x19, 0x14, 0xce, 0xf1, + 0xd0, 0xb6, 0x66, 0xbf, 0x19, 0xc2, 0x7b, 0x28, 0xc7, 0x40, 0x38, 0xc1, 0x9f, 0xfc, 0x00, 0xe5, + 0x9b, 0x2f, 0x33, 0x52, 0xa0, 0xd4, 0xd1, 0x6b, 0xad, 0xf6, 0x0b, 0x55, 0x37, 0xb4, 0x96, 0x71, + 0xaa, 0x9f, 0x1c, 0xe9, 0x6a, 0xbb, 0x2d, 0x27, 0x50, 0x11, 0xf2, 0x31, 0xf2, 0xa2, 0xa6, 0x35, + 0xd5, 0x86, 0x2c, 0xa1, 0x12, 0xc8, 0x0d, 0xb5, 0xa9, 0x1e, 0xd5, 0x3a, 0xda, 0x49, 0xcb, 0xf8, + 0xa6, 0xab, 0x76, 0x55, 0x39, 0x89, 0x1e, 0x40, 0x71, 0xc6, 0x5a, 0x3f, 0x39, 0x3e, 0x6d, 0xaa, + 0x1d, 0x55, 0x4e, 0x6d, 0xa5, 0x7f, 0xfa, 0xad, 0x92, 0x78, 0xf2, 0xab, 0x04, 0xf7, 0x6f, 0xbc, + 0xcd, 0xe8, 0x21, 0x28, 0xb5, 0x7a, 0xbd, 0x7b, 0xdc, 0x6d, 0xd6, 0x3a, 0x5a, 0xeb, 0xc8, 0xd0, + 0xd5, 0x86, 0x7a, 0x7c, 0xea, 0xb3, 0x84, 0x3b, 0xe8, 0xb6, 0x0e, 0x4f, 0x5a, 0x0d, 0x1f, 0x0a, + 0xb4, 0x24, 0xb4, 0x09, 0xf7, 0xa7, 0xc6, 0xd9, 0x1d, 0x27, 0xd1, 0x06, 0x64, 0x02, 0x48, 0x6d, + 0xc8, 0x29, 0x94, 0x85, 0xbb, 0xf5, 0x66, 0x4d, 0x3b, 0xae, 0x1d, 0x36, 0x55, 0x39, 0x8d, 0xee, + 0xc1, 0xba, 0x58, 0xaa, 0x0d, 0xf9, 0x4e, 0xb0, 0xaf, 0xc3, 0xe6, 0xdb, 0xcb, 0x8a, 0xf4, 0xee, + 0xb2, 0x22, 0xfd, 0x73, 0x59, 0x91, 0x7e, 0xb9, 0xaa, 0x24, 0xde, 0x5d, 0x55, 0x12, 0x7f, 0x5d, + 0x55, 0x12, 0xaf, 0x0e, 0x66, 0x5a, 0x12, 0x7c, 0xfc, 0xec, 0x35, 0x71, 0x8f, 0xed, 0x87, 0x7f, + 0x0c, 0xce, 0x0f, 0x0e, 0xf6, 0x27, 0xd3, 0xbf, 0x07, 0xa2, 0x45, 0xbd, 0x35, 0xf1, 0x69, 0xff, + 0xf4, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb0, 0xdb, 0x16, 0xf1, 0x3e, 0x0c, 0x00, 0x00, +} + +func (m *HostZone) 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 *HostZone) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *HostZone) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Halted { + i-- + if m.Halted { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xa0 + } + if m.UnbondingPeriodSeconds != 0 { + i = encodeVarintStaketia(dAtA, i, uint64(m.UnbondingPeriodSeconds)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x98 + } + { + size := m.DelegatedBalance.Size() + i -= size + if _, err := m.DelegatedBalance.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintStaketia(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x92 + { + size := m.MaxInnerRedemptionRate.Size() + i -= size + if _, err := m.MaxInnerRedemptionRate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintStaketia(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x8a + { + size := m.MinInnerRedemptionRate.Size() + i -= size + if _, err := m.MinInnerRedemptionRate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintStaketia(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x82 + { + size := m.MaxRedemptionRate.Size() + i -= size + if _, err := m.MaxRedemptionRate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintStaketia(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x7a + { + size := m.MinRedemptionRate.Size() + i -= size + if _, err := m.MinRedemptionRate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintStaketia(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x72 + { + size := m.RedemptionRate.Size() + i -= size + if _, err := m.RedemptionRate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintStaketia(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x6a + { + size := m.LastRedemptionRate.Size() + i -= size + if _, err := m.LastRedemptionRate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintStaketia(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x62 + if len(m.SafeAddressOnStride) > 0 { + i -= len(m.SafeAddressOnStride) + copy(dAtA[i:], m.SafeAddressOnStride) + i = encodeVarintStaketia(dAtA, i, uint64(len(m.SafeAddressOnStride))) + i-- + dAtA[i] = 0x5a + } + if len(m.OperatorAddressOnStride) > 0 { + i -= len(m.OperatorAddressOnStride) + copy(dAtA[i:], m.OperatorAddressOnStride) + i = encodeVarintStaketia(dAtA, i, uint64(len(m.OperatorAddressOnStride))) + i-- + dAtA[i] = 0x52 + } + if len(m.ClaimAddress) > 0 { + i -= len(m.ClaimAddress) + copy(dAtA[i:], m.ClaimAddress) + i = encodeVarintStaketia(dAtA, i, uint64(len(m.ClaimAddress))) + i-- + dAtA[i] = 0x4a + } + if len(m.RedemptionAddress) > 0 { + i -= len(m.RedemptionAddress) + copy(dAtA[i:], m.RedemptionAddress) + i = encodeVarintStaketia(dAtA, i, uint64(len(m.RedemptionAddress))) + i-- + dAtA[i] = 0x42 + } + if len(m.DepositAddress) > 0 { + i -= len(m.DepositAddress) + copy(dAtA[i:], m.DepositAddress) + i = encodeVarintStaketia(dAtA, i, uint64(len(m.DepositAddress))) + i-- + dAtA[i] = 0x3a + } + if len(m.RewardAddress) > 0 { + i -= len(m.RewardAddress) + copy(dAtA[i:], m.RewardAddress) + i = encodeVarintStaketia(dAtA, i, uint64(len(m.RewardAddress))) + i-- + dAtA[i] = 0x32 + } + if len(m.DelegationAddress) > 0 { + i -= len(m.DelegationAddress) + copy(dAtA[i:], m.DelegationAddress) + i = encodeVarintStaketia(dAtA, i, uint64(len(m.DelegationAddress))) + i-- + dAtA[i] = 0x2a + } + if len(m.TransferChannelId) > 0 { + i -= len(m.TransferChannelId) + copy(dAtA[i:], m.TransferChannelId) + i = encodeVarintStaketia(dAtA, i, uint64(len(m.TransferChannelId))) + i-- + dAtA[i] = 0x22 + } + if len(m.NativeTokenIbcDenom) > 0 { + i -= len(m.NativeTokenIbcDenom) + copy(dAtA[i:], m.NativeTokenIbcDenom) + i = encodeVarintStaketia(dAtA, i, uint64(len(m.NativeTokenIbcDenom))) + i-- + dAtA[i] = 0x1a + } + if len(m.NativeTokenDenom) > 0 { + i -= len(m.NativeTokenDenom) + copy(dAtA[i:], m.NativeTokenDenom) + i = encodeVarintStaketia(dAtA, i, uint64(len(m.NativeTokenDenom))) + i-- + dAtA[i] = 0x12 + } + if len(m.ChainId) > 0 { + i -= len(m.ChainId) + copy(dAtA[i:], m.ChainId) + i = encodeVarintStaketia(dAtA, i, uint64(len(m.ChainId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *DelegationRecord) 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 *DelegationRecord) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DelegationRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.TxHash) > 0 { + i -= len(m.TxHash) + copy(dAtA[i:], m.TxHash) + i = encodeVarintStaketia(dAtA, i, uint64(len(m.TxHash))) + i-- + dAtA[i] = 0x22 + } + if m.Status != 0 { + i = encodeVarintStaketia(dAtA, i, uint64(m.Status)) + i-- + dAtA[i] = 0x18 + } + { + size := m.NativeAmount.Size() + i -= size + if _, err := m.NativeAmount.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintStaketia(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if m.Id != 0 { + i = encodeVarintStaketia(dAtA, i, uint64(m.Id)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *UnbondingRecord) 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 *UnbondingRecord) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *UnbondingRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.UnbondedTokenSweepTxHash) > 0 { + i -= len(m.UnbondedTokenSweepTxHash) + copy(dAtA[i:], m.UnbondedTokenSweepTxHash) + i = encodeVarintStaketia(dAtA, i, uint64(len(m.UnbondedTokenSweepTxHash))) + i-- + dAtA[i] = 0x3a + } + if len(m.UndelegationTxHash) > 0 { + i -= len(m.UndelegationTxHash) + copy(dAtA[i:], m.UndelegationTxHash) + i = encodeVarintStaketia(dAtA, i, uint64(len(m.UndelegationTxHash))) + i-- + dAtA[i] = 0x32 + } + if m.UnbondingCompletionTimeSeconds != 0 { + i = encodeVarintStaketia(dAtA, i, uint64(m.UnbondingCompletionTimeSeconds)) + i-- + dAtA[i] = 0x28 + } + { + size := m.NativeAmount.Size() + i -= size + if _, err := m.NativeAmount.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintStaketia(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + { + size := m.StTokenAmount.Size() + i -= size + if _, err := m.StTokenAmount.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintStaketia(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if m.Status != 0 { + i = encodeVarintStaketia(dAtA, i, uint64(m.Status)) + i-- + dAtA[i] = 0x10 + } + if m.Id != 0 { + i = encodeVarintStaketia(dAtA, i, uint64(m.Id)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *RedemptionRecord) 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 *RedemptionRecord) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RedemptionRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.NativeAmount.Size() + i -= size + if _, err := m.NativeAmount.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintStaketia(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + { + size := m.StTokenAmount.Size() + i -= size + if _, err := m.StTokenAmount.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintStaketia(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Redeemer) > 0 { + i -= len(m.Redeemer) + copy(dAtA[i:], m.Redeemer) + i = encodeVarintStaketia(dAtA, i, uint64(len(m.Redeemer))) + i-- + dAtA[i] = 0x12 + } + if m.UnbondingRecordId != 0 { + i = encodeVarintStaketia(dAtA, i, uint64(m.UnbondingRecordId)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *SlashRecord) 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 *SlashRecord) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *SlashRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ValidatorAddress) > 0 { + i -= len(m.ValidatorAddress) + copy(dAtA[i:], m.ValidatorAddress) + i = encodeVarintStaketia(dAtA, i, uint64(len(m.ValidatorAddress))) + i-- + dAtA[i] = 0x22 + } + { + size := m.NativeAmount.Size() + i -= size + if _, err := m.NativeAmount.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintStaketia(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if m.Time != 0 { + i = encodeVarintStaketia(dAtA, i, uint64(m.Time)) + i-- + dAtA[i] = 0x10 + } + if m.Id != 0 { + i = encodeVarintStaketia(dAtA, i, uint64(m.Id)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintStaketia(dAtA []byte, offset int, v uint64) int { + offset -= sovStaketia(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *HostZone) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ChainId) + if l > 0 { + n += 1 + l + sovStaketia(uint64(l)) + } + l = len(m.NativeTokenDenom) + if l > 0 { + n += 1 + l + sovStaketia(uint64(l)) + } + l = len(m.NativeTokenIbcDenom) + if l > 0 { + n += 1 + l + sovStaketia(uint64(l)) + } + l = len(m.TransferChannelId) + if l > 0 { + n += 1 + l + sovStaketia(uint64(l)) + } + l = len(m.DelegationAddress) + if l > 0 { + n += 1 + l + sovStaketia(uint64(l)) + } + l = len(m.RewardAddress) + if l > 0 { + n += 1 + l + sovStaketia(uint64(l)) + } + l = len(m.DepositAddress) + if l > 0 { + n += 1 + l + sovStaketia(uint64(l)) + } + l = len(m.RedemptionAddress) + if l > 0 { + n += 1 + l + sovStaketia(uint64(l)) + } + l = len(m.ClaimAddress) + if l > 0 { + n += 1 + l + sovStaketia(uint64(l)) + } + l = len(m.OperatorAddressOnStride) + if l > 0 { + n += 1 + l + sovStaketia(uint64(l)) + } + l = len(m.SafeAddressOnStride) + if l > 0 { + n += 1 + l + sovStaketia(uint64(l)) + } + l = m.LastRedemptionRate.Size() + n += 1 + l + sovStaketia(uint64(l)) + l = m.RedemptionRate.Size() + n += 1 + l + sovStaketia(uint64(l)) + l = m.MinRedemptionRate.Size() + n += 1 + l + sovStaketia(uint64(l)) + l = m.MaxRedemptionRate.Size() + n += 1 + l + sovStaketia(uint64(l)) + l = m.MinInnerRedemptionRate.Size() + n += 2 + l + sovStaketia(uint64(l)) + l = m.MaxInnerRedemptionRate.Size() + n += 2 + l + sovStaketia(uint64(l)) + l = m.DelegatedBalance.Size() + n += 2 + l + sovStaketia(uint64(l)) + if m.UnbondingPeriodSeconds != 0 { + n += 2 + sovStaketia(uint64(m.UnbondingPeriodSeconds)) + } + if m.Halted { + n += 3 + } + return n +} + +func (m *DelegationRecord) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Id != 0 { + n += 1 + sovStaketia(uint64(m.Id)) + } + l = m.NativeAmount.Size() + n += 1 + l + sovStaketia(uint64(l)) + if m.Status != 0 { + n += 1 + sovStaketia(uint64(m.Status)) + } + l = len(m.TxHash) + if l > 0 { + n += 1 + l + sovStaketia(uint64(l)) + } + return n +} + +func (m *UnbondingRecord) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Id != 0 { + n += 1 + sovStaketia(uint64(m.Id)) + } + if m.Status != 0 { + n += 1 + sovStaketia(uint64(m.Status)) + } + l = m.StTokenAmount.Size() + n += 1 + l + sovStaketia(uint64(l)) + l = m.NativeAmount.Size() + n += 1 + l + sovStaketia(uint64(l)) + if m.UnbondingCompletionTimeSeconds != 0 { + n += 1 + sovStaketia(uint64(m.UnbondingCompletionTimeSeconds)) + } + l = len(m.UndelegationTxHash) + if l > 0 { + n += 1 + l + sovStaketia(uint64(l)) + } + l = len(m.UnbondedTokenSweepTxHash) + if l > 0 { + n += 1 + l + sovStaketia(uint64(l)) + } + return n +} + +func (m *RedemptionRecord) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.UnbondingRecordId != 0 { + n += 1 + sovStaketia(uint64(m.UnbondingRecordId)) + } + l = len(m.Redeemer) + if l > 0 { + n += 1 + l + sovStaketia(uint64(l)) + } + l = m.StTokenAmount.Size() + n += 1 + l + sovStaketia(uint64(l)) + l = m.NativeAmount.Size() + n += 1 + l + sovStaketia(uint64(l)) + return n +} + +func (m *SlashRecord) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Id != 0 { + n += 1 + sovStaketia(uint64(m.Id)) + } + if m.Time != 0 { + n += 1 + sovStaketia(uint64(m.Time)) + } + l = m.NativeAmount.Size() + n += 1 + l + sovStaketia(uint64(l)) + l = len(m.ValidatorAddress) + if l > 0 { + n += 1 + l + sovStaketia(uint64(l)) + } + return n +} + +func sovStaketia(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozStaketia(x uint64) (n int) { + return sovStaketia(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *HostZone) 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 ErrIntOverflowStaketia + } + 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: HostZone: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: HostZone: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChainId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChainId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NativeTokenDenom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NativeTokenDenom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NativeTokenIbcDenom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NativeTokenIbcDenom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TransferChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TransferChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DelegationAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DelegationAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RewardAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RewardAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DepositAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DepositAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RedemptionAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RedemptionAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClaimAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClaimAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OperatorAddressOnStride", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.OperatorAddressOnStride = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SafeAddressOnStride", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SafeAddressOnStride = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastRedemptionRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.LastRedemptionRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RedemptionRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.RedemptionRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MinRedemptionRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MinRedemptionRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 15: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxRedemptionRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MaxRedemptionRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 16: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MinInnerRedemptionRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MinInnerRedemptionRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 17: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxInnerRedemptionRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MaxInnerRedemptionRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 18: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DelegatedBalance", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.DelegatedBalance.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 19: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingPeriodSeconds", wireType) + } + m.UnbondingPeriodSeconds = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.UnbondingPeriodSeconds |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 20: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Halted", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Halted = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipStaketia(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthStaketia + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DelegationRecord) 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 ErrIntOverflowStaketia + } + 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: DelegationRecord: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DelegationRecord: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + m.Id = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Id |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NativeAmount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.NativeAmount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + m.Status = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Status |= DelegationRecordStatus(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TxHash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TxHash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipStaketia(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthStaketia + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *UnbondingRecord) 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 ErrIntOverflowStaketia + } + 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: UnbondingRecord: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: UnbondingRecord: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + m.Id = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Id |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + m.Status = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Status |= UnbondingRecordStatus(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StTokenAmount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.StTokenAmount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NativeAmount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.NativeAmount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingCompletionTimeSeconds", wireType) + } + m.UnbondingCompletionTimeSeconds = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.UnbondingCompletionTimeSeconds |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UndelegationTxHash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UndelegationTxHash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondedTokenSweepTxHash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UnbondedTokenSweepTxHash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipStaketia(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthStaketia + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RedemptionRecord) 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 ErrIntOverflowStaketia + } + 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: RedemptionRecord: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RedemptionRecord: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingRecordId", wireType) + } + m.UnbondingRecordId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.UnbondingRecordId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Redeemer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Redeemer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StTokenAmount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.StTokenAmount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NativeAmount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.NativeAmount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipStaketia(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthStaketia + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SlashRecord) 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 ErrIntOverflowStaketia + } + 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: SlashRecord: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SlashRecord: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + m.Id = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Id |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) + } + m.Time = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Time |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NativeAmount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.NativeAmount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStaketia + } + 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 ErrInvalidLengthStaketia + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthStaketia + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValidatorAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipStaketia(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthStaketia + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipStaketia(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowStaketia + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowStaketia + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowStaketia + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthStaketia + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupStaketia + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthStaketia + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthStaketia = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowStaketia = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupStaketia = fmt.Errorf("proto: unexpected end of group") +) \ No newline at end of file diff --git a/x/staketia/types/celestia.go b/x/staketia/types/celestia.go index 8883768bf..1511248ad 100644 --- a/x/staketia/types/celestia.go +++ b/x/staketia/types/celestia.go @@ -16,5 +16,11 @@ const ( SafeAddressOnStride = "stride18p7xg4hj2u3zpk0v9gq68pjyuuua5wa387sjjc" // S3 OperatorAddressOnStride = "stride1ghhu67ttgmxrsyxljfl2tysyayswklvxs7pepw" // OP-STRIDE - CelestiaUnbondingPeriodSeconds = uint64(21 * 24 * 60 * 60) // 21 days + CelestiaUnbondingPeriodDays = 21 + CelestiaUnbondingPeriodSeconds = uint64(CelestiaUnbondingPeriodDays * 24 * 60 * 60) // 21 days + + CelestiaBechPrefix = "celestia" ) + +// The connection ID is stored as a var so it can be overriden in tests +var CelestiaConnectionId = "connection-125" diff --git a/x/staketia/types/errors.go b/x/staketia/types/errors.go index 0c8c35c0b..715086e0b 100644 --- a/x/staketia/types/errors.go +++ b/x/staketia/types/errors.go @@ -29,4 +29,5 @@ var ( ErrDivisionByZero = errorsmod.Register(ModuleName, 1924, "division by zero") ErrInvalidRecordType = errorsmod.Register(ModuleName, 1925, "invalid record type") ErrInvalidGenesisRecords = errorsmod.Register(ModuleName, 1926, "invalid records during genesis") + ErrRedemptionsDisabled = errorsmod.Register(ModuleName, 1927, "redemptions are no longer enabled with staketia") ) diff --git a/x/staketia/types/expected_keepers.go b/x/staketia/types/expected_keepers.go index c39d87c3c..738e95081 100644 --- a/x/staketia/types/expected_keepers.go +++ b/x/staketia/types/expected_keepers.go @@ -6,6 +6,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + + recordtypes "github.com/Stride-Labs/stride/v22/x/records/types" + stakeibctypes "github.com/Stride-Labs/stride/v22/x/stakeibc/types" ) // Required AccountKeeper functions @@ -41,3 +44,19 @@ type RatelimitKeeper interface { type ICAOracleKeeper interface { QueueMetricUpdate(ctx sdk.Context, key, value, metricType, attributes string) } + +// Required StakeibcKeeper functions +type StakeibcKeeper interface { + GetHostZone(ctx sdk.Context, chainId string) (val stakeibctypes.HostZone, found bool) + GetActiveHostZone(ctx sdk.Context, chainId string) (hostZone stakeibctypes.HostZone, err error) + SetHostZone(ctx sdk.Context, hostZone stakeibctypes.HostZone) + RedeemStake(ctx sdk.Context, msg *stakeibctypes.MsgRedeemStake) (*stakeibctypes.MsgRedeemStakeResponse, error) + EnableRedemptions(ctx sdk.Context, chainId string) error + RegisterHostZone(ctx sdk.Context, msg *stakeibctypes.MsgRegisterHostZone) (*stakeibctypes.MsgRegisterHostZoneResponse, error) +} + +// Required RecordsKeeper functions +type RecordsKeeper interface { + GetAllDepositRecord(ctx sdk.Context) (list []recordtypes.DepositRecord) + SetDepositRecord(ctx sdk.Context, depositRecord recordtypes.DepositRecord) +} diff --git a/x/staketia/types/genesis.go b/x/staketia/types/genesis.go index 7baba778e..17ecb4c4b 100644 --- a/x/staketia/types/genesis.go +++ b/x/staketia/types/genesis.go @@ -2,7 +2,6 @@ package types import ( sdkmath "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" ) // DefaultGenesis returns the default genesis state @@ -30,14 +29,8 @@ func DefaultGenesis() *GenesisState { SafeAddressOnStride: SafeAddressOnStride, OperatorAddressOnStride: OperatorAddressOnStride, - RedemptionRate: sdk.OneDec(), - LastRedemptionRate: sdk.OneDec(), - MinRedemptionRate: sdk.MustNewDecFromStr("0.95"), - MaxRedemptionRate: sdk.MustNewDecFromStr("1.1"), - MinInnerRedemptionRate: sdk.MustNewDecFromStr("0.95"), - MaxInnerRedemptionRate: sdk.MustNewDecFromStr("1.1"), - DelegatedBalance: sdkmath.ZeroInt(), - Halted: false, + RemainingDelegatedBalance: sdkmath.ZeroInt(), + Halted: false, }, UnbondingRecords: []UnbondingRecord{ { diff --git a/x/staketia/types/host_zone.go b/x/staketia/types/host_zone.go index c5b9ee25e..d50b833b4 100644 --- a/x/staketia/types/host_zone.go +++ b/x/staketia/types/host_zone.go @@ -76,14 +76,6 @@ func (h HostZone) ValidateGenesis() error { return errorsmod.Wrapf(err, "invalid safe address") } - // Validate the redemption rate bounds are set properly - if !h.RedemptionRate.IsPositive() { - return ErrInvalidHostZone.Wrap("redemption rate must be positive") - } - if err := h.ValidateRedemptionRateBoundsInitalized(); err != nil { - return err - } - // Validate unbonding period is set if h.UnbondingPeriodSeconds == 0 { return ErrInvalidHostZone.Wrap("unbonding period must be set") @@ -91,35 +83,3 @@ func (h HostZone) ValidateGenesis() error { return nil } - -// Verify the redemption rate bounds are set properly on the host zone -func (h HostZone) ValidateRedemptionRateBoundsInitalized() error { - // Validate outer bounds are set - if h.MinRedemptionRate.IsNil() || !h.MinRedemptionRate.IsPositive() { - return ErrInvalidRedemptionRateBounds.Wrapf("min outer redemption rate bound not set") - } - if h.MaxRedemptionRate.IsNil() || !h.MaxRedemptionRate.IsPositive() { - return ErrInvalidRedemptionRateBounds.Wrapf("max outer redemption rate bound not set") - } - - // Validate inner bounds set - if h.MinInnerRedemptionRate.IsNil() || !h.MinInnerRedemptionRate.IsPositive() { - return ErrInvalidRedemptionRateBounds.Wrapf("min inner redemption rate bound not set") - } - if h.MaxInnerRedemptionRate.IsNil() || !h.MaxInnerRedemptionRate.IsPositive() { - return ErrInvalidRedemptionRateBounds.Wrapf("max inner redemption rate bound not set") - } - - // Validate inner bounds are within outer bounds - if h.MinInnerRedemptionRate.LT(h.MinRedemptionRate) { - return ErrInvalidRedemptionRateBounds.Wrapf("min inner redemption rate bound outside of min outer bound") - } - if h.MaxInnerRedemptionRate.GT(h.MaxRedemptionRate) { - return ErrInvalidRedemptionRateBounds.Wrapf("max inner redemption rate bound outside of max outer bound") - } - if h.MinInnerRedemptionRate.GT(h.MaxInnerRedemptionRate) { - return ErrInvalidRedemptionRateBounds.Wrapf("min inner redemption rate greater than max inner bound") - } - - return nil -} diff --git a/x/staketia/types/host_zone_test.go b/x/staketia/types/host_zone_test.go index ca4c49212..b23b3d219 100644 --- a/x/staketia/types/host_zone_test.go +++ b/x/staketia/types/host_zone_test.go @@ -3,7 +3,6 @@ package types_test import ( "testing" - sdk "github.com/cosmos/cosmos-sdk/types" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" "github.com/stretchr/testify/require" @@ -52,14 +51,6 @@ func fillDefaultHostZone(hostZone types.HostZone) types.HostZone { hostZone.OperatorAddressOnStride = fillDefaultValue(hostZone.OperatorAddressOnStride, validAddress) hostZone.SafeAddressOnStride = fillDefaultValue(hostZone.SafeAddressOnStride, validAddress) - if hostZone.RedemptionRate.IsNil() { - hostZone.RedemptionRate = sdk.OneDec() - hostZone.MinRedemptionRate = sdk.MustNewDecFromStr("0.8") - hostZone.MinInnerRedemptionRate = sdk.MustNewDecFromStr("0.9") - hostZone.MaxInnerRedemptionRate = sdk.MustNewDecFromStr("1.1") - hostZone.MaxRedemptionRate = sdk.MustNewDecFromStr("1.2") - } - if hostZone.UnbondingPeriodSeconds == UninitializedInt { hostZone.UnbondingPeriodSeconds = 0 // invalid } else { @@ -200,22 +191,6 @@ func TestValidateHostZoneGenesis(t *testing.T) { }, expectedError: "invalid safe address", }, - { - name: "invalid redemption rate", - hostZone: types.HostZone{ - RedemptionRate: sdk.OneDec().Neg(), - }, - expectedError: "redemption rate must be positive", - }, - { - name: "invalid redemption rate bounds", - hostZone: types.HostZone{ - RedemptionRate: sdk.OneDec(), - MinRedemptionRate: sdk.MustNewDecFromStr("1.1"), - MinInnerRedemptionRate: sdk.MustNewDecFromStr("0.9"), - }, - expectedError: "invalid host zone redemption rate inner bounds", - }, { name: "missing unbonding period", hostZone: types.HostZone{ @@ -238,111 +213,3 @@ func TestValidateHostZoneGenesis(t *testing.T) { }) } } - -func TestValidateRedemptionRateBoundsInitalized(t *testing.T) { - testCases := []struct { - name string - hostZone types.HostZone - valid bool - }{ - { - name: "valid bounds", - hostZone: types.HostZone{ - MinRedemptionRate: sdk.MustNewDecFromStr("0.8"), - MinInnerRedemptionRate: sdk.MustNewDecFromStr("0.9"), - RedemptionRate: sdk.MustNewDecFromStr("1.0"), - MaxInnerRedemptionRate: sdk.MustNewDecFromStr("1.1"), - MaxRedemptionRate: sdk.MustNewDecFromStr("1.2"), - }, - valid: true, - }, - { - name: "min outer negative", - hostZone: types.HostZone{ - MinRedemptionRate: sdk.MustNewDecFromStr("0.8").Neg(), - MinInnerRedemptionRate: sdk.MustNewDecFromStr("0.9"), - RedemptionRate: sdk.MustNewDecFromStr("1.0"), - MaxInnerRedemptionRate: sdk.MustNewDecFromStr("1.1"), - MaxRedemptionRate: sdk.MustNewDecFromStr("1.2"), - }, - valid: false, - }, - { - name: "min inner negative", - hostZone: types.HostZone{ - MinRedemptionRate: sdk.MustNewDecFromStr("0.8"), - MinInnerRedemptionRate: sdk.MustNewDecFromStr("0.9").Neg(), - RedemptionRate: sdk.MustNewDecFromStr("1.0"), - MaxInnerRedemptionRate: sdk.MustNewDecFromStr("1.1"), - MaxRedemptionRate: sdk.MustNewDecFromStr("1.2"), - }, - valid: false, - }, - { - name: "max inner negative", - hostZone: types.HostZone{ - MinRedemptionRate: sdk.MustNewDecFromStr("0.8"), - MinInnerRedemptionRate: sdk.MustNewDecFromStr("0.9"), - RedemptionRate: sdk.MustNewDecFromStr("1.0"), - MaxInnerRedemptionRate: sdk.MustNewDecFromStr("1.1").Neg(), - MaxRedemptionRate: sdk.MustNewDecFromStr("1.2"), - }, - valid: false, - }, - { - name: "max outer negative", - hostZone: types.HostZone{ - MinRedemptionRate: sdk.MustNewDecFromStr("0.8"), - MinInnerRedemptionRate: sdk.MustNewDecFromStr("0.9"), - RedemptionRate: sdk.MustNewDecFromStr("1.0"), - MaxInnerRedemptionRate: sdk.MustNewDecFromStr("1.1"), - MaxRedemptionRate: sdk.MustNewDecFromStr("1.2").Neg(), - }, - valid: false, - }, - { - name: "max inner outside outer", - hostZone: types.HostZone{ - MinRedemptionRate: sdk.MustNewDecFromStr("0.8"), - MinInnerRedemptionRate: sdk.MustNewDecFromStr("0.9"), - RedemptionRate: sdk.MustNewDecFromStr("1.0"), - MaxInnerRedemptionRate: sdk.MustNewDecFromStr("1.3"), // <-- - MaxRedemptionRate: sdk.MustNewDecFromStr("1.2"), - }, - valid: false, - }, - { - name: "min inner outside outer", - hostZone: types.HostZone{ - MinRedemptionRate: sdk.MustNewDecFromStr("0.8"), - MinInnerRedemptionRate: sdk.MustNewDecFromStr("0.7"), // <-- - RedemptionRate: sdk.MustNewDecFromStr("1.0"), - MaxInnerRedemptionRate: sdk.MustNewDecFromStr("1.1"), - MaxRedemptionRate: sdk.MustNewDecFromStr("1.2"), - }, - valid: false, - }, - { - name: "min inner greater than min outer", - hostZone: types.HostZone{ - MinRedemptionRate: sdk.MustNewDecFromStr("0.8"), - MinInnerRedemptionRate: sdk.MustNewDecFromStr("1.1"), // <-- - RedemptionRate: sdk.MustNewDecFromStr("1.0"), - MaxInnerRedemptionRate: sdk.MustNewDecFromStr("0.9"), // <-- - MaxRedemptionRate: sdk.MustNewDecFromStr("1.2"), - }, - valid: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - err := tc.hostZone.ValidateRedemptionRateBoundsInitalized() - if tc.valid { - require.NoError(t, err, "no error expected") - } else { - require.ErrorIs(t, err, types.ErrInvalidRedemptionRateBounds) - } - }) - } -} diff --git a/x/staketia/types/staketia.pb.go b/x/staketia/types/staketia.pb.go index d756658aa..955f8c2cb 100644 --- a/x/staketia/types/staketia.pb.go +++ b/x/staketia/types/staketia.pb.go @@ -142,20 +142,8 @@ type HostZone struct { OperatorAddressOnStride string `protobuf:"bytes,10,opt,name=operator_address_on_stride,json=operatorAddressOnStride,proto3" json:"operator_address_on_stride,omitempty"` // admin address set upon host zone creation, on stride SafeAddressOnStride string `protobuf:"bytes,11,opt,name=safe_address_on_stride,json=safeAddressOnStride,proto3" json:"safe_address_on_stride,omitempty"` - // Previous redemption rate - LastRedemptionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,12,opt,name=last_redemption_rate,json=lastRedemptionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"last_redemption_rate"` - // Current redemption rate - RedemptionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,13,opt,name=redemption_rate,json=redemptionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"redemption_rate"` - // Min outer redemption rate - adjusted by governance - MinRedemptionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,14,opt,name=min_redemption_rate,json=minRedemptionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"min_redemption_rate"` - // Max outer redemption rate - adjusted by governance - MaxRedemptionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,15,opt,name=max_redemption_rate,json=maxRedemptionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"max_redemption_rate"` - // Min inner redemption rate - adjusted by controller - MinInnerRedemptionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,16,opt,name=min_inner_redemption_rate,json=minInnerRedemptionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"min_inner_redemption_rate"` - // Max inner redemption rate - adjusted by controller - MaxInnerRedemptionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,17,opt,name=max_inner_redemption_rate,json=maxInnerRedemptionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"max_inner_redemption_rate"` // Total delegated balance on the host zone delegation account - DelegatedBalance github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,18,opt,name=delegated_balance,json=delegatedBalance,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"delegated_balance"` + RemainingDelegatedBalance github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,18,opt,name=remaining_delegated_balance,json=remainingDelegatedBalance,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"remaining_delegated_balance"` // The undelegation period for Celestia in days UnbondingPeriodSeconds uint64 `protobuf:"varint,19,opt,name=unbonding_period_seconds,json=unbondingPeriodSeconds,proto3" json:"unbonding_period_seconds,omitempty"` // Indicates whether the host zone has been halted @@ -569,89 +557,83 @@ func (m *SlashRecord) GetValidatorAddress() string { } func init() { - proto.RegisterEnum("stride.staketia.DelegationRecordStatus", DelegationRecordStatus_name, DelegationRecordStatus_value) - proto.RegisterEnum("stride.staketia.UnbondingRecordStatus", UnbondingRecordStatus_name, UnbondingRecordStatus_value) - proto.RegisterType((*HostZone)(nil), "stride.staketia.HostZone") - proto.RegisterType((*DelegationRecord)(nil), "stride.staketia.DelegationRecord") - proto.RegisterType((*UnbondingRecord)(nil), "stride.staketia.UnbondingRecord") - proto.RegisterType((*RedemptionRecord)(nil), "stride.staketia.RedemptionRecord") - proto.RegisterType((*SlashRecord)(nil), "stride.staketia.SlashRecord") + proto.RegisterEnum("stride.staketia.legacy.DelegationRecordStatus", DelegationRecordStatus_name, DelegationRecordStatus_value) + proto.RegisterEnum("stride.staketia.legacy.UnbondingRecordStatus", UnbondingRecordStatus_name, UnbondingRecordStatus_value) + proto.RegisterType((*HostZone)(nil), "stride.staketia.legacy.HostZone") + proto.RegisterType((*DelegationRecord)(nil), "stride.staketia.legacy.DelegationRecord") + proto.RegisterType((*UnbondingRecord)(nil), "stride.staketia.legacy.UnbondingRecord") + proto.RegisterType((*RedemptionRecord)(nil), "stride.staketia.legacy.RedemptionRecord") + proto.RegisterType((*SlashRecord)(nil), "stride.staketia.legacy.SlashRecord") } func init() { proto.RegisterFile("stride/staketia/staketia.proto", fileDescriptor_d306d9365b78f7b2) } var fileDescriptor_d306d9365b78f7b2 = []byte{ - // 1119 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x57, 0x4f, 0x6f, 0x1a, 0x47, - 0x14, 0x67, 0x81, 0xd8, 0xe4, 0xc5, 0xc0, 0x32, 0x10, 0xb2, 0xb6, 0x22, 0xe2, 0xfa, 0x90, 0x5a, - 0x69, 0x8d, 0x2b, 0xe7, 0xd2, 0x43, 0x9b, 0x08, 0xc3, 0xc6, 0x59, 0x09, 0x63, 0x77, 0x81, 0x1e, - 0xd2, 0xc3, 0x6a, 0xd8, 0x9d, 0xc0, 0xca, 0xec, 0x2c, 0xda, 0x19, 0x6c, 0x2a, 0xf5, 0xde, 0x4a, - 0xbd, 0xf4, 0xd2, 0x4f, 0xd0, 0x8f, 0xd0, 0x7c, 0x88, 0x48, 0xbd, 0xa4, 0x39, 0x55, 0x3d, 0x44, - 0x95, 0xfd, 0x45, 0xaa, 0x9d, 0xfd, 0x03, 0x06, 0x5b, 0x28, 0x15, 0x97, 0x9e, 0xd8, 0x79, 0xbf, - 0xf7, 0x7e, 0xbf, 0x79, 0xef, 0xcd, 0xec, 0x5b, 0xa0, 0xc2, 0xb8, 0x67, 0x5b, 0x64, 0x9f, 0x71, - 0x7c, 0x46, 0xb8, 0x8d, 0xe3, 0x87, 0xea, 0xc8, 0x73, 0xb9, 0x8b, 0xf2, 0x01, 0x5e, 0x8d, 0xcc, - 0x5b, 0x9b, 0xa6, 0xcb, 0x1c, 0x97, 0x19, 0x02, 0xde, 0x0f, 0x16, 0x81, 0xef, 0x56, 0xa9, 0xef, - 0xf6, 0xdd, 0xc0, 0xee, 0x3f, 0x05, 0xd6, 0x9d, 0x1f, 0x37, 0x20, 0xf3, 0xd2, 0x65, 0xfc, 0x95, - 0x4b, 0x09, 0xda, 0x84, 0x8c, 0x39, 0xc0, 0x36, 0x35, 0x6c, 0x4b, 0x91, 0xb6, 0xa5, 0xdd, 0xbb, - 0xfa, 0xba, 0x58, 0x6b, 0x16, 0xfa, 0x1c, 0x10, 0xc5, 0xdc, 0x3e, 0x27, 0x06, 0x77, 0xcf, 0x08, - 0x35, 0x2c, 0x42, 0x5d, 0x47, 0x49, 0x0a, 0x27, 0x39, 0x40, 0x3a, 0x3e, 0xd0, 0xf0, 0xed, 0xe8, - 0x29, 0x94, 0xaf, 0x79, 0xdb, 0x3d, 0x33, 0x8c, 0x48, 0x89, 0x88, 0xe2, 0x4c, 0x84, 0xd6, 0x33, - 0x83, 0xa0, 0x2a, 0x14, 0xb9, 0x87, 0x29, 0x7b, 0x4d, 0x3c, 0xc3, 0x1c, 0x60, 0x4a, 0xc9, 0xd0, - 0xdf, 0x48, 0x5a, 0x44, 0x14, 0x22, 0xa8, 0x1e, 0x20, 0x9a, 0x85, 0x8e, 0x00, 0x59, 0x64, 0x48, - 0xfa, 0x98, 0xdb, 0x2e, 0x35, 0xb0, 0x65, 0x79, 0x84, 0x31, 0xe5, 0x8e, 0xef, 0x7e, 0xa8, 0xbc, - 0x7f, 0xb3, 0x57, 0x0a, 0xd3, 0xaf, 0x05, 0x48, 0x9b, 0x7b, 0x36, 0xed, 0xeb, 0x85, 0x69, 0x4c, - 0x08, 0xa0, 0xe7, 0x90, 0xf3, 0xc8, 0x05, 0xf6, 0xac, 0x98, 0x64, 0x6d, 0x09, 0x49, 0x36, 0xf0, - 0x8f, 0x08, 0x6a, 0x90, 0xb7, 0xc8, 0xc8, 0x65, 0x36, 0x8f, 0x19, 0xd6, 0x97, 0x30, 0xe4, 0xc2, - 0x80, 0x88, 0xe2, 0x08, 0x90, 0x47, 0x2c, 0xe2, 0x8c, 0xae, 0x25, 0x93, 0x59, 0x96, 0xcc, 0x34, - 0x26, 0x22, 0xfa, 0x1a, 0xb2, 0xe6, 0x10, 0xdb, 0x4e, 0xcc, 0x71, 0x77, 0x09, 0xc7, 0x86, 0x70, - 0x8f, 0xc2, 0xbb, 0xb0, 0xe5, 0x8e, 0x88, 0x87, 0xb9, 0xeb, 0x45, 0x0c, 0x86, 0x4b, 0x8d, 0xe0, - 0x9c, 0x29, 0xb0, 0x84, 0xeb, 0x41, 0x14, 0x1b, 0x9a, 0x4f, 0x68, 0x5b, 0x04, 0xa2, 0x63, 0x28, - 0x33, 0xfc, 0x9a, 0xdc, 0x40, 0x79, 0x6f, 0x09, 0x65, 0xd1, 0x8f, 0x9b, 0xa7, 0xa3, 0x50, 0x1a, - 0x62, 0xc6, 0x8d, 0x99, 0x92, 0x79, 0x98, 0x13, 0x65, 0x43, 0x90, 0x7d, 0xf5, 0xf6, 0xc3, 0xa3, - 0xc4, 0xdf, 0x1f, 0x1e, 0x3d, 0xee, 0xdb, 0x7c, 0x30, 0xee, 0x55, 0x4d, 0xd7, 0x09, 0xaf, 0x42, - 0xf8, 0xb3, 0xc7, 0xac, 0xb3, 0x7d, 0xfe, 0xfd, 0x88, 0xb0, 0x6a, 0x83, 0x98, 0xef, 0xdf, 0xec, - 0x41, 0x28, 0xdd, 0x20, 0xa6, 0x8e, 0x7c, 0x66, 0x3d, 0x26, 0xd6, 0x31, 0x27, 0x88, 0x40, 0x7e, - 0x5e, 0x2a, 0xbb, 0x02, 0xa9, 0x9c, 0x77, 0x5d, 0x66, 0x08, 0x45, 0xc7, 0xa6, 0x0b, 0x59, 0xe5, - 0x56, 0x20, 0x55, 0x70, 0x6c, 0xaa, 0x2f, 0xaa, 0xe1, 0xc9, 0x82, 0x5a, 0x7e, 0x25, 0x6a, 0x78, - 0x32, 0xa7, 0x76, 0x01, 0x9b, 0x7e, 0x6e, 0x36, 0xa5, 0xc4, 0x5b, 0xd0, 0x94, 0x57, 0xa0, 0x59, - 0x76, 0x6c, 0xaa, 0xf9, 0xec, 0x37, 0x08, 0xe3, 0xc9, 0x2d, 0xc2, 0x85, 0x95, 0x08, 0xe3, 0xc9, - 0x4d, 0xc2, 0xdf, 0x41, 0xf4, 0xae, 0x21, 0x96, 0xd1, 0xc3, 0x43, 0x4c, 0x4d, 0xa2, 0x20, 0x21, - 0x58, 0xfd, 0x08, 0x41, 0x8d, 0x72, 0x5d, 0x8e, 0x89, 0x0e, 0x03, 0x1e, 0xf4, 0x25, 0x28, 0x63, - 0xda, 0x73, 0xa9, 0x65, 0xd3, 0xbe, 0x31, 0x22, 0x9e, 0xed, 0x5a, 0x06, 0x23, 0xa6, 0x4b, 0x2d, - 0xa6, 0x14, 0xb7, 0xa5, 0xdd, 0xb4, 0x5e, 0x8e, 0xf1, 0x53, 0x01, 0xb7, 0x03, 0x14, 0x95, 0x61, - 0x6d, 0x80, 0x87, 0x9c, 0x58, 0x4a, 0x69, 0x5b, 0xda, 0xcd, 0xe8, 0xe1, 0x6a, 0xe7, 0x4f, 0x09, - 0xe4, 0x46, 0xfc, 0x6e, 0xd4, 0x89, 0xe9, 0x7a, 0x16, 0xca, 0x41, 0x32, 0x9c, 0x05, 0x69, 0x3d, - 0x69, 0x5b, 0xa8, 0x0d, 0xd9, 0xf0, 0xc5, 0x8e, 0x1d, 0x77, 0x4c, 0x79, 0x30, 0x01, 0x3e, 0x3a, - 0x9f, 0x8d, 0x80, 0xa4, 0x26, 0x38, 0xd0, 0x73, 0x58, 0x63, 0x1c, 0xf3, 0x31, 0x13, 0xd3, 0x21, - 0x77, 0xf0, 0x69, 0x75, 0x6e, 0xac, 0x55, 0xe7, 0xf7, 0xd5, 0x16, 0xee, 0x7a, 0x18, 0x86, 0x1e, - 0xc0, 0x3a, 0x9f, 0x18, 0x03, 0xcc, 0x06, 0xe1, 0xb4, 0x58, 0xe3, 0x93, 0x97, 0x98, 0x0d, 0x76, - 0xfe, 0x48, 0x41, 0xbe, 0x1b, 0x95, 0xe1, 0x96, 0x94, 0x9e, 0xc5, 0xea, 0x49, 0xa1, 0xfe, 0x78, - 0x41, 0x7d, 0x8e, 0x61, 0x4e, 0xfc, 0x5b, 0xc8, 0x33, 0x1e, 0xce, 0xb9, 0xb0, 0x28, 0xa9, 0xff, - 0x54, 0x94, 0x2c, 0xe3, 0x62, 0x20, 0x86, 0x55, 0x59, 0x28, 0x75, 0x7a, 0x05, 0xa5, 0xd6, 0xe0, - 0x93, 0xe9, 0xb1, 0x31, 0x5d, 0x67, 0x34, 0x24, 0xe2, 0x32, 0x70, 0xdb, 0x21, 0xf1, 0xf9, 0xb9, - 0x23, 0x6a, 0x53, 0x89, 0x1d, 0xeb, 0xb1, 0x5f, 0xc7, 0x76, 0x48, 0x74, 0x8e, 0xbe, 0x80, 0xd2, - 0x98, 0xce, 0x0c, 0xe0, 0xa8, 0x03, 0x62, 0x76, 0xea, 0x68, 0x16, 0xeb, 0x88, 0x6e, 0xa0, 0x67, - 0xf0, 0x30, 0xe0, 0x24, 0x56, 0x58, 0x2f, 0x76, 0x41, 0xc8, 0x28, 0x8e, 0x14, 0x33, 0x53, 0x57, - 0x22, 0x1f, 0x51, 0x8c, 0xb6, 0xef, 0x11, 0xc4, 0xef, 0xfc, 0x9c, 0x04, 0x79, 0xe6, 0x8e, 0x05, - 0xed, 0xac, 0x42, 0x71, 0x9a, 0x91, 0x27, 0x6c, 0x46, 0xdc, 0xdf, 0xc2, 0xf8, 0x7a, 0xeb, 0x34, - 0x0b, 0x6d, 0x41, 0xc6, 0x7f, 0x09, 0x10, 0x87, 0x78, 0xe1, 0xe7, 0x4b, 0xbc, 0xfe, 0x5f, 0xb5, - 0x72, 0xe7, 0x77, 0x09, 0xee, 0xb5, 0x87, 0x98, 0x0d, 0x6e, 0x39, 0xd7, 0x08, 0xd2, 0x7e, 0x57, - 0x45, 0x92, 0x69, 0x5d, 0x3c, 0x2f, 0x6e, 0x24, 0xb5, 0x82, 0x33, 0xf5, 0x19, 0x14, 0xce, 0xf1, - 0xd0, 0xb6, 0x66, 0xbf, 0x19, 0xc2, 0x7b, 0x28, 0xc7, 0x40, 0x38, 0xc1, 0x9f, 0xfc, 0x00, 0xe5, - 0x9b, 0x2f, 0x33, 0x52, 0xa0, 0xd4, 0xd1, 0x6b, 0xad, 0xf6, 0x0b, 0x55, 0x37, 0xb4, 0x96, 0x71, - 0xaa, 0x9f, 0x1c, 0xe9, 0x6a, 0xbb, 0x2d, 0x27, 0x50, 0x11, 0xf2, 0x31, 0xf2, 0xa2, 0xa6, 0x35, - 0xd5, 0x86, 0x2c, 0xa1, 0x12, 0xc8, 0x0d, 0xb5, 0xa9, 0x1e, 0xd5, 0x3a, 0xda, 0x49, 0xcb, 0xf8, - 0xa6, 0xab, 0x76, 0x55, 0x39, 0x89, 0x1e, 0x40, 0x71, 0xc6, 0x5a, 0x3f, 0x39, 0x3e, 0x6d, 0xaa, - 0x1d, 0x55, 0x4e, 0x6d, 0xa5, 0x7f, 0xfa, 0xad, 0x92, 0x78, 0xf2, 0xab, 0x04, 0xf7, 0x6f, 0xbc, - 0xcd, 0xe8, 0x21, 0x28, 0xb5, 0x7a, 0xbd, 0x7b, 0xdc, 0x6d, 0xd6, 0x3a, 0x5a, 0xeb, 0xc8, 0xd0, - 0xd5, 0x86, 0x7a, 0x7c, 0xea, 0xb3, 0x84, 0x3b, 0xe8, 0xb6, 0x0e, 0x4f, 0x5a, 0x0d, 0x1f, 0x0a, - 0xb4, 0x24, 0xb4, 0x09, 0xf7, 0xa7, 0xc6, 0xd9, 0x1d, 0x27, 0xd1, 0x06, 0x64, 0x02, 0x48, 0x6d, - 0xc8, 0x29, 0x94, 0x85, 0xbb, 0xf5, 0x66, 0x4d, 0x3b, 0xae, 0x1d, 0x36, 0x55, 0x39, 0x8d, 0xee, - 0xc1, 0xba, 0x58, 0xaa, 0x0d, 0xf9, 0x4e, 0xb0, 0xaf, 0xc3, 0xe6, 0xdb, 0xcb, 0x8a, 0xf4, 0xee, - 0xb2, 0x22, 0xfd, 0x73, 0x59, 0x91, 0x7e, 0xb9, 0xaa, 0x24, 0xde, 0x5d, 0x55, 0x12, 0x7f, 0x5d, - 0x55, 0x12, 0xaf, 0x0e, 0x66, 0x5a, 0x12, 0x7c, 0xfc, 0xec, 0x35, 0x71, 0x8f, 0xed, 0x87, 0x7f, - 0x0c, 0xce, 0x0f, 0x0e, 0xf6, 0x27, 0xd3, 0xbf, 0x07, 0xa2, 0x45, 0xbd, 0x35, 0xf1, 0x69, 0xff, - 0xf4, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb0, 0xdb, 0x16, 0xf1, 0x3e, 0x0c, 0x00, 0x00, + // 1024 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x56, 0x4f, 0x6f, 0x1a, 0xc7, + 0x1b, 0x66, 0x81, 0x60, 0xfc, 0xda, 0xd8, 0xeb, 0x81, 0xd8, 0x6b, 0xff, 0x22, 0xe2, 0x9f, 0x0f, + 0xa9, 0x95, 0xd6, 0xb8, 0x72, 0x2e, 0xbd, 0x34, 0x11, 0x86, 0x8d, 0x43, 0x85, 0xb1, 0xbb, 0x40, + 0x0f, 0xb9, 0xac, 0x06, 0x66, 0x02, 0x2b, 0xb3, 0x33, 0x68, 0x67, 0x70, 0x5c, 0xa9, 0x1f, 0xa0, + 0x52, 0x2f, 0xbd, 0xf4, 0x13, 0xf4, 0x23, 0xb4, 0x1f, 0x22, 0x52, 0x2f, 0x69, 0x4f, 0x55, 0x0f, + 0x51, 0x65, 0x7f, 0x8c, 0x5e, 0xaa, 0x9d, 0x9d, 0x5d, 0xf0, 0x9f, 0x08, 0xb5, 0xca, 0xa5, 0x27, + 0x76, 0xde, 0xe7, 0x7d, 0x9e, 0x99, 0x79, 0xff, 0x31, 0x50, 0x16, 0x32, 0xf0, 0x08, 0xdd, 0x17, + 0x12, 0x9f, 0x51, 0xe9, 0xe1, 0xe4, 0xa3, 0x32, 0x0e, 0xb8, 0xe4, 0x68, 0x35, 0xc2, 0x2b, 0xb1, + 0x79, 0x6b, 0xb3, 0xcf, 0x85, 0xcf, 0x85, 0xab, 0xe0, 0xfd, 0x68, 0x11, 0xf9, 0x6e, 0x95, 0x06, + 0x7c, 0xc0, 0x23, 0x7b, 0xf8, 0x15, 0x59, 0x77, 0xfe, 0xca, 0x41, 0xfe, 0x05, 0x17, 0xf2, 0x25, + 0x67, 0x14, 0x6d, 0x42, 0xbe, 0x3f, 0xc4, 0x1e, 0x73, 0x3d, 0x62, 0x19, 0xdb, 0xc6, 0xee, 0xa2, + 0xb3, 0xa0, 0xd6, 0x0d, 0x82, 0x3e, 0x01, 0xc4, 0xb0, 0xf4, 0xce, 0xa9, 0x2b, 0xf9, 0x19, 0x65, + 0x2e, 0xa1, 0x8c, 0xfb, 0x56, 0x5a, 0x39, 0x99, 0x11, 0xd2, 0x09, 0x81, 0x7a, 0x68, 0x47, 0x4f, + 0x60, 0xfd, 0x9a, 0xb7, 0xd7, 0xeb, 0x6b, 0x46, 0x46, 0x31, 0x8a, 0x33, 0x8c, 0x46, 0xaf, 0x1f, + 0x91, 0x2a, 0x50, 0x94, 0x01, 0x66, 0xe2, 0x15, 0x0d, 0xdc, 0xfe, 0x10, 0x33, 0x46, 0x47, 0xe1, + 0x41, 0xb2, 0x8a, 0xb1, 0x16, 0x43, 0xb5, 0x08, 0x69, 0x10, 0x74, 0x04, 0x88, 0xd0, 0x11, 0x1d, + 0x60, 0xe9, 0x71, 0xe6, 0x62, 0x42, 0x02, 0x2a, 0x84, 0x75, 0x2f, 0x74, 0x3f, 0xb4, 0x7e, 0xfb, + 0x79, 0xaf, 0xa4, 0xaf, 0x5f, 0x8d, 0x90, 0xb6, 0x0c, 0x3c, 0x36, 0x70, 0xd6, 0xa6, 0x1c, 0x0d, + 0xa0, 0x67, 0xb0, 0x12, 0xd0, 0xd7, 0x38, 0x20, 0x89, 0x48, 0x6e, 0x8e, 0x48, 0x21, 0xf2, 0x8f, + 0x05, 0xaa, 0xb0, 0x4a, 0xe8, 0x98, 0x0b, 0x4f, 0x26, 0x0a, 0x0b, 0x73, 0x14, 0x56, 0x34, 0x21, + 0x96, 0x38, 0x02, 0x14, 0x50, 0x42, 0xfd, 0xf1, 0xb5, 0xcb, 0xe4, 0xe7, 0x5d, 0x66, 0xca, 0x89, + 0x85, 0x3e, 0x87, 0x42, 0x7f, 0x84, 0x3d, 0x3f, 0xd1, 0x58, 0x9c, 0xa3, 0xb1, 0xac, 0xdc, 0x63, + 0x7a, 0x17, 0xb6, 0xf8, 0x98, 0x06, 0x58, 0xf2, 0x20, 0x56, 0x70, 0x39, 0x73, 0xa3, 0x3a, 0xb3, + 0x60, 0x8e, 0xd6, 0x46, 0xcc, 0xd5, 0xe6, 0x13, 0xd6, 0x56, 0x44, 0x74, 0x0c, 0xeb, 0x02, 0xbf, + 0xa2, 0x77, 0x48, 0x2e, 0xcd, 0x91, 0x2c, 0x86, 0xbc, 0x9b, 0x72, 0x0c, 0xfe, 0x17, 0x50, 0x1f, + 0x7b, 0xcc, 0x63, 0x03, 0x57, 0x27, 0x94, 0x12, 0xb7, 0x87, 0x47, 0x98, 0xf5, 0xa9, 0x85, 0x94, + 0x66, 0xe5, 0xcd, 0xbb, 0x87, 0xa9, 0x3f, 0xde, 0x3d, 0x7c, 0x34, 0xf0, 0xe4, 0x70, 0xd2, 0xab, + 0xf4, 0xb9, 0xaf, 0x3b, 0x42, 0xff, 0xec, 0x09, 0x72, 0xb6, 0x2f, 0xbf, 0x1e, 0x53, 0x51, 0x69, + 0x30, 0xe9, 0x6c, 0x26, 0x92, 0xf5, 0x58, 0xf1, 0x30, 0x12, 0x44, 0x9f, 0x81, 0x35, 0x61, 0x3d, + 0xce, 0x48, 0xb8, 0xdf, 0x98, 0x06, 0x1e, 0x27, 0xae, 0xa0, 0x7d, 0xce, 0x88, 0xb0, 0x8a, 0xdb, + 0xc6, 0x6e, 0xd6, 0x59, 0x4f, 0xf0, 0x53, 0x05, 0xb7, 0x23, 0x14, 0xad, 0x43, 0x6e, 0x88, 0x47, + 0x92, 0x12, 0xab, 0xb4, 0x6d, 0xec, 0xe6, 0x1d, 0xbd, 0xfa, 0x22, 0x9b, 0x2f, 0x98, 0x2b, 0x3b, + 0xbf, 0x1a, 0x60, 0xd6, 0x93, 0x7a, 0x74, 0x68, 0x9f, 0x07, 0x04, 0xad, 0x40, 0x5a, 0xf7, 0x5f, + 0xd6, 0x49, 0x7b, 0x04, 0xb5, 0xa1, 0xa0, 0x9b, 0x09, 0xfb, 0x7c, 0xc2, 0x64, 0xd4, 0x75, 0xff, + 0xf8, 0x7a, 0xcb, 0x91, 0x48, 0x55, 0x69, 0xa0, 0x67, 0x90, 0x13, 0x12, 0xcb, 0x89, 0x50, 0x1d, + 0xb9, 0x72, 0xf0, 0x51, 0xe5, 0xc6, 0x28, 0xa9, 0xdc, 0x3c, 0x57, 0x5b, 0xb9, 0x3b, 0x9a, 0x86, + 0x36, 0x60, 0x41, 0x5e, 0xb8, 0x43, 0x2c, 0x86, 0xba, 0x43, 0x73, 0xf2, 0xe2, 0x05, 0x16, 0xc3, + 0x9d, 0x5f, 0x32, 0xb0, 0xda, 0x8d, 0x83, 0xf1, 0x9e, 0x2b, 0x3d, 0x4d, 0x76, 0x4f, 0xab, 0xdd, + 0x1f, 0xdd, 0xda, 0xfd, 0x86, 0xc2, 0x8d, 0xcd, 0xbf, 0x82, 0x55, 0x21, 0xf5, 0x6c, 0xd1, 0x41, + 0xc9, 0xfc, 0xab, 0xa0, 0x14, 0x84, 0x54, 0x43, 0x48, 0x47, 0xe5, 0x56, 0xa8, 0xb3, 0x1f, 0x20, + 0xd4, 0x0d, 0xf8, 0xff, 0xb4, 0x78, 0xfa, 0xdc, 0x1f, 0x8f, 0xa8, 0x6a, 0x72, 0xe9, 0xf9, 0x34, + 0xa9, 0xa2, 0x7b, 0x2a, 0x36, 0xe5, 0xc4, 0xb1, 0x96, 0xf8, 0x75, 0x3c, 0x9f, 0xc6, 0xd5, 0xf4, + 0x29, 0x94, 0x26, 0x6c, 0x66, 0xe8, 0xc5, 0x19, 0x50, 0xf3, 0xca, 0x41, 0xb3, 0x58, 0x47, 0x65, + 0x03, 0x3d, 0x85, 0x07, 0x91, 0x26, 0x25, 0x3a, 0x5e, 0xe2, 0x35, 0xa5, 0xe3, 0x84, 0xa9, 0xe6, + 0x94, 0x63, 0xc5, 0x3e, 0x2a, 0x18, 0xed, 0xd0, 0x23, 0xe2, 0xef, 0x7c, 0x97, 0x06, 0xd3, 0x49, + 0x86, 0x8c, 0x4e, 0x67, 0x05, 0x8a, 0xd3, 0x1b, 0x05, 0xca, 0xe6, 0x26, 0xf9, 0x5d, 0x9b, 0x5c, + 0x4f, 0x5d, 0x83, 0xa0, 0x2d, 0xc8, 0x87, 0x83, 0x8a, 0xfa, 0x34, 0xd0, 0x7f, 0x19, 0xc9, 0xfa, + 0x3f, 0x95, 0xca, 0x9d, 0x9f, 0x0c, 0x58, 0x6a, 0x8f, 0xb0, 0x18, 0xbe, 0xa7, 0xae, 0x11, 0x64, + 0xc3, 0xac, 0xaa, 0x4b, 0x66, 0x1d, 0xf5, 0x7d, 0xfb, 0x20, 0x99, 0x0f, 0x50, 0x53, 0x1f, 0xc3, + 0xda, 0x39, 0x1e, 0x79, 0x64, 0x76, 0x4e, 0xeb, 0x3e, 0x34, 0x13, 0x40, 0x4f, 0xcd, 0xc7, 0xdf, + 0xc0, 0xfa, 0xdd, 0xcd, 0x8c, 0x2c, 0x28, 0x75, 0x9c, 0x6a, 0xab, 0xfd, 0xdc, 0x76, 0xdc, 0x46, + 0xcb, 0x3d, 0x75, 0x4e, 0x8e, 0x1c, 0xbb, 0xdd, 0x36, 0x53, 0xa8, 0x08, 0xab, 0x09, 0xf2, 0xbc, + 0xda, 0x68, 0xda, 0x75, 0xd3, 0x40, 0x25, 0x30, 0xeb, 0x76, 0xd3, 0x3e, 0xaa, 0x76, 0x1a, 0x27, + 0x2d, 0xf7, 0xcb, 0xae, 0xdd, 0xb5, 0xcd, 0x34, 0xda, 0x80, 0xe2, 0x8c, 0xb5, 0x76, 0x72, 0x7c, + 0xda, 0xb4, 0x3b, 0xb6, 0x99, 0xd9, 0xca, 0x7e, 0xfb, 0x63, 0x39, 0xf5, 0xf8, 0x07, 0x03, 0xee, + 0xdf, 0xd9, 0xcd, 0xe8, 0x01, 0x58, 0xd5, 0x5a, 0xad, 0x7b, 0xdc, 0x6d, 0x56, 0x3b, 0x8d, 0xd6, + 0x91, 0xeb, 0xd8, 0x75, 0xfb, 0xf8, 0x34, 0x54, 0xd1, 0x27, 0xe8, 0xb6, 0x0e, 0x4f, 0x5a, 0xf5, + 0x10, 0x8a, 0xf6, 0x32, 0xd0, 0x26, 0xdc, 0x9f, 0x1a, 0x67, 0x4f, 0x9c, 0x46, 0xcb, 0x90, 0x8f, + 0x20, 0xbb, 0x6e, 0x66, 0x50, 0x01, 0x16, 0x6b, 0xcd, 0x6a, 0xe3, 0xb8, 0x7a, 0xd8, 0xb4, 0xcd, + 0x2c, 0x5a, 0x82, 0x05, 0xb5, 0xb4, 0xeb, 0xe6, 0xbd, 0xe8, 0x5c, 0x87, 0xcd, 0x37, 0x97, 0x65, + 0xe3, 0xed, 0x65, 0xd9, 0xf8, 0xf3, 0xb2, 0x6c, 0x7c, 0x7f, 0x55, 0x4e, 0xbd, 0xbd, 0x2a, 0xa7, + 0x7e, 0xbf, 0x2a, 0xa7, 0x5e, 0x1e, 0xcc, 0xa4, 0x24, 0xfa, 0xc3, 0xd9, 0x6b, 0xe2, 0x9e, 0xd8, + 0xd7, 0x8f, 0xb1, 0xf3, 0x83, 0x83, 0xfd, 0x8b, 0xe9, 0x93, 0x4c, 0xa5, 0xa8, 0x97, 0x53, 0xcf, + 0xa9, 0x27, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x48, 0x5b, 0x05, 0xbc, 0xb2, 0x09, 0x00, 0x00, } func (m *HostZone) Marshal() (dAtA []byte, err error) { @@ -694,9 +676,9 @@ func (m *HostZone) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x98 } { - size := m.DelegatedBalance.Size() + size := m.RemainingDelegatedBalance.Size() i -= size - if _, err := m.DelegatedBalance.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.RemainingDelegatedBalance.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintStaketia(dAtA, i, uint64(size)) @@ -705,70 +687,6 @@ func (m *HostZone) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x1 i-- dAtA[i] = 0x92 - { - size := m.MaxInnerRedemptionRate.Size() - i -= size - if _, err := m.MaxInnerRedemptionRate.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintStaketia(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1 - i-- - dAtA[i] = 0x8a - { - size := m.MinInnerRedemptionRate.Size() - i -= size - if _, err := m.MinInnerRedemptionRate.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintStaketia(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1 - i-- - dAtA[i] = 0x82 - { - size := m.MaxRedemptionRate.Size() - i -= size - if _, err := m.MaxRedemptionRate.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintStaketia(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x7a - { - size := m.MinRedemptionRate.Size() - i -= size - if _, err := m.MinRedemptionRate.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintStaketia(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x72 - { - size := m.RedemptionRate.Size() - i -= size - if _, err := m.RedemptionRate.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintStaketia(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x6a - { - size := m.LastRedemptionRate.Size() - i -= size - if _, err := m.LastRedemptionRate.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintStaketia(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x62 if len(m.SafeAddressOnStride) > 0 { i -= len(m.SafeAddressOnStride) copy(dAtA[i:], m.SafeAddressOnStride) @@ -1137,19 +1055,7 @@ func (m *HostZone) Size() (n int) { if l > 0 { n += 1 + l + sovStaketia(uint64(l)) } - l = m.LastRedemptionRate.Size() - n += 1 + l + sovStaketia(uint64(l)) - l = m.RedemptionRate.Size() - n += 1 + l + sovStaketia(uint64(l)) - l = m.MinRedemptionRate.Size() - n += 1 + l + sovStaketia(uint64(l)) - l = m.MaxRedemptionRate.Size() - n += 1 + l + sovStaketia(uint64(l)) - l = m.MinInnerRedemptionRate.Size() - n += 2 + l + sovStaketia(uint64(l)) - l = m.MaxInnerRedemptionRate.Size() - n += 2 + l + sovStaketia(uint64(l)) - l = m.DelegatedBalance.Size() + l = m.RemainingDelegatedBalance.Size() n += 2 + l + sovStaketia(uint64(l)) if m.UnbondingPeriodSeconds != 0 { n += 2 + sovStaketia(uint64(m.UnbondingPeriodSeconds)) @@ -1639,213 +1545,9 @@ func (m *HostZone) Unmarshal(dAtA []byte) error { } m.SafeAddressOnStride = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 12: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LastRedemptionRate", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowStaketia - } - 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 ErrInvalidLengthStaketia - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthStaketia - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.LastRedemptionRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 13: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field RedemptionRate", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowStaketia - } - 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 ErrInvalidLengthStaketia - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthStaketia - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.RedemptionRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 14: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MinRedemptionRate", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowStaketia - } - 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 ErrInvalidLengthStaketia - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthStaketia - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.MinRedemptionRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 15: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MaxRedemptionRate", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowStaketia - } - 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 ErrInvalidLengthStaketia - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthStaketia - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.MaxRedemptionRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 16: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MinInnerRedemptionRate", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowStaketia - } - 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 ErrInvalidLengthStaketia - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthStaketia - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.MinInnerRedemptionRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 17: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MaxInnerRedemptionRate", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowStaketia - } - 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 ErrInvalidLengthStaketia - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthStaketia - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.MaxInnerRedemptionRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex case 18: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DelegatedBalance", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field RemainingDelegatedBalance", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -1873,7 +1575,7 @@ func (m *HostZone) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.DelegatedBalance.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.RemainingDelegatedBalance.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/staketia/types/tx.pb.go b/x/staketia/types/tx.pb.go index 329c94fdc..69f131f7e 100644 --- a/x/staketia/types/tx.pb.go +++ b/x/staketia/types/tx.pb.go @@ -61,7 +61,10 @@ func (OverwritableRecordType) EnumDescriptor() ([]byte, []int) { return fileDescriptor_98ceebce67c1ff4c, []int{0} } +// Deprecated: Liquid stakes should be handled in stakeibc // LiquidStake +// +// Deprecated: Do not use. type MsgLiquidStake struct { Staker string `protobuf:"bytes,1,opt,name=staker,proto3" json:"staker,omitempty"` NativeAmount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,2,opt,name=native_amount,json=nativeAmount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"native_amount"` @@ -107,6 +110,7 @@ func (m *MsgLiquidStake) GetStaker() string { return "" } +// Deprecated: Do not use. type MsgLiquidStakeResponse struct { StToken types.Coin `protobuf:"bytes,1,opt,name=st_token,json=stToken,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"st_token"` } @@ -155,6 +159,9 @@ func (m *MsgLiquidStakeResponse) GetStToken() types.Coin { type MsgRedeemStake struct { Redeemer string `protobuf:"bytes,1,opt,name=redeemer,proto3" json:"redeemer,omitempty"` StTokenAmount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,2,opt,name=st_token_amount,json=stTokenAmount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"st_token_amount"` + // The receiver field is a celestia address + // It is only used in the case where the redemption spills over to stakeibc + Receiver string `protobuf:"bytes,3,opt,name=receiver,proto3" json:"receiver,omitempty"` } func (m *MsgRedeemStake) Reset() { *m = MsgRedeemStake{} } @@ -197,6 +204,13 @@ func (m *MsgRedeemStake) GetRedeemer() string { return "" } +func (m *MsgRedeemStake) GetReceiver() string { + if m != nil { + return m.Receiver + } + return "" +} + type MsgRedeemStakeResponse struct { NativeToken types.Coin `protobuf:"bytes,1,opt,name=native_token,json=nativeToken,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"native_token"` } @@ -1260,92 +1274,93 @@ func init() { func init() { proto.RegisterFile("stride/staketia/tx.proto", fileDescriptor_98ceebce67c1ff4c) } var fileDescriptor_98ceebce67c1ff4c = []byte{ - // 1355 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcf, 0x6f, 0x1b, 0xc5, - 0x17, 0xf7, 0xa6, 0x55, 0x7f, 0x8c, 0xbf, 0x6d, 0x9d, 0xfd, 0xb6, 0xae, 0xbd, 0xa5, 0x76, 0xba, - 0x6d, 0xd3, 0xd6, 0x6d, 0x76, 0x89, 0xdb, 0x82, 0x64, 0xb8, 0xc4, 0xb5, 0xd5, 0x46, 0xc4, 0x71, - 0xe5, 0x24, 0x54, 0x94, 0xc3, 0xb2, 0xf6, 0x4e, 0xd6, 0x4b, 0xe3, 0x1d, 0x77, 0x67, 0xec, 0xb8, - 0x42, 0x42, 0xc0, 0xa9, 0xe2, 0x84, 0xc4, 0x89, 0x03, 0x27, 0x24, 0x24, 0xb8, 0xd0, 0x03, 0x37, - 0x2e, 0x1c, 0x7b, 0xac, 0x38, 0x21, 0x0e, 0x05, 0x25, 0x87, 0x8a, 0x7f, 0x00, 0xae, 0x68, 0x7f, - 0x76, 0x76, 0x77, 0xd6, 0x76, 0x0a, 0xb9, 0xc4, 0xde, 0x79, 0x9f, 0x79, 0xef, 0xf3, 0x3e, 0x6f, - 0xf6, 0xcd, 0x8b, 0x41, 0x0e, 0x13, 0xcb, 0xd0, 0xa0, 0x8c, 0x89, 0xfa, 0x00, 0x12, 0x43, 0x95, - 0xc9, 0x48, 0xea, 0x5b, 0x88, 0x20, 0xfe, 0x84, 0x6b, 0x91, 0x7c, 0x8b, 0x70, 0xba, 0x83, 0x70, - 0x0f, 0x61, 0xb9, 0x87, 0x75, 0x79, 0xb8, 0x68, 0x7f, 0xb8, 0x48, 0x61, 0x56, 0xed, 0x19, 0x26, - 0x92, 0x9d, 0xbf, 0xde, 0xd2, 0x49, 0x1d, 0xe9, 0xc8, 0xf9, 0x2a, 0xdb, 0xdf, 0xbc, 0xd5, 0x82, - 0xe7, 0xa1, 0xad, 0x62, 0x28, 0x0f, 0x17, 0xdb, 0x90, 0xa8, 0x8b, 0x72, 0x07, 0x19, 0xa6, 0x67, - 0xcf, 0xbb, 0x76, 0xc5, 0xdd, 0xe8, 0x3e, 0xf8, 0x5b, 0xa3, 0x3c, 0xfd, 0x2f, 0xae, 0x5d, 0xfc, - 0x96, 0x03, 0xc7, 0x1b, 0x58, 0x5f, 0x31, 0x1e, 0x0e, 0x0c, 0x6d, 0xcd, 0xb6, 0xf1, 0x59, 0x70, - 0xc8, 0x01, 0x59, 0x39, 0x6e, 0x8e, 0xbb, 0x7c, 0xb4, 0xe5, 0x3d, 0xf1, 0x6b, 0xe0, 0x98, 0xa9, - 0x12, 0x63, 0x08, 0x15, 0xb5, 0x87, 0x06, 0x26, 0xc9, 0xcd, 0xd8, 0xe6, 0xaa, 0xf4, 0xf4, 0x79, - 0x31, 0xf5, 0xdb, 0xf3, 0xe2, 0xbc, 0x6e, 0x90, 0xee, 0xa0, 0x2d, 0x75, 0x50, 0xcf, 0xa3, 0xe0, - 0x7d, 0x2c, 0x60, 0xed, 0x81, 0x4c, 0x1e, 0xf5, 0x21, 0x96, 0x96, 0x4d, 0xd2, 0xfa, 0x9f, 0xeb, - 0x64, 0xc9, 0xf1, 0x51, 0xb9, 0xf4, 0xd9, 0x8b, 0x27, 0x25, 0x2f, 0xc2, 0xe7, 0x2f, 0x9e, 0x94, - 0x4e, 0x07, 0x44, 0xc3, 0xac, 0xc4, 0x4f, 0x38, 0x90, 0x0d, 0x2f, 0xb5, 0x20, 0xee, 0x23, 0x13, - 0x43, 0x7e, 0x13, 0x1c, 0xc1, 0x44, 0x21, 0xe8, 0x01, 0x34, 0x1d, 0xca, 0xe9, 0x72, 0x5e, 0xf2, - 0x44, 0xb0, 0x15, 0x93, 0x3c, 0xc5, 0xa4, 0x5b, 0xc8, 0x30, 0xab, 0xaf, 0xdb, 0x74, 0xbf, 0xff, - 0xbd, 0x78, 0x79, 0x0a, 0xba, 0xf6, 0x06, 0xdc, 0x3a, 0x8c, 0xc9, 0xba, 0xed, 0x5b, 0xfc, 0xc1, - 0xd5, 0xaa, 0x05, 0x35, 0x08, 0x7b, 0xae, 0x56, 0x02, 0x38, 0x62, 0x39, 0x8f, 0x81, 0x5a, 0xc1, - 0x33, 0xff, 0x2e, 0x38, 0xe1, 0xd3, 0xfa, 0x77, 0x8a, 0x1d, 0xf3, 0x08, 0x78, 0x92, 0x5d, 0xb1, - 0x25, 0x0b, 0xc2, 0xc4, 0x44, 0xa3, 0xe8, 0x89, 0x8f, 0x5d, 0xd1, 0xa8, 0xa5, 0x40, 0x34, 0x13, - 0x78, 0x85, 0xd8, 0x3f, 0xe1, 0xd2, 0x6e, 0x00, 0x57, 0xbc, 0xaf, 0x38, 0x70, 0xb2, 0x81, 0xf5, - 0x5b, 0xc8, 0xdc, 0x34, 0xac, 0x5e, 0x0d, 0x6e, 0x41, 0x5d, 0x25, 0x06, 0x32, 0x6d, 0x09, 0x51, - 0x1f, 0x5a, 0x2a, 0x41, 0x81, 0x84, 0xfe, 0x33, 0x7f, 0x06, 0x1c, 0xb5, 0x60, 0x07, 0x59, 0x9a, - 0x62, 0x68, 0x8e, 0x78, 0x07, 0x6d, 0x7d, 0xed, 0x85, 0x65, 0x8d, 0x3f, 0x0d, 0x0e, 0x93, 0x91, - 0xd2, 0x55, 0x71, 0x37, 0x77, 0xc0, 0x3d, 0xa8, 0x64, 0x74, 0x47, 0xc5, 0xdd, 0x8a, 0xec, 0x08, - 0xe4, 0x3b, 0xb1, 0x05, 0x3a, 0x4b, 0x0b, 0x14, 0xa3, 0x20, 0x16, 0xc0, 0x6b, 0xac, 0x75, 0x5f, - 0x2b, 0xf1, 0x6b, 0x57, 0x46, 0x0f, 0xb0, 0x61, 0x6a, 0xfb, 0xc9, 0x7e, 0x31, 0xc6, 0xbe, 0xc8, - 0x60, 0x4f, 0x93, 0x10, 0xe7, 0x40, 0x81, 0x6d, 0x09, 0x32, 0xf8, 0x8e, 0xa3, 0x53, 0xdc, 0x30, - 0xdb, 0xc8, 0xd4, 0xa0, 0xe6, 0x54, 0x66, 0x6d, 0x1b, 0xc2, 0xfe, 0x3e, 0xe4, 0xf1, 0x66, 0x2c, - 0x8f, 0x8b, 0xcc, 0x3c, 0xa2, 0x54, 0xc4, 0x79, 0x70, 0x61, 0x9c, 0x3d, 0xc8, 0xe9, 0x6f, 0x0e, - 0xe4, 0x1b, 0x58, 0x5f, 0xd2, 0x3e, 0x1c, 0x60, 0xe2, 0x55, 0x0d, 0x6a, 0x55, 0x75, 0x4b, 0x35, - 0x3b, 0x70, 0x6c, 0x42, 0xef, 0x83, 0xd9, 0x97, 0x1a, 0x29, 0x68, 0x73, 0x13, 0xc3, 0x57, 0x7d, - 0x37, 0x33, 0x2f, 0x1d, 0x35, 0x1d, 0x3f, 0xfc, 0x55, 0x30, 0x3b, 0x54, 0xb7, 0x0c, 0xcd, 0x8e, - 0xa4, 0xa8, 0x9a, 0x66, 0x41, 0x8c, 0x3d, 0x69, 0x32, 0x81, 0x61, 0xc9, 0x5d, 0xaf, 0xdc, 0x88, - 0x89, 0x24, 0xd2, 0x22, 0xb1, 0x73, 0x13, 0xcf, 0x83, 0x73, 0x89, 0xc6, 0x40, 0x9e, 0x3f, 0x67, - 0x80, 0xd8, 0xc0, 0xfa, 0x46, 0x5f, 0x53, 0x09, 0x5c, 0x36, 0x4d, 0x68, 0xd9, 0x6d, 0xa0, 0xd7, - 0x77, 0xce, 0x85, 0x4a, 0x60, 0x15, 0x0d, 0x4c, 0x0d, 0xf3, 0x39, 0x70, 0xb8, 0x63, 0x41, 0x4a, - 0x26, 0xff, 0x91, 0xdf, 0x06, 0xf9, 0x9e, 0x61, 0x2a, 0x86, 0xbd, 0x55, 0xb1, 0x82, 0xbd, 0x8a, - 0xa5, 0x12, 0xe8, 0xa9, 0xf5, 0xf6, 0x1e, 0xd4, 0xaa, 0xc1, 0xce, 0x2f, 0x3f, 0x2e, 0x00, 0xaf, - 0xbf, 0xd4, 0x60, 0xa7, 0x95, 0xed, 0x19, 0x26, 0x83, 0x98, 0x13, 0x58, 0x1d, 0x25, 0x04, 0x3e, - 0xf0, 0x9f, 0x04, 0x56, 0x47, 0x8c, 0xc0, 0xee, 0x91, 0xf5, 0xf3, 0xb7, 0x8b, 0x31, 0x4f, 0x17, - 0xc3, 0x55, 0x92, 0x25, 0xa2, 0x78, 0x0d, 0x94, 0x26, 0x4b, 0x1d, 0x54, 0xe6, 0x3e, 0x98, 0x75, - 0x9a, 0x32, 0x1e, 0xf4, 0xe0, 0x1d, 0x84, 0xc9, 0x7d, 0x64, 0xc2, 0xe4, 0x3a, 0x54, 0xae, 0x46, - 0x59, 0x09, 0xe1, 0x76, 0x4f, 0xbb, 0x11, 0xcf, 0x38, 0xef, 0x44, 0x78, 0x31, 0x08, 0xdc, 0x05, - 0x39, 0xc7, 0xb8, 0x69, 0x41, 0xdc, 0x8d, 0x88, 0x9e, 0x1c, 0xbf, 0x1c, 0x8d, 0x7f, 0x2e, 0x1c, - 0x9f, 0xe1, 0x4d, 0x14, 0xc1, 0x5c, 0x92, 0x2d, 0x60, 0xf3, 0xb3, 0xdb, 0x93, 0x9a, 0x43, 0x68, - 0x6d, 0x5b, 0x06, 0x81, 0x74, 0xe3, 0xb5, 0x9b, 0xcb, 0x98, 0xa3, 0xb9, 0x1a, 0x7a, 0x81, 0xdd, - 0x5e, 0xe4, 0x1c, 0xc9, 0x74, 0xf9, 0x9c, 0x14, 0x99, 0xbf, 0xa4, 0xa8, 0x5f, 0xfa, 0x9d, 0x75, - 0x57, 0x2a, 0x6f, 0x44, 0x53, 0x0c, 0xb5, 0xaa, 0x44, 0x86, 0x5e, 0xab, 0x4a, 0xb4, 0x07, 0xa9, - 0xfe, 0xc4, 0x81, 0x33, 0x34, 0xd0, 0xed, 0x6a, 0x86, 0xa9, 0x4f, 0xcc, 0xf4, 0x1d, 0x90, 0x19, - 0xf8, 0xe0, 0x70, 0xa2, 0x73, 0xb1, 0x44, 0x23, 0x5e, 0x5b, 0x27, 0x06, 0xe1, 0x85, 0xca, 0xcd, - 0x68, 0x9a, 0x17, 0x98, 0x69, 0x46, 0xfc, 0x88, 0x17, 0xc1, 0xf9, 0x31, 0xe6, 0xc4, 0x7a, 0x52, - 0x65, 0x9f, 0xa2, 0x9e, 0xf4, 0x7b, 0x3e, 0xbe, 0x9e, 0x51, 0xbf, 0xad, 0x8c, 0x15, 0x59, 0x99, - 0xb6, 0x9e, 0x51, 0x4f, 0xd1, 0x7a, 0xc6, 0x22, 0xf9, 0xa9, 0x7e, 0x04, 0x4e, 0x35, 0xb0, 0xbe, - 0x06, 0x49, 0xd3, 0xeb, 0xdc, 0x5e, 0x3f, 0x77, 0x66, 0x67, 0x43, 0x37, 0xa9, 0xd9, 0xd9, 0x79, - 0x0a, 0xdd, 0x46, 0x33, 0xe1, 0xdb, 0xa8, 0x22, 0xb9, 0x23, 0xb0, 0x03, 0xb4, 0xb9, 0x16, 0x68, - 0xae, 0xf1, 0x18, 0x62, 0x11, 0x9c, 0x65, 0x1a, 0x7c, 0x76, 0xa5, 0x87, 0x20, 0xeb, 0xa7, 0xa0, - 0xb6, 0xb7, 0xa0, 0xcb, 0x7d, 0xfd, 0x51, 0xdf, 0xbe, 0x14, 0xb3, 0xad, 0xfa, 0xad, 0x66, 0xab, - 0xa6, 0xac, 0xbf, 0x77, 0xb7, 0xae, 0xd4, 0xea, 0x2b, 0xf5, 0xdb, 0x4b, 0xeb, 0xcb, 0xcd, 0xd5, - 0x4c, 0x8a, 0xcf, 0x83, 0x53, 0xb4, 0x6d, 0x63, 0xb5, 0xda, 0x5c, 0xad, 0x2d, 0xaf, 0xde, 0xce, - 0x70, 0xd1, 0x6d, 0xad, 0x7a, 0xad, 0xde, 0xb8, 0xeb, 0x6c, 0x9b, 0x11, 0x0e, 0x3e, 0xfe, 0xa6, - 0x90, 0x2a, 0xff, 0x95, 0x06, 0x07, 0x1a, 0x58, 0xe7, 0xef, 0x81, 0x34, 0xfd, 0xaf, 0x44, 0x31, - 0x56, 0xbc, 0xf0, 0x08, 0x2f, 0x5c, 0x9a, 0x00, 0x08, 0xc6, 0xd5, 0x7b, 0x20, 0x4d, 0xcf, 0xdd, - 0x4c, 0xc7, 0x14, 0x80, 0xed, 0x98, 0x35, 0x07, 0x1b, 0x60, 0x36, 0x3e, 0x93, 0x5e, 0x64, 0xed, - 0x8e, 0xc1, 0x84, 0x85, 0xa9, 0x60, 0x41, 0x28, 0x04, 0xfe, 0xcf, 0x1a, 0x21, 0x2f, 0x8d, 0xf1, - 0x42, 0x03, 0x05, 0x79, 0x4a, 0x60, 0x10, 0xf0, 0x53, 0x0e, 0xe4, 0x93, 0x47, 0xbe, 0x85, 0xb1, - 0xee, 0xa2, 0x70, 0xe1, 0xe6, 0x9e, 0xe0, 0x01, 0x87, 0x11, 0xc8, 0x26, 0x4c, 0x68, 0x25, 0x96, - 0x43, 0x36, 0x56, 0x28, 0x4f, 0x8f, 0x0d, 0x22, 0x7f, 0xc9, 0x81, 0xe2, 0xa4, 0xe9, 0xe7, 0x3a, - 0xcb, 0xef, 0x84, 0x4d, 0xc2, 0x5b, 0xaf, 0xb0, 0x29, 0x60, 0xf5, 0x01, 0x38, 0x1e, 0xb9, 0xf9, - 0x45, 0xf6, 0x51, 0xa5, 0x31, 0x42, 0x69, 0x32, 0x26, 0x88, 0x30, 0x00, 0xa7, 0xd8, 0x57, 0xfc, - 0x15, 0xb6, 0x13, 0x06, 0x54, 0x58, 0x9c, 0x1a, 0x1a, 0x3a, 0x6c, 0xc9, 0x77, 0x39, 0xf3, 0xb0, - 0x25, 0xc2, 0xd9, 0x87, 0x6d, 0xe2, 0x3d, 0xcb, 0x7f, 0x0c, 0x72, 0x89, 0x77, 0xec, 0xb5, 0xb1, - 0x2e, 0x23, 0x68, 0xe1, 0xc6, 0x5e, 0xd0, 0x6c, 0x0d, 0x62, 0xf7, 0xdf, 0x78, 0x0d, 0xa2, 0xf0, - 0x09, 0x1a, 0x24, 0xdd, 0x4d, 0xfc, 0x16, 0xe0, 0x19, 0x17, 0xd3, 0x3c, 0xcb, 0x59, 0x1c, 0x27, - 0x48, 0xd3, 0xe1, 0xfc, 0x68, 0xd5, 0x95, 0xa7, 0x3b, 0x05, 0xee, 0xd9, 0x4e, 0x81, 0xfb, 0x63, - 0xa7, 0xc0, 0x7d, 0xb1, 0x5b, 0x48, 0x3d, 0xdb, 0x2d, 0xa4, 0x7e, 0xdd, 0x2d, 0xa4, 0xee, 0x97, - 0xa9, 0xd1, 0x7c, 0xcd, 0xf1, 0xb9, 0xb0, 0xa2, 0xb6, 0xb1, 0xec, 0xfd, 0x20, 0x35, 0x2c, 0x97, - 0xe5, 0x11, 0xf5, 0xf3, 0x99, 0x3d, 0xaa, 0xb7, 0x0f, 0x39, 0x3f, 0x4a, 0x5d, 0xff, 0x27, 0x00, - 0x00, 0xff, 0xff, 0xae, 0x9c, 0xa1, 0xe8, 0x5e, 0x13, 0x00, 0x00, + // 1373 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcf, 0x73, 0xd3, 0xc6, + 0x17, 0x8f, 0x02, 0xc3, 0x8f, 0xcd, 0x17, 0x48, 0xf4, 0x85, 0xe0, 0x88, 0x62, 0x07, 0x01, 0x01, + 0x02, 0x91, 0x1a, 0x03, 0xed, 0x8c, 0xdb, 0x4b, 0x8c, 0x3d, 0x90, 0x69, 0x1c, 0x33, 0x4e, 0x52, + 0xa6, 0xf4, 0xa0, 0xca, 0xd6, 0x46, 0x56, 0x89, 0xb5, 0x46, 0xbb, 0x36, 0x66, 0x3a, 0xd3, 0x99, + 0x76, 0x7a, 0x60, 0x7a, 0xea, 0x4c, 0x4f, 0x3d, 0xf4, 0xd4, 0x4b, 0xdb, 0x13, 0x87, 0xde, 0x7a, + 0xe9, 0x91, 0x99, 0x5e, 0x98, 0x9e, 0x3a, 0x3d, 0xd0, 0x0e, 0x1c, 0x98, 0xfe, 0x03, 0xed, 0xb5, + 0xa3, 0x5d, 0x49, 0xac, 0xa4, 0x95, 0x6d, 0x68, 0x73, 0x89, 0xb5, 0xfb, 0x3e, 0xfb, 0xde, 0xe7, + 0x7d, 0xde, 0x6a, 0xf7, 0x45, 0x20, 0x87, 0x89, 0xe7, 0x58, 0x50, 0xc7, 0xc4, 0xbc, 0x03, 0x89, + 0x63, 0xea, 0x64, 0xa0, 0x75, 0x3d, 0x44, 0x90, 0x7c, 0x84, 0x59, 0xb4, 0xd0, 0xa2, 0x1c, 0x6f, + 0x21, 0xdc, 0x41, 0x58, 0xef, 0x60, 0x5b, 0xef, 0x2f, 0xfb, 0x3f, 0x0c, 0xa9, 0xcc, 0x98, 0x1d, + 0xc7, 0x45, 0x3a, 0xfd, 0x1b, 0x4c, 0x1d, 0xb5, 0x91, 0x8d, 0xe8, 0xa3, 0xee, 0x3f, 0x05, 0xb3, + 0xf9, 0xc0, 0x43, 0xd3, 0xc4, 0x50, 0xef, 0x2f, 0x37, 0x21, 0x31, 0x97, 0xf5, 0x16, 0x72, 0xdc, + 0xc0, 0x3e, 0xc7, 0xec, 0x06, 0x5b, 0xc8, 0x06, 0xe1, 0xd2, 0x24, 0xcf, 0xf0, 0x81, 0xd9, 0xd5, + 0x6f, 0x25, 0x70, 0xb8, 0x86, 0xed, 0x35, 0xe7, 0x6e, 0xcf, 0xb1, 0x36, 0x7c, 0x9b, 0x3c, 0x0b, + 0xf6, 0x51, 0x90, 0x97, 0x93, 0xe6, 0xa5, 0xf3, 0x07, 0x1b, 0xc1, 0x48, 0xde, 0x00, 0x87, 0x5c, + 0x93, 0x38, 0x7d, 0x68, 0x98, 0x1d, 0xd4, 0x73, 0x49, 0x6e, 0xd2, 0x37, 0x97, 0xb5, 0x47, 0x4f, + 0x0a, 0x13, 0xbf, 0x3d, 0x29, 0x2c, 0xd8, 0x0e, 0x69, 0xf7, 0x9a, 0x5a, 0x0b, 0x75, 0x02, 0x0a, + 0xc1, 0xcf, 0x12, 0xb6, 0xee, 0xe8, 0xe4, 0x7e, 0x17, 0x62, 0x6d, 0xd5, 0x25, 0x8d, 0xff, 0x31, + 0x27, 0x2b, 0xd4, 0x47, 0xe9, 0xc2, 0xa7, 0xcf, 0x1f, 0x2e, 0x06, 0x11, 0x3e, 0x7f, 0xfe, 0x70, + 0xf1, 0x78, 0x44, 0x34, 0xce, 0x2a, 0x27, 0xa9, 0x9f, 0x49, 0x60, 0x36, 0x3e, 0xd9, 0x80, 0xb8, + 0x8b, 0x5c, 0x0c, 0xe5, 0x6d, 0x70, 0x00, 0x13, 0x83, 0xa0, 0x3b, 0xd0, 0xa5, 0xa4, 0xa7, 0x8a, + 0x73, 0x5a, 0x20, 0x83, 0xaf, 0x99, 0x16, 0x68, 0xa6, 0x5d, 0x43, 0x8e, 0x5b, 0x7e, 0xdd, 0x27, + 0xfc, 0xfd, 0xef, 0x85, 0xf3, 0x63, 0x10, 0xf6, 0x17, 0xe0, 0xc6, 0x7e, 0x4c, 0x36, 0x7d, 0xdf, + 0xa5, 0xc9, 0x9c, 0xa4, 0xfe, 0xcc, 0x14, 0x6b, 0x40, 0x0b, 0xc2, 0x0e, 0x53, 0x4c, 0x01, 0x07, + 0x3c, 0x3a, 0x8c, 0x34, 0x8b, 0xc6, 0xf2, 0xbb, 0xe0, 0x48, 0x48, 0xed, 0xdf, 0xe9, 0x76, 0x28, + 0x20, 0xc1, 0x84, 0x63, 0x31, 0x5b, 0xd0, 0xe9, 0x43, 0x2f, 0xb7, 0x27, 0x8c, 0xc9, 0xc6, 0x4c, + 0xd4, 0x88, 0x42, 0x4a, 0x56, 0x8e, 0xba, 0xfa, 0x80, 0x89, 0xca, 0x4d, 0x45, 0xa2, 0xba, 0x20, + 0x28, 0xd5, 0xee, 0x09, 0x3b, 0xc5, 0x02, 0xd0, 0xbc, 0xd4, 0xaf, 0x24, 0x70, 0xb4, 0x86, 0xed, + 0x6b, 0xc8, 0xdd, 0x76, 0xbc, 0x4e, 0x05, 0xee, 0x40, 0xdb, 0x24, 0x0e, 0x72, 0xfd, 0x54, 0x51, + 0x17, 0x7a, 0x26, 0x41, 0x91, 0xbc, 0xe1, 0x58, 0x3e, 0x01, 0x0e, 0x7a, 0xb0, 0x85, 0x3c, 0xcb, + 0x70, 0x2c, 0x2a, 0xec, 0x5e, 0xaa, 0x03, 0xf2, 0xac, 0x55, 0x4b, 0x3e, 0x0e, 0xf6, 0x93, 0x81, + 0xd1, 0x36, 0x71, 0x3b, 0x90, 0x68, 0x1f, 0x19, 0xdc, 0x30, 0x71, 0xbb, 0xa4, 0x53, 0x81, 0x42, + 0x27, 0xbe, 0x40, 0x27, 0x79, 0x81, 0x52, 0x14, 0xd4, 0x3c, 0x78, 0x4d, 0x34, 0x1f, 0x6a, 0xa5, + 0x7e, 0xcd, 0x64, 0x0c, 0x00, 0x5b, 0xae, 0xb5, 0x9b, 0xec, 0x97, 0x53, 0xec, 0x0b, 0x02, 0xf6, + 0x3c, 0x09, 0x75, 0x1e, 0xe4, 0xc5, 0x96, 0x28, 0x83, 0xef, 0x24, 0x3e, 0xc5, 0x2d, 0xb7, 0x89, + 0x5c, 0x0b, 0x5a, 0xb4, 0x32, 0x1b, 0xf7, 0x20, 0xec, 0xee, 0x42, 0x1e, 0x6f, 0xa6, 0xf2, 0x38, + 0x2b, 0xcc, 0x23, 0x49, 0x45, 0x5d, 0x00, 0x67, 0x86, 0xd9, 0xa3, 0x9c, 0xfe, 0x96, 0xc0, 0x5c, + 0x0d, 0xdb, 0x2b, 0xd6, 0x87, 0x3d, 0x4c, 0x82, 0xaa, 0x41, 0xab, 0x6c, 0xee, 0x98, 0x6e, 0x0b, + 0x0e, 0x4d, 0xe8, 0x7d, 0x30, 0xf3, 0x42, 0x23, 0x03, 0x6d, 0x6f, 0x63, 0xf8, 0xaa, 0xef, 0xed, + 0xf4, 0x0b, 0x47, 0x75, 0xea, 0x47, 0xbe, 0x08, 0x66, 0xfa, 0xe6, 0x8e, 0x63, 0xf9, 0x91, 0x0c, + 0xd3, 0xb2, 0x3c, 0x88, 0x71, 0x20, 0xcd, 0x74, 0x64, 0x58, 0x61, 0xf3, 0xa5, 0x2b, 0x29, 0x91, + 0x54, 0x5e, 0x24, 0x71, 0x6e, 0xea, 0x69, 0x70, 0x2a, 0xd3, 0x18, 0xc9, 0xf3, 0xe7, 0x24, 0x50, + 0x6b, 0xd8, 0xde, 0xea, 0x5a, 0x26, 0x81, 0xab, 0xae, 0x0b, 0x3d, 0xff, 0x18, 0xe8, 0x74, 0xe9, + 0xbe, 0x30, 0x09, 0x2c, 0xa3, 0x9e, 0x6b, 0x61, 0x39, 0x07, 0xf6, 0xb7, 0x3c, 0xc8, 0xc9, 0x14, + 0x0e, 0xe5, 0x7b, 0x60, 0xae, 0xe3, 0xb8, 0x86, 0xe3, 0x2f, 0x35, 0xbc, 0x68, 0xad, 0xe1, 0x99, + 0x04, 0x06, 0x6a, 0xbd, 0xfd, 0x12, 0x6a, 0x55, 0x60, 0xeb, 0x97, 0x1f, 0x96, 0x40, 0x70, 0xbe, + 0x54, 0x60, 0xab, 0x31, 0xdb, 0x71, 0x5c, 0x01, 0x31, 0x1a, 0xd8, 0x1c, 0x64, 0x04, 0xde, 0xf3, + 0x9f, 0x04, 0x36, 0x07, 0x82, 0xc0, 0x6c, 0xcb, 0x86, 0xf9, 0xfb, 0xc5, 0x58, 0xe0, 0x8b, 0xc1, + 0x94, 0x14, 0x89, 0xa8, 0x5e, 0x02, 0x8b, 0xa3, 0xa5, 0x8e, 0x2a, 0x73, 0x1b, 0xcc, 0xd0, 0x43, + 0x19, 0xf7, 0x3a, 0xf0, 0x06, 0xc2, 0xe4, 0x36, 0x72, 0x61, 0x76, 0x1d, 0x4a, 0x17, 0x93, 0xac, + 0x94, 0xf8, 0x71, 0xcf, 0xbb, 0x51, 0x4f, 0xd0, 0x77, 0x22, 0x3e, 0x19, 0x05, 0x6e, 0x83, 0x1c, + 0x35, 0x6e, 0x7b, 0x10, 0xb7, 0x13, 0xa2, 0x67, 0xc7, 0x2f, 0x26, 0xe3, 0x9f, 0x8a, 0xc7, 0x17, + 0x78, 0x53, 0x55, 0x30, 0x9f, 0x65, 0x8b, 0xd8, 0xfc, 0xc4, 0xce, 0xa4, 0x7a, 0x1f, 0x7a, 0xf7, + 0x3c, 0x87, 0x40, 0xfe, 0xe0, 0xf5, 0x0f, 0x97, 0x21, 0x5b, 0x73, 0x3d, 0xf6, 0x02, 0xb3, 0xb3, + 0x88, 0x6e, 0xc9, 0xa9, 0xe2, 0x29, 0x2d, 0xd1, 0xa1, 0x69, 0x49, 0xbf, 0xfc, 0x3b, 0xcb, 0x66, + 0x4a, 0x6f, 0x24, 0x53, 0x8c, 0x1d, 0x55, 0x99, 0x0c, 0x83, 0xa3, 0x2a, 0xd3, 0x1e, 0xa5, 0xfa, + 0xa3, 0x04, 0x4e, 0xf0, 0x40, 0x76, 0xaa, 0x39, 0xae, 0x3d, 0x32, 0xd3, 0x77, 0xc0, 0x74, 0x2f, + 0x04, 0xc7, 0x13, 0x9d, 0x4f, 0x25, 0x9a, 0xf0, 0xda, 0x38, 0xd2, 0x8b, 0x4f, 0x94, 0xae, 0x26, + 0xd3, 0x3c, 0x23, 0x4c, 0x33, 0xe1, 0x47, 0x3d, 0x0b, 0x4e, 0x0f, 0x31, 0x67, 0xd6, 0x93, 0x2b, + 0xfb, 0x18, 0xf5, 0xe4, 0xdf, 0xf3, 0xe1, 0xf5, 0x4c, 0xfa, 0x6d, 0x4c, 0x7b, 0x89, 0x99, 0x71, + 0xeb, 0x99, 0xf4, 0x94, 0xac, 0x67, 0x2a, 0x52, 0x98, 0xea, 0x47, 0xe0, 0x58, 0x0d, 0xdb, 0x1b, + 0x90, 0xd4, 0x83, 0x93, 0x3b, 0x38, 0xcf, 0x69, 0x77, 0xed, 0xd8, 0x2e, 0xd7, 0x5d, 0xd3, 0x51, + 0xec, 0x36, 0x9a, 0x8c, 0xdf, 0x46, 0x25, 0x8d, 0x35, 0xc9, 0x14, 0xe8, 0x73, 0xcd, 0xf3, 0x5c, + 0xd3, 0x31, 0xd4, 0x02, 0x38, 0x29, 0x34, 0x84, 0xec, 0x16, 0xef, 0x82, 0xd9, 0x30, 0x05, 0xb3, + 0xb9, 0x03, 0x19, 0xf7, 0xcd, 0xfb, 0x5d, 0xff, 0x52, 0x9c, 0x6d, 0x54, 0xaf, 0xd5, 0x1b, 0x15, + 0x63, 0xf3, 0xbd, 0x9b, 0x55, 0xa3, 0x52, 0x5d, 0xab, 0x5e, 0x5f, 0xd9, 0x5c, 0xad, 0xaf, 0x4f, + 0x4f, 0xc8, 0x73, 0xe0, 0x18, 0x6f, 0xdb, 0x5a, 0x2f, 0xd7, 0xd7, 0x2b, 0xab, 0xeb, 0xd7, 0xa7, + 0xa5, 0xe4, 0xb2, 0x46, 0xb5, 0x52, 0xad, 0xdd, 0xa4, 0xcb, 0x26, 0x95, 0xbd, 0x0f, 0xbe, 0xc9, + 0x4f, 0x14, 0xff, 0x9a, 0x02, 0x7b, 0x6a, 0xd8, 0x96, 0x6f, 0x81, 0x29, 0xfe, 0x9f, 0x8d, 0x42, + 0xaa, 0x78, 0xf1, 0x16, 0x5f, 0x39, 0x37, 0x02, 0x10, 0xb5, 0xab, 0xb7, 0xc0, 0x14, 0xdf, 0x93, + 0x0b, 0x1d, 0x73, 0x00, 0xb1, 0x63, 0x51, 0x1f, 0xec, 0x80, 0x99, 0x74, 0x4f, 0x7a, 0x56, 0xb4, + 0x3a, 0x05, 0x53, 0x96, 0xc6, 0x82, 0x45, 0xa1, 0x10, 0xf8, 0xbf, 0xa8, 0x85, 0x3c, 0x37, 0xc4, + 0x0b, 0x0f, 0x54, 0xf4, 0x31, 0x81, 0x51, 0xc0, 0x4f, 0x24, 0x30, 0x97, 0xdd, 0xf2, 0x2d, 0x0d, + 0x75, 0x97, 0x84, 0x2b, 0x57, 0x5f, 0x0a, 0x1e, 0x71, 0x18, 0x80, 0xd9, 0x8c, 0x0e, 0x6d, 0x51, + 0xe4, 0x50, 0x8c, 0x55, 0x8a, 0xe3, 0x63, 0xa3, 0xc8, 0x5f, 0x4a, 0xa0, 0x30, 0xaa, 0xfb, 0xb9, + 0x2c, 0xf2, 0x3b, 0x62, 0x91, 0xf2, 0xd6, 0x2b, 0x2c, 0x8a, 0x58, 0x7d, 0x00, 0x0e, 0x27, 0x6e, + 0x7e, 0x55, 0xbc, 0x55, 0x79, 0x8c, 0xb2, 0x38, 0x1a, 0x13, 0x45, 0xe8, 0x81, 0x63, 0xe2, 0x2b, + 0xfe, 0x82, 0xd8, 0x89, 0x00, 0xaa, 0x2c, 0x8f, 0x0d, 0x8d, 0x6d, 0xb6, 0xec, 0xbb, 0x5c, 0xb8, + 0xd9, 0x32, 0xe1, 0xe2, 0xcd, 0x36, 0xf2, 0x9e, 0x95, 0x3f, 0x06, 0xb9, 0xcc, 0x3b, 0xf6, 0xd2, + 0x50, 0x97, 0x09, 0xb4, 0x72, 0xe5, 0x65, 0xd0, 0x62, 0x0d, 0x52, 0xf7, 0xdf, 0x70, 0x0d, 0x92, + 0xf0, 0x11, 0x1a, 0x64, 0xdd, 0x4d, 0xf2, 0x0e, 0x90, 0x05, 0x17, 0xd3, 0x82, 0xc8, 0x59, 0x1a, + 0xa7, 0x68, 0xe3, 0xe1, 0xc2, 0x68, 0xe5, 0xb5, 0x47, 0x4f, 0xf3, 0xd2, 0xe3, 0xa7, 0x79, 0xe9, + 0x8f, 0xa7, 0x79, 0xe9, 0x8b, 0x67, 0xf9, 0x89, 0xc7, 0xcf, 0xf2, 0x13, 0xbf, 0x3e, 0xcb, 0x4f, + 0xdc, 0x2e, 0x72, 0xad, 0xf9, 0x06, 0xf5, 0xb9, 0xb4, 0x66, 0x36, 0xb1, 0x1e, 0x7c, 0xb2, 0xea, + 0x17, 0x8b, 0xfa, 0x80, 0xfb, 0xc0, 0xe6, 0xb7, 0xea, 0xcd, 0x7d, 0xf4, 0xb3, 0xd5, 0xe5, 0x7f, + 0x02, 0x00, 0x00, 0xff, 0xff, 0x46, 0xd9, 0x3b, 0xe1, 0x80, 0x13, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1987,6 +2002,13 @@ func (m *MsgRedeemStake) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.Receiver) > 0 { + i -= len(m.Receiver) + copy(dAtA[i:], m.Receiver) + i = encodeVarintTx(dAtA, i, uint64(len(m.Receiver))) + i-- + dAtA[i] = 0x1a + } { size := m.StTokenAmount.Size() i -= size @@ -2788,6 +2810,10 @@ func (m *MsgRedeemStake) Size() (n int) { } l = m.StTokenAmount.Size() n += 1 + l + sovTx(uint64(l)) + l = len(m.Receiver) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } return n } @@ -3391,6 +3417,38 @@ func (m *MsgRedeemStake) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Receiver", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + 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 ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Receiver = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:])