diff --git a/go.sum b/go.sum index b7dd8f85989..181ec5f621c 100644 --- a/go.sum +++ b/go.sum @@ -938,8 +938,6 @@ github.com/osmosis-labs/go-mutesting v0.0.0-20221208041716-b43bcd97b3b3 h1:Ylmch github.com/osmosis-labs/go-mutesting v0.0.0-20221208041716-b43bcd97b3b3/go.mod h1:lV6KnqXYD/ayTe7310MHtM3I2q8Z6bBfMAi+bhwPYtI= github.com/osmosis-labs/osmosis/osmomath v0.0.3-dev.0.20230503232557-ba905586c111 h1:1ahWbf9iF9sxDOjxrHWFaBGLE0nWFdpiX1pqObUaJO8= github.com/osmosis-labs/osmosis/osmomath v0.0.3-dev.0.20230503232557-ba905586c111/go.mod h1:a7lhiXRpn8QJ21OhFpaEnUNErTSIafaYpp02q6uI/Dk= -github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230504190933-b174397f0bc5 h1:fBzTtgZHxvZkpwlg6YtAsNaexEHYaFZDXsYfPQWu9GE= -github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230504190933-b174397f0bc5/go.mod h1:hk/o9/kmTSZmZqwXcSrPuwj/gpRMCqbE/d3vj6teL2A= github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230510161551-8bf252f26bae h1:I1Cy+GpTPWbVi0lBw9+bS1w42YfQjvXNK9bW4YbHhcs= github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230510161551-8bf252f26bae/go.mod h1:hk/o9/kmTSZmZqwXcSrPuwj/gpRMCqbE/d3vj6teL2A= github.com/osmosis-labs/osmosis/x/epochs v0.0.0-20230328024000-175ec88e4304 h1:RIrWLzIiZN5Xd2JOfSOtGZaf6V3qEQYg6EaDTAkMnCo= diff --git a/proto/osmosis/superfluid/tx.proto b/proto/osmosis/superfluid/tx.proto index a30df7a5299..26375d43ff9 100644 --- a/proto/osmosis/superfluid/tx.proto +++ b/proto/osmosis/superfluid/tx.proto @@ -150,8 +150,9 @@ message MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition { ]; // token_out_mins indicates minimum token to exit Balancer pool with. repeated cosmos.base.v1beta1.Coin token_out_mins = 4 [ - (gogoproto.moretags) = "yaml:\"token_out_min_amounts\"", - (gogoproto.nullable) = false + (gogoproto.moretags) = "yaml:\"token_out_mins\"", + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" ]; } diff --git a/x/gamm/types/key.go b/x/gamm/types/key.go index ebfea87e42f..36a00421380 100644 --- a/x/gamm/types/key.go +++ b/x/gamm/types/key.go @@ -33,12 +33,11 @@ var ( ) func MustGetPoolIdFromShareDenom(denom string) uint64 { - numberStr := strings.TrimLeft(denom, GAMMTokenPrefix) - number, err := strconv.Atoi(numberStr) + number, err := GetPoolIdFromShareDenom(denom) if err != nil { panic(err) } - return uint64(number) + return number } func GetPoolIdFromShareDenom(denom string) (uint64, error) { diff --git a/x/superfluid/README.md b/x/superfluid/README.md index 8ff1f497b9a..b283ef7e493 100644 --- a/x/superfluid/README.md +++ b/x/superfluid/README.md @@ -376,6 +376,41 @@ It then mints concentrated liquidity shares and locks them up for the staking duration. From there, the normal superfluid delegation logic is executed. +### Create Full Range Position and Superfluid Delegate + +```{.go} +type MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition struct { + Sender string + LockId uint64 + SharesToMigrate sdk.Coin + TokenOutMins sdk.Coins +} +``` + +Upon completion, the following response is given: + +```{.go} +type MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse struct { + Amount0 string + Amount1 string + LiquidityCreated sdk.Dec + JoinTime time.Time +} +``` + +The message starts by determining which migration method to use. +- If underlying lock is superfluid bonded + - `migrateSuperfluidBondedBalancerToConcentrated` +- If underlying lock is superfluid unbonding + - `migrateSuperfluidUnbondingBalancerToConcentrated` +- If underlying lock is not superfluid bonded (vanilla lock) + - `migrateNonSuperfluidLockBalancerToConcentrated` + +It then routes to that migration message, which will migrate the gamm lock from +the previous state it was in, to the same state but in the concentrated pool. If +the sharesToMigrate is zero, then the entire lock is migrated. If only a subset +of the shares are migrated, then the remaining shares are left in the gamm pool. + ## Epochs Overall Epoch sequence diff --git a/x/superfluid/client/cli/tx.go b/x/superfluid/client/cli/tx.go index 6f7e38137dd..bb3f0de6496 100644 --- a/x/superfluid/client/cli/tx.go +++ b/x/superfluid/client/cli/tx.go @@ -37,6 +37,7 @@ func GetTxCmd() *cobra.Command { ) osmocli.AddTxCmd(cmd, NewCreateFullRangePositionAndSuperfluidDelegateCmd) osmocli.AddTxCmd(cmd, NewAddToConcentratedLiquiditySuperfluidPositionCmd) + osmocli.AddTxCmd(cmd, NewUnlockAndMigrateSharesToFullRangeConcentratedPositionCmd) return cmd } @@ -414,3 +415,11 @@ func NewAddToConcentratedLiquiditySuperfluidPositionCmd() (*osmocli.TxCliDesc, * Example: "add-to-superfluid-cl-position 10 1000000000uosmo 10000000uion", }, &types.MsgAddToConcentratedLiquiditySuperfluidPosition{} } + +func NewUnlockAndMigrateSharesToFullRangeConcentratedPositionCmd() (*osmocli.TxCliDesc, *types.MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) { + return &osmocli.TxCliDesc{ + Use: "unlock-and-migrate-to-cl [lock-id] [shares-to-migrate] [token-out-mins]", + Short: "unlock and migrate gamm shares to full range concentrated position", + Example: "unlock-and-migrate-cl 10 25000000000gamm/pool/2 1000000000uosmo,10000000uion", + }, &types.MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition{} +} diff --git a/x/superfluid/keeper/concentrated_liquidity.go b/x/superfluid/keeper/concentrated_liquidity.go index 0a2085831c8..bdba4278db9 100644 --- a/x/superfluid/keeper/concentrated_liquidity.go +++ b/x/superfluid/keeper/concentrated_liquidity.go @@ -69,7 +69,7 @@ func (k Keeper) addToConcentratedLiquiditySuperfluidPosition(ctx sdk.Context, ow // Superfluid undelegate the superfluid delegated position. // This deletes the connection between the lock and the intermediate account, deletes the synthetic lock, and burns the synthetic osmo. - intermediateAccount, err := k.SuperfluidUndelegateToConcentratedPosition(ctx, owner.String(), lockId) + intermediateAccount, err := k.superfluidUndelegateToConcentratedPosition(ctx, owner.String(), lockId) if err != nil { return 0, sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, err } diff --git a/x/superfluid/keeper/export_test.go b/x/superfluid/keeper/export_test.go index 87c821c7cb5..8e060af977a 100644 --- a/x/superfluid/keeper/export_test.go +++ b/x/superfluid/keeper/export_test.go @@ -7,6 +7,7 @@ import ( cltypes "github.com/osmosis-labs/osmosis/v15/x/concentrated-liquidity/types" lockuptypes "github.com/osmosis-labs/osmosis/v15/x/lockup/types" + "github.com/osmosis-labs/osmosis/v15/x/superfluid/types" ) var ( @@ -45,3 +46,15 @@ func (k Keeper) PrepareMigration(ctx sdk.Context, sender sdk.AccAddress, lockId func (k Keeper) AddToConcentratedLiquiditySuperfluidPosition(ctx sdk.Context, owner sdk.AccAddress, positionId uint64, amount0Added, amount1Added sdk.Int) (uint64, sdk.Int, sdk.Int, sdk.Dec, uint64, error) { return k.addToConcentratedLiquiditySuperfluidPosition(ctx, owner, positionId, amount0Added, amount1Added) } + +func (k Keeper) SuperfluidUndelegateToConcentratedPosition(ctx sdk.Context, sender string, gammLockID uint64) (types.SuperfluidIntermediaryAccount, error) { + return k.superfluidUndelegateToConcentratedPosition(ctx, sender, gammLockID) +} + +func (k Keeper) ValidateGammLockForSuperfluidStaking(ctx sdk.Context, sender sdk.AccAddress, poolId uint64, lockId uint64) (*lockuptypes.PeriodLock, error) { + return k.validateGammLockForSuperfluidStaking(ctx, sender, poolId, lockId) +} + +func (k Keeper) GetExistingLockRemainingDuration(ctx sdk.Context, lock *lockuptypes.PeriodLock) (time.Duration, error) { + return k.getExistingLockRemainingDuration(ctx, lock) +} diff --git a/x/superfluid/keeper/migrate.go b/x/superfluid/keeper/migrate.go index b9a5ea10f60..a505605d4d3 100644 --- a/x/superfluid/keeper/migrate.go +++ b/x/superfluid/keeper/migrate.go @@ -66,7 +66,7 @@ func (k Keeper) migrateSuperfluidBondedBalancerToConcentrated(ctx sdk.Context, // Superfluid undelegate the superfluid delegated position. // This deletes the connection between the lock and the intermediate account, deletes the synthetic lock, and burns the synthetic osmo. - intermediateAccount, err := k.SuperfluidUndelegateToConcentratedPosition(ctx, sender.String(), lockId) + intermediateAccount, err := k.superfluidUndelegateToConcentratedPosition(ctx, sender.String(), lockId) if err != nil { return 0, sdk.Int{}, sdk.Int{}, sdk.Dec{}, time.Time{}, 0, 0, err } @@ -286,13 +286,16 @@ func (k Keeper) prepareMigration(ctx sdk.Context, sender sdk.AccAddress, lockId } // Check that lockID corresponds to sender, and contains correct denomination of LP shares. - preMigrationLock, err = k.validateLockForUnpool(ctx, sender, poolIdLeaving, lockId) + preMigrationLock, err = k.validateGammLockForSuperfluidStaking(ctx, sender, poolIdLeaving, lockId) if err != nil { return 0, 0, nil, &lockuptypes.PeriodLock{}, 0, nil, false, false, err } // Before we break the lock, we must note the time remaining on the lock. - remainingLockTime = k.getExistingLockRemainingDuration(ctx, preMigrationLock) + remainingLockTime, err = k.getExistingLockRemainingDuration(ctx, preMigrationLock) + if err != nil { + return 0, 0, nil, &lockuptypes.PeriodLock{}, 0, nil, false, false, err + } // Check if the lock has a corresponding synthetic lock. // Synthetic lock existence implies that the lock is superfluid delegated or undelegating. diff --git a/x/superfluid/keeper/msg_server.go b/x/superfluid/keeper/msg_server.go index 7b9a8339ebf..cded0c8eab4 100644 --- a/x/superfluid/keeper/msg_server.go +++ b/x/superfluid/keeper/msg_server.go @@ -132,38 +132,6 @@ func (server msgServer) LockAndSuperfluidDelegate(goCtx context.Context, msg *ty }, err } -func (server msgServer) CreateFullRangePositionAndSuperfluidDelegate(goCtx context.Context, msg *types.MsgCreateFullRangePositionAndSuperfluidDelegate) (*types.MsgCreateFullRangePositionAndSuperfluidDelegateResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - - address, err := sdk.AccAddressFromBech32(msg.Sender) - if err != nil { - return &types.MsgCreateFullRangePositionAndSuperfluidDelegateResponse{}, err - } - positionId, _, _, _, _, lockId, err := server.keeper.clk.CreateFullRangePositionLocked(ctx, msg.PoolId, address, msg.Coins, server.keeper.sk.GetParams(ctx).UnbondingTime) - if err != nil { - return &types.MsgCreateFullRangePositionAndSuperfluidDelegateResponse{}, err - } - - superfluidDelegateMsg := types.MsgSuperfluidDelegate{ - Sender: msg.Sender, - LockId: lockId, - ValAddr: msg.ValAddr, - } - - _, err = server.SuperfluidDelegate(goCtx, &superfluidDelegateMsg) - - if err != nil { - return &types.MsgCreateFullRangePositionAndSuperfluidDelegateResponse{}, err - } - - events.EmitCreateFullRangePositionAndSuperfluidDelegateEvent(ctx, lockId, positionId, msg.ValAddr) - - return &types.MsgCreateFullRangePositionAndSuperfluidDelegateResponse{ - LockID: lockId, - PositionID: positionId, - }, nil -} - func (server msgServer) UnPoolWhitelistedPool(goCtx context.Context, msg *types.MsgUnPoolWhitelistedPool) (*types.MsgUnPoolWhitelistedPoolResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) @@ -197,6 +165,38 @@ func (server msgServer) UnPoolWhitelistedPool(goCtx context.Context, msg *types. return &types.MsgUnPoolWhitelistedPoolResponse{ExitedLockIds: allExitedLockIDs}, nil } +func (server msgServer) CreateFullRangePositionAndSuperfluidDelegate(goCtx context.Context, msg *types.MsgCreateFullRangePositionAndSuperfluidDelegate) (*types.MsgCreateFullRangePositionAndSuperfluidDelegateResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + address, err := sdk.AccAddressFromBech32(msg.Sender) + if err != nil { + return &types.MsgCreateFullRangePositionAndSuperfluidDelegateResponse{}, err + } + positionId, _, _, _, _, lockId, err := server.keeper.clk.CreateFullRangePositionLocked(ctx, msg.PoolId, address, msg.Coins, server.keeper.sk.GetParams(ctx).UnbondingTime) + if err != nil { + return &types.MsgCreateFullRangePositionAndSuperfluidDelegateResponse{}, err + } + + superfluidDelegateMsg := types.MsgSuperfluidDelegate{ + Sender: msg.Sender, + LockId: lockId, + ValAddr: msg.ValAddr, + } + + _, err = server.SuperfluidDelegate(goCtx, &superfluidDelegateMsg) + + if err != nil { + return &types.MsgCreateFullRangePositionAndSuperfluidDelegateResponse{}, err + } + + events.EmitCreateFullRangePositionAndSuperfluidDelegateEvent(ctx, lockId, positionId, msg.ValAddr) + + return &types.MsgCreateFullRangePositionAndSuperfluidDelegateResponse{ + LockID: lockId, + PositionID: positionId, + }, nil +} + func (server msgServer) UnlockAndMigrateSharesToFullRangeConcentratedPosition(goCtx context.Context, msg *types.MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) (*types.MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) diff --git a/x/superfluid/keeper/msg_server_test.go b/x/superfluid/keeper/msg_server_test.go index e7e329ee876..3f1da11de32 100644 --- a/x/superfluid/keeper/msg_server_test.go +++ b/x/superfluid/keeper/msg_server_test.go @@ -473,15 +473,6 @@ func (suite *KeeperTestSuite) TestMsgUnPoolWhitelistedPool_Event() { func (suite *KeeperTestSuite) TestUnlockAndMigrateSharesToFullRangeConcentratedPosition_Event() { suite.SetupTest() - const ( - token0Denom = "token0" - ) - - // Update authorized quote denoms with the quote denom relied on by the test - concentratedLiquidityParams := suite.App.ConcentratedLiquidityKeeper.GetParams(suite.Ctx) - concentratedLiquidityParams.AuthorizedQuoteDenoms = append(concentratedLiquidityParams.AuthorizedQuoteDenoms, token0Denom) - suite.App.ConcentratedLiquidityKeeper.SetParams(suite.Ctx, concentratedLiquidityParams) - msgServer := keeper.NewMsgServerImpl(suite.App.SuperfluidKeeper) suite.FundAcc(suite.TestAccs[0], defaultAcctFunds) fullRangeCoins := sdk.NewCoins(defaultPoolAssets[0].Token, defaultPoolAssets[1].Token) @@ -489,7 +480,7 @@ func (suite *KeeperTestSuite) TestUnlockAndMigrateSharesToFullRangeConcentratedP // Set validators valAddrs := suite.SetupValidators([]stakingtypes.BondStatus{stakingtypes.Bonded}) - // Set balancer pool and make its respective gamm share an authorized superfluid asset + // Set balancer pool (foo and stake) and make its respective gamm share an authorized superfluid asset msg := balancer.NewMsgCreateBalancerPool(suite.TestAccs[0], balancer.PoolParams{ SwapFee: sdk.NewDecWithPrec(1, 2), ExitFee: sdk.NewDec(0), @@ -505,7 +496,7 @@ func (suite *KeeperTestSuite) TestUnlockAndMigrateSharesToFullRangeConcentratedP }) suite.Require().NoError(err) - // Set concentrated pool with the same denoms as the balancer pool + // Set concentrated pool with the same denoms as the balancer pool (foo and stake) clPool := suite.PrepareCustomConcentratedPool(suite.TestAccs[0], defaultPoolAssets[0].Token.Denom, defaultPoolAssets[1].Token.Denom, 1, sdk.ZeroDec()) // Set migration link between the balancer and concentrated pool @@ -529,7 +520,8 @@ func (suite *KeeperTestSuite) TestUnlockAndMigrateSharesToFullRangeConcentratedP suite.Require().NoError(err) // Execute UnlockAndMigrateSharesToFullRangeConcentratedPosition message - sender, _ := sdk.AccAddressFromBech32(locks[0].Owner) + sender, err := sdk.AccAddressFromBech32(locks[0].Owner) + suite.Require().NoError(err) _, err = msgServer.UnlockAndMigrateSharesToFullRangeConcentratedPosition(sdk.WrapSDKContext(suite.Ctx), types.NewMsgUnlockAndMigrateSharesToFullRangeConcentratedPosition(sender, locks[0].ID, locks[0].Coins[0])) suite.Require().NoError(err) diff --git a/x/superfluid/keeper/stake.go b/x/superfluid/keeper/stake.go index d643ce94c9d..64dfcb276fc 100644 --- a/x/superfluid/keeper/stake.go +++ b/x/superfluid/keeper/stake.go @@ -242,7 +242,7 @@ func (k Keeper) SuperfluidDelegate(ctx sdk.Context, sender string, lockID uint64 return k.mintOsmoTokensAndDelegate(ctx, amount, acc) } -// undelegateCommon is a helper function for SuperfluidUndelegate and SuperfluidUndelegateToConcentratedPosition. +// undelegateCommon is a helper function for SuperfluidUndelegate and superfluidUndelegateToConcentratedPosition. // It performs the following tasks: // - checks that the lock is valid for superfluid staking // - gets the intermediary account associated with the lock id @@ -300,10 +300,10 @@ func (k Keeper) SuperfluidUndelegate(ctx sdk.Context, sender string, lockID uint return k.createSyntheticLockup(ctx, lockID, intermediaryAcc, unlockingStatus) } -// SuperfluidUndelegateToConcentratedPosition starts undelegating superfluid delegated position for the given lock. It behaves similarly to SuperfluidUndelegate, +// superfluidUndelegateToConcentratedPosition starts undelegating superfluid delegated position for the given lock. It behaves similarly to SuperfluidUndelegate, // however it does not create a new synthetic lockup representing the unstaking side. This is because at the time this function is called, the new concentrated liquidity side // lock has not yet been created. Once the new cl side lock is created, the synthetic lockup representing the unstaking side is created. -func (k Keeper) SuperfluidUndelegateToConcentratedPosition(ctx sdk.Context, sender string, gammLockID uint64) (types.SuperfluidIntermediaryAccount, error) { +func (k Keeper) superfluidUndelegateToConcentratedPosition(ctx sdk.Context, sender string, gammLockID uint64) (types.SuperfluidIntermediaryAccount, error) { return k.undelegateCommon(ctx, sender, gammLockID) } diff --git a/x/superfluid/keeper/unpool.go b/x/superfluid/keeper/unpool.go index 0f3dfadc4b5..f2be67e163c 100644 --- a/x/superfluid/keeper/unpool.go +++ b/x/superfluid/keeper/unpool.go @@ -33,14 +33,17 @@ func (k Keeper) UnpoolAllowedPools(ctx sdk.Context, sender sdk.AccAddress, poolI // 2) Consistency check that lockID corresponds to sender, and contains correct LP shares. // These are expected to be true by the caller, but good to double check // TODO: Try to minimize dependence on lock here - lock, err := k.validateLockForUnpool(ctx, sender, poolId, lockId) + lock, err := k.validateGammLockForSuperfluidStaking(ctx, sender, poolId, lockId) if err != nil { return []uint64{}, err } gammSharesInLock := lock.Coins[0] // 3) Get remaining duration on the lock. Handle if the lock was unbonding. - lockRemainingDuration := k.getExistingLockRemainingDuration(ctx, lock) + lockRemainingDuration, err := k.getExistingLockRemainingDuration(ctx, lock) + if err != nil { + return []uint64{}, err + } // 4) If superfluid delegated, superfluid undelegate err = k.unbondSuperfluidIfExists(ctx, sender, lockId) @@ -99,39 +102,48 @@ func (k Keeper) checkUnpoolWhitelisted(ctx sdk.Context, poolId uint64) error { return types.ErrPoolNotWhitelisted } -// check if pool is whitelisted for unpool -func (k Keeper) validateLockForUnpool(ctx sdk.Context, sender sdk.AccAddress, poolId uint64, lockId uint64) (*lockuptypes.PeriodLock, error) { +// validateGammLockForSuperfluidStaking checks if the provided lock: +// 1) is owned by the provided sender +// 2) contains only 1 coin +// 3) contains the gamm LP shares associated with the provided poolId +func (k Keeper) validateGammLockForSuperfluidStaking(ctx sdk.Context, sender sdk.AccAddress, poolId uint64, lockId uint64) (*lockuptypes.PeriodLock, error) { lock, err := k.lk.GetLockByID(ctx, lockId) if err != nil { - return lock, err + return &lockuptypes.PeriodLock{}, err } - // consistency check: validate lock owner - // However, we expect this to be guaranteed by caller though. + // Validate lock owner. + // We expect this to be guaranteed by caller, though. if lock.Owner != sender.String() { - return lock, lockuptypes.ErrNotLockOwner + return &lockuptypes.PeriodLock{}, lockuptypes.ErrNotLockOwner } if lock.Coins.Len() != 1 { - return lock, types.ErrMultipleCoinsLockupNotSupported + return &lockuptypes.PeriodLock{}, types.ErrMultipleCoinsLockupNotSupported } gammShare := lock.Coins[0] if gammShare.Denom != gammtypes.GetPoolShareDenom(poolId) { - return lock, types.ErrLockUnpoolNotAllowed + return &lockuptypes.PeriodLock{}, types.UnexpectedDenomError{ExpectedDenom: gammtypes.GetPoolShareDenom(poolId), ProvidedDenom: gammShare.Denom} } return lock, nil } -func (k Keeper) getExistingLockRemainingDuration(ctx sdk.Context, lock *lockuptypes.PeriodLock) time.Duration { +// getExistingLockRemainingDuration returns the time remaining until the lock is finished unlocking. +// If the lock is not unlocking, then the duration field of the lock is returned. +func (k Keeper) getExistingLockRemainingDuration(ctx sdk.Context, lock *lockuptypes.PeriodLock) (time.Duration, error) { if lock.IsUnlocking() { - // lock is unlocking, so remaining duration equals lock.EndTime - ctx.BlockTime + // Lock is unlocking, so remaining duration equals lock.EndTime - ctx.BlockTime. remainingDuration := lock.EndTime.Sub(ctx.BlockTime()) - return remainingDuration + // Defense in depth, ensure the duration is not negative. + if remainingDuration < 0 { + return 0, types.NegativeDurationError{Duration: remainingDuration} + } + return remainingDuration, nil } - // lock is bonded, thus the time it should take to unlock is lock.Duration - return lock.Duration + // Lock is not unlocking, thus the time it should take to unlock is the locks duration. + return lock.Duration, nil } // TODO: Review this in more depth diff --git a/x/superfluid/keeper/unpool_test.go b/x/superfluid/keeper/unpool_test.go index 19433dab95f..86d78dd9e4c 100644 --- a/x/superfluid/keeper/unpool_test.go +++ b/x/superfluid/keeper/unpool_test.go @@ -1,14 +1,17 @@ package keeper_test import ( + "fmt" "time" + 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" "github.com/osmosis-labs/osmosis/v15/x/gamm/pool-models/balancer" gammtypes "github.com/osmosis-labs/osmosis/v15/x/gamm/types" + lockuptypes "github.com/osmosis-labs/osmosis/v15/x/lockup/types" "github.com/osmosis-labs/osmosis/v15/x/superfluid/keeper" "github.com/osmosis-labs/osmosis/v15/x/superfluid/types" ) @@ -280,3 +283,146 @@ func (suite *KeeperTestSuite) TestUnpoolAllowedPools_WhiteList() { suite.Error(err) suite.Require().ErrorIs(err, types.ErrPoolNotWhitelisted) } + +func (suite *KeeperTestSuite) TestValidateGammLockForSuperfluid() { + lockCreator := suite.TestAccs[0] + nonLockCreator := suite.TestAccs[1] + type sendTest struct { + fundsToLock sdk.Coins + accountToValidate sdk.AccAddress + poolIdToValidate uint64 + lockIdToValidate uint64 + expectedError error + } + testCases := map[string]sendTest{ + "happy path": { + fundsToLock: sdk.NewCoins(sdk.NewCoin("gamm/pool/1", sdk.NewInt(100))), + accountToValidate: lockCreator, + poolIdToValidate: 1, + lockIdToValidate: 1, + }, + "error: non-existent lock ID": { + fundsToLock: sdk.NewCoins(sdk.NewCoin("gamm/pool/1", sdk.NewInt(100))), + accountToValidate: lockCreator, + poolIdToValidate: 1, + lockIdToValidate: 2, + expectedError: errorsmod.Wrap(lockuptypes.ErrLockupNotFound, fmt.Sprintf("lock with ID %d does not exist", 2)), + }, + "error: mismatched owner": { + fundsToLock: sdk.NewCoins(sdk.NewCoin("gamm/pool/1", sdk.NewInt(100))), + accountToValidate: nonLockCreator, + poolIdToValidate: 1, + lockIdToValidate: 1, + expectedError: lockuptypes.ErrNotLockOwner, + }, + "error: more than one coin in lock": { + fundsToLock: sdk.NewCoins(sdk.NewCoin("gamm/pool/1", sdk.NewInt(100)), + sdk.NewCoin("gamm/pool/2", sdk.NewInt(100))), + accountToValidate: lockCreator, + poolIdToValidate: 1, + lockIdToValidate: 1, + expectedError: types.ErrMultipleCoinsLockupNotSupported, + }, + "error: wrong pool ID provided when compared to lock denom": { + fundsToLock: sdk.NewCoins(sdk.NewCoin("gamm/pool/1", sdk.NewInt(100))), + accountToValidate: lockCreator, + poolIdToValidate: 2, + lockIdToValidate: 1, + expectedError: types.UnexpectedDenomError{ExpectedDenom: gammtypes.GetPoolShareDenom(2), ProvidedDenom: "gamm/pool/1"}, + }, + "error: right pool ID provided but not gamm/pool/ prefix": { + fundsToLock: sdk.NewCoins(sdk.NewCoin("cl/pool/1", sdk.NewInt(100))), + accountToValidate: lockCreator, + poolIdToValidate: 1, + lockIdToValidate: 1, + expectedError: types.UnexpectedDenomError{ExpectedDenom: gammtypes.GetPoolShareDenom(1), ProvidedDenom: "cl/pool/1"}, + }, + } + + for name, tc := range testCases { + suite.Run(name, func() { + suite.SetupTest() + + ctx := suite.Ctx + superfluidKeeper := suite.App.SuperfluidKeeper + + suite.FundAcc(lockCreator, tc.fundsToLock) + _, err := suite.App.LockupKeeper.CreateLock(ctx, lockCreator, tc.fundsToLock, time.Hour) + suite.Require().NoError(err) + + // System under test + _, err = superfluidKeeper.ValidateGammLockForSuperfluidStaking(ctx, tc.accountToValidate, tc.poolIdToValidate, tc.lockIdToValidate) + if tc.expectedError != nil { + suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expectedError.Error()) + return + } + suite.Require().NoError(err) + }) + } +} + +func (suite *KeeperTestSuite) TestGetExistingLockRemainingDuration() { + defaultJoinTime := suite.Ctx.BlockTime() + lockCreator := suite.TestAccs[0] + type sendTest struct { + isUnlocking bool + lockDuration time.Duration + timePassed time.Duration + expectedRemainingDuration time.Duration + expectedError error + } + testCases := map[string]sendTest{ + "lock that is not unlocking": { + isUnlocking: false, + lockDuration: time.Hour, + timePassed: time.Hour, + expectedRemainingDuration: time.Hour, + }, + "lock that is unlocking": { + isUnlocking: true, + lockDuration: time.Hour, + timePassed: time.Minute, + expectedRemainingDuration: time.Hour - time.Minute, + }, + "error: negative duration": { + isUnlocking: true, + lockDuration: time.Hour, + timePassed: time.Hour + time.Minute, + expectedError: types.NegativeDurationError{Duration: -time.Minute}, + }, + } + + for name, tc := range testCases { + suite.Run(name, func() { + suite.SetupTest() + ctx := suite.Ctx.WithBlockTime(defaultJoinTime) + + superfluidKeeper := suite.App.SuperfluidKeeper + + suite.FundAcc(lockCreator, defaultAcctFunds) + lock, err := suite.App.LockupKeeper.CreateLock(ctx, lockCreator, defaultAcctFunds, tc.lockDuration) + suite.Require().NoError(err) + + if tc.isUnlocking { + _, err = suite.App.LockupKeeper.BeginUnlock(ctx, lock.ID, defaultAcctFunds) + suite.Require().NoError(err) + } + + ctx = ctx.WithBlockTime(defaultJoinTime.Add(tc.timePassed)) + + lockAfterTime, err := suite.App.LockupKeeper.GetLockByID(ctx, lock.ID) + suite.Require().NoError(err) + + // System under test + remainingDuration, err := superfluidKeeper.GetExistingLockRemainingDuration(ctx, lockAfterTime) + if tc.expectedError != nil { + suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expectedError.Error()) + return + } + suite.Require().NoError(err) + suite.Require().Equal(tc.expectedRemainingDuration, remainingDuration) + }) + } +} diff --git a/x/superfluid/types/errors.go b/x/superfluid/types/errors.go index 45f8c886c8a..27db450f7d5 100644 --- a/x/superfluid/types/errors.go +++ b/x/superfluid/types/errors.go @@ -2,6 +2,7 @@ package types import ( fmt "fmt" + "time" errorsmod "cosmossdk.io/errors" ) @@ -51,3 +52,20 @@ type LockOwnerMismatchError struct { func (e LockOwnerMismatchError) Error() string { return fmt.Sprintf("lock ID %d owner %s does not match provided owner %s.", e.LockId, e.LockOwner, e.ProvidedOwner) } + +type NegativeDurationError struct { + Duration time.Duration +} + +func (e NegativeDurationError) Error() string { + return fmt.Sprintf("duration cannot be negative (%s)", e.Duration) +} + +type UnexpectedDenomError struct { + ExpectedDenom string + ProvidedDenom string +} + +func (e UnexpectedDenomError) Error() string { + return fmt.Sprintf("provided denom (%s) was expected to be formatted as follows: %s", e.ProvidedDenom, e.ExpectedDenom) +} diff --git a/x/superfluid/types/tx.pb.go b/x/superfluid/types/tx.pb.go index 786cc740f9b..ecd48822493 100644 --- a/x/superfluid/types/tx.pb.go +++ b/x/superfluid/types/tx.pb.go @@ -766,7 +766,7 @@ type MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition struct { LockId uint64 `protobuf:"varint,2,opt,name=lock_id,json=lockId,proto3" json:"lock_id,omitempty" yaml:"lock_id"` SharesToMigrate types.Coin `protobuf:"bytes,3,opt,name=shares_to_migrate,json=sharesToMigrate,proto3" json:"shares_to_migrate" yaml:"shares_to_migrate"` // token_out_mins indicates minimum token to exit Balancer pool with. - TokenOutMins []types.Coin `protobuf:"bytes,4,rep,name=token_out_mins,json=tokenOutMins,proto3" json:"token_out_mins" yaml:"token_out_min_amounts"` + TokenOutMins github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,4,rep,name=token_out_mins,json=tokenOutMins,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"token_out_mins" yaml:"token_out_mins"` } func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) Reset() { @@ -827,7 +827,7 @@ func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) GetSharesToMi return types.Coin{} } -func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) GetTokenOutMins() []types.Coin { +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) GetTokenOutMins() github_com_cosmos_cosmos_sdk_types.Coins { if m != nil { return m.TokenOutMins } @@ -1040,81 +1040,80 @@ func init() { func init() { proto.RegisterFile("osmosis/superfluid/tx.proto", fileDescriptor_55b645f187d22814) } var fileDescriptor_55b645f187d22814 = []byte{ - // 1173 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x58, 0x41, 0x4f, 0x1b, 0x47, - 0x14, 0x66, 0x6d, 0x17, 0x92, 0xa1, 0x90, 0xb0, 0x0d, 0x89, 0x71, 0x89, 0xd7, 0x99, 0x46, 0x11, - 0x55, 0xc8, 0x2e, 0x26, 0x4d, 0x40, 0x3d, 0x81, 0xb1, 0x5a, 0x39, 0xc5, 0x2a, 0xda, 0x80, 0x2a, - 0x45, 0xaa, 0x56, 0x6b, 0xcf, 0x64, 0xd9, 0xb2, 0xde, 0x71, 0x76, 0xc6, 0x14, 0xd4, 0x1f, 0x90, - 0x6b, 0xfe, 0x41, 0xef, 0x3d, 0xf4, 0x37, 0xf4, 0xd6, 0xa8, 0xa7, 0xdc, 0x5a, 0xb5, 0x92, 0x53, - 0xc1, 0x3f, 0xe0, 0xd8, 0x53, 0x35, 0xbb, 0xb3, 0xeb, 0x35, 0x78, 0x31, 0xeb, 0x90, 0x43, 0x4e, - 0xec, 0xcc, 0xbc, 0xf7, 0x7d, 0xef, 0xbd, 0x79, 0xef, 0xcd, 0xc3, 0xe0, 0x53, 0x42, 0x5b, 0x84, - 0xda, 0x54, 0xa3, 0x9d, 0x36, 0xf6, 0x9e, 0x3b, 0x1d, 0x1b, 0x69, 0xec, 0x40, 0x6d, 0x7b, 0x84, - 0x11, 0x59, 0x16, 0x87, 0x6a, 0xef, 0xb0, 0x70, 0xc3, 0x22, 0x16, 0xf1, 0x8f, 0x35, 0xfe, 0x15, - 0x48, 0x16, 0x8a, 0x16, 0x21, 0x96, 0x83, 0x35, 0x7f, 0xd5, 0xe8, 0x3c, 0xd7, 0x50, 0xc7, 0x33, - 0x99, 0x4d, 0xdc, 0xf0, 0xbc, 0xe9, 0x43, 0x69, 0x0d, 0x93, 0x62, 0x6d, 0xbf, 0xdc, 0xc0, 0xcc, - 0x2c, 0x6b, 0x4d, 0x62, 0x87, 0xe7, 0xca, 0x69, 0x7d, 0x66, 0xb7, 0x30, 0x65, 0x66, 0xab, 0x2d, - 0x04, 0x3e, 0x1b, 0x60, 0x67, 0xef, 0x33, 0x10, 0x82, 0xfb, 0x60, 0xb6, 0x4e, 0xad, 0xa7, 0xd1, - 0x76, 0x15, 0x3b, 0xd8, 0x32, 0x19, 0x96, 0x3f, 0x07, 0xe3, 0x14, 0xbb, 0x08, 0x7b, 0x79, 0xa9, - 0x24, 0x2d, 0x5c, 0xad, 0xcc, 0x9c, 0x74, 0x95, 0xa9, 0x43, 0xb3, 0xe5, 0x7c, 0x09, 0x83, 0x7d, - 0xa8, 0x0b, 0x01, 0xf9, 0x16, 0x98, 0x70, 0x48, 0x73, 0xcf, 0xb0, 0x51, 0x3e, 0x53, 0x92, 0x16, - 0x72, 0xfa, 0x38, 0x5f, 0xd6, 0x90, 0x3c, 0x07, 0xae, 0xec, 0x9b, 0x8e, 0x61, 0x22, 0xe4, 0xe5, - 0xb3, 0x1c, 0x45, 0x9f, 0xd8, 0x37, 0x9d, 0x75, 0x84, 0x3c, 0xa8, 0x80, 0xdb, 0x03, 0x79, 0x75, - 0x4c, 0xdb, 0xc4, 0xa5, 0x18, 0x7e, 0x0f, 0x6e, 0xf5, 0x09, 0xec, 0xb8, 0xe8, 0x12, 0x4d, 0x83, - 0x77, 0x80, 0x92, 0x00, 0x7f, 0x8e, 0x05, 0x0d, 0xe2, 0xa2, 0x4d, 0xd2, 0xdc, 0x7b, 0x4f, 0x16, - 0x84, 0xf0, 0x91, 0x05, 0xbf, 0x4a, 0xe0, 0x6e, 0x82, 0x95, 0xeb, 0xee, 0x25, 0xdb, 0x23, 0x57, - 0x40, 0x8e, 0x67, 0x97, 0x7f, 0x51, 0x93, 0xcb, 0x73, 0x6a, 0x90, 0x7e, 0x2a, 0x4f, 0x3f, 0x55, - 0xa4, 0x9f, 0xba, 0x41, 0x6c, 0xb7, 0xf2, 0xc9, 0xeb, 0xae, 0x32, 0x76, 0xd2, 0x55, 0x26, 0x03, - 0x02, 0xae, 0x04, 0x75, 0x5f, 0x17, 0x7e, 0x0d, 0x16, 0x2f, 0x62, 0x6f, 0xe8, 0x60, 0xdc, 0x18, - 0xa9, 0x2f, 0x38, 0xbf, 0x4b, 0x60, 0xbe, 0x4e, 0x2d, 0x2e, 0xbc, 0xee, 0xa2, 0x77, 0x4b, 0x4f, - 0x13, 0x7c, 0xc4, 0x8d, 0xa3, 0xf9, 0x4c, 0x29, 0x7b, 0xbe, 0x67, 0x4b, 0xdc, 0xb3, 0x5f, 0xde, - 0x2a, 0x0b, 0x96, 0xcd, 0x76, 0x3b, 0x0d, 0xb5, 0x49, 0x5a, 0x9a, 0xa8, 0xc2, 0xe0, 0xcf, 0x03, - 0x8a, 0xf6, 0x34, 0x76, 0xd8, 0xc6, 0xd4, 0x57, 0xa0, 0x7a, 0x80, 0x7c, 0x5e, 0xa2, 0x3f, 0xf6, - 0xaf, 0x30, 0xd1, 0x91, 0x28, 0x14, 0xd3, 0x20, 0x53, 0xab, 0x8a, 0x28, 0x64, 0x6a, 0x55, 0xf8, - 0x32, 0x03, 0xb4, 0x3a, 0xb5, 0x36, 0x3c, 0x6c, 0x32, 0xfc, 0x55, 0xc7, 0x71, 0x74, 0xd3, 0xb5, - 0xf0, 0x16, 0xa1, 0x36, 0xef, 0x11, 0x1f, 0x76, 0x50, 0xe4, 0xfb, 0x60, 0xa2, 0x4d, 0x88, 0xc3, - 0xef, 0x3d, 0xc7, 0x3d, 0xae, 0xc8, 0x27, 0x5d, 0x65, 0x3a, 0xb0, 0x54, 0x1c, 0x40, 0x7d, 0x9c, - 0x7f, 0xd5, 0x10, 0x7c, 0x01, 0x56, 0x52, 0x06, 0x22, 0x0a, 0xea, 0x4d, 0x10, 0x24, 0x54, 0xb5, - 0x2f, 0xbd, 0xaa, 0x72, 0x11, 0x80, 0xb6, 0x00, 0xa8, 0x55, 0x45, 0x1d, 0xc4, 0x76, 0xa0, 0x07, - 0xf2, 0x75, 0x6a, 0xed, 0xb8, 0x5b, 0x84, 0x38, 0xdf, 0xed, 0xda, 0x0c, 0x3b, 0x36, 0x65, 0x18, - 0xf1, 0x65, 0x9a, 0x20, 0xc7, 0xdc, 0xcc, 0x0c, 0x75, 0xf3, 0x09, 0x28, 0x25, 0x71, 0x46, 0xfe, - 0xdc, 0x03, 0xd7, 0xf0, 0x81, 0xcd, 0x30, 0x32, 0x44, 0xd9, 0xd0, 0xbc, 0x54, 0xca, 0x2e, 0xe4, - 0xf4, 0xa9, 0x60, 0x7b, 0xd3, 0xaf, 0x1e, 0x0a, 0x8f, 0x32, 0x60, 0xd5, 0x07, 0x73, 0x82, 0xbc, - 0xab, 0xdb, 0x96, 0x67, 0x32, 0xfc, 0x74, 0xd7, 0xf4, 0x30, 0xdd, 0x26, 0x51, 0x14, 0x37, 0x88, - 0xdb, 0xc4, 0x2e, 0xe3, 0x67, 0x28, 0x8c, 0x68, 0x4a, 0x07, 0xfb, 0x9a, 0x49, 0xdc, 0x41, 0x71, - 0x00, 0xa3, 0x06, 0x63, 0x81, 0x19, 0xea, 0x1b, 0x60, 0x30, 0x62, 0xb4, 0x02, 0x8b, 0x86, 0x77, - 0x9b, 0x92, 0xe8, 0x36, 0x79, 0x61, 0xc1, 0x69, 0x04, 0xa8, 0x5f, 0xa3, 0xc2, 0x2d, 0xe1, 0xa5, - 0x8c, 0xc1, 0x34, 0x23, 0x7b, 0xd8, 0x35, 0x48, 0x87, 0x19, 0x2d, 0x9e, 0xe4, 0xb9, 0x61, 0x49, - 0x7e, 0x57, 0xb0, 0xcc, 0x07, 0x2c, 0x7d, 0xea, 0x86, 0xd9, 0x22, 0x1d, 0x97, 0x51, 0xa8, 0x7f, - 0xec, 0xef, 0x7f, 0xdb, 0x61, 0x75, 0xdb, 0xa5, 0xf0, 0x8f, 0x2c, 0x58, 0x1b, 0x35, 0xc8, 0xd1, - 0x8d, 0x3e, 0x03, 0x13, 0x01, 0xfc, 0x92, 0x88, 0xf6, 0x1a, 0xb7, 0xe4, 0xef, 0xae, 0x72, 0xef, - 0x02, 0xe5, 0x56, 0x73, 0x59, 0x2f, 0xde, 0x02, 0x06, 0xea, 0x21, 0x60, 0x0f, 0xbb, 0xec, 0xdf, - 0xce, 0x3b, 0x63, 0x97, 0x23, 0xec, 0xb2, 0xfc, 0x23, 0x98, 0x71, 0xec, 0x17, 0x1d, 0x1b, 0xd9, - 0xec, 0xd0, 0x68, 0xfa, 0xb5, 0x89, 0x82, 0x2a, 0xaf, 0x3c, 0x49, 0xc1, 0x52, 0xc5, 0xcd, 0xde, - 0xdd, 0x9e, 0x01, 0x84, 0xfa, 0xf5, 0x68, 0x2f, 0xa8, 0x7f, 0x24, 0xef, 0x80, 0xab, 0x3f, 0x10, - 0xdb, 0x35, 0xf8, 0xb4, 0xe3, 0x37, 0x8f, 0xc9, 0xe5, 0x82, 0x1a, 0x8c, 0x42, 0x6a, 0x38, 0x0a, - 0xa9, 0xdb, 0xe1, 0x28, 0x54, 0x99, 0x17, 0x17, 0x7b, 0x3d, 0xa0, 0x88, 0x54, 0xe1, 0xab, 0xb7, - 0x8a, 0xa4, 0x5f, 0xe1, 0x6b, 0x2e, 0x0c, 0xff, 0x0c, 0xda, 0xed, 0x3a, 0x42, 0xdb, 0x24, 0x7e, - 0x61, 0x9b, 0x21, 0x7f, 0xaf, 0xcb, 0x44, 0x85, 0xb2, 0x02, 0x26, 0xc3, 0x9e, 0x11, 0xbd, 0x60, - 0x95, 0x9b, 0x27, 0x5d, 0x45, 0x0e, 0x4b, 0x3c, 0x3a, 0x84, 0xb1, 0xf6, 0x82, 0x62, 0x15, 0x96, - 0x19, 0x56, 0x61, 0x46, 0x98, 0xcb, 0x08, 0x53, 0xdb, 0xc3, 0x68, 0x69, 0x78, 0xc5, 0xdc, 0x16, - 0x2e, 0xcf, 0xc6, 0x73, 0x39, 0x54, 0x87, 0xfa, 0x94, 0xbf, 0x51, 0x15, 0xeb, 0x33, 0x04, 0x65, - 0x11, 0xd4, 0x11, 0x09, 0xca, 0xa7, 0x08, 0xca, 0x3c, 0xb2, 0x2b, 0x29, 0x23, 0x1b, 0x55, 0xc7, - 0xc8, 0x11, 0x8e, 0x95, 0x55, 0xe6, 0x3d, 0x96, 0x55, 0xf6, 0xb2, 0xcb, 0x2a, 0xd6, 0x50, 0x73, - 0xc3, 0x1a, 0xea, 0xf2, 0x7f, 0x00, 0x64, 0xeb, 0xd4, 0x92, 0x3d, 0x20, 0x0f, 0x1a, 0x06, 0xd4, - 0xb3, 0xff, 0x8a, 0xa8, 0x03, 0x67, 0xee, 0x42, 0xf9, 0xc2, 0xa2, 0xd1, 0xcd, 0x1c, 0x80, 0x1b, - 0x03, 0x67, 0xf3, 0xfb, 0x43, 0xa1, 0x7a, 0xc2, 0x85, 0x87, 0x29, 0x84, 0x93, 0x98, 0xa3, 0x19, - 0xf8, 0x22, 0xcc, 0xa1, 0xf0, 0x85, 0x98, 0xcf, 0x4c, 0xab, 0x3f, 0x4b, 0xe0, 0xce, 0xf0, 0x59, - 0x7c, 0x35, 0x85, 0x53, 0x7d, 0x9a, 0x85, 0xb5, 0x51, 0x35, 0x23, 0x0b, 0x5f, 0x4a, 0x60, 0x2e, - 0x79, 0x66, 0x5e, 0x4a, 0xc0, 0x4f, 0xd4, 0x28, 0xac, 0xa6, 0xd5, 0x88, 0x2c, 0xf9, 0x4d, 0x02, - 0x8b, 0xa9, 0x66, 0xd7, 0x8d, 0x04, 0xaa, 0x34, 0x20, 0x85, 0x6f, 0x2e, 0x01, 0x24, 0x72, 0xe1, - 0x27, 0x30, 0x3b, 0x78, 0x02, 0x5c, 0x4c, 0x60, 0x19, 0x28, 0x5d, 0xf8, 0x22, 0x8d, 0x74, 0x44, - 0xfe, 0x8f, 0x04, 0x1e, 0x8d, 0x36, 0xbe, 0x6d, 0x26, 0xf2, 0x8d, 0x80, 0x56, 0xd8, 0xbe, 0x4c, - 0xb4, 0xbe, 0xec, 0x48, 0xf5, 0xd4, 0x26, 0x65, 0x47, 0x1a, 0x90, 0xc4, 0xec, 0x18, 0xe5, 0x69, - 0xaa, 0x6c, 0xbd, 0x3e, 0x2a, 0x4a, 0x6f, 0x8e, 0x8a, 0xd2, 0xbf, 0x47, 0x45, 0xe9, 0xd5, 0x71, - 0x71, 0xec, 0xcd, 0x71, 0x71, 0xec, 0xaf, 0xe3, 0xe2, 0xd8, 0xb3, 0xc7, 0xb1, 0x67, 0x40, 0x10, - 0x3e, 0x70, 0xcc, 0x06, 0x0d, 0x17, 0xda, 0x7e, 0xf9, 0x91, 0x76, 0xd0, 0xf7, 0xeb, 0x11, 0x7f, - 0x1a, 0x1a, 0xe3, 0xfe, 0xf8, 0xf2, 0xf0, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x1f, 0x23, 0xf9, - 0xff, 0x60, 0x12, 0x00, 0x00, + // 1168 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x58, 0x4f, 0x4f, 0x1b, 0x47, + 0x14, 0x67, 0x6d, 0x17, 0x92, 0xa1, 0x21, 0x61, 0x1b, 0x12, 0xe3, 0x26, 0x5e, 0x67, 0x5a, 0x45, + 0x54, 0x21, 0xbb, 0x98, 0x34, 0x01, 0xf5, 0x04, 0xc6, 0x6a, 0xe5, 0x14, 0xab, 0x68, 0x03, 0xaa, + 0x14, 0xa9, 0xb2, 0xd6, 0x9e, 0xc9, 0xb2, 0x65, 0xbd, 0xe3, 0xec, 0x8c, 0x29, 0xa8, 0x1f, 0x20, + 0x55, 0x4f, 0xf9, 0x06, 0xbd, 0xf7, 0xd0, 0xcf, 0xd0, 0x5b, 0xa3, 0x9e, 0x72, 0x6b, 0xd5, 0x4a, + 0x4e, 0x05, 0xdf, 0x80, 0x63, 0x4f, 0xd5, 0xec, 0xce, 0x8e, 0x6d, 0xf0, 0x62, 0xd6, 0x71, 0x0e, + 0x3d, 0xb1, 0x33, 0xef, 0xbd, 0xdf, 0xfb, 0xff, 0xe6, 0x61, 0xf0, 0x21, 0xa1, 0x4d, 0x42, 0x1d, + 0x6a, 0xd0, 0x76, 0x0b, 0xfb, 0xcf, 0xdc, 0xb6, 0x83, 0x0c, 0x76, 0xa0, 0xb7, 0x7c, 0xc2, 0x88, + 0xaa, 0x0a, 0xa2, 0xde, 0x25, 0xe6, 0xae, 0xdb, 0xc4, 0x26, 0x01, 0xd9, 0xe0, 0x5f, 0x21, 0x67, + 0x2e, 0x6f, 0x13, 0x62, 0xbb, 0xd8, 0x08, 0x4e, 0xf5, 0xf6, 0x33, 0x03, 0xb5, 0x7d, 0x8b, 0x39, + 0xc4, 0x8b, 0xe8, 0x8d, 0x00, 0xca, 0xa8, 0x5b, 0x14, 0x1b, 0xfb, 0xc5, 0x3a, 0x66, 0x56, 0xd1, + 0x68, 0x10, 0x27, 0xa2, 0x6b, 0xa7, 0xe5, 0x99, 0xd3, 0xc4, 0x94, 0x59, 0xcd, 0x96, 0x60, 0xf8, + 0x68, 0x80, 0x9d, 0xdd, 0xcf, 0x90, 0x09, 0xee, 0x83, 0xb9, 0x2a, 0xb5, 0x9f, 0xc8, 0xeb, 0x32, + 0x76, 0xb1, 0x6d, 0x31, 0xac, 0x7e, 0x02, 0x26, 0x29, 0xf6, 0x10, 0xf6, 0xb3, 0x4a, 0x41, 0x59, + 0xb8, 0x5c, 0x9a, 0x3d, 0xe9, 0x68, 0x57, 0x0e, 0xad, 0xa6, 0xfb, 0x19, 0x0c, 0xef, 0xa1, 0x29, + 0x18, 0xd4, 0x9b, 0x60, 0xca, 0x25, 0x8d, 0xbd, 0x9a, 0x83, 0xb2, 0xa9, 0x82, 0xb2, 0x90, 0x31, + 0x27, 0xf9, 0xb1, 0x82, 0xd4, 0x79, 0x70, 0x69, 0xdf, 0x72, 0x6b, 0x16, 0x42, 0x7e, 0x36, 0xcd, + 0x51, 0xcc, 0xa9, 0x7d, 0xcb, 0x5d, 0x47, 0xc8, 0x87, 0x1a, 0xb8, 0x3d, 0x50, 0xaf, 0x89, 0x69, + 0x8b, 0x78, 0x14, 0xc3, 0x6f, 0xc0, 0xcd, 0x3e, 0x86, 0x1d, 0x0f, 0x8d, 0xd1, 0x34, 0x78, 0x07, + 0x68, 0x31, 0xf0, 0xe7, 0x58, 0x50, 0x27, 0x1e, 0xda, 0x24, 0x8d, 0xbd, 0x77, 0x64, 0x41, 0x04, + 0x2f, 0x2d, 0xf8, 0x45, 0x01, 0x1f, 0xc7, 0x58, 0xb9, 0xee, 0x8d, 0xd9, 0x1e, 0xb5, 0x04, 0x32, + 0xbc, 0xba, 0x82, 0x44, 0x4d, 0x2f, 0xcf, 0xeb, 0x61, 0xf9, 0xe9, 0xbc, 0xfc, 0x74, 0x51, 0x7e, + 0xfa, 0x06, 0x71, 0xbc, 0xd2, 0x07, 0xaf, 0x3a, 0xda, 0xc4, 0x49, 0x47, 0x9b, 0x0e, 0x15, 0x70, + 0x21, 0x68, 0x06, 0xb2, 0xf0, 0x0b, 0xb0, 0x78, 0x11, 0x7b, 0x23, 0x07, 0x7b, 0x8d, 0x51, 0xfa, + 0x82, 0xf3, 0x9b, 0x02, 0x6e, 0x55, 0xa9, 0xcd, 0x99, 0xd7, 0x3d, 0xf4, 0x76, 0xe5, 0x69, 0x81, + 0xf7, 0xb8, 0x71, 0x34, 0x9b, 0x2a, 0xa4, 0xcf, 0xf7, 0x6c, 0x89, 0x7b, 0xf6, 0xf3, 0x1b, 0x6d, + 0xc1, 0x76, 0xd8, 0x6e, 0xbb, 0xae, 0x37, 0x48, 0xd3, 0x10, 0x5d, 0x18, 0xfe, 0xb9, 0x4f, 0xd1, + 0x9e, 0xc1, 0x0e, 0x5b, 0x98, 0x06, 0x02, 0xd4, 0x0c, 0x91, 0xcf, 0x2b, 0xf4, 0x47, 0x41, 0x0a, + 0x63, 0x1d, 0x91, 0xa1, 0x98, 0x01, 0xa9, 0x4a, 0x59, 0x44, 0x21, 0x55, 0x29, 0xc3, 0x17, 0x29, + 0x60, 0x54, 0xa9, 0xbd, 0xe1, 0x63, 0x8b, 0xe1, 0xcf, 0xdb, 0xae, 0x6b, 0x5a, 0x9e, 0x8d, 0xb7, + 0x08, 0x75, 0xf8, 0x8c, 0xf8, 0x7f, 0x07, 0x45, 0xbd, 0x07, 0xa6, 0x5a, 0x84, 0xb8, 0x3c, 0xef, + 0x19, 0xee, 0x71, 0x49, 0x3d, 0xe9, 0x68, 0x33, 0xa1, 0xa5, 0x82, 0x00, 0xcd, 0x49, 0xfe, 0x55, + 0x41, 0xf0, 0x39, 0x58, 0x49, 0x18, 0x08, 0x19, 0xd4, 0x1b, 0x20, 0x2c, 0xa8, 0x72, 0x5f, 0x79, + 0x95, 0xd5, 0x3c, 0x00, 0x2d, 0x01, 0x50, 0x29, 0x8b, 0x3e, 0xe8, 0xb9, 0x81, 0x3e, 0xc8, 0x56, + 0xa9, 0xbd, 0xe3, 0x6d, 0x11, 0xe2, 0x7e, 0xbd, 0xeb, 0x30, 0xec, 0x3a, 0x94, 0x61, 0xc4, 0x8f, + 0x49, 0x82, 0xdc, 0xe3, 0x66, 0x6a, 0xa8, 0x9b, 0x8f, 0x41, 0x21, 0x4e, 0xa7, 0xf4, 0xe7, 0x2e, + 0xb8, 0x8a, 0x0f, 0x1c, 0x86, 0x51, 0x4d, 0xb4, 0x0d, 0xcd, 0x2a, 0x85, 0xf4, 0x42, 0xc6, 0xbc, + 0x12, 0x5e, 0x6f, 0x06, 0xdd, 0x43, 0xe1, 0x0f, 0x69, 0xb0, 0x1a, 0x80, 0xb9, 0x61, 0xdd, 0x55, + 0x1d, 0xdb, 0xb7, 0x18, 0x7e, 0xb2, 0x6b, 0xf9, 0x98, 0x6e, 0x13, 0x19, 0xc5, 0x0d, 0xe2, 0x35, + 0xb0, 0xc7, 0x38, 0x0d, 0x45, 0x11, 0x4d, 0xe8, 0x60, 0xdf, 0x30, 0xe9, 0x75, 0x50, 0x10, 0xa0, + 0x1c, 0x30, 0x36, 0x98, 0xa5, 0x81, 0x01, 0x35, 0x46, 0x6a, 0xcd, 0xd0, 0xa2, 0xe1, 0xd3, 0xa6, + 0x20, 0xa6, 0x4d, 0x56, 0x58, 0x70, 0x1a, 0x01, 0x9a, 0x57, 0xa9, 0x70, 0x4b, 0x78, 0xa9, 0xfe, + 0xa8, 0x80, 0x19, 0x46, 0xf6, 0xb0, 0x57, 0x23, 0x6d, 0x56, 0x6b, 0xf2, 0x2a, 0xcf, 0x0c, 0xab, + 0xf2, 0x8a, 0x50, 0x33, 0x17, 0xaa, 0xe9, 0x17, 0x87, 0x89, 0xca, 0xff, 0xfd, 0x40, 0xf8, 0xab, + 0x36, 0xab, 0x72, 0xd1, 0xdf, 0xd3, 0x60, 0x6d, 0xd4, 0x54, 0xc8, 0xbc, 0x3f, 0x05, 0x53, 0x56, + 0x93, 0xb4, 0x3d, 0xb6, 0x24, 0x72, 0xb2, 0xc6, 0xcd, 0xfd, 0xab, 0xa3, 0xdd, 0xbd, 0x80, 0x55, + 0x15, 0x8f, 0x75, 0xb3, 0x22, 0x60, 0xa0, 0x19, 0x01, 0x76, 0xb1, 0x8b, 0x41, 0x0e, 0xdf, 0x1a, + 0xbb, 0x28, 0xb1, 0x8b, 0xea, 0x77, 0x60, 0xd6, 0x75, 0x9e, 0xb7, 0x1d, 0xe4, 0xb0, 0xc3, 0x5a, + 0x23, 0xe8, 0x60, 0x14, 0xce, 0x82, 0xd2, 0xe3, 0x04, 0x5a, 0xca, 0xb8, 0xd1, 0xad, 0x80, 0x33, + 0x80, 0xd0, 0xbc, 0x26, 0xef, 0xc2, 0x29, 0x81, 0xd4, 0x1d, 0x70, 0xf9, 0x5b, 0xe2, 0x78, 0x35, + 0xbe, 0x13, 0x05, 0x23, 0x66, 0x7a, 0x39, 0xa7, 0x87, 0x0b, 0x93, 0x1e, 0x2d, 0x4c, 0xfa, 0x76, + 0xb4, 0x30, 0x95, 0x6e, 0x89, 0xec, 0x5f, 0x0b, 0x55, 0x48, 0x51, 0xf8, 0xf2, 0x8d, 0xa6, 0x98, + 0x97, 0xf8, 0x99, 0x33, 0xc3, 0x3f, 0xc2, 0xa1, 0xbc, 0x8e, 0xd0, 0x36, 0xe9, 0x4d, 0xd8, 0x66, + 0xa4, 0xbf, 0x3b, 0x8b, 0x64, 0x3b, 0xad, 0x80, 0xe9, 0x68, 0xb2, 0xc8, 0x77, 0xae, 0x74, 0xe3, + 0xa4, 0xa3, 0xa9, 0xd1, 0x20, 0x90, 0x44, 0xd8, 0x33, 0x84, 0x50, 0x4f, 0x1f, 0xa6, 0x86, 0xf5, + 0x61, 0x2d, 0x2a, 0x78, 0x84, 0xa9, 0xe3, 0x63, 0xb4, 0x34, 0xbc, 0xaf, 0x6e, 0x0f, 0x2a, 0xf8, + 0x48, 0x1c, 0x9a, 0x57, 0x82, 0x8b, 0xb2, 0x38, 0x9f, 0x51, 0x50, 0x14, 0x41, 0x1d, 0x51, 0x41, + 0xf1, 0x94, 0x82, 0x22, 0x8f, 0xec, 0x4a, 0xc2, 0xc8, 0xca, 0xee, 0x18, 0x39, 0xc2, 0x3d, 0x6d, + 0x95, 0x7a, 0x87, 0x6d, 0x95, 0x1e, 0x77, 0x5b, 0xf5, 0x8c, 0xdd, 0xcc, 0xb0, 0xb1, 0xbb, 0xfc, + 0x2f, 0x00, 0xe9, 0x2a, 0xb5, 0x55, 0x1f, 0xa8, 0x83, 0x56, 0x06, 0xfd, 0xec, 0x3f, 0x2c, 0xfa, + 0xc0, 0xcd, 0x3c, 0x57, 0xbc, 0x30, 0xab, 0xcc, 0xcc, 0x01, 0xb8, 0x3e, 0x70, 0x83, 0xbf, 0x37, + 0x14, 0xaa, 0xcb, 0x9c, 0x7b, 0x90, 0x80, 0x39, 0x4e, 0xb3, 0xdc, 0x94, 0x2f, 0xa2, 0x39, 0x62, + 0xbe, 0x90, 0xe6, 0x33, 0x3b, 0xed, 0x4f, 0x0a, 0xb8, 0x33, 0x7c, 0x63, 0x5f, 0x4d, 0xe0, 0x54, + 0x9f, 0x64, 0x6e, 0x6d, 0x54, 0x49, 0x69, 0xe1, 0x0b, 0x05, 0xcc, 0xc7, 0x6f, 0xd6, 0x4b, 0x31, + 0xf8, 0xb1, 0x12, 0xb9, 0xd5, 0xa4, 0x12, 0xd2, 0x92, 0x5f, 0x15, 0xb0, 0x98, 0x68, 0xc3, 0xdd, + 0x88, 0x51, 0x95, 0x04, 0x24, 0xf7, 0xe5, 0x18, 0x40, 0xa4, 0x0b, 0xdf, 0x83, 0xb9, 0xc1, 0x7b, + 0xe2, 0x62, 0x8c, 0x96, 0x81, 0xdc, 0xb9, 0x4f, 0x93, 0x70, 0x4b, 0xe5, 0x7f, 0x2b, 0xe0, 0xe1, + 0x68, 0x4b, 0xde, 0x66, 0xac, 0xbe, 0x11, 0xd0, 0x72, 0xdb, 0xe3, 0x44, 0xeb, 0xab, 0x8e, 0x44, + 0x4f, 0x6d, 0x5c, 0x75, 0x24, 0x01, 0x89, 0xad, 0x8e, 0x51, 0x9e, 0xa6, 0xd2, 0xd6, 0xab, 0xa3, + 0xbc, 0xf2, 0xfa, 0x28, 0xaf, 0xfc, 0x73, 0x94, 0x57, 0x5e, 0x1e, 0xe7, 0x27, 0x5e, 0x1f, 0xe7, + 0x27, 0xfe, 0x3c, 0xce, 0x4f, 0x3c, 0x7d, 0xd4, 0xf3, 0x0c, 0x08, 0x85, 0xf7, 0x5d, 0xab, 0x4e, + 0xa3, 0x83, 0xb1, 0x5f, 0x7c, 0x68, 0x1c, 0xf4, 0xfd, 0xc6, 0xc4, 0x9f, 0x86, 0xfa, 0x64, 0xb0, + 0xbe, 0x3c, 0xf8, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x2f, 0x8b, 0x8c, 0x15, 0x86, 0x12, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used.