From 72026f89f85736b79b58535d0045729f2a76d536 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 17 Aug 2023 16:27:09 +0200 Subject: [PATCH] Create Message for Staking Conversion (#5949) (#6094) * WIP * Almost done, need more test cases... * Finish adding test cases * Use val set * Add changelog * Fix simple problems * Update tx.pb.go * Remove partial unlocking * Use val set delegation and edit test cases * Update proto/osmosis/superfluid/tx.proto Co-authored-by: Adam Tucker * Update x/superfluid/keeper/stake.go Co-authored-by: alpo <62043214+AlpinYukseloglu@users.noreply.github.com> * Romans comment, add balancer check * Adam's comment * Change errors from bool to check error * Add validate basic, bring back partial share migration for liquid gamm shares * Try abstracting check logics from test * Romans comment * Add cli * Fix cli * Update x/superfluid/keeper/stake.go * Update x/superfluid/client/cli/tx.go * Update x/superfluid/keeper/stake.go Co-authored-by: Roman * Roman and Adam * Update x/superfluid/keeper/stake.go * Adams comment * Update x/superfluid/keeper/stake.go Co-authored-by: Roman * Romans comments --------- Co-authored-by: devbot-wizard <141283918+devbot-wizard@users.noreply.github.com> Co-authored-by: Dev Ojha Co-authored-by: Adam Tucker Co-authored-by: alpo <62043214+AlpinYukseloglu@users.noreply.github.com> Co-authored-by: Adam Tucker Co-authored-by: Roman (cherry picked from commit d99138f6a51c8403d04b99a32df806c787c0fb1e) Co-authored-by: Matt, Park <45252226+mattverse@users.noreply.github.com> --- CHANGELOG.md | 1 + app/keepers/keepers.go | 10 +- proto/osmosis/superfluid/tx.proto | 41 ++ x/superfluid/client/cli/tx.go | 54 ++ x/superfluid/keeper/export_test.go | 30 +- x/superfluid/keeper/keeper.go | 24 +- x/superfluid/keeper/migrate.go | 22 +- x/superfluid/keeper/migrate_test.go | 37 +- x/superfluid/keeper/msg_server.go | 11 + x/superfluid/keeper/stake.go | 221 ++++++++ x/superfluid/keeper/stake_test.go | 711 +++++++++++++++++++++++ x/superfluid/types/codec.go | 2 + x/superfluid/types/errors.go | 11 + x/superfluid/types/expected_keepers.go | 15 + x/superfluid/types/msg_test.go | 77 +++ x/superfluid/types/msgs.go | 42 ++ x/superfluid/types/tx.pb.go | 753 ++++++++++++++++++++++--- x/valset-pref/keeper.go | 3 +- x/valset-pref/types/errors.go | 7 + x/valset-pref/validator_set.go | 2 +- 20 files changed, 1942 insertions(+), 132 deletions(-) create mode 100644 x/valset-pref/types/errors.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 22c01ec6b9f..bcb38f984aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [#5874](https://github.com/osmosis-labs/osmosis/pull/5874) Remove Partial Migration from superfluid migration to CL * [#5901](https://github.com/osmosis-labs/osmosis/pull/5901) Adding support for CW pools in ProtoRev * [#5937](https://github.com/osmosis-labs/osmosis/pull/5937) feat: add SetScalingFactorController gov prop +* [#5949](https://github.com/osmosis-labs/osmosis/pull/5949) Add message to convert from superfluid / locks to native staking directly. * [#5939](https://github.com/osmosis-labs/osmosis/pull/5939) Fix: Flip existing twapRecords base/quote price denoms * [#5938](https://github.com/osmosis-labs/osmosis/pull/5938) Chore: Fix valset amino codec diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 93a45b47ff3..7b49bfcf60e 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -394,11 +394,6 @@ func (appKeepers *AppKeepers) InitNormalKeepers( appKeepers.ConcentratedLiquidityKeeper.SetIncentivesKeeper(appKeepers.IncentivesKeeper) appKeepers.GAMMKeeper.SetIncentivesKeeper(appKeepers.IncentivesKeeper) - appKeepers.SuperfluidKeeper = superfluidkeeper.NewKeeper( - appKeepers.keys[superfluidtypes.StoreKey], appKeepers.GetSubspace(superfluidtypes.ModuleName), - *appKeepers.AccountKeeper, appKeepers.BankKeeper, appKeepers.StakingKeeper, appKeepers.DistrKeeper, appKeepers.EpochsKeeper, appKeepers.LockupKeeper, appKeepers.GAMMKeeper, appKeepers.IncentivesKeeper, - lockupkeeper.NewMsgServerImpl(appKeepers.LockupKeeper), appKeepers.ConcentratedLiquidityKeeper) - mintKeeper := mintkeeper.NewKeeper( appKeepers.keys[minttypes.StoreKey], appKeepers.GetSubspace(minttypes.ModuleName), @@ -446,6 +441,11 @@ func (appKeepers *AppKeepers) InitNormalKeepers( appKeepers.ValidatorSetPreferenceKeeper = &validatorSetPreferenceKeeper + appKeepers.SuperfluidKeeper = superfluidkeeper.NewKeeper( + appKeepers.keys[superfluidtypes.StoreKey], appKeepers.GetSubspace(superfluidtypes.ModuleName), + *appKeepers.AccountKeeper, appKeepers.BankKeeper, appKeepers.StakingKeeper, appKeepers.DistrKeeper, appKeepers.EpochsKeeper, appKeepers.LockupKeeper, appKeepers.GAMMKeeper, appKeepers.IncentivesKeeper, + lockupkeeper.NewMsgServerImpl(appKeepers.LockupKeeper), appKeepers.ConcentratedLiquidityKeeper, appKeepers.PoolManagerKeeper, appKeepers.ValidatorSetPreferenceKeeper) + // The last arguments can contain custom message handlers, and custom query handlers, // if we want to allow any custom callbacks supportedFeatures := "iterator,staking,stargate,osmosis,cosmwasm_1_1,cosmwasm_1_2" diff --git a/proto/osmosis/superfluid/tx.proto b/proto/osmosis/superfluid/tx.proto index 4c514aa0646..9ca6df217f5 100644 --- a/proto/osmosis/superfluid/tx.proto +++ b/proto/osmosis/superfluid/tx.proto @@ -52,6 +52,11 @@ service Msg { rpc AddToConcentratedLiquiditySuperfluidPosition( MsgAddToConcentratedLiquiditySuperfluidPosition) returns (MsgAddToConcentratedLiquiditySuperfluidPositionResponse); + + // UnbondConvertAndStake breaks all locks / superfluid staked assets, + // converts them to osmo then stakes the osmo to the designated validator. + rpc UnbondConvertAndStake(MsgUnbondConvertAndStake) + returns (MsgUnbondConvertAndStakeResponse); } message MsgSuperfluidDelegate { @@ -231,4 +236,40 @@ message MsgAddToConcentratedLiquiditySuperfluidPositionResponse { (gogoproto.nullable) = false ]; uint64 lock_id = 4 [ (gogoproto.moretags) = "yaml:\"lock_id\"" ]; +} + +// ===================== MsgUnbondConvertAndStake +message MsgUnbondConvertAndStake { + option (amino.name) = "osmosis/unbond-convert-and-stake"; + + // lock ID to convert and stake. + // lock id with 0 should be provided if converting liquid gamm shares to stake + uint64 lock_id = 1 [ (gogoproto.moretags) = "yaml:\"lock_id\"" ]; + string sender = 2 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; + // validator address to delegate to. + // If provided empty string, we use the validators returned from + // valset-preference module. + string val_addr = 3; + // min_amt_to_stake indicates the minimum amount to stake after conversion + string min_amt_to_stake = 4 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.moretags) = "yaml:\"min_amt_to_stake\"", + (gogoproto.nullable) = false + ]; + // shares_to_convert indicates shares wanted to stake. + // Note that this field is only used for liquid(unlocked) gamm shares. + // For all other cases, this field would be disregarded. + cosmos.base.v1beta1.Coin shares_to_convert = 5 [ + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"shares_to_convert\"", + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coin" + ]; +} + +message MsgUnbondConvertAndStakeResponse { + string total_amt_staked = 1 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.moretags) = "yaml:\"total_amt_staked\"", + (gogoproto.nullable) = false + ]; } \ No newline at end of file diff --git a/x/superfluid/client/cli/tx.go b/x/superfluid/client/cli/tx.go index 0de2f6b6e34..68902957820 100644 --- a/x/superfluid/client/cli/tx.go +++ b/x/superfluid/client/cli/tx.go @@ -34,6 +34,7 @@ func GetTxCmd() *cobra.Command { // NewSuperfluidRedelegateCmd(), NewCmdLockAndSuperfluidDelegate(), NewCmdUnPoolWhitelistedPool(), + NewUnbondConvertAndStake(), ) osmocli.AddTxCmd(cmd, NewCreateFullRangePositionAndSuperfluidDelegateCmd) osmocli.AddTxCmd(cmd, NewAddToConcentratedLiquiditySuperfluidPositionCmd) @@ -423,3 +424,56 @@ func NewUnlockAndMigrateSharesToFullRangeConcentratedPositionCmd() (*osmocli.TxC Example: "unlock-and-migrate-cl 10 25000000000gamm/pool/2 1000000000uosmo,10000000uion", }, &types.MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition{} } + +func NewUnbondConvertAndStake() *cobra.Command { + cmd := &cobra.Command{ + Use: "unbond-convert-and-stake [lock-id] [valAddr] [min-amount-to-stake](optional) [shares-to-convert](optional)", + Short: "instantly unbond any locked gamm shares convert them into osmo and stake", + Example: "unbond-convert-and-stake 10 osmo1xxx 100000uosmo", + Args: cobra.MinimumNArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + txf := tx.NewFactoryCLI(clientCtx, cmd.Flags()).WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) + + sender := clientCtx.GetFromAddress() + lockId, err := strconv.Atoi(args[0]) + if err != nil { + return err + } + + valAddr := args[1] + + var minAmtToStake sdk.Int + // if user provided args for min amount to stake, use it. If not, use empty coin struct + var sharesToConvert sdk.Coin + if len(args) >= 3 { + convertedInt, ok := sdk.NewIntFromString(args[2]) + if !ok { + return fmt.Errorf("Conversion for sdk.Int failed") + } + minAmtToStake = convertedInt + if len(args) == 4 { + coins, err := sdk.ParseCoinNormalized(args[3]) + if err != nil { + return err + } + sharesToConvert = coins + } + } else { + minAmtToStake = sdk.ZeroInt() + sharesToConvert = sdk.Coin{} + } + + msg := types.NewMsgUnbondConvertAndStake(sender, uint64(lockId), valAddr, minAmtToStake, sharesToConvert) + + return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} diff --git a/x/superfluid/keeper/export_test.go b/x/superfluid/keeper/export_test.go index 413303147d5..1be8b4dde18 100644 --- a/x/superfluid/keeper/export_test.go +++ b/x/superfluid/keeper/export_test.go @@ -35,12 +35,12 @@ func (k Keeper) MigrateNonSuperfluidLockBalancerToConcentrated(ctx sdk.Context, return k.migrateNonSuperfluidLockBalancerToConcentrated(ctx, sender, lockId, sharesToMigrate, tokenOutMins) } -func (k Keeper) ValidateSharesToMigrateUnlockAndExitBalancerPool(ctx sdk.Context, sender sdk.AccAddress, poolIdLeaving uint64, lock *lockuptypes.PeriodLock, sharesToMigrate sdk.Coin, tokenOutMins sdk.Coins) (exitCoins sdk.Coins, err error) { - return k.validateSharesToMigrateUnlockAndExitBalancerPool(ctx, sender, poolIdLeaving, lock, sharesToMigrate, tokenOutMins) +func (k Keeper) ForceUnlockAndExitBalancerPool(ctx sdk.Context, sender sdk.AccAddress, poolIdLeaving uint64, lock *lockuptypes.PeriodLock, sharesToMigrate sdk.Coin, tokenOutMins sdk.Coins, exitCoinsLengthIsTwo bool) (exitCoins sdk.Coins, err error) { + return k.forceUnlockAndExitBalancerPool(ctx, sender, poolIdLeaving, lock, sharesToMigrate, tokenOutMins, exitCoinsLengthIsTwo) } -func (k Keeper) RouteMigration(ctx sdk.Context, sender sdk.AccAddress, lockId int64, sharesToMigrate sdk.Coin) (synthLockBeforeMigration lockuptypes.SyntheticLock, migrationType MigrationType, err error) { - return k.routeMigration(ctx, lockId) +func (k Keeper) GetMigrationType(ctx sdk.Context, lockId int64) (synthLockBeforeMigration lockuptypes.SyntheticLock, migrationType MigrationType, err error) { + return k.getMigrationType(ctx, lockId) } func (k Keeper) ValidateMigration(ctx sdk.Context, sender sdk.AccAddress, lockId uint64, sharesToMigrate sdk.Coin) (types.MigrationPoolIDs, *lockuptypes.PeriodLock, time.Duration, error) { @@ -62,3 +62,25 @@ func (k Keeper) GetExistingLockRemainingDuration(ctx sdk.Context, lock *lockupty func (k Keeper) DistributeSuperfluidGauges(ctx sdk.Context) { k.distributeSuperfluidGauges(ctx) } + +func (k Keeper) ConvertLockToStake(ctx sdk.Context, sender sdk.AccAddress, valAddr string, lockId uint64, + minAmtToStake sdk.Int) (totalAmtConverted sdk.Int, err error) { + return k.convertLockToStake(ctx, sender, valAddr, lockId, minAmtToStake) +} + +func (k Keeper) ConvertGammSharesToOsmoAndStake( + ctx sdk.Context, + sender sdk.AccAddress, valAddr string, + poolIdLeaving uint64, exitCoins sdk.Coins, minAmtToStake sdk.Int, originalSuperfluidValAddr string, +) (totalAmtCoverted sdk.Int, err error) { + return k.convertGammSharesToOsmoAndStake(ctx, sender, valAddr, poolIdLeaving, exitCoins, minAmtToStake, originalSuperfluidValAddr) +} + +func (k Keeper) ConvertUnlockedToStake(ctx sdk.Context, sender sdk.AccAddress, valAddr string, sharesToStake sdk.Coin, + minAmtToStake sdk.Int) (totalAmtConverted sdk.Int, err error) { + return k.convertUnlockedToStake(ctx, sender, valAddr, sharesToStake, minAmtToStake) +} + +func (k Keeper) DelegateBaseOnValsetPref(ctx sdk.Context, sender sdk.AccAddress, valAddr, originalSuperfluidValAddr string, totalAmtToStake sdk.Int) error { + return k.delegateBaseOnValsetPref(ctx, sender, valAddr, originalSuperfluidValAddr, totalAmtToStake) +} diff --git a/x/superfluid/keeper/keeper.go b/x/superfluid/keeper/keeper.go index db6c7bc264d..3670570caa6 100644 --- a/x/superfluid/keeper/keeper.go +++ b/x/superfluid/keeper/keeper.go @@ -18,15 +18,17 @@ type Keeper struct { storeKey sdk.StoreKey paramSpace paramtypes.Subspace - ak authkeeper.AccountKeeper - bk types.BankKeeper - sk types.StakingKeeper - ck types.CommunityPoolKeeper - ek types.EpochKeeper - lk types.LockupKeeper - gk types.GammKeeper - ik types.IncentivesKeeper - clk types.ConcentratedKeeper + ak authkeeper.AccountKeeper + bk types.BankKeeper + sk types.StakingKeeper + ck types.CommunityPoolKeeper + ek types.EpochKeeper + lk types.LockupKeeper + gk types.GammKeeper + ik types.IncentivesKeeper + clk types.ConcentratedKeeper + pmk types.PoolManagerKeeper + vspk types.ValSetPreferenceKeeper lms types.LockupMsgServer } @@ -34,7 +36,7 @@ type Keeper struct { var _ govtypes.StakingKeeper = (*Keeper)(nil) // NewKeeper returns an instance of Keeper. -func NewKeeper(storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, ak authkeeper.AccountKeeper, bk types.BankKeeper, sk types.StakingKeeper, dk types.CommunityPoolKeeper, ek types.EpochKeeper, lk types.LockupKeeper, gk types.GammKeeper, ik types.IncentivesKeeper, lms types.LockupMsgServer, clk types.ConcentratedKeeper) *Keeper { +func NewKeeper(storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, ak authkeeper.AccountKeeper, bk types.BankKeeper, sk types.StakingKeeper, dk types.CommunityPoolKeeper, ek types.EpochKeeper, lk types.LockupKeeper, gk types.GammKeeper, ik types.IncentivesKeeper, lms types.LockupMsgServer, clk types.ConcentratedKeeper, pmk types.PoolManagerKeeper, vspk types.ValSetPreferenceKeeper) *Keeper { // set KeyTable if it has not already been set if !paramSpace.HasKeyTable() { paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) @@ -52,6 +54,8 @@ func NewKeeper(storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, ak authkee gk: gk, ik: ik, clk: clk, + pmk: pmk, + vspk: vspk, lms: lms, } diff --git a/x/superfluid/keeper/migrate.go b/x/superfluid/keeper/migrate.go index 9546b2185c6..9e6dc12b7c1 100644 --- a/x/superfluid/keeper/migrate.go +++ b/x/superfluid/keeper/migrate.go @@ -47,7 +47,7 @@ const ( // // Errors if the lock is not found, if the lock is not a balancer pool lock, or if the lock is not owned by the sender. func (k Keeper) RouteLockedBalancerToConcentratedMigration(ctx sdk.Context, sender sdk.AccAddress, providedLockId int64, sharesToMigrate sdk.Coin, tokenOutMins sdk.Coins) (positionData cltypes.CreateFullRangePositionData, migratedPoolIDs types.MigrationPoolIDs, concentratedLockId uint64, err error) { - synthLockBeforeMigration, migrationType, err := k.routeMigration(ctx, providedLockId) + synthLockBeforeMigration, migrationType, err := k.getMigrationType(ctx, providedLockId) if err != nil { return cltypes.CreateFullRangePositionData{}, types.MigrationPoolIDs{}, 0, err } @@ -105,7 +105,7 @@ func (k Keeper) migrateSuperfluidBondedBalancerToConcentrated(ctx sdk.Context, // Force unlock, validate the provided sharesToMigrate, and exit the balancer pool. // This will return the coins that will be used to create the concentrated liquidity position. // It also returns the lock object that contains the remaining shares that were not used in this migration. - exitCoins, err := k.validateSharesToMigrateUnlockAndExitBalancerPool(ctx, sender, migratedPoolIDs.LeavingID, preMigrationLock, sharesToMigrate, tokenOutMins) + exitCoins, err := k.forceUnlockAndExitBalancerPool(ctx, sender, migratedPoolIDs.LeavingID, preMigrationLock, sharesToMigrate, tokenOutMins, true) if err != nil { return cltypes.CreateFullRangePositionData{}, 0, types.MigrationPoolIDs{}, err } @@ -151,7 +151,7 @@ func (k Keeper) migrateSuperfluidUnbondingBalancerToConcentrated(ctx sdk.Context // Force unlock, validate the provided sharesToMigrate, and exit the balancer pool. // This will return the coins that will be used to create the concentrated liquidity position. // It also returns the lock object that contains the remaining shares that were not used in this migration. - exitCoins, err := k.validateSharesToMigrateUnlockAndExitBalancerPool(ctx, sender, migratedPoolIDs.LeavingID, preMigrationLock, sharesToMigrate, tokenOutMins) + exitCoins, err := k.forceUnlockAndExitBalancerPool(ctx, sender, migratedPoolIDs.LeavingID, preMigrationLock, sharesToMigrate, tokenOutMins, true) if err != nil { return cltypes.CreateFullRangePositionData{}, 0, types.MigrationPoolIDs{}, err } @@ -199,7 +199,7 @@ func (k Keeper) migrateNonSuperfluidLockBalancerToConcentrated(ctx sdk.Context, // Force unlock, validate the provided sharesToMigrate, and exit the balancer pool. // This will return the coins that will be used to create the concentrated liquidity position. // It also returns the lock object that contains the remaining shares that were not used in this migration. - exitCoins, err := k.validateSharesToMigrateUnlockAndExitBalancerPool(ctx, sender, migratedPoolIDs.LeavingID, preMigrationLock, sharesToMigrate, tokenOutMins) + exitCoins, err := k.forceUnlockAndExitBalancerPool(ctx, sender, migratedPoolIDs.LeavingID, preMigrationLock, sharesToMigrate, tokenOutMins, true) if err != nil { return cltypes.CreateFullRangePositionData{}, 0, types.MigrationPoolIDs{}, err } @@ -215,9 +215,9 @@ func (k Keeper) migrateNonSuperfluidLockBalancerToConcentrated(ctx sdk.Context, return positionData, concentratedLockId, migratedPoolIDs, nil } -// routeMigration determines the status of the provided lock which is used to determine the method for migration. +// getMigrationType determines the status of the provided lock which is used to determine the method for migration. // It also returns the underlying synthetic locks of the provided lock, if any exist. -func (k Keeper) routeMigration(ctx sdk.Context, providedLockId int64) (synthLockBeforeMigration lockuptypes.SyntheticLock, migrationType MigrationType, err error) { +func (k Keeper) getMigrationType(ctx sdk.Context, providedLockId int64) (synthLockBeforeMigration lockuptypes.SyntheticLock, migrationType MigrationType, err error) { // As a hack around to get frontend working, we decided to allow negative values for the provided lock ID to indicate that the user wants to migrate shares that are not locked. if providedLockId <= 0 { return lockuptypes.SyntheticLock{}, Unlocked, nil @@ -294,7 +294,7 @@ func (k Keeper) validateMigration(ctx sdk.Context, sender sdk.AccAddress, lockId }, preMigrationLock, remainingLockTime, nil } -// validateSharesToMigrateUnlockAndExitBalancerPool validates the unlocking and exiting of gamm LP tokens from the Balancer pool. It performs the following steps: +// forceUnlockAndExitBalancerPool validates the unlocking and exiting of gamm LP tokens from the Balancer pool. It performs the following steps: // // 1. Completes the unlocking process / deletes synthetic locks for the provided lock. // 2. If shares to migrate are not specified, all shares in the lock are migrated. @@ -302,7 +302,7 @@ func (k Keeper) validateMigration(ctx sdk.Context, sender sdk.AccAddress, lockId // 4. Exits the position in the Balancer pool. // 5. Ensures that exactly two coins are returned. // 6. Any remaining shares that were not migrated are re-locked as a new lock for the remaining time on the lock. -func (k Keeper) validateSharesToMigrateUnlockAndExitBalancerPool(ctx sdk.Context, sender sdk.AccAddress, poolIdLeaving uint64, lock *lockuptypes.PeriodLock, sharesToMigrate sdk.Coin, tokenOutMins sdk.Coins) (exitCoins sdk.Coins, err error) { +func (k Keeper) forceUnlockAndExitBalancerPool(ctx sdk.Context, sender sdk.AccAddress, poolIdLeaving uint64, lock *lockuptypes.PeriodLock, sharesToMigrate sdk.Coin, tokenOutMins sdk.Coins, exitCoinsLengthIsTwo bool) (exitCoins sdk.Coins, err error) { // validateMigration ensures that the preMigrationLock contains coins of length 1. gammSharesInLock := lock.Coins[0] @@ -321,7 +321,7 @@ func (k Keeper) validateSharesToMigrateUnlockAndExitBalancerPool(ctx sdk.Context return sdk.Coins{}, types.MigratePartialSharesError{SharesToMigrate: sharesToMigrate.Amount.String(), SharesInLock: gammSharesInLock.Amount.String()} } - // Force migrate, which breaks and deletes associated synthetic locks. + // Force migrate, which breaks and deletes associated synthetic locks (if exists). err = k.lk.ForceUnlock(ctx, *lock) if err != nil { return sdk.Coins{}, err @@ -333,8 +333,8 @@ func (k Keeper) validateSharesToMigrateUnlockAndExitBalancerPool(ctx sdk.Context return sdk.Coins{}, err } - // Defense in depth, ensuring we are returning exactly two coins. - if len(exitCoins) != 2 { + // if exit coins length should be two, check exitCoins length + if exitCoinsLengthIsTwo && len(exitCoins) != 2 { return sdk.Coins{}, types.TwoTokenBalancerPoolError{NumberOfTokens: len(exitCoins)} } diff --git a/x/superfluid/keeper/migrate_test.go b/x/superfluid/keeper/migrate_test.go index 6c2b4bf839a..6afaae00432 100644 --- a/x/superfluid/keeper/migrate_test.go +++ b/x/superfluid/keeper/migrate_test.go @@ -302,9 +302,9 @@ func (s *KeeperTestSuite) TestMigrateSuperfluidBondedBalancerToConcentrated() { coinsToMigrate := balancerPoolShareOut coinsToMigrate.Amount = coinsToMigrate.Amount.ToDec().Mul(tc.percentOfSharesToMigrate).RoundInt() - // RouteMigration is called via the migration message router and is always run prior to the migration itself. + // GetMigrationType is called via the migration message router and is always run prior to the migration itself. // We use it here just to retrieve the synthetic lock before the migration. - synthLockBeforeMigration, migrationType, err := superfluidKeeper.RouteMigration(s.Ctx, poolJoinAcc, int64(originalGammLockId), coinsToMigrate) + synthLockBeforeMigration, migrationType, err := superfluidKeeper.GetMigrationType(s.Ctx, int64(originalGammLockId)) s.Require().NoError(err) s.Require().Equal(migrationType, keeper.SuperfluidBonded) @@ -458,8 +458,8 @@ func (s *KeeperTestSuite) TestMigrateSuperfluidUnbondingBalancerToConcentrated() coinsToMigrate := balancerPoolShareOut coinsToMigrate.Amount = coinsToMigrate.Amount.ToDec().Mul(tc.percentOfSharesToMigrate).RoundInt() - // RouteMigration is called via the migration message router and is always run prior to the migration itself - synthLockBeforeMigration, migrationType, err := superfluidKeeper.RouteMigration(s.Ctx, poolJoinAcc, int64(originalGammLockId), coinsToMigrate) + // GetMigrationType is called via the migration message router and is always run prior to the migration itself + synthLockBeforeMigration, migrationType, err := superfluidKeeper.GetMigrationType(s.Ctx, int64(originalGammLockId)) s.Require().NoError(err) s.Require().Equal(migrationType, keeper.SuperfluidUnbonding) @@ -573,8 +573,8 @@ func (s *KeeperTestSuite) TestMigrateNonSuperfluidLockBalancerToConcentrated() { coinsToMigrate := balancerPoolShareOut coinsToMigrate.Amount = coinsToMigrate.Amount.ToDec().Mul(tc.percentOfSharesToMigrate).RoundInt() - // RouteMigration is called via the migration message router and is always run prior to the migration itself - synthLockBeforeMigration, migrationType, err := superfluidKeeper.RouteMigration(s.Ctx, poolJoinAcc, int64(originalGammLockId), coinsToMigrate) + // GetMigrationType is called via the migration message router and is always run prior to the migration itself + synthLockBeforeMigration, migrationType, err := superfluidKeeper.GetMigrationType(s.Ctx, int64(originalGammLockId)) s.Require().NoError(err) s.Require().Equal((lockuptypes.SyntheticLock{}), synthLockBeforeMigration) s.Require().Equal(migrationType, keeper.NonSuperfluid) @@ -656,8 +656,8 @@ func (s *KeeperTestSuite) TestMigrateUnlockedPositionFromBalancerToConcentrated( coinsToMigrate := balancerPoolShareOut coinsToMigrate.Amount = coinsToMigrate.Amount.ToDec().Mul(tc.percentOfSharesToMigrate).RoundInt() - // RouteMigration is called via the migration message router and is always run prior to the migration itself - synthLockBeforeMigration, migrationType, err := superfluidKeeper.RouteMigration(s.Ctx, poolJoinAcc, 0, coinsToMigrate) + // GetMigrationType is called via the migration message router and is always run prior to the migration itself + synthLockBeforeMigration, migrationType, err := superfluidKeeper.GetMigrationType(s.Ctx, 0) s.Require().NoError(err) s.Require().Equal((lockuptypes.SyntheticLock{}), synthLockBeforeMigration) s.Require().Equal(migrationType, keeper.Unlocked) @@ -797,13 +797,14 @@ func (s *KeeperTestSuite) TestValidateMigration() { } } -func (s *KeeperTestSuite) TestValidateSharesToMigrateUnlockAndExitBalancerPool() { +func (s *KeeperTestSuite) TestForceUnlockAndExitBalancerPool() { defaultJoinTime := s.Ctx.BlockTime() type sendTest struct { overwritePreMigrationLock bool overwriteShares bool overwritePool bool overwritePoolId bool + exitCoinsLengthIsTwo bool percentOfSharesToMigrate sdk.Dec tokenOutMins sdk.Coins expectedError error @@ -816,6 +817,11 @@ func (s *KeeperTestSuite) TestValidateSharesToMigrateUnlockAndExitBalancerPool() percentOfSharesToMigrate: sdk.MustNewDecFromStr("0.4"), expectedError: types.MigratePartialSharesError{SharesToMigrate: "20000000000000000000", SharesInLock: "50000000000000000000"}, }, + "attempt to leave a pool that has more than two denoms": { + percentOfSharesToMigrate: sdk.MustNewDecFromStr("1"), + overwritePool: true, + exitCoinsLengthIsTwo: false, + }, "error: lock does not exist": { percentOfSharesToMigrate: sdk.MustNewDecFromStr("1"), overwritePreMigrationLock: true, @@ -831,9 +837,10 @@ func (s *KeeperTestSuite) TestValidateSharesToMigrateUnlockAndExitBalancerPool() overwritePoolId: true, expectedError: fmt.Errorf("pool with ID %d does not exist", 2), }, - "error: attempt to leave a pool that has more than two denoms": { + "error: attempt to leave a pool that has more than two denoms with exitCoinsLengthIsTwo true": { percentOfSharesToMigrate: sdk.MustNewDecFromStr("1"), overwritePool: true, + exitCoinsLengthIsTwo: true, expectedError: types.TwoTokenBalancerPoolError{NumberOfTokens: 4}, }, "error: happy path (full shares), token out mins is more than exit coins": { @@ -917,7 +924,7 @@ func (s *KeeperTestSuite) TestValidateSharesToMigrateUnlockAndExitBalancerPool() } // System under test - exitCoins, err := superfluidKeeper.ValidateSharesToMigrateUnlockAndExitBalancerPool(ctx, poolJoinAcc, balancerPooId, lock, coinsToMigrate, tc.tokenOutMins) + exitCoins, err := superfluidKeeper.ForceUnlockAndExitBalancerPool(ctx, poolJoinAcc, balancerPooId, lock, coinsToMigrate, tc.tokenOutMins, tc.exitCoinsLengthIsTwo) if tc.expectedError != nil { s.Require().Error(err) s.Require().ErrorContains(err, tc.expectedError.Error()) @@ -942,9 +949,11 @@ func (s *KeeperTestSuite) TestValidateSharesToMigrateUnlockAndExitBalancerPool() s.Require().Equal(expectedSharesStillInOldLock.String(), lock.Coins[0].Amount.String()) } - for _, coin := range exitCoins { - // Check that the exit coin is the same amount that we joined with (with one unit rounding down) - s.Require().Equal(0, defaultErrorTolerance.Compare(tokensIn.AmountOf(coin.Denom).ToDec().Mul(tc.percentOfSharesToMigrate).RoundInt(), coin.Amount)) + if tc.exitCoinsLengthIsTwo { + for _, coin := range exitCoins { + // Check that the exit coin is the same amount that we joined with (with one unit rounding down) + s.Require().Equal(0, defaultErrorTolerance.Compare(tokensIn.AmountOf(coin.Denom).ToDec().Mul(tc.percentOfSharesToMigrate).RoundInt(), coin.Amount)) + } } }) } diff --git a/x/superfluid/keeper/msg_server.go b/x/superfluid/keeper/msg_server.go index 9f92f22f64f..360be10a0fa 100644 --- a/x/superfluid/keeper/msg_server.go +++ b/x/superfluid/keeper/msg_server.go @@ -242,3 +242,14 @@ func (server msgServer) AddToConcentratedLiquiditySuperfluidPosition(goCtx conte return &types.MsgAddToConcentratedLiquiditySuperfluidPositionResponse{PositionId: positionData.ID, Amount0: positionData.Amount0, Amount1: positionData.Amount1, LockId: newLockId, NewLiquidity: positionData.Liquidity}, nil } + +func (server msgServer) UnbondConvertAndStake(goCtx context.Context, msg *types.MsgUnbondConvertAndStake) (*types.MsgUnbondConvertAndStakeResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + totalAmtConverted, err := server.keeper.UnbondConvertAndStake(ctx, msg.LockId, msg.Sender, msg.ValAddr, msg.MinAmtToStake, msg.SharesToConvert) + if err != nil { + return nil, err + } + + return &types.MsgUnbondConvertAndStakeResponse{TotalAmtStaked: totalAmtConverted}, nil +} diff --git a/x/superfluid/keeper/stake.go b/x/superfluid/keeper/stake.go index 9d19b96fff4..0df2464477d 100644 --- a/x/superfluid/keeper/stake.go +++ b/x/superfluid/keeper/stake.go @@ -2,12 +2,15 @@ package keeper import ( "fmt" + "strings" errorsmod "cosmossdk.io/errors" "github.com/osmosis-labs/osmosis/osmoutils" + gammtypes "github.com/osmosis-labs/osmosis/v17/x/gamm/types" lockuptypes "github.com/osmosis-labs/osmosis/v17/x/lockup/types" "github.com/osmosis-labs/osmosis/v17/x/superfluid/types" + valsettypes "github.com/osmosis-labs/osmosis/v17/x/valset-pref/types" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -656,3 +659,221 @@ func (k Keeper) IterateDelegations(ctx sdk.Context, delegator sdk.AccAddress, fn fn(index+int64(i), delegation) } } + +// UnbondConvertAndStake converts given lock to osmo and stakes it to given validator. +// Supports conversion of 1)superfluid bonded 2)superfluid undelegating 3)vanilla unlocking. +// Liquid gamm shares will not be supported for conversion. +// Delegation is done in the following logic: +// - If valAddr provided, single delegate. +// - If valAddr not provided and valset exists, valsetpref.Delegate +// - If valAddr not provided and valset delegation is not possible, refer back to original lock's superfluid validator if it was a superfluid lock +// - Else: error +func (k Keeper) UnbondConvertAndStake(ctx sdk.Context, lockID uint64, sender, valAddr string, + minAmtToStake sdk.Int, sharesToConvert sdk.Coin) (totalAmtConverted sdk.Int, err error) { + senderAddr, err := sdk.AccAddressFromBech32(sender) + if err != nil { + return sdk.Int{}, err + } + + // use getMigrationType method to check status of lock (either superfluid staked, superfluid unbonding, vanilla locked, unlocked) + _, migrationType, err := k.getMigrationType(ctx, int64(lockID)) + if err != nil { + return sdk.Int{}, err + } + + // if superfluid bonded, first change it into superfluid undelegate to burn minted osmo and instantly undelegate. + if migrationType == SuperfluidBonded { + _, err = k.undelegateCommon(ctx, sender, lockID) + if err != nil { + return sdk.Int{}, err + } + } + + if migrationType == SuperfluidBonded || migrationType == SuperfluidUnbonding || migrationType == NonSuperfluid { + totalAmtConverted, err = k.convertLockToStake(ctx, senderAddr, valAddr, lockID, minAmtToStake) + } else if migrationType == Unlocked { // liquid gamm shares without locks + totalAmtConverted, err = k.convertUnlockedToStake(ctx, senderAddr, valAddr, sharesToConvert, minAmtToStake) + } else { // any other types of migration should fail + return sdk.Int{}, fmt.Errorf("unsupported staking conversion type") + } + + if err != nil { + return sdk.Int{}, err + } + + return totalAmtConverted, nil +} + +// convertLockToStake handles locks that are superfluid bonded, superfluid unbonding, vanilla locked(unlocking) locks. +// Deletes all associated state, converts the lock itself to staking delegation by going through exit pool and swap. +func (k Keeper) convertLockToStake(ctx sdk.Context, sender sdk.AccAddress, valAddr string, lockId uint64, + minAmtToStake sdk.Int) (totalAmtConverted sdk.Int, err error) { + lock, err := k.lk.GetLockByID(ctx, lockId) + if err != nil { + return sdk.Int{}, err + } + + // check lock owner is sender + if lock.Owner != sender.String() { + return sdk.ZeroInt(), types.LockOwnerMismatchError{ + LockId: lock.ID, + LockOwner: lock.Owner, + ProvidedOwner: sender.String(), + } + } + + lockCoin := lock.Coins[0] + + // Ensuring the sharesToMigrate contains gamm pool share prefix. + if !strings.HasPrefix(lockCoin.Denom, gammtypes.GAMMTokenPrefix) { + return sdk.Int{}, types.SharesToMigrateDenomPrefixError{Denom: lockCoin.Denom, ExpectedDenomPrefix: gammtypes.GAMMTokenPrefix} + } + + poolIdLeaving, err := gammtypes.GetPoolIdFromShareDenom(lockCoin.Denom) + if err != nil { + return sdk.Int{}, err + } + + var superfluidValAddr string + interAcc, found := k.GetIntermediaryAccountFromLockId(ctx, lockId) + if found { + superfluidValAddr = interAcc.ValAddr + } + + // Force unlock, validate the provided sharesToStake, and exit the balancer pool. + // we exit with min token out amount zero since we are checking min amount designated to stake later on anyways. + exitCoins, err := k.forceUnlockAndExitBalancerPool(ctx, sender, poolIdLeaving, lock, lockCoin, sdk.NewCoins(), false) + if err != nil { + return sdk.Int{}, err + } + + totalAmtConverted, err = k.convertGammSharesToOsmoAndStake(ctx, sender, valAddr, poolIdLeaving, exitCoins, minAmtToStake, superfluidValAddr) + if err != nil { + return sdk.Int{}, err + } + + return totalAmtConverted, nil +} + +// convertUnlockedToStake converts liquid gamm shares to staking delegation. +// minAmtToStake works as slippage bound for the conversion process. +func (k Keeper) convertUnlockedToStake(ctx sdk.Context, sender sdk.AccAddress, valAddr string, sharesToStake sdk.Coin, + minAmtToStake sdk.Int) (totalAmtConverted sdk.Int, err error) { + if !strings.HasPrefix(sharesToStake.Denom, gammtypes.GAMMTokenPrefix) { + return sdk.Int{}, types.SharesToMigrateDenomPrefixError{Denom: sharesToStake.Denom, ExpectedDenomPrefix: gammtypes.GAMMTokenPrefix} + } + + // Get the balancer poolId by parsing the gamm share denom. + poolIdLeaving, err := gammtypes.GetPoolIdFromShareDenom(sharesToStake.Denom) + if err != nil { + return sdk.Int{}, err + } + + // Exit the balancer pool position. + // we exit with min token out amount zero since we are checking min amount designated to stake later on anyways. + exitCoins, err := k.gk.ExitPool(ctx, sender, poolIdLeaving, sharesToStake.Amount, sdk.NewCoins()) + if err != nil { + return sdk.Int{}, err + } + + totalAmtConverted, err = k.convertGammSharesToOsmoAndStake(ctx, sender, valAddr, poolIdLeaving, exitCoins, minAmtToStake, "") + if err != nil { + return sdk.Int{}, err + } + + return totalAmtConverted, nil +} + +// convertGammSharesToOsmoAndStake converts given gamm shares to osmo by swapping in the given pool +// then stakes it to the designated validator. +// minAmtToStake works as slippage bound, and would error if total amount being staked is less than min amount to stake. +// Depending on user inputs, valAddr and originalSuperfluidValAddr could be an empty string, +// each leading to a different delegation scenario. +func (k Keeper) convertGammSharesToOsmoAndStake( + ctx sdk.Context, + sender sdk.AccAddress, valAddr string, + poolIdLeaving uint64, exitCoins sdk.Coins, minAmtToStake sdk.Int, originalSuperfluidValAddr string, +) (totalAmtCoverted sdk.Int, err error) { + var nonOsmoCoins sdk.Coins + bondDenom := k.sk.BondDenom(ctx) + + // from the exit coins, separate non-bond denom and bond denom. + for _, exitCoin := range exitCoins { + // if coin is not uosmo, add it to non-osmo Coins + if exitCoin.Denom != bondDenom { + nonOsmoCoins = append(nonOsmoCoins, exitCoin) + } + } + originalBondDenomAmt := exitCoins.AmountOf(bondDenom) + + // track how much non-uosmo tokens we have converted to uosmo + totalAmtCoverted = sdk.ZeroInt() + + // iterate over non-bond denom coins and swap them into bond denom + for _, coinToConvert := range nonOsmoCoins { + tokenOutAmt, err := k.pmk.SwapExactAmountIn(ctx, sender, poolIdLeaving, coinToConvert, bondDenom, sdk.ZeroInt()) + if err != nil { + return sdk.Int{}, err + } + + totalAmtCoverted = totalAmtCoverted.Add(tokenOutAmt) + } + + // add the converted amount with the amount of osmo from exit coin to get total amount we would be staking + totalAmtToStake := originalBondDenomAmt.Add(totalAmtCoverted) + + // check if the total amount to stake after all conversion is greater than provided min amount to stake + if totalAmtToStake.LT(minAmtToStake) { + return sdk.Int{}, types.TokenConvertedLessThenDesiredStakeError{ + ActualTotalAmtToStake: totalAmtToStake, + ExpectedTotalAmtToStake: minAmtToStake, + } + } + + err = k.delegateBaseOnValsetPref(ctx, sender, valAddr, originalSuperfluidValAddr, totalAmtToStake) + if err != nil { + return sdk.Int{}, err + } + + return totalAmtToStake, nil +} + +// delegateBaseOnValsetPref delegates based on given input parameters. +// valAddr and originalSuperfluidValAddr can be an empty string depending on user input and original lock's status. +// Delegation is done in the following logic: +// - If valAddr provided, single delegate. +// - If valAddr not provided and valset exists, valsetpref.Delegate +// - If valAddr not provided and valset delegation is not possible, refer back to original lock's superfluid validator if it was a superfluid lock +// - Else: error +func (k Keeper) delegateBaseOnValsetPref(ctx sdk.Context, sender sdk.AccAddress, valAddr, originalSuperfluidValAddr string, totalAmtToStake sdk.Int) error { + bondDenom := k.sk.BondDenom(ctx) + + // if given valAddr is empty, we use delegation preference given from valset-pref module or reference from superfluid staking + if valAddr == "" { + err := k.vspk.DelegateToValidatorSet(ctx, sender.String(), sdk.NewCoin(bondDenom, totalAmtToStake)) + // if valset-pref delegation succeeded without error, end method + if err == nil { + return nil + } + + // if valset-pref delegation errored due to no existing delegation existing, fall back and try using superfluid staked validator + if err == valsettypes.ErrNoDelegation { + valAddr = originalSuperfluidValAddr + } else if err != nil { // for other errors, handle error + return err + } + } + + val, err := k.validateValAddrForDelegate(ctx, valAddr) + if err != nil { + return err + } + + // delegate now! + _, err = k.sk.Delegate(ctx, sender, totalAmtToStake, stakingtypes.Unbonded, val, true) + if err != nil { + return err + } + + return nil +} diff --git a/x/superfluid/keeper/stake_test.go b/x/superfluid/keeper/stake_test.go index 7eed33376a0..c0eb11aa5d9 100644 --- a/x/superfluid/keeper/stake_test.go +++ b/x/superfluid/keeper/stake_test.go @@ -6,11 +6,14 @@ import ( abci "github.com/tendermint/tendermint/abci/types" cltypes "github.com/osmosis-labs/osmosis/v17/x/concentrated-liquidity/types" + "github.com/osmosis-labs/osmosis/v17/x/gamm/pool-models/balancer" + gammtypes "github.com/osmosis-labs/osmosis/v17/x/gamm/types" lockuptypes "github.com/osmosis-labs/osmosis/v17/x/lockup/types" "github.com/osmosis-labs/osmosis/v17/x/superfluid/keeper" "github.com/osmosis-labs/osmosis/v17/x/superfluid/types" errorsmod "cosmossdk.io/errors" + "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) @@ -1028,6 +1031,714 @@ func (s *KeeperTestSuite) TestRefreshIntermediaryDelegationAmounts() { } } +func (s *KeeperTestSuite) TestUnbondConvertAndStake() { + defaultJoinTime := s.Ctx.BlockTime() + type tc struct { + notSuperfluidDelegated bool + superfluidUndelegating bool + unlocking bool + unlocked bool + testCLLock bool + expectedError error + } + testCases := map[string]tc{ + "lock that is superfluid delegated": {}, + "lock that is superfluid undelegating": { + superfluidUndelegating: true, + }, + "bonded lock, not superfluid delegated": { + notSuperfluidDelegated: true, + }, + "lock that is unlocking": { + unlocking: true, + superfluidUndelegating: true, + }, + "unlocked gamm shares": { + notSuperfluidDelegated: true, + unlocked: true, + }, + "error: concentrated lock should fail": { + testCLLock: true, + expectedError: types.SharesToMigrateDenomPrefixError{ + Denom: "cl/pool/2", + ExpectedDenomPrefix: "gamm/pool/", + }, + }, + } + + for name, tc := range testCases { + s.Run(name, func() { + s.SetupTest() + s.Ctx = s.Ctx.WithBlockTime(defaultJoinTime) + + var ( + lock *lockuptypes.PeriodLock + lockId uint64 + joinPoolAcc sdk.AccAddress + originalValAddr sdk.ValAddress + balancerShareOut sdk.Coin + ) + // we use migration setup for testing with cl lock + if tc.testCLLock { + _, _, lock, _, joinPoolAcc, _, _, balancerShareOut, originalValAddr = s.SetupMigrationTest(s.Ctx, !tc.notSuperfluidDelegated, tc.superfluidUndelegating, tc.unlocking, tc.unlocked, sdk.MustNewDecFromStr("1")) + synthLockBeforeMigration, _, err := s.App.SuperfluidKeeper.GetMigrationType(s.Ctx, int64(lock.ID)) + s.Require().NoError(err) + _, lockId, _, err = s.App.SuperfluidKeeper.MigrateSuperfluidBondedBalancerToConcentrated(s.Ctx, joinPoolAcc, lock.ID, lock.Coins[0], synthLockBeforeMigration.SynthDenom, sdk.NewCoins()) + s.Require().NoError(err) + } else { + // We bundle all migration setup into a single function to avoid repeating the same code for each test case. + _, _, lock, _, joinPoolAcc, _, balancerShareOut, originalValAddr = s.SetupUnbondConvertAndStakeTest(s.Ctx, !tc.notSuperfluidDelegated, tc.superfluidUndelegating, tc.unlocking, tc.unlocked) + lockId = lock.ID + } + + sender := sdk.MustAccAddressFromBech32(joinPoolAcc.String()) + valAddr := s.SetupValidator(stakingtypes.Bonded) + minAmountToStake := sdk.ZeroInt() + sharesToConvert := sdk.NewInt64Coin("foo", 0) + if tc.unlocked { + sharesToConvert = balancerShareOut + } + + // only test with test related denoms + balanceBeforeConvertLockToStake := s.App.BankKeeper.GetAllBalances(s.Ctx, sender).FilterDenoms([]string{"foo", "stake", "uosmo"}) + + // system under test + totalAmtConverted, err := s.App.SuperfluidKeeper.UnbondConvertAndStake(s.Ctx, lockId, sender.String(), valAddr.String(), minAmountToStake, sharesToConvert) + if tc.expectedError != nil { + s.Require().Equal(err.Error(), tc.expectedError.Error()) + s.Require().Error(err) + return + } + s.Require().NoError(err) + + // Staking & Delegation check + s.delegationCheck(s.Ctx, sender, originalValAddr, valAddr, totalAmtConverted) + + // Bank check + balanceAfterConvertLockToStake := s.App.BankKeeper.GetAllBalances(s.Ctx, sender).FilterDenoms([]string{"foo", "stake", "uosmo"}) + s.Require().True(balanceBeforeConvertLockToStake.IsEqual(balanceAfterConvertLockToStake)) + + // if unlocked, no need to check locks since there is no lock existing + if tc.unlocked { + return + } + + // lock check + s.lockCheck(s.Ctx, *lock, valAddr.String(), true) + + }) + } +} + +func (s *KeeperTestSuite) TestConvertLockToStake() { + defaultJoinTime := s.Ctx.BlockTime() + type tc struct { + superfluidUndelegating bool + unlocking bool + notSuperfluidDelegated bool + + useMinAmountToStake bool + senderIsNotOwnerOfLock bool + useNonBalancerLock bool + + expectedError error + } + testCases := map[string]tc{ + "lock that is superfluid delegated": {}, + "lock that is superfluid undelegating": { + unlocking: true, + superfluidUndelegating: true, + }, + "lock that is unlocking": { + unlocking: true, + superfluidUndelegating: false, + }, + "bonded lock, not superfluid delegated": { + notSuperfluidDelegated: true, + }, + // error cases + "error: min amount to stake greater than actual amount": { + useMinAmountToStake: true, + expectedError: types.TokenConvertedLessThenDesiredStakeError{ + ActualTotalAmtToStake: sdk.NewInt(8309), + ExpectedTotalAmtToStake: sdk.NewInt(999999999), + }, + }, + "error: use non balancer lock": { + useNonBalancerLock: true, + expectedError: types.SharesToMigrateDenomPrefixError{ + Denom: "foo", + ExpectedDenomPrefix: "gamm/pool/", + }, + }, + "error: sender is not owner of lock ": { + senderIsNotOwnerOfLock: true, + expectedError: types.LockOwnerMismatchError{ + LockId: 1, + LockOwner: s.TestAccs[0].String(), + ProvidedOwner: s.TestAccs[1].String(), + }, + }, + } + + for name, tc := range testCases { + s.Run(name, func() { + s.SetupTest() + s.Ctx = s.Ctx.WithBlockTime(defaultJoinTime) + // We bundle all migration setup into a single function to avoid repeating the same code for each test case. + _, _, lock, _, _, _, _, originalValAddr := s.SetupUnbondConvertAndStakeTest(s.Ctx, !tc.notSuperfluidDelegated, tc.superfluidUndelegating, false, false) + + // testing params + sender := sdk.MustAccAddressFromBech32(lock.Owner) + if tc.senderIsNotOwnerOfLock { + sender = s.TestAccs[1] + } + + if tc.useNonBalancerLock { + nonBalancerShareDenomCoins := sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(100))) + s.FundAcc(sender, nonBalancerShareDenomCoins) + newLock, err := s.App.LockupKeeper.CreateLock(s.Ctx, sender, nonBalancerShareDenomCoins, time.Second) + s.Require().NoError(err) + lock = &newLock + } + + valAddr := s.SetupValidator(stakingtypes.Bonded) + minAmountToStake := sdk.ZeroInt() + if tc.useMinAmountToStake { + minAmountToStake = sdk.NewInt(999999999) + } + + balanceBeforeConvertLockToStake := s.App.BankKeeper.GetAllBalances(s.Ctx, sender) + + // system under test + totalAmtConverted, err := s.App.SuperfluidKeeper.ConvertLockToStake(s.Ctx, sender, valAddr.String(), lock.ID, minAmountToStake) + if tc.expectedError != nil { + s.Require().Error(err) + // TODO: come back to this specific err case + // err check for LockOwnerMismatchError needs further refactoring for all these test cases + // since lock owner is not know-able at the time of test creation + if !tc.senderIsNotOwnerOfLock { + s.Require().Equal(err.Error(), tc.expectedError.Error()) + } + return + } + s.Require().NoError(err) + + // Staking & Delegation check + s.delegationCheck(s.Ctx, sender, originalValAddr, valAddr, totalAmtConverted) + + // Lock check + s.lockCheck(s.Ctx, *lock, valAddr.String(), true) + + // Bank check + balanceAfterConvertLockToStake := s.App.BankKeeper.GetAllBalances(s.Ctx, sender) + s.Require().True(balanceBeforeConvertLockToStake.IsEqual(balanceAfterConvertLockToStake)) + }) + } +} + +func (s *KeeperTestSuite) TestConvertUnlockedToStake() { + defaultJoinTime := s.Ctx.BlockTime() + type tc struct { + usePartialShares bool + useMinAmountToStake bool + useNonGammPrefix bool + expectedError error + } + testCases := map[string]tc{ + "convert unlocked gamm shares": {}, + "convert partial shares": { + usePartialShares: true, + }, + "min amount to stake exceeds exit pool amount": { + useMinAmountToStake: true, + expectedError: types.TokenConvertedLessThenDesiredStakeError{ + ActualTotalAmtToStake: sdk.NewInt(8309), + ExpectedTotalAmtToStake: sdk.NewInt(999999999), + }, + }, + "error: use non gamm prefix": { + useNonGammPrefix: true, + expectedError: types.TokenConvertedLessThenDesiredStakeError{ + ActualTotalAmtToStake: sdk.NewInt(8309), + ExpectedTotalAmtToStake: sdk.NewInt(999999999), + }, + }, + } + + for name, tc := range testCases { + s.Run(name, func() { + s.SetupTest() + s.Ctx = s.Ctx.WithBlockTime(defaultJoinTime) + + // We bundle all migration setup into a single function to avoid repeating the same code for each test case. + _, _, _, _, sender, poolId, shareOut, _ := s.SetupUnbondConvertAndStakeTest(s.Ctx, false, false, false, true) + + // testing params + valAddr := s.SetupValidator(stakingtypes.Bonded) + minAmtToStake := sdk.ZeroInt() + if tc.useMinAmountToStake { + minAmtToStake = sdk.NewInt(9999999999) + } + sharesToStake := shareOut + if tc.usePartialShares { + sharesToStake.Amount = sharesToStake.Amount.Quo(sdk.NewInt(2)) + } + if tc.useNonGammPrefix { + sharesToStake = sdk.NewInt64Coin("foo", 10) + } + + balanceBeforeConvert := s.App.BankKeeper.GetBalance(s.Ctx, sender, shareOut.Denom) + s.Require().True(!balanceBeforeConvert.Amount.IsZero()) + + bondDenom := s.App.StakingKeeper.BondDenom(s.Ctx) + totalPoolLiquidityBeforeConvert, err := s.App.GAMMKeeper.GetTotalPoolLiquidity(s.Ctx, poolId) + s.Require().NoError(err) + bondDenomPoolAmtBeforeConvert := totalPoolLiquidityBeforeConvert.AmountOf(bondDenom) + + var expectedBondDenomAmt sdk.Int + // check expected bond denom pool liquidity amount after conversion(only for non error cases) + if tc.expectedError == nil { + expectedBondDenomAmt = s.getExpectedBondDenomPoolAmtAfterConvert(sender, poolId, sharesToStake) + } + + // system under test + totalAmtConverted, err := s.App.SuperfluidKeeper.ConvertUnlockedToStake(s.Ctx, sender, valAddr.String(), sharesToStake, minAmtToStake) + if tc.expectedError != nil { + s.Require().Error(err) + return + } + s.Require().NoError(err) + + // gamm check + totalPoolLiquidityAfterConvert, err := s.App.GAMMKeeper.GetTotalPoolLiquidity(s.Ctx, poolId) + s.Require().NoError(err) + // check that pool liquidity have reduced + bondDenomPoolAmtAfterConvert := totalPoolLiquidityAfterConvert.AmountOf(bondDenom) + s.Require().True(bondDenomPoolAmtAfterConvert.LT(bondDenomPoolAmtBeforeConvert)) + s.Require().True(expectedBondDenomAmt.Equal(bondDenomPoolAmtAfterConvert)) + + // Staking & Delegation check + s.delegationCheck(s.Ctx, sender, sdk.ValAddress{}, valAddr, totalAmtConverted) + + // Bank check + balanceAfterConvertLockToStake := s.App.BankKeeper.GetBalance(s.Ctx, sender, shareOut.Denom) + if tc.usePartialShares { + s.Require().True(balanceAfterConvertLockToStake.Amount.Equal(sharesToStake.Amount)) + } else { + s.Require().True(balanceAfterConvertLockToStake.IsZero()) + } + }) + } +} + +func (s *KeeperTestSuite) TestConvertGammSharesToOsmoAndStake() { + type tc struct { + useInvalidValAddr bool + useMinAmtToStake bool + useValSetPrefSingleVal bool + useValSetPrefMultipleVal bool + useSuperfluid bool + + expectedError string + } + testCases := map[string]tc{ + "superfluid staked, provide validator address": {}, + "use val set preference (single validator)": { + useValSetPrefSingleVal: true, + }, + "multiple validator returned from valset pref": { + useValSetPrefMultipleVal: true, + }, + "No validator returned from valset, fall back to superfluid delegation": { + useSuperfluid: true, + }, + "error: invalid val address": { + useInvalidValAddr: true, + expectedError: "invalid Bech32 prefix; expected osmovaloper, got osmo", + }, + "error: min amount to stake exceeds actual amount staking": { + useMinAmtToStake: true, + expectedError: "actual amount converted to stake (8309) is less then minimum amount expected to be staked (999999999)", + }, + } + + for name, tc := range testCases { + s.Run(name, func() { + s.SetupTest() + bondDenom := s.App.StakingKeeper.BondDenom(s.Ctx) + + // use setup helper function to setup pool, fund account with gamm shares + // note that we're not creating any locks here. + _, _, _, _, sender, poolId, shareOut, _ := s.SetupUnbondConvertAndStakeTest(s.Ctx, false, false, false, true) + // exit pool + exitCoins, err := s.App.GAMMKeeper.ExitPool(s.Ctx, sender, poolId, shareOut.Amount, sdk.NewCoins()) + s.Require().NoError(err) + + // test params + originalSuperfluidValAddr := "" + valAddr := s.SetupValidator(stakingtypes.Bonded) + valAddrString := valAddr.String() + if tc.useInvalidValAddr { + valAddrString = s.TestAccs[0].String() + } + + stakeCoin := sdk.NewInt64Coin(bondDenom, 100000) + if tc.useValSetPrefSingleVal || tc.useValSetPrefMultipleVal { + valAddrString = "" + + s.FundAcc(sender, sdk.NewCoins(stakeCoin)) + validator, found := s.App.StakingKeeper.GetValidator(s.Ctx, valAddr) + s.Require().True(found) + + _, err = s.App.StakingKeeper.Delegate(s.Ctx, sender, stakeCoin.Amount, stakingtypes.Unbonded, validator, true) + s.Require().NoError(err) + } + if tc.useSuperfluid { + originalSuperfluidValAddr = valAddrString + valAddrString = "" + } + + // if test case is setting multiple validator, stake one more time to a different validator + if tc.useValSetPrefMultipleVal { + valAddr2 := s.SetupValidator(stakingtypes.Bonded) + stakeCoin := sdk.NewInt64Coin(bondDenom, 100000) + s.FundAcc(sender, sdk.NewCoins(stakeCoin)) + validator, found := s.App.StakingKeeper.GetValidator(s.Ctx, valAddr2) + s.Require().True(found) + _, err = s.App.StakingKeeper.Delegate(s.Ctx, sender, stakeCoin.Amount, stakingtypes.Unbonded, validator, true) + s.Require().NoError(err) + } + + minAmtToStake := sdk.ZeroInt() + if tc.useMinAmtToStake { + minAmtToStake = sdk.NewInt(999999999) + } + + // mark expected shares before swap + nonStakeDenomCoin := exitCoins.FilterDenoms([]string{"foo"})[0] + stakeDenomCoin := exitCoins.AmountOf(bondDenom) + // use cache context to get expected amount after swap without changing test state + cc, _ := s.Ctx.CacheContext() + tokenOutAmt, err := s.App.PoolManagerKeeper.SwapExactAmountIn(cc, sender, poolId, nonStakeDenomCoin, bondDenom, sdk.ZeroInt()) + s.Require().NoError(err) + expectedTotalAmtStaked := tokenOutAmt.Add(stakeDenomCoin) + + // mark pool liquidity + pool, err := s.App.GAMMKeeper.GetPoolAndPoke(s.Ctx, poolId) + s.Require().NoError(err) + poolLiquidityBeforeSwap := pool.GetTotalPoolLiquidity(s.Ctx) + poolBeforeBondDenomAmt := poolLiquidityBeforeSwap.AmountOf("stake") + poolBeforeNonBondDenomAmt := poolLiquidityBeforeSwap.AmountOf("foo") + + // system under test. + totalAmtConverted, err := s.App.SuperfluidKeeper.ConvertGammSharesToOsmoAndStake(s.Ctx, sender, valAddrString, poolId, exitCoins, minAmtToStake, originalSuperfluidValAddr) + if tc.expectedError != "" { + s.Require().Equal(err.Error(), tc.expectedError) + s.Require().Error(err) + return + } + s.Require().NoError(err) + + // check that total Amount converted is equal to (swap result + original stake denom amount) + s.Require().True(expectedTotalAmtStaked.Equal(totalAmtConverted)) + + // check staking + if tc.useValSetPrefMultipleVal { + delegations := s.App.StakingKeeper.GetAllDelegatorDelegations(s.Ctx, sender) + // we used two validators + s.Require().True(len(delegations) == 2) + + delegation0Shares := delegations[0].Shares + delegation1Shares := delegations[1].Shares + + shareDiff := delegation0Shares.Sub(delegation1Shares).Abs() + + // in practice, the share amount between two validators should be equal, + // but due to how we handle truncation and rounding in valset pref, we expect the diff to be under one dec. + s.Require().True(shareDiff.LTE(sdk.OneDec())) + } else { + _, found := s.App.StakingKeeper.GetDelegation(s.Ctx, sender, valAddr) + s.Require().True(found) + } + + // check pool + pool, err = s.App.GAMMKeeper.GetPoolAndPoke(s.Ctx, poolId) + s.Require().NoError(err) + poolLiquidityAfterSwap := pool.GetTotalPoolLiquidity(s.Ctx) + poolAfterBondDenomAmt := poolLiquidityAfterSwap.AmountOf("stake") + poolAfterNonBondDenomAmt := poolLiquidityAfterSwap.AmountOf("foo") + // we swapped from non-bond denom to bond denom, + // thus bond denom token in pool should have decreased, non bond denom token should have increased + s.Require().True(poolBeforeBondDenomAmt.GT(poolAfterBondDenomAmt)) + s.Require().True(poolBeforeNonBondDenomAmt.LT(poolAfterNonBondDenomAmt)) + }) + } +} + +func (s *KeeperTestSuite) TestDelegateBaseOnValsetPref() { + type tc struct { + useValAddr bool + haveExistingDelegation bool + useOriginalSuperfluidValAddr bool + + useInvalidValAddr bool + + expectedError string + } + testCases := map[string]tc{ + "provide val address": { + useValAddr: true, + }, + "use valset pref delegation": { + haveExistingDelegation: true, + }, + "using valset pref fail, fallback to using provided original superfluid address": { + useOriginalSuperfluidValAddr: true, + }, + "error: using valset pref fail, no superfluid address provided": { + expectedError: "empty address string is not allowed", + }, + "error: invalid val address provded": { + useInvalidValAddr: true, + expectedError: "ecoding bech32 failed: invalid character not part of charset", + }, + } + + for name, tc := range testCases { + s.Run(name, func() { + s.Setup() + bondDenom := s.App.StakingKeeper.BondDenom(s.Ctx) + stakeAmount := sdk.NewInt(100) + + sender := s.TestAccs[0] + s.FundAcc(sender, sdk.NewCoins(sdk.NewCoin(bondDenom, stakeAmount))) + + var valAddr string + if tc.useValAddr { + valAddr = s.SetupValidator(stakingtypes.Bonded).String() + } + if tc.useInvalidValAddr { + valAddr = s.SetupValidator(stakingtypes.Bonded).String() + "invalid" + } + + var originalSuperfluidValAddr string + if tc.useOriginalSuperfluidValAddr { + originalSuperfluidValAddr = s.SetupValidator(stakingtypes.Bonded).String() + } + + // by having existing delegation, we can test val set pref based delgation + var superfluidStakedValAddr sdk.ValAddress + if tc.haveExistingDelegation { + superfluidStakedValAddr = s.SetupValidator(stakingtypes.Bonded) + + stakeCoin := sdk.NewInt64Coin(bondDenom, 100) + s.FundAcc(sender, sdk.NewCoins(stakeCoin)) + validator, found := s.App.StakingKeeper.GetValidator(s.Ctx, superfluidStakedValAddr) + s.Require().True(found) + _, err := s.App.StakingKeeper.Delegate(s.Ctx, sender, stakeCoin.Amount, stakingtypes.Unbonded, validator, true) + s.Require().NoError(err) + } + + // system under test + err := s.App.SuperfluidKeeper.DelegateBaseOnValsetPref(s.Ctx, sender, valAddr, originalSuperfluidValAddr, stakeAmount) + if tc.expectedError != "" { + s.Require().Error(err) + s.Require().ErrorContains(err, tc.expectedError) + return + } + + s.Require().NoError(err) + + // check delegation + if valAddr != "" || originalSuperfluidValAddr != "" { + // we want to check which ever param that was passed in with value + var delegatedAddr string + if valAddr == "" { + delegatedAddr = originalSuperfluidValAddr + } else { + delegatedAddr = valAddr + } + + val, err := sdk.ValAddressFromBech32(delegatedAddr) + s.Require().NoError(err) + del, found := s.App.StakingKeeper.GetDelegation(s.Ctx, sender, val) + s.Require().True(found) + s.Require().True(del.Shares.RoundInt().Equal(stakeAmount)) + return + } + + // if we are testing valset-pref case(already deleated), check existing delegation address to see if delegation increased + if tc.haveExistingDelegation { + del, found := s.App.StakingKeeper.GetDelegation(s.Ctx, sender, superfluidStakedValAddr) + s.Require().True(found) + // should be 200(original delegated amount + newly staked amount) + s.Require().True(del.Shares.RoundInt().Equal(stakeAmount.Mul(sdk.NewInt(2)))) + return + } + }) + } +} + +func (s *KeeperTestSuite) SetupUnbondConvertAndStakeTest(ctx sdk.Context, superfluidDelegated, superfluidUndelegating, unlocking, noLock bool) (joinPoolAmt sdk.Coins, balancerIntermediaryAcc types.SuperfluidIntermediaryAccount, balancerLock *lockuptypes.PeriodLock, poolCreateAcc, poolJoinAcc sdk.AccAddress, balancerPooId uint64, balancerPoolShareOut sdk.Coin, valAddr sdk.ValAddress) { + bankKeeper := s.App.BankKeeper + gammKeeper := s.App.GAMMKeeper + superfluidKeeper := s.App.SuperfluidKeeper + lockupKeeper := s.App.LockupKeeper + stakingKeeper := s.App.StakingKeeper + poolmanagerKeeper := s.App.PoolManagerKeeper + + // Generate and fund two accounts. + // Account 1 will be the account that creates the pool. + // Account 2 will be the account that joins the pool. + delAddrs := CreateRandomAccounts(2) + poolCreateAcc = delAddrs[0] + poolJoinAcc = delAddrs[1] + for _, acc := range delAddrs { + err := simapp.FundAccount(bankKeeper, ctx, acc, defaultAcctFunds) + s.Require().NoError(err) + } + + // Set up a single validator. + valAddr = s.SetupValidator(stakingtypes.Bonded) + + // Create a balancer pool of "stake" and "foo". + msg := balancer.NewMsgCreateBalancerPool(poolCreateAcc, balancer.PoolParams{ + SwapFee: sdk.NewDecWithPrec(1, 2), + ExitFee: sdk.NewDec(0), + }, defaultPoolAssets, defaultFutureGovernor) + balancerPooId, err := poolmanagerKeeper.CreatePool(ctx, msg) + s.Require().NoError(err) + + // Join the balancer pool. + // Note the account balance before and after joining the pool. + balanceBeforeJoin := bankKeeper.GetAllBalances(ctx, poolJoinAcc) + _, _, err = gammKeeper.JoinPoolNoSwap(ctx, poolJoinAcc, balancerPooId, gammtypes.OneShare.MulRaw(50), sdk.Coins{}) + s.Require().NoError(err) + balanceAfterJoin := bankKeeper.GetAllBalances(ctx, poolJoinAcc) + + // The balancer join pool amount is the difference between the account balance before and after joining the pool. + joinPoolAmt, _ = balanceBeforeJoin.SafeSub(balanceAfterJoin) + + // Determine the balancer pool's LP token denomination. + balancerPoolDenom := gammtypes.GetPoolShareDenom(balancerPooId) + + // Register the balancer pool's LP token as a superfluid asset + err = superfluidKeeper.AddNewSuperfluidAsset(ctx, types.SuperfluidAsset{ + Denom: balancerPoolDenom, + AssetType: types.SuperfluidAssetTypeLPShare, + }) + s.Require().NoError(err) + + // Note how much of the balancer pool's LP token the account that joined the pool has. + balancerPoolShareOut = bankKeeper.GetBalance(ctx, poolJoinAcc, balancerPoolDenom) + + // The unbonding duration is the same as the staking module's unbonding duration. + unbondingDuration := stakingKeeper.GetParams(ctx).UnbondingTime + + // Lock the LP tokens for the duration of the unbonding period. + originalGammLockId := uint64(0) + if !noLock { + originalGammLockId = s.LockTokens(poolJoinAcc, sdk.NewCoins(balancerPoolShareOut), unbondingDuration) + } + + // Superfluid delegate the balancer lock if the test case requires it. + // Note the intermediary account that was created. + if superfluidDelegated { + err = superfluidKeeper.SuperfluidDelegate(ctx, poolJoinAcc.String(), originalGammLockId, valAddr.String()) + s.Require().NoError(err) + intermediaryAccConnection := superfluidKeeper.GetLockIdIntermediaryAccountConnection(ctx, originalGammLockId) + balancerIntermediaryAcc = superfluidKeeper.GetIntermediaryAccount(ctx, intermediaryAccConnection) + } + + // Superfluid undelegate the lock if the test case requires it. + if superfluidUndelegating { + err = superfluidKeeper.SuperfluidUndelegate(ctx, poolJoinAcc.String(), originalGammLockId) + s.Require().NoError(err) + } + + // Unlock the balancer lock if the test case requires it. + if unlocking { + // If lock was superfluid staked, we can't unlock via `BeginUnlock`, + // we need to unlock lock via `SuperfluidUnbondLock` + if superfluidUndelegating { + err = superfluidKeeper.SuperfluidUnbondLock(ctx, originalGammLockId, poolJoinAcc.String()) + s.Require().NoError(err) + } else { + lock, err := lockupKeeper.GetLockByID(ctx, originalGammLockId) + s.Require().NoError(err) + _, err = lockupKeeper.BeginUnlock(ctx, originalGammLockId, lock.Coins) + s.Require().NoError(err) + } + } + + balancerLock = &lockuptypes.PeriodLock{} + if !noLock { + balancerLock, err = lockupKeeper.GetLockByID(ctx, originalGammLockId) + s.Require().NoError(err) + } + + s.Require().NoError(err) + return joinPoolAmt, balancerIntermediaryAcc, balancerLock, poolCreateAcc, poolJoinAcc, balancerPooId, balancerPoolShareOut, valAddr + +} + +// delegationCheck checks staking related invariants of the test. +// We check the following in this method: +// - if superfluid staked previously, check if the original validator's delegation has been deleted. +// - Cehck if the delegation of the new validator matches what's expected. +func (s *KeeperTestSuite) delegationCheck(ctx sdk.Context, sender sdk.AccAddress, originalValAddr, newValAddr sdk.ValAddress, totalAmtConverted sdk.Int) { + if !originalValAddr.Empty() { + // check if original superfluid staked lock's delgation is successfully deleted + _, found := s.App.StakingKeeper.GetDelegation(s.Ctx, sender, originalValAddr) + s.Require().False(found) + } + // check if delegation amount matches + delegation, found := s.App.StakingKeeper.GetDelegation(s.Ctx, sender, newValAddr) + s.Require().True(found) + s.Require().True(totalAmtConverted.ToDec().Equal(delegation.Shares)) + s.Require().True(delegation.Shares.Equal(totalAmtConverted.ToDec())) +} + +// lockCheck checks lock related invariants of the test. +// We check the following in this method: +// - check if old synth lock has been deleted (both staking & unstaking) +// - check if old lock has been succesfully deleted. +func (s *KeeperTestSuite) lockCheck(ctx sdk.Context, lock lockuptypes.PeriodLock, valAddr string, checkUnstakingSynthLock bool) { + // The synthetic lockup should be deleted. + _, err := s.App.LockupKeeper.GetSyntheticLockup(s.Ctx, lock.ID, keeper.StakingSyntheticDenom(lock.Coins[0].Denom, valAddr)) + s.Require().Error(err) + + // intermediary account should have been deleted + _, err = s.App.LockupKeeper.GetSyntheticLockup(s.Ctx, lock.ID, keeper.UnstakingSyntheticDenom(lock.Coins[0].Denom, valAddr)) + s.Require().Error(err) + + // Lock check + _, err = s.App.LockupKeeper.GetLockByID(s.Ctx, lock.ID) + s.Require().Error(err) +} + +func (s *KeeperTestSuite) getExpectedBondDenomPoolAmtAfterConvert(sender sdk.AccAddress, poolId uint64, sharesToStake sdk.Coin) sdk.Int { + bondDenom := s.App.StakingKeeper.BondDenom(s.Ctx) + cc, _ := s.Ctx.CacheContext() + exitCoins, err := s.App.GAMMKeeper.ExitPool(cc, sender, poolId, sharesToStake.Amount, sdk.NewCoins()) + s.Require().NoError(err) + + var nonOsmoCoin sdk.Coin + for _, exitCoin := range exitCoins { + // if coin is not uosmo, add it to non-osmo Coins + if exitCoin.Denom != bondDenom { + nonOsmoCoin = exitCoin + } + } + _, err = s.App.PoolManagerKeeper.SwapExactAmountIn(cc, sender, poolId, nonOsmoCoin, bondDenom, sdk.ZeroInt()) + s.Require().NoError(err) + expectedLiquidity, err := s.App.GAMMKeeper.GetTotalPoolLiquidity(cc, poolId) + s.Require().NoError(err) + + return expectedLiquidity.AmountOf(bondDenom) +} + // type superfluidRedelegation struct { // lockId uint64 // oldValIndex int64 diff --git a/x/superfluid/types/codec.go b/x/superfluid/types/codec.go index aff8c223d21..4485fbb0af0 100644 --- a/x/superfluid/types/codec.go +++ b/x/superfluid/types/codec.go @@ -22,6 +22,7 @@ func RegisterCodec(cdc *codec.LegacyAmino) { cdc.RegisterConcrete(&MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition{}, "osmosis/unlock-and-migrate", nil) cdc.RegisterConcrete(&MsgCreateFullRangePositionAndSuperfluidDelegate{}, "osmosis/full-range-and-sf-delegate", nil) cdc.RegisterConcrete(&MsgAddToConcentratedLiquiditySuperfluidPosition{}, "osmosis/add-to-cl-superfluid-position", nil) + cdc.RegisterConcrete(&MsgUnbondConvertAndStake{}, "osmosis/unbond-convert-and-stake", nil) } func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { @@ -36,6 +37,7 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { &MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition{}, &MsgCreateFullRangePositionAndSuperfluidDelegate{}, &MsgAddToConcentratedLiquiditySuperfluidPosition{}, + &MsgUnbondConvertAndStake{}, ) registry.RegisterImplementations( diff --git a/x/superfluid/types/errors.go b/x/superfluid/types/errors.go index ada05c155d5..61dbf5625af 100644 --- a/x/superfluid/types/errors.go +++ b/x/superfluid/types/errors.go @@ -6,6 +6,8 @@ import ( errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + cltypes "github.com/osmosis-labs/osmosis/v17/x/concentrated-liquidity/types" ) @@ -115,3 +117,12 @@ type UnexpectedDenomError struct { func (e UnexpectedDenomError) Error() string { return fmt.Sprintf("provided denom (%s) was expected to be formatted as follows: %s", e.ProvidedDenom, e.ExpectedDenom) } + +type TokenConvertedLessThenDesiredStakeError struct { + ActualTotalAmtToStake sdk.Int + ExpectedTotalAmtToStake sdk.Int +} + +func (e TokenConvertedLessThenDesiredStakeError) Error() string { + return fmt.Sprintf("actual amount converted to stake (%s) is less then minimum amount expected to be staked (%s)", e.ActualTotalAmtToStake, e.ExpectedTotalAmtToStake) +} diff --git a/x/superfluid/types/expected_keepers.go b/x/superfluid/types/expected_keepers.go index 0c8b7b2f895..f762177d2bf 100644 --- a/x/superfluid/types/expected_keepers.go +++ b/x/superfluid/types/expected_keepers.go @@ -121,3 +121,18 @@ type ConcentratedKeeper interface { GetUserPositions(ctx sdk.Context, addr sdk.AccAddress, poolId uint64) ([]model.Position, error) GetLockIdFromPositionId(ctx sdk.Context, positionId uint64) (uint64, error) } + +type PoolManagerKeeper interface { + SwapExactAmountIn( + ctx sdk.Context, + sender sdk.AccAddress, + poolId uint64, + tokenIn sdk.Coin, + tokenOutDenom string, + tokenOutMinAmount sdk.Int, + ) (sdk.Int, error) +} + +type ValSetPreferenceKeeper interface { + DelegateToValidatorSet(ctx sdk.Context, delegatorAddr string, coin sdk.Coin) error +} diff --git a/x/superfluid/types/msg_test.go b/x/superfluid/types/msg_test.go index 9d3d4ab6f20..761a1792d46 100644 --- a/x/superfluid/types/msg_test.go +++ b/x/superfluid/types/msg_test.go @@ -4,6 +4,8 @@ import ( "testing" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto/secp256k1" "github.com/osmosis-labs/osmosis/v17/app/apptesting" "github.com/osmosis-labs/osmosis/v17/x/superfluid/types" @@ -73,3 +75,78 @@ func TestAuthzMsg(t *testing.T) { }) } } + +func TestUnbondConvertAndStakeMsg(t *testing.T) { + pk1 := ed25519.GenPrivKey().PubKey() + addr1 := sdk.AccAddress(pk1.Address()).String() + + valPub := secp256k1.GenPrivKey().PubKey() + valAddr := sdk.ValAddress(valPub.Address()).String() + + testCases := []struct { + name string + msg sdk.Msg + expectedError bool + }{ + { + name: "happy case", + msg: &types.MsgUnbondConvertAndStake{ + LockId: 2, + Sender: addr1, + ValAddr: valAddr, + MinAmtToStake: sdk.NewInt(10), + SharesToConvert: sdk.NewInt64Coin("foo", 10), + }, + }, + { + name: "lock id is 0 should not fail", + msg: &types.MsgUnbondConvertAndStake{ + LockId: 0, + Sender: addr1, + ValAddr: valAddr, + MinAmtToStake: sdk.NewInt(10), + SharesToConvert: sdk.NewInt64Coin("foo", 10), + }, + }, + { + name: "no val address should not fail", + msg: &types.MsgUnbondConvertAndStake{ + LockId: 0, + Sender: addr1, + MinAmtToStake: sdk.NewInt(10), + SharesToConvert: sdk.NewInt64Coin("foo", 10), + }, + }, + { + name: "err: sender is invalid", + msg: &types.MsgUnbondConvertAndStake{ + LockId: 0, + Sender: "abcd", + ValAddr: valAddr, + MinAmtToStake: sdk.NewInt(10), + SharesToConvert: sdk.NewInt64Coin("foo", 10), + }, + expectedError: true, + }, + { + name: "err: min amount to stake is negative", + msg: &types.MsgUnbondConvertAndStake{ + LockId: 0, + Sender: addr1, + MinAmtToStake: sdk.NewInt(10).Neg(), + SharesToConvert: sdk.NewInt64Coin("foo", 10), + }, + expectedError: true, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/x/superfluid/types/msgs.go b/x/superfluid/types/msgs.go index b752105464d..c6a85aa2195 100644 --- a/x/superfluid/types/msgs.go +++ b/x/superfluid/types/msgs.go @@ -21,6 +21,7 @@ const ( TypeMsgUnlockAndMigrateShares = "unlock_and_migrate_shares" TypeMsgCreateFullRangePositionAndSuperfluidDelegate = "create_full_range_position_and_delegate" TypeMsgAddToConcentratedLiquiditySuperfluidPosition = "add_to_concentrated_liquidity_superfluid_position" + TypeMsgUnbondConvertAndStake = "add_to_concentrated_liquidity_superfluid_position" ) var _ sdk.Msg = &MsgSuperfluidDelegate{} @@ -392,3 +393,44 @@ func (msg MsgAddToConcentratedLiquiditySuperfluidPosition) GetSigners() []sdk.Ac } return []sdk.AccAddress{sender} } + +var _ sdk.Msg = &MsgUnbondConvertAndStake{} + +func NewMsgUnbondConvertAndStake(sender sdk.AccAddress, lockId uint64, valAddr string, minAmtToStake sdk.Int, sharesToConvert sdk.Coin) *MsgUnbondConvertAndStake { + return &MsgUnbondConvertAndStake{ + Sender: sender.String(), + LockId: lockId, + ValAddr: valAddr, + MinAmtToStake: minAmtToStake, + SharesToConvert: sharesToConvert, + } +} + +func (msg MsgUnbondConvertAndStake) Route() string { return RouterKey } +func (msg MsgUnbondConvertAndStake) Type() string { + return TypeMsgUnbondConvertAndStake +} + +func (msg MsgUnbondConvertAndStake) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Sender) + if err != nil { + return fmt.Errorf("Invalid sender address (%s)", err) + } + + if msg.MinAmtToStake.IsNegative() { + return fmt.Errorf("Min amount to stake cannot be negative") + } + return nil +} + +func (msg MsgUnbondConvertAndStake) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg)) +} + +func (msg MsgUnbondConvertAndStake) GetSigners() []sdk.AccAddress { + sender, err := sdk.AccAddressFromBech32(msg.Sender) + if err != nil { + panic(err) + } + return []sdk.AccAddress{sender} +} diff --git a/x/superfluid/types/tx.pb.go b/x/superfluid/types/tx.pb.go index e14270d6fbe..1aa375fd1c2 100644 --- a/x/superfluid/types/tx.pb.go +++ b/x/superfluid/types/tx.pb.go @@ -1021,6 +1021,122 @@ func (m *MsgAddToConcentratedLiquiditySuperfluidPositionResponse) GetLockId() ui return 0 } +// ===================== MsgUnbondConvertAndStake +type MsgUnbondConvertAndStake struct { + // lock ID to convert and stake. + // lock id with 0 should be provided if converting liquid gamm shares to stake + LockId uint64 `protobuf:"varint,1,opt,name=lock_id,json=lockId,proto3" json:"lock_id,omitempty" yaml:"lock_id"` + Sender string `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` + // validator address to delegate to. + // If provided empty string, we use the validators returned from + // valset-preference module. + ValAddr string `protobuf:"bytes,3,opt,name=val_addr,json=valAddr,proto3" json:"val_addr,omitempty"` + // min_amt_to_stake indicates the minimum amount to stake after conversion + MinAmtToStake github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,4,opt,name=min_amt_to_stake,json=minAmtToStake,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"min_amt_to_stake" yaml:"min_amt_to_stake"` + // shares_to_convert indicates shares wanted to stake. + // Note that this field is only used for liquid(unlocked) gamm shares. + // For all other cases, this field would be disregarded. + SharesToConvert types.Coin `protobuf:"bytes,5,opt,name=shares_to_convert,json=sharesToConvert,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coin" json:"shares_to_convert" yaml:"shares_to_convert"` +} + +func (m *MsgUnbondConvertAndStake) Reset() { *m = MsgUnbondConvertAndStake{} } +func (m *MsgUnbondConvertAndStake) String() string { return proto.CompactTextString(m) } +func (*MsgUnbondConvertAndStake) ProtoMessage() {} +func (*MsgUnbondConvertAndStake) Descriptor() ([]byte, []int) { + return fileDescriptor_55b645f187d22814, []int{18} +} +func (m *MsgUnbondConvertAndStake) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUnbondConvertAndStake) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUnbondConvertAndStake.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 *MsgUnbondConvertAndStake) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUnbondConvertAndStake.Merge(m, src) +} +func (m *MsgUnbondConvertAndStake) XXX_Size() int { + return m.Size() +} +func (m *MsgUnbondConvertAndStake) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUnbondConvertAndStake.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUnbondConvertAndStake proto.InternalMessageInfo + +func (m *MsgUnbondConvertAndStake) GetLockId() uint64 { + if m != nil { + return m.LockId + } + return 0 +} + +func (m *MsgUnbondConvertAndStake) GetSender() string { + if m != nil { + return m.Sender + } + return "" +} + +func (m *MsgUnbondConvertAndStake) GetValAddr() string { + if m != nil { + return m.ValAddr + } + return "" +} + +func (m *MsgUnbondConvertAndStake) GetSharesToConvert() types.Coin { + if m != nil { + return m.SharesToConvert + } + return types.Coin{} +} + +type MsgUnbondConvertAndStakeResponse struct { + TotalAmtStaked github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,1,opt,name=total_amt_staked,json=totalAmtStaked,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"total_amt_staked" yaml:"total_amt_staked"` +} + +func (m *MsgUnbondConvertAndStakeResponse) Reset() { *m = MsgUnbondConvertAndStakeResponse{} } +func (m *MsgUnbondConvertAndStakeResponse) String() string { return proto.CompactTextString(m) } +func (*MsgUnbondConvertAndStakeResponse) ProtoMessage() {} +func (*MsgUnbondConvertAndStakeResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_55b645f187d22814, []int{19} +} +func (m *MsgUnbondConvertAndStakeResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUnbondConvertAndStakeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUnbondConvertAndStakeResponse.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 *MsgUnbondConvertAndStakeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUnbondConvertAndStakeResponse.Merge(m, src) +} +func (m *MsgUnbondConvertAndStakeResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUnbondConvertAndStakeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUnbondConvertAndStakeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUnbondConvertAndStakeResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgSuperfluidDelegate)(nil), "osmosis.superfluid.MsgSuperfluidDelegate") proto.RegisterType((*MsgSuperfluidDelegateResponse)(nil), "osmosis.superfluid.MsgSuperfluidDelegateResponse") @@ -1040,96 +1156,109 @@ func init() { proto.RegisterType((*MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse)(nil), "osmosis.superfluid.MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse") proto.RegisterType((*MsgAddToConcentratedLiquiditySuperfluidPosition)(nil), "osmosis.superfluid.MsgAddToConcentratedLiquiditySuperfluidPosition") proto.RegisterType((*MsgAddToConcentratedLiquiditySuperfluidPositionResponse)(nil), "osmosis.superfluid.MsgAddToConcentratedLiquiditySuperfluidPositionResponse") + proto.RegisterType((*MsgUnbondConvertAndStake)(nil), "osmosis.superfluid.MsgUnbondConvertAndStake") + proto.RegisterType((*MsgUnbondConvertAndStakeResponse)(nil), "osmosis.superfluid.MsgUnbondConvertAndStakeResponse") } func init() { proto.RegisterFile("osmosis/superfluid/tx.proto", fileDescriptor_55b645f187d22814) } var fileDescriptor_55b645f187d22814 = []byte{ - // 1343 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x58, 0xcf, 0x73, 0xd3, 0xc6, - 0x17, 0x8f, 0xec, 0x90, 0xc0, 0x86, 0x00, 0xd1, 0x97, 0x1f, 0xc6, 0x5f, 0xb0, 0xcc, 0x42, 0x69, - 0xf8, 0x61, 0x29, 0x86, 0x16, 0x98, 0x9c, 0x88, 0xe3, 0xa1, 0x63, 0x1a, 0x4f, 0x19, 0x11, 0xa6, - 0x33, 0x5c, 0x3c, 0xb2, 0x77, 0x23, 0xd4, 0xc8, 0x5a, 0xa3, 0x95, 0x92, 0x30, 0x3d, 0xb5, 0x3d, - 0x74, 0x86, 0x13, 0xc7, 0xde, 0x7a, 0x6e, 0x0f, 0x1d, 0xfe, 0x84, 0x1e, 0x7a, 0x60, 0x7a, 0xe2, - 0xd8, 0x69, 0x67, 0x42, 0x87, 0x1c, 0x7a, 0xcf, 0xb1, 0xa7, 0xce, 0x4a, 0xab, 0xb5, 0x9c, 0x48, - 0x38, 0x32, 0xee, 0xa1, 0x97, 0x44, 0xbb, 0xfb, 0x7e, 0x7c, 0xde, 0xdb, 0xf7, 0x79, 0xbb, 0x6b, - 0xf0, 0x7f, 0x42, 0xbb, 0x84, 0x5a, 0x54, 0xa3, 0x7e, 0x0f, 0xbb, 0x6b, 0xb6, 0x6f, 0x21, 0xcd, - 0xdb, 0x52, 0x7b, 0x2e, 0xf1, 0x88, 0x2c, 0xf3, 0x45, 0xb5, 0xbf, 0x58, 0x3c, 0x69, 0x12, 0x93, - 0x04, 0xcb, 0x1a, 0xfb, 0x0a, 0x25, 0x8b, 0x73, 0x46, 0xd7, 0x72, 0x88, 0x16, 0xfc, 0xe5, 0x53, - 0x25, 0x93, 0x10, 0xd3, 0xc6, 0x5a, 0x30, 0x6a, 0xfb, 0x6b, 0x1a, 0xf2, 0x5d, 0xc3, 0xb3, 0x88, - 0x13, 0xad, 0x77, 0x02, 0xeb, 0x5a, 0xdb, 0xa0, 0x58, 0xdb, 0xa8, 0xb6, 0xb1, 0x67, 0x54, 0xb5, - 0x0e, 0xb1, 0xa2, 0x75, 0x65, 0xaf, 0xbe, 0x67, 0x75, 0x31, 0xf5, 0x8c, 0x6e, 0x8f, 0x0b, 0x5c, - 0x4c, 0x80, 0xde, 0xff, 0x0c, 0x85, 0xe0, 0x77, 0x12, 0x38, 0xd5, 0xa4, 0xe6, 0x43, 0x31, 0x5f, - 0xc7, 0x36, 0x36, 0x0d, 0x0f, 0xcb, 0x57, 0xc0, 0x14, 0xc5, 0x0e, 0xc2, 0x6e, 0x41, 0x2a, 0x4b, - 0xf3, 0x47, 0x6a, 0x73, 0xbb, 0xdb, 0xca, 0xec, 0x33, 0xa3, 0x6b, 0x2f, 0xc2, 0x70, 0x1e, 0xea, - 0x5c, 0x40, 0x3e, 0x03, 0xa6, 0x6d, 0xd2, 0x59, 0x6f, 0x59, 0xa8, 0x90, 0x2b, 0x4b, 0xf3, 0x93, - 0xfa, 0x14, 0x1b, 0x36, 0x90, 0x7c, 0x16, 0x1c, 0xde, 0x30, 0xec, 0x96, 0x81, 0x90, 0x5b, 0xc8, - 0x33, 0x2b, 0xfa, 0xf4, 0x86, 0x61, 0x2f, 0x21, 0xe4, 0x2e, 0x96, 0x9f, 0xff, 0xf5, 0xf2, 0x6a, - 0x42, 0x76, 0x2b, 0x88, 0x03, 0x80, 0x0a, 0x38, 0x9f, 0x88, 0x4c, 0xc7, 0xb4, 0x47, 0x1c, 0x8a, - 0xe1, 0x57, 0x12, 0x38, 0x33, 0x20, 0xf1, 0xc8, 0x41, 0x63, 0x44, 0xbf, 0x08, 0x19, 0xc4, 0xf3, - 0x09, 0x10, 0x7d, 0xe1, 0x07, 0x5e, 0x00, 0x4a, 0x0a, 0x04, 0x01, 0xf3, 0xeb, 0xfd, 0x30, 0xdb, - 0xc4, 0x41, 0x2b, 0xa4, 0xb3, 0x3e, 0x16, 0x98, 0x17, 0x19, 0xcc, 0x52, 0x22, 0x4c, 0xe6, 0xa7, - 0xc2, 0xc4, 0x12, 0x70, 0x46, 0x18, 0x04, 0xce, 0x9f, 0x24, 0x70, 0x29, 0x25, 0x96, 0x25, 0x67, - 0xcc, 0xa0, 0xe5, 0x1a, 0x98, 0x64, 0xb5, 0x1c, 0x54, 0xc5, 0xcc, 0x8d, 0xb3, 0x6a, 0x58, 0xec, - 0x2a, 0x2b, 0x76, 0x95, 0x17, 0xbb, 0xba, 0x4c, 0x2c, 0xa7, 0xf6, 0xbf, 0x57, 0xdb, 0xca, 0xc4, - 0xee, 0xb6, 0x32, 0x13, 0x3a, 0x60, 0x4a, 0x50, 0x0f, 0x74, 0xe1, 0x27, 0xe0, 0xfa, 0x41, 0xf0, - 0x46, 0x01, 0xc6, 0xc1, 0x48, 0x71, 0x30, 0x70, 0x57, 0x02, 0xe7, 0x9a, 0xd4, 0x64, 0xc2, 0x4b, - 0x0e, 0x7a, 0x3f, 0x2e, 0x18, 0xe0, 0x10, 0x03, 0x47, 0x0b, 0xb9, 0x72, 0xfe, 0xdd, 0x91, 0x2d, - 0xb0, 0xc8, 0x7e, 0x7c, 0xa3, 0xcc, 0x9b, 0x96, 0xf7, 0xc4, 0x6f, 0xab, 0x1d, 0xd2, 0xd5, 0x38, - 0xe7, 0xc3, 0x7f, 0x15, 0x8a, 0xd6, 0x35, 0xef, 0x59, 0x0f, 0xd3, 0x40, 0x81, 0xea, 0xa1, 0xe5, - 0x77, 0xb1, 0xea, 0x0a, 0xab, 0x85, 0x4b, 0x51, 0x2d, 0xb0, 0xf0, 0x2a, 0x86, 0x83, 0x2a, 0x49, - 0xf4, 0xba, 0x15, 0xec, 0x76, 0x6a, 0xcc, 0x22, 0x6b, 0xc7, 0x40, 0xae, 0x51, 0xe7, 0x09, 0xcb, - 0x35, 0xea, 0xf0, 0x65, 0x0e, 0x68, 0x4d, 0x6a, 0x2e, 0xbb, 0xd8, 0xf0, 0xf0, 0x3d, 0xdf, 0xb6, - 0x75, 0xc3, 0x31, 0xf1, 0x03, 0x42, 0x2d, 0xd6, 0xbc, 0xfe, 0xdb, 0xf9, 0x93, 0xaf, 0x81, 0xe9, - 0x1e, 0x21, 0x36, 0x2b, 0x91, 0x49, 0x16, 0x71, 0x4d, 0xde, 0xdd, 0x56, 0x8e, 0x85, 0x48, 0xf9, - 0x02, 0xd4, 0xa7, 0xd8, 0x57, 0x03, 0x2d, 0x7e, 0xc8, 0x92, 0x0d, 0xa3, 0x64, 0xaf, 0xf9, 0xb6, - 0x5d, 0x71, 0x59, 0x2e, 0xc2, 0x94, 0xaf, 0xf5, 0x53, 0xfd, 0x14, 0xdc, 0xce, 0x98, 0x31, 0x91, - 0xfd, 0xd3, 0x20, 0x2c, 0xd2, 0xfa, 0x40, 0xc9, 0xd6, 0xe5, 0x12, 0x00, 0x3d, 0x6e, 0xa0, 0x51, - 0xe7, 0xdc, 0x8a, 0xcd, 0xb0, 0xbe, 0x5e, 0x68, 0x52, 0xf3, 0x91, 0xf3, 0x80, 0x10, 0xfb, 0xf3, - 0x27, 0x96, 0x87, 0x6d, 0x8b, 0x7a, 0x18, 0xb1, 0x61, 0x96, 0xed, 0x88, 0x25, 0x24, 0x37, 0x34, - 0x21, 0x97, 0x58, 0x42, 0x94, 0x28, 0x21, 0xbe, 0xc3, 0xa6, 0x2b, 0x9b, 0x7d, 0xe7, 0x15, 0x36, - 0x01, 0xef, 0x83, 0x72, 0x1a, 0x32, 0x11, 0xf6, 0x65, 0x70, 0x1c, 0x6f, 0x59, 0x1e, 0x46, 0x2d, - 0xce, 0x58, 0x5a, 0x90, 0xca, 0xf9, 0xf9, 0x49, 0x7d, 0x36, 0x9c, 0x5e, 0x09, 0x88, 0x4b, 0xe1, - 0x0f, 0x79, 0x70, 0x27, 0x30, 0x66, 0x87, 0x75, 0xdc, 0xb4, 0x4c, 0xd7, 0xf0, 0xf0, 0xc3, 0x27, - 0x86, 0x8b, 0xe9, 0x2a, 0x11, 0xc9, 0x5e, 0x26, 0x4e, 0x07, 0x3b, 0x1e, 0x5b, 0x43, 0x51, 0xe2, - 0x33, 0xa6, 0x21, 0xde, 0xc7, 0xf2, 0xf1, 0x34, 0xf0, 0x05, 0x28, 0x7a, 0x9b, 0x09, 0xe6, 0x68, - 0x00, 0xa0, 0xe5, 0x91, 0x56, 0x37, 0x44, 0x34, 0xbc, 0xd1, 0x95, 0x79, 0xa3, 0x2b, 0x70, 0x04, - 0x7b, 0x2d, 0x40, 0xfd, 0x38, 0xe5, 0x61, 0xf1, 0x28, 0xe5, 0xe7, 0x12, 0x38, 0xe6, 0x91, 0x75, - 0xec, 0xb4, 0x88, 0xef, 0xb5, 0xba, 0x8c, 0x35, 0x93, 0xc3, 0x58, 0xd3, 0xe0, 0x6e, 0x4e, 0x85, - 0x6e, 0x06, 0xd5, 0x61, 0x26, 0x3a, 0x1d, 0x0d, 0x94, 0x3f, 0xf3, 0xbd, 0xa6, 0xe5, 0xd0, 0x45, - 0x85, 0x6d, 0x7e, 0xb1, 0xbf, 0xf9, 0xa2, 0xf9, 0x44, 0xf8, 0x7f, 0xcd, 0x83, 0xbb, 0xa3, 0xee, - 0x95, 0x28, 0x8c, 0xc7, 0x60, 0xda, 0xe8, 0x12, 0xdf, 0xf1, 0x16, 0xf8, 0xa6, 0xdd, 0x65, 0xf1, - 0xfc, 0xbe, 0xad, 0x5c, 0x3e, 0x00, 0xec, 0x86, 0xe3, 0xf5, 0xb7, 0x8d, 0x9b, 0x81, 0x7a, 0x64, - 0xb0, 0x6f, 0xbb, 0x1a, 0x6c, 0xf2, 0x7b, 0xdb, 0xae, 0x0a, 0xdb, 0x55, 0x79, 0x13, 0xcc, 0xd9, - 0xd6, 0x53, 0xdf, 0x42, 0x96, 0xf7, 0xac, 0xd5, 0x09, 0x3a, 0x01, 0x0a, 0x9b, 0x4f, 0xed, 0x7e, - 0x06, 0x2f, 0x75, 0xdc, 0xe9, 0x97, 0xc8, 0x3e, 0x83, 0x50, 0x3f, 0x21, 0xe6, 0xc2, 0x6e, 0x83, - 0xe4, 0x47, 0xe0, 0xc8, 0x17, 0xc4, 0x72, 0x5a, 0xec, 0x76, 0x18, 0xf4, 0xb4, 0x99, 0x1b, 0x45, - 0x35, 0xbc, 0x3a, 0xaa, 0xd1, 0xd5, 0x51, 0x5d, 0x8d, 0xae, 0x8e, 0xb5, 0x73, 0xbc, 0x3c, 0x4e, - 0x84, 0x2e, 0x84, 0x2a, 0x7c, 0xf1, 0x46, 0x91, 0xf4, 0xc3, 0x6c, 0xcc, 0x84, 0xe1, 0x37, 0xf9, - 0xe0, 0x14, 0x58, 0x42, 0x68, 0x95, 0xc4, 0x37, 0x6c, 0x25, 0xf2, 0xdf, 0xef, 0x69, 0x82, 0x6f, - 0xb7, 0xc1, 0x4c, 0xd4, 0xa1, 0xc4, 0x19, 0x5c, 0x3b, 0xbd, 0xbb, 0xad, 0xc8, 0x51, 0x3f, 0x11, - 0x8b, 0x30, 0xd6, 0xcc, 0x50, 0x8c, 0xa8, 0xb9, 0x61, 0x44, 0x6d, 0x45, 0x8c, 0x40, 0x98, 0x5a, - 0x2e, 0x46, 0x0b, 0xc3, 0x89, 0x77, 0x3e, 0x89, 0x11, 0x91, 0x3a, 0xd4, 0x67, 0x83, 0x89, 0x3a, - 0x1f, 0xef, 0x73, 0x50, 0xe5, 0x49, 0x1d, 0xd1, 0x41, 0x75, 0x8f, 0x83, 0xea, 0xe2, 0x55, 0xc6, - 0xa3, 0x0f, 0x22, 0x1e, 0x19, 0x08, 0x55, 0x3c, 0x52, 0xe9, 0xd8, 0xf1, 0x33, 0x3c, 0x4a, 0x0d, - 0xfc, 0x25, 0x1f, 0x9c, 0x2c, 0x59, 0x76, 0x41, 0x30, 0x69, 0xe4, 0xdd, 0x88, 0x51, 0x30, 0xf7, - 0x2f, 0x52, 0x30, 0x3f, 0x6e, 0x0a, 0xae, 0x83, 0x59, 0x07, 0x6f, 0xb6, 0x04, 0x43, 0x0a, 0x87, - 0x02, 0x0f, 0xf7, 0x32, 0xd3, 0xef, 0x64, 0xe8, 0x61, 0xc0, 0x18, 0xd4, 0x8f, 0x3a, 0x78, 0x53, - 0xe4, 0x3d, 0x7e, 0x60, 0xec, 0xbb, 0x48, 0xec, 0x3d, 0x30, 0x6e, 0xfc, 0x0d, 0x40, 0xbe, 0x49, - 0x4d, 0xd9, 0x05, 0x72, 0xd2, 0xe5, 0x49, 0xdd, 0xff, 0xcc, 0x54, 0x13, 0x5f, 0x46, 0xc5, 0xea, - 0x81, 0x45, 0x45, 0x19, 0x6c, 0x81, 0x93, 0x89, 0x0f, 0xa8, 0x6b, 0x43, 0x4d, 0xf5, 0x85, 0x8b, - 0x37, 0x33, 0x08, 0xa7, 0x79, 0x16, 0xcf, 0x8b, 0x83, 0x78, 0x8e, 0x84, 0x0f, 0xe4, 0x79, 0xdf, - 0x43, 0xe0, 0x7b, 0x09, 0x5c, 0x18, 0xfe, 0xcc, 0xb9, 0x93, 0x21, 0xa8, 0x01, 0xcd, 0xe2, 0xdd, - 0x51, 0x35, 0x05, 0xc2, 0x6f, 0x25, 0x70, 0x36, 0xfd, 0x39, 0xb2, 0x90, 0x62, 0x3f, 0x55, 0xa3, - 0x78, 0x27, 0xab, 0x86, 0x40, 0xf2, 0xb3, 0x04, 0xae, 0x67, 0xba, 0xeb, 0x2f, 0xa7, 0xb8, 0xca, - 0x62, 0xa4, 0xf8, 0xe9, 0x18, 0x8c, 0x88, 0x10, 0xbe, 0x04, 0xa7, 0x92, 0xef, 0xc1, 0xd7, 0x53, - 0xbc, 0x24, 0x4a, 0x17, 0x3f, 0xca, 0x22, 0x2d, 0x9c, 0xff, 0x21, 0x81, 0x8f, 0x47, 0xbb, 0x9e, - 0xae, 0xa4, 0xfa, 0x1b, 0xc1, 0x5a, 0x71, 0x75, 0x9c, 0xd6, 0x06, 0xaa, 0x23, 0xd3, 0x1d, 0x20, - 0xad, 0x3a, 0xb2, 0x18, 0x49, 0xad, 0x8e, 0x51, 0xce, 0xc1, 0xda, 0x83, 0x57, 0x6f, 0x4b, 0xd2, - 0xeb, 0xb7, 0x25, 0xe9, 0xcf, 0xb7, 0x25, 0xe9, 0xc5, 0x4e, 0x69, 0xe2, 0xf5, 0x4e, 0x69, 0xe2, - 0xb7, 0x9d, 0xd2, 0xc4, 0xe3, 0x5b, 0xb1, 0x13, 0x81, 0x3b, 0xac, 0xd8, 0x46, 0x9b, 0x46, 0x03, - 0x6d, 0xa3, 0x7a, 0x5b, 0xdb, 0x1a, 0xf8, 0x65, 0x90, 0x9d, 0x12, 0xed, 0xa9, 0xe0, 0x5e, 0x75, - 0xf3, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd3, 0x81, 0x35, 0xa1, 0x3c, 0x14, 0x00, 0x00, + // 1510 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x58, 0xcd, 0x6f, 0x13, 0x47, + 0x1b, 0xcf, 0xda, 0x21, 0x81, 0x09, 0x09, 0xc9, 0xbe, 0x7c, 0x18, 0xbf, 0x60, 0x9b, 0xe1, 0x2b, + 0x7c, 0xd8, 0x1b, 0xc3, 0xfb, 0x02, 0xca, 0x89, 0x38, 0x16, 0xaf, 0x02, 0xb1, 0x5e, 0xb4, 0x04, + 0x55, 0xe2, 0x62, 0xad, 0x3d, 0x93, 0x65, 0x9b, 0xdd, 0x1d, 0xb3, 0x33, 0x4e, 0x82, 0x7a, 0x6a, + 0x7b, 0xa8, 0xc4, 0x09, 0xf5, 0xd2, 0xde, 0x7a, 0x6e, 0x0f, 0x15, 0xff, 0x40, 0xa5, 0x1e, 0x7a, + 0x40, 0x3d, 0x71, 0xac, 0x5a, 0x29, 0x54, 0x70, 0xe8, 0x3d, 0x97, 0x5e, 0xab, 0xd9, 0x9d, 0x1d, + 0xaf, 0x93, 0xdd, 0x38, 0x6b, 0xd2, 0x43, 0x2f, 0xe0, 0x9d, 0x79, 0xe6, 0x79, 0x7e, 0xcf, 0xc7, + 0xef, 0x79, 0x66, 0x02, 0xfe, 0x4d, 0xa8, 0x43, 0xa8, 0x45, 0x35, 0xda, 0xed, 0x60, 0x6f, 0xd5, + 0xee, 0x5a, 0x48, 0x63, 0x9b, 0x95, 0x8e, 0x47, 0x18, 0x51, 0x55, 0xb1, 0x59, 0xe9, 0x6d, 0xe6, + 0x8f, 0x9b, 0xc4, 0x24, 0xfe, 0xb6, 0xc6, 0x7f, 0x05, 0x92, 0xf9, 0x19, 0xc3, 0xb1, 0x5c, 0xa2, + 0xf9, 0xff, 0x8a, 0xa5, 0x82, 0x49, 0x88, 0x69, 0x63, 0xcd, 0xff, 0x6a, 0x75, 0x57, 0x35, 0xd4, + 0xf5, 0x0c, 0x66, 0x11, 0x37, 0xdc, 0x6f, 0xfb, 0xda, 0xb5, 0x96, 0x41, 0xb1, 0xb6, 0x5e, 0x6d, + 0x61, 0x66, 0x54, 0xb5, 0x36, 0xb1, 0xc2, 0xfd, 0xe2, 0xce, 0xf3, 0xcc, 0x72, 0x30, 0x65, 0x86, + 0xd3, 0x11, 0x02, 0xe7, 0x63, 0xa0, 0xf7, 0x7e, 0x06, 0x42, 0xf0, 0x6b, 0x05, 0x9c, 0x68, 0x50, + 0xf3, 0x91, 0x5c, 0xaf, 0x63, 0x1b, 0x9b, 0x06, 0xc3, 0xea, 0x15, 0x30, 0x46, 0xb1, 0x8b, 0xb0, + 0x97, 0x53, 0x4a, 0xca, 0xec, 0x91, 0xda, 0xcc, 0xf6, 0x56, 0x71, 0xf2, 0xb9, 0xe1, 0xd8, 0xf3, + 0x30, 0x58, 0x87, 0xba, 0x10, 0x50, 0x4f, 0x81, 0x71, 0x9b, 0xb4, 0xd7, 0x9a, 0x16, 0xca, 0x65, + 0x4a, 0xca, 0xec, 0xa8, 0x3e, 0xc6, 0x3f, 0x97, 0x90, 0x7a, 0x1a, 0x1c, 0x5e, 0x37, 0xec, 0xa6, + 0x81, 0x90, 0x97, 0xcb, 0x72, 0x2d, 0xfa, 0xf8, 0xba, 0x61, 0x2f, 0x20, 0xe4, 0xcd, 0x97, 0x5e, + 0xfc, 0xf1, 0xea, 0x6a, 0x4c, 0x74, 0xcb, 0x48, 0x00, 0x80, 0x45, 0x70, 0x36, 0x16, 0x99, 0x8e, + 0x69, 0x87, 0xb8, 0x14, 0xc3, 0x4f, 0x15, 0x70, 0xaa, 0x4f, 0xe2, 0xb1, 0x8b, 0x0e, 0x10, 0xfd, + 0x3c, 0xe4, 0x10, 0xcf, 0xc6, 0x40, 0xec, 0x4a, 0x3b, 0xf0, 0x1c, 0x28, 0x26, 0x40, 0x90, 0x30, + 0x3f, 0xdb, 0x0d, 0xb3, 0x45, 0x5c, 0xb4, 0x4c, 0xda, 0x6b, 0x07, 0x02, 0xf3, 0x3c, 0x87, 0x59, + 0x88, 0x85, 0xc9, 0xed, 0x94, 0xb9, 0x58, 0x0c, 0xce, 0x10, 0x83, 0xc4, 0xf9, 0xbd, 0x02, 0x2e, + 0x24, 0xf8, 0xb2, 0xe0, 0x1e, 0x30, 0x68, 0xb5, 0x06, 0x46, 0x79, 0x2d, 0xfb, 0x55, 0x31, 0x71, + 0xe3, 0x74, 0x25, 0x28, 0xf6, 0x0a, 0x2f, 0xf6, 0x8a, 0x28, 0xf6, 0xca, 0x22, 0xb1, 0xdc, 0xda, + 0xbf, 0x5e, 0x6f, 0x15, 0x47, 0xb6, 0xb7, 0x8a, 0x13, 0x81, 0x01, 0x7e, 0x08, 0xea, 0xfe, 0x59, + 0xf8, 0x3f, 0x70, 0x7d, 0x3f, 0x78, 0x43, 0x07, 0xa3, 0x60, 0x94, 0x28, 0x18, 0xb8, 0xad, 0x80, + 0x33, 0x0d, 0x6a, 0x72, 0xe1, 0x05, 0x17, 0x7d, 0x18, 0x17, 0x0c, 0x70, 0x88, 0x83, 0xa3, 0xb9, + 0x4c, 0x29, 0xbb, 0xb7, 0x67, 0x73, 0xdc, 0xb3, 0xef, 0xde, 0x16, 0x67, 0x4d, 0x8b, 0x3d, 0xed, + 0xb6, 0x2a, 0x6d, 0xe2, 0x68, 0x82, 0xf3, 0xc1, 0x7f, 0x65, 0x8a, 0xd6, 0x34, 0xf6, 0xbc, 0x83, + 0xa9, 0x7f, 0x80, 0xea, 0x81, 0xe6, 0xbd, 0x58, 0x75, 0x85, 0xd7, 0xc2, 0x85, 0xb0, 0x16, 0xb8, + 0x7b, 0x65, 0xc3, 0x45, 0xe5, 0x38, 0x7a, 0xdd, 0xf2, 0xb3, 0x9d, 0xe8, 0xb3, 0x8c, 0xda, 0x14, + 0xc8, 0x2c, 0xd5, 0x45, 0xc0, 0x32, 0x4b, 0x75, 0xf8, 0x2a, 0x03, 0xb4, 0x06, 0x35, 0x17, 0x3d, + 0x6c, 0x30, 0x7c, 0xaf, 0x6b, 0xdb, 0xba, 0xe1, 0x9a, 0xf8, 0x21, 0xa1, 0x16, 0x6f, 0x5e, 0xff, + 0xec, 0xf8, 0xa9, 0xd7, 0xc0, 0x78, 0x87, 0x10, 0x9b, 0x97, 0xc8, 0x28, 0xf7, 0xb8, 0xa6, 0x6e, + 0x6f, 0x15, 0xa7, 0x02, 0xa4, 0x62, 0x03, 0xea, 0x63, 0xfc, 0xd7, 0x12, 0x9a, 0xbf, 0xcc, 0x83, + 0x0d, 0xc3, 0x60, 0xaf, 0x76, 0x6d, 0xbb, 0xec, 0xf1, 0x58, 0x04, 0x21, 0x5f, 0xed, 0x85, 0xfa, + 0x19, 0xb8, 0x9d, 0x32, 0x62, 0x32, 0xfa, 0x27, 0x41, 0x50, 0xa4, 0xf5, 0xbe, 0x92, 0xad, 0xab, + 0x05, 0x00, 0x3a, 0x42, 0xc1, 0x52, 0x5d, 0x70, 0x2b, 0xb2, 0xc2, 0xfb, 0x7a, 0xae, 0x41, 0xcd, + 0xc7, 0xee, 0x43, 0x42, 0xec, 0x8f, 0x9e, 0x5a, 0x0c, 0xdb, 0x16, 0x65, 0x18, 0xf1, 0xcf, 0x34, + 0xe9, 0x88, 0x04, 0x24, 0x33, 0x30, 0x20, 0x17, 0x78, 0x40, 0x8a, 0x61, 0x40, 0xba, 0x2e, 0x5f, + 0x2e, 0x6f, 0xf4, 0x8c, 0x97, 0xf9, 0x02, 0xbc, 0x0f, 0x4a, 0x49, 0xc8, 0xa4, 0xdb, 0x97, 0xc0, + 0x31, 0xbc, 0x69, 0x31, 0x8c, 0x9a, 0x82, 0xb1, 0x34, 0xa7, 0x94, 0xb2, 0xb3, 0xa3, 0xfa, 0x64, + 0xb0, 0xbc, 0xec, 0x13, 0x97, 0xc2, 0x6f, 0xb3, 0xe0, 0x8e, 0xaf, 0xcc, 0x0e, 0xea, 0xb8, 0x61, + 0x99, 0x9e, 0xc1, 0xf0, 0xa3, 0xa7, 0x86, 0x87, 0xe9, 0x0a, 0x91, 0xc1, 0x5e, 0x24, 0x6e, 0x1b, + 0xbb, 0x8c, 0xef, 0xa1, 0x30, 0xf0, 0x29, 0xc3, 0x10, 0xed, 0x63, 0xd9, 0x68, 0x18, 0xc4, 0x06, + 0x94, 0xbd, 0xcd, 0x04, 0x33, 0xd4, 0x07, 0xd0, 0x64, 0xa4, 0xe9, 0x04, 0x88, 0x06, 0x37, 0xba, + 0x92, 0x68, 0x74, 0x39, 0x81, 0x60, 0xa7, 0x06, 0xa8, 0x1f, 0xa3, 0xc2, 0x2d, 0xe1, 0xa5, 0xfa, + 0x42, 0x01, 0x53, 0x8c, 0xac, 0x61, 0xb7, 0x49, 0xba, 0xac, 0xe9, 0x70, 0xd6, 0x8c, 0x0e, 0x62, + 0xcd, 0x92, 0x30, 0x73, 0x22, 0x30, 0xd3, 0x7f, 0x1c, 0xa6, 0xa2, 0xd3, 0x51, 0xff, 0xf0, 0xff, + 0xbb, 0xac, 0x61, 0xb9, 0x74, 0xbe, 0xc8, 0x93, 0x9f, 0xef, 0x25, 0x5f, 0x36, 0x9f, 0x10, 0xff, + 0xcf, 0x59, 0x70, 0x77, 0xd8, 0x5c, 0xc9, 0xc2, 0x78, 0x02, 0xc6, 0x0d, 0x87, 0x74, 0x5d, 0x36, + 0x27, 0x92, 0x76, 0x97, 0xfb, 0xf3, 0xeb, 0x56, 0xf1, 0xd2, 0x3e, 0x60, 0x2f, 0xb9, 0xac, 0x97, + 0x36, 0xa1, 0x06, 0xea, 0xa1, 0xc2, 0x9e, 0xee, 0xaa, 0x9f, 0xe4, 0x0f, 0xd6, 0x5d, 0x95, 0xba, + 0xab, 0xea, 0x06, 0x98, 0xb1, 0xad, 0x67, 0x5d, 0x0b, 0x59, 0xec, 0x79, 0xb3, 0xed, 0x77, 0x02, + 0x14, 0x34, 0x9f, 0xda, 0xfd, 0x14, 0x56, 0xea, 0xb8, 0xdd, 0x2b, 0x91, 0x5d, 0x0a, 0xa1, 0x3e, + 0x2d, 0xd7, 0x82, 0x6e, 0x83, 0xd4, 0xc7, 0xe0, 0xc8, 0xc7, 0xc4, 0x72, 0x9b, 0xfc, 0x76, 0xe8, + 0xf7, 0xb4, 0x89, 0x1b, 0xf9, 0x4a, 0x70, 0x75, 0xac, 0x84, 0x57, 0xc7, 0xca, 0x4a, 0x78, 0x75, + 0xac, 0x9d, 0x11, 0xe5, 0x31, 0x1d, 0x98, 0x90, 0x47, 0xe1, 0xcb, 0xb7, 0x45, 0x45, 0x3f, 0xcc, + 0xbf, 0xb9, 0x30, 0xfc, 0x3c, 0xeb, 0x4f, 0x81, 0x05, 0x84, 0x56, 0x48, 0x34, 0x61, 0xcb, 0xa1, + 0xfd, 0x5e, 0x4f, 0x93, 0x7c, 0xbb, 0x0d, 0x26, 0xc2, 0x0e, 0x25, 0x67, 0x70, 0xed, 0xe4, 0xf6, + 0x56, 0x51, 0x0d, 0xfb, 0x89, 0xdc, 0x84, 0x91, 0x66, 0x86, 0x22, 0x44, 0xcd, 0x0c, 0x22, 0x6a, + 0x33, 0x64, 0x04, 0xc2, 0xd4, 0xf2, 0x30, 0x9a, 0x1b, 0x4c, 0xbc, 0xb3, 0x71, 0x8c, 0x08, 0x8f, + 0x43, 0x7d, 0xd2, 0x5f, 0xa8, 0x8b, 0xef, 0x5d, 0x06, 0xaa, 0x22, 0xa8, 0x43, 0x1a, 0xa8, 0xee, + 0x30, 0x50, 0x9d, 0xbf, 0xca, 0x79, 0x74, 0x31, 0xe4, 0x91, 0x81, 0x50, 0x99, 0x91, 0x72, 0xdb, + 0x8e, 0xce, 0xf0, 0x30, 0x34, 0xf0, 0xa7, 0xac, 0x3f, 0x59, 0xd2, 0x64, 0x41, 0x32, 0x69, 0xe8, + 0x6c, 0x44, 0x28, 0x98, 0xf9, 0x1b, 0x29, 0x98, 0x3d, 0x68, 0x0a, 0xae, 0x81, 0x49, 0x17, 0x6f, + 0x34, 0x25, 0x43, 0x72, 0x87, 0x7c, 0x0b, 0xf7, 0x52, 0xd3, 0xef, 0x78, 0x60, 0xa1, 0x4f, 0x19, + 0xd4, 0x8f, 0xba, 0x78, 0x43, 0xc6, 0x3d, 0x3a, 0x30, 0x76, 0x5d, 0x24, 0x76, 0x0e, 0x0c, 0xf8, + 0x43, 0x56, 0x0c, 0x6b, 0x7e, 0x65, 0x5d, 0x24, 0xee, 0x3a, 0xf6, 0x18, 0xbf, 0x16, 0x30, 0x63, + 0x0d, 0x47, 0x35, 0x29, 0x83, 0x34, 0xa5, 0x61, 0xca, 0x1e, 0xb7, 0x20, 0x0f, 0x4c, 0x3b, 0x96, + 0xdb, 0x34, 0x1c, 0xc6, 0xe7, 0x0f, 0xe5, 0x30, 0x7c, 0x2f, 0x8e, 0x04, 0xd3, 0x23, 0x55, 0x3a, + 0x4e, 0x05, 0xd6, 0x77, 0xea, 0x83, 0xfa, 0xa4, 0x63, 0xb9, 0x0b, 0x0e, 0x5b, 0x21, 0x81, 0x9b, + 0x5f, 0x2a, 0xd1, 0xa9, 0xd9, 0x0e, 0x82, 0xe0, 0xa7, 0x68, 0x4f, 0x6e, 0x3d, 0x48, 0x9a, 0x9a, + 0x42, 0x03, 0x9f, 0x68, 0x97, 0xf7, 0x39, 0xd1, 0x7a, 0x03, 0x56, 0xe4, 0x60, 0xfe, 0x22, 0xe7, + 0x62, 0xa9, 0x37, 0xd3, 0xfc, 0xf7, 0x94, 0xd0, 0x1c, 0xdc, 0xf2, 0x7c, 0x5f, 0xbe, 0x52, 0xc4, + 0x95, 0x26, 0x26, 0x7f, 0x92, 0x6f, 0x14, 0x4c, 0x33, 0xc2, 0x78, 0xc4, 0x1d, 0x16, 0xc4, 0x00, + 0x89, 0x11, 0x36, 0x74, 0x50, 0x77, 0xea, 0x83, 0xfa, 0x94, 0xbf, 0xb4, 0xe0, 0x30, 0xdf, 0x36, + 0xba, 0xf1, 0xe7, 0x04, 0xc8, 0x36, 0xa8, 0xa9, 0x7a, 0x40, 0x8d, 0xbb, 0x96, 0x57, 0x76, 0xff, + 0x01, 0xa3, 0x12, 0xfb, 0xe6, 0xce, 0x57, 0xf7, 0x2d, 0x2a, 0x1d, 0xde, 0x04, 0xc7, 0x63, 0x9f, + 0xe6, 0xd7, 0x06, 0xaa, 0xea, 0x09, 0xe7, 0x6f, 0xa6, 0x10, 0x4e, 0xb2, 0x2c, 0x1f, 0xae, 0xfb, + 0xb1, 0x1c, 0x0a, 0xef, 0xcb, 0xf2, 0xae, 0x27, 0xe6, 0x37, 0x0a, 0x38, 0x37, 0xf8, 0x01, 0x7d, + 0x27, 0x85, 0x53, 0x7d, 0x27, 0xf3, 0x77, 0x87, 0x3d, 0x29, 0x11, 0x7e, 0xa1, 0x80, 0xd3, 0xc9, + 0x0f, 0xdd, 0xb9, 0x04, 0xfd, 0x89, 0x27, 0xf2, 0x77, 0xd2, 0x9e, 0x90, 0x48, 0x7e, 0x54, 0xc0, + 0xf5, 0x54, 0xaf, 0xc8, 0xc5, 0x04, 0x53, 0x69, 0x94, 0xe4, 0x1f, 0x1c, 0x80, 0x12, 0xe9, 0xc2, + 0x27, 0xe0, 0x44, 0xfc, 0x0b, 0xeb, 0x7a, 0x82, 0x95, 0x58, 0xe9, 0xfc, 0x7f, 0xd2, 0x48, 0x4b, + 0xe3, 0xbf, 0x29, 0xe0, 0xbf, 0xc3, 0x3d, 0x7c, 0x96, 0x13, 0xed, 0x0d, 0xa1, 0x2d, 0xbf, 0x72, + 0x90, 0xda, 0xfa, 0xaa, 0x23, 0xd5, 0xed, 0x32, 0xa9, 0x3a, 0xd2, 0x28, 0x49, 0xac, 0x8e, 0xa1, + 0x6e, 0x58, 0x7e, 0x75, 0xc4, 0x8d, 0xf4, 0xe4, 0xea, 0x88, 0x91, 0xde, 0xa3, 0x3a, 0xf6, 0x18, + 0x37, 0xb5, 0x87, 0xaf, 0xdf, 0x15, 0x94, 0x37, 0xef, 0x0a, 0xca, 0xef, 0xef, 0x0a, 0xca, 0xcb, + 0xf7, 0x85, 0x91, 0x37, 0xef, 0x0b, 0x23, 0xbf, 0xbc, 0x2f, 0x8c, 0x3c, 0xb9, 0x15, 0x19, 0x33, + 0x42, 0x73, 0xd9, 0x36, 0x5a, 0x34, 0xfc, 0xd0, 0xd6, 0xab, 0xb7, 0xb5, 0xcd, 0xbe, 0x3f, 0x78, + 0xf3, 0xd1, 0xd3, 0x1a, 0xf3, 0x9f, 0x0b, 0x37, 0xff, 0x0a, 0x00, 0x00, 0xff, 0xff, 0xca, 0x88, + 0xcf, 0x73, 0x13, 0x17, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1159,6 +1288,9 @@ type MsgClient interface { UnPoolWhitelistedPool(ctx context.Context, in *MsgUnPoolWhitelistedPool, opts ...grpc.CallOption) (*MsgUnPoolWhitelistedPoolResponse, error) UnlockAndMigrateSharesToFullRangeConcentratedPosition(ctx context.Context, in *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition, opts ...grpc.CallOption) (*MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse, error) AddToConcentratedLiquiditySuperfluidPosition(ctx context.Context, in *MsgAddToConcentratedLiquiditySuperfluidPosition, opts ...grpc.CallOption) (*MsgAddToConcentratedLiquiditySuperfluidPositionResponse, error) + // UnbondConvertAndStake breaks all locks / superfluid staked assets, + // converts them to osmo then stakes the osmo to the designated validator. + UnbondConvertAndStake(ctx context.Context, in *MsgUnbondConvertAndStake, opts ...grpc.CallOption) (*MsgUnbondConvertAndStakeResponse, error) } type msgClient struct { @@ -1250,6 +1382,15 @@ func (c *msgClient) AddToConcentratedLiquiditySuperfluidPosition(ctx context.Con return out, nil } +func (c *msgClient) UnbondConvertAndStake(ctx context.Context, in *MsgUnbondConvertAndStake, opts ...grpc.CallOption) (*MsgUnbondConvertAndStakeResponse, error) { + out := new(MsgUnbondConvertAndStakeResponse) + err := c.cc.Invoke(ctx, "/osmosis.superfluid.Msg/UnbondConvertAndStake", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { // Execute superfluid delegation for a lockup @@ -1267,6 +1408,9 @@ type MsgServer interface { UnPoolWhitelistedPool(context.Context, *MsgUnPoolWhitelistedPool) (*MsgUnPoolWhitelistedPoolResponse, error) UnlockAndMigrateSharesToFullRangeConcentratedPosition(context.Context, *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) (*MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse, error) AddToConcentratedLiquiditySuperfluidPosition(context.Context, *MsgAddToConcentratedLiquiditySuperfluidPosition) (*MsgAddToConcentratedLiquiditySuperfluidPositionResponse, error) + // UnbondConvertAndStake breaks all locks / superfluid staked assets, + // converts them to osmo then stakes the osmo to the designated validator. + UnbondConvertAndStake(context.Context, *MsgUnbondConvertAndStake) (*MsgUnbondConvertAndStakeResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -1300,6 +1444,9 @@ func (*UnimplementedMsgServer) UnlockAndMigrateSharesToFullRangeConcentratedPosi func (*UnimplementedMsgServer) AddToConcentratedLiquiditySuperfluidPosition(ctx context.Context, req *MsgAddToConcentratedLiquiditySuperfluidPosition) (*MsgAddToConcentratedLiquiditySuperfluidPositionResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method AddToConcentratedLiquiditySuperfluidPosition not implemented") } +func (*UnimplementedMsgServer) UnbondConvertAndStake(ctx context.Context, req *MsgUnbondConvertAndStake) (*MsgUnbondConvertAndStakeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UnbondConvertAndStake not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -1467,6 +1614,24 @@ func _Msg_AddToConcentratedLiquiditySuperfluidPosition_Handler(srv interface{}, return interceptor(ctx, in, info, handler) } +func _Msg_UnbondConvertAndStake_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUnbondConvertAndStake) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UnbondConvertAndStake(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/osmosis.superfluid.Msg/UnbondConvertAndStake", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UnbondConvertAndStake(ctx, req.(*MsgUnbondConvertAndStake)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "osmosis.superfluid.Msg", HandlerType: (*MsgServer)(nil), @@ -1507,6 +1672,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "AddToConcentratedLiquiditySuperfluidPosition", Handler: _Msg_AddToConcentratedLiquiditySuperfluidPosition_Handler, }, + { + MethodName: "UnbondConvertAndStake", + Handler: _Msg_UnbondConvertAndStake_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "osmosis/superfluid/tx.proto", @@ -2248,6 +2417,101 @@ func (m *MsgAddToConcentratedLiquiditySuperfluidPositionResponse) MarshalToSized return len(dAtA) - i, nil } +func (m *MsgUnbondConvertAndStake) 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 *MsgUnbondConvertAndStake) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUnbondConvertAndStake) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.SharesToConvert.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + { + size := m.MinAmtToStake.Size() + i -= size + if _, err := m.MinAmtToStake.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if len(m.ValAddr) > 0 { + i -= len(m.ValAddr) + copy(dAtA[i:], m.ValAddr) + i = encodeVarintTx(dAtA, i, uint64(len(m.ValAddr))) + i-- + dAtA[i] = 0x1a + } + if len(m.Sender) > 0 { + i -= len(m.Sender) + copy(dAtA[i:], m.Sender) + i = encodeVarintTx(dAtA, i, uint64(len(m.Sender))) + i-- + dAtA[i] = 0x12 + } + if m.LockId != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.LockId)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgUnbondConvertAndStakeResponse) 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 *MsgUnbondConvertAndStakeResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUnbondConvertAndStakeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.TotalAmtStaked.Size() + i -= size + if _, err := m.TotalAmtStaked.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -2558,6 +2822,41 @@ func (m *MsgAddToConcentratedLiquiditySuperfluidPositionResponse) Size() (n int) return n } +func (m *MsgUnbondConvertAndStake) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.LockId != 0 { + n += 1 + sovTx(uint64(m.LockId)) + } + l = len(m.Sender) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ValAddr) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.MinAmtToStake.Size() + n += 1 + l + sovTx(uint64(l)) + l = m.SharesToConvert.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgUnbondConvertAndStakeResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.TotalAmtStaked.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -4661,6 +4960,290 @@ func (m *MsgAddToConcentratedLiquiditySuperfluidPositionResponse) Unmarshal(dAtA } return nil } +func (m *MsgUnbondConvertAndStake) 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 ErrIntOverflowTx + } + 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: MsgUnbondConvertAndStake: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUnbondConvertAndStake: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LockId", wireType) + } + m.LockId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.LockId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sender", 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.Sender = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValAddr", 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.ValAddr = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MinAmtToStake", 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 + } + if err := m.MinAmtToStake.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SharesToConvert", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.SharesToConvert.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUnbondConvertAndStakeResponse) 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 ErrIntOverflowTx + } + 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: MsgUnbondConvertAndStakeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUnbondConvertAndStakeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TotalAmtStaked", 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 + } + if err := m.TotalAmtStaked.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/valset-pref/keeper.go b/x/valset-pref/keeper.go index bbdfff2847e..b511a329971 100644 --- a/x/valset-pref/keeper.go +++ b/x/valset-pref/keeper.go @@ -48,7 +48,6 @@ func (k Keeper) GetDelegationPreferences(ctx sdk.Context, delegator string) (typ if err != nil { return types.ValidatorSetPreferences{}, err } - existingDelsValSetFormatted, err := k.GetExistingStakingDelegations(ctx, delAddr) if err != nil { return types.ValidatorSetPreferences{}, err @@ -69,7 +68,7 @@ func (k Keeper) GetExistingStakingDelegations(ctx sdk.Context, delAddr sdk.AccAd existingDelegations := k.stakingKeeper.GetDelegatorDelegations(ctx, delAddr, math.MaxUint16) if len(existingDelegations) == 0 { - return nil, fmt.Errorf("No Existing delegation") + return nil, types.ErrNoDelegation } existingTotalShares := sdk.NewDec(0) diff --git a/x/valset-pref/types/errors.go b/x/valset-pref/types/errors.go new file mode 100644 index 00000000000..0d2b988e047 --- /dev/null +++ b/x/valset-pref/types/errors.go @@ -0,0 +1,7 @@ +package types + +import "errors" + +var ( + ErrNoDelegation = errors.New("No existing delegation") +) diff --git a/x/valset-pref/validator_set.go b/x/valset-pref/validator_set.go index 744171955ad..7fe800f6d59 100644 --- a/x/valset-pref/validator_set.go +++ b/x/valset-pref/validator_set.go @@ -74,7 +74,7 @@ func (k Keeper) DelegateToValidatorSet(ctx sdk.Context, delegatorAddr string, co // get valset formatted delegation either from existing val set preference or existing delegations existingSet, err := k.GetDelegationPreferences(ctx, delegatorAddr) if err != nil { - return fmt.Errorf("error upon getting delegation preference for addr %s", delegatorAddr) + return err } delegator, err := sdk.AccAddressFromBech32(delegatorAddr)