diff --git a/docs/static/openapi.yml b/docs/static/openapi.yml index bc5121f77..c09d7d299 100644 --- a/docs/static/openapi.yml +++ b/docs/static/openapi.yml @@ -41020,6 +41020,41 @@ paths: type: string tags: - Query + /elys-network/elys/leveragelp/get_add/{id}: + get: + summary: Queries a list of GetAdd items. + operationId: ElysLeveragelpGetAdd + responses: + '200': + description: A successful response. + schema: + type: object + default: + description: An unexpected error response. + schema: + type: object + properties: + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + '@type': + type: string + additionalProperties: {} + parameters: + - name: id + in: path + required: true + type: integer + format: int32 + tags: + - Query /elys-network/elys/leveragelp/is-whitelisted: get: summary: Queries a list of IsWhitelisted items. @@ -41407,40 +41442,46 @@ paths: position: type: object properties: - address: - type: string - collateral: + position: type: object properties: - denom: - type: string - amount: + address: type: string - description: >- - Coin defines a token with a denomination and an amount. + collateral: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an + amount. - NOTE: The amount field is an Int which implements the - custom method + NOTE: The amount field is an Int which implements the + custom method - signatures required by gogoproto. - liabilities: - type: string - title: For recording - leverage: - type: string - title: For recording - leveraged_lp_amount: - type: string - position_health: - type: string - id: - type: string - format: uint64 - amm_pool_id: - type: string - format: uint64 - stop_loss_price: + signatures required by gogoproto. + liabilities: + type: string + title: For recording + leverage: + type: string + title: For recording + leveraged_lp_amount: + type: string + position_health: + type: string + id: + type: string + format: uint64 + amm_pool_id: + type: string + format: uint64 + stop_loss_price: + type: string + updated_leverage: type: string locked_lp_token: type: string @@ -41489,40 +41530,46 @@ paths: items: type: object properties: - address: - type: string - collateral: + position: type: object properties: - denom: - type: string - amount: + address: type: string - description: >- - Coin defines a token with a denomination and an amount. + collateral: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an + amount. - NOTE: The amount field is an Int which implements the - custom method + NOTE: The amount field is an Int which implements + the custom method - signatures required by gogoproto. - liabilities: - type: string - title: For recording - leverage: - type: string - title: For recording - leveraged_lp_amount: - type: string - position_health: - type: string - id: - type: string - format: uint64 - amm_pool_id: - type: string - format: uint64 - stop_loss_price: + signatures required by gogoproto. + liabilities: + type: string + title: For recording + leverage: + type: string + title: For recording + leveraged_lp_amount: + type: string + position_health: + type: string + id: + type: string + format: uint64 + amm_pool_id: + type: string + format: uint64 + stop_loss_price: + type: string + updated_leverage: type: string pagination: type: object @@ -41816,40 +41863,46 @@ paths: items: type: object properties: - address: - type: string - collateral: + position: type: object properties: - denom: - type: string - amount: + address: type: string - description: >- - Coin defines a token with a denomination and an amount. + collateral: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an + amount. - NOTE: The amount field is an Int which implements the - custom method + NOTE: The amount field is an Int which implements + the custom method - signatures required by gogoproto. - liabilities: - type: string - title: For recording - leverage: - type: string - title: For recording - leveraged_lp_amount: - type: string - position_health: - type: string - id: - type: string - format: uint64 - amm_pool_id: - type: string - format: uint64 - stop_loss_price: + signatures required by gogoproto. + liabilities: + type: string + title: For recording + leverage: + type: string + title: For recording + leveraged_lp_amount: + type: string + position_health: + type: string + id: + type: string + format: uint64 + amm_pool_id: + type: string + format: uint64 + stop_loss_price: + type: string + updated_leverage: type: string pagination: type: object @@ -87331,6 +87384,18 @@ definitions: method signatures required by gogoproto. + elys.leveragelp.AddPool: + type: object + properties: + amm_pool_id: + type: string + format: uint64 + enabled: + type: boolean + closed: + type: boolean + leverage_max: + type: string elys.leveragelp.IsWhitelistedResponse: type: object properties: @@ -87340,6 +87405,8 @@ definitions: type: boolean elys.leveragelp.MsgAddCollateralResponse: type: object + elys.leveragelp.MsgAddPoolResponse: + type: object elys.leveragelp.MsgClaimRewardsResponse: type: object elys.leveragelp.MsgClosePositionsResponse: @@ -87350,6 +87417,8 @@ definitions: type: object elys.leveragelp.MsgOpenResponse: type: object + elys.leveragelp.MsgRemovePoolResponse: + type: object elys.leveragelp.MsgUpdateParamsResponse: type: object elys.leveragelp.MsgUpdatePoolsResponse: @@ -87520,40 +87589,45 @@ definitions: position: type: object properties: - address: - type: string - collateral: + position: type: object properties: - denom: - type: string - amount: + address: type: string - description: >- - Coin defines a token with a denomination and an amount. + collateral: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an amount. - NOTE: The amount field is an Int which implements the custom - method + NOTE: The amount field is an Int which implements the custom + method - signatures required by gogoproto. - liabilities: - type: string - title: For recording - leverage: - type: string - title: For recording - leveraged_lp_amount: - type: string - position_health: - type: string - id: - type: string - format: uint64 - amm_pool_id: - type: string - format: uint64 - stop_loss_price: + signatures required by gogoproto. + liabilities: + type: string + title: For recording + leverage: + type: string + title: For recording + leveraged_lp_amount: + type: string + position_health: + type: string + id: + type: string + format: uint64 + amm_pool_id: + type: string + format: uint64 + stop_loss_price: + type: string + updated_leverage: type: string locked_lp_token: type: string @@ -87565,40 +87639,45 @@ definitions: items: type: object properties: - address: - type: string - collateral: + position: type: object properties: - denom: - type: string - amount: + address: type: string - description: >- - Coin defines a token with a denomination and an amount. + collateral: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an amount. - NOTE: The amount field is an Int which implements the custom - method + NOTE: The amount field is an Int which implements the custom + method - signatures required by gogoproto. - liabilities: - type: string - title: For recording - leverage: - type: string - title: For recording - leveraged_lp_amount: - type: string - position_health: - type: string - id: - type: string - format: uint64 - amm_pool_id: - type: string - format: uint64 - stop_loss_price: + signatures required by gogoproto. + liabilities: + type: string + title: For recording + leverage: + type: string + title: For recording + leveraged_lp_amount: + type: string + position_health: + type: string + id: + type: string + format: uint64 + amm_pool_id: + type: string + format: uint64 + stop_loss_price: + type: string + updated_leverage: type: string pagination: type: object @@ -87710,40 +87789,45 @@ definitions: items: type: object properties: - address: - type: string - collateral: + position: type: object properties: - denom: - type: string - amount: + address: type: string - description: >- - Coin defines a token with a denomination and an amount. + collateral: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an amount. - NOTE: The amount field is an Int which implements the custom - method + NOTE: The amount field is an Int which implements the custom + method - signatures required by gogoproto. - liabilities: - type: string - title: For recording - leverage: - type: string - title: For recording - leveraged_lp_amount: - type: string - position_health: - type: string - id: - type: string - format: uint64 - amm_pool_id: - type: string - format: uint64 - stop_loss_price: + signatures required by gogoproto. + liabilities: + type: string + title: For recording + leverage: + type: string + title: For recording + leveraged_lp_amount: + type: string + position_health: + type: string + id: + type: string + format: uint64 + amm_pool_id: + type: string + format: uint64 + stop_loss_price: + type: string + updated_leverage: type: string pagination: type: object @@ -87860,6 +87944,8 @@ definitions: NOTE: The amount field is an Int which implements the custom method signatures required by gogoproto. + elys.leveragelp.QueryGetAddResponse: + type: object elys.leveragelp.QueryGetPoolResponse: type: object properties: @@ -87893,6 +87979,49 @@ definitions: type: string borrow_fee: type: string + elys.leveragelp.QueryPosition: + type: object + properties: + position: + type: object + properties: + address: + type: string + collateral: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an amount. + + + NOTE: The amount field is an Int which implements the custom + method + + signatures required by gogoproto. + liabilities: + type: string + title: For recording + leverage: + type: string + title: For recording + leveraged_lp_amount: + type: string + position_health: + type: string + id: + type: string + format: uint64 + amm_pool_id: + type: string + format: uint64 + stop_loss_price: + type: string + updated_leverage: + type: string elys.leveragelp.QueryRewardsResponse: type: object properties: diff --git a/proto/elys/leveragelp/query.proto b/proto/elys/leveragelp/query.proto index 51a034bff..814b21e3e 100644 --- a/proto/elys/leveragelp/query.proto +++ b/proto/elys/leveragelp/query.proto @@ -8,7 +8,6 @@ import "cosmos/base/v1beta1/coin.proto"; import "cosmos/base/query/v1beta1/pagination.proto"; import "elys/leveragelp/params.proto"; import "elys/leveragelp/types.proto"; - import "elys/leveragelp/pool.proto"; option go_package = "github.com/elys-network/elys/x/leveragelp/types"; @@ -119,7 +118,7 @@ message PositionsRequest { } message PositionsResponse { - repeated QueryPosition positions = 1; + repeated QueryPosition positions = 1; cosmos.base.query.v1beta1.PageResponse pagination = 2; } @@ -129,7 +128,7 @@ message PositionsByPoolRequest { } message PositionsByPoolResponse { - repeated QueryPosition positions = 1; + repeated QueryPosition positions = 1; cosmos.base.query.v1beta1.PageResponse pagination = 2; } @@ -187,7 +186,7 @@ message QueryAllPoolResponse { message PositionResponse { QueryPosition position = 1; - string locked_lp_token = 2 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false]; + string locked_lp_token = 2 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false]; } message QueryLiquidationPriceRequest { @@ -244,13 +243,12 @@ message QueryCommittedTokensLockedRequest { } message QueryCommittedTokensLockedResponse { - string address = 1; + string address = 1; repeated cosmos.base.v1beta1.Coin locked_committed = 2 [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; repeated cosmos.base.v1beta1.Coin total_committed = 3 [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; } message QueryPosition { - Position position = 1; + Position position = 1; string updated_leverage = 2 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false]; } - diff --git a/x/leveragelp/keeper/begin_blocker.go b/x/leveragelp/keeper/begin_blocker.go index 2a77ee638..f38601b12 100644 --- a/x/leveragelp/keeper/begin_blocker.go +++ b/x/leveragelp/keeper/begin_blocker.go @@ -74,8 +74,11 @@ func (k Keeper) LiquidatePositionIfUnhealthy(ctx sdk.Context, position *types.Po params := k.GetParams(ctx) isHealthy = position.PositionHealth.GT(params.SafetyFactor) - if isHealthy { - return isHealthy, false, h, err + + debt := k.stableKeeper.UpdateInterestAndGetDebt(ctx, position.GetPositionAddress()) + liab := debt.GetTotalLiablities() + if isHealthy || liab.IsZero() { + return true, false, h, err } repayAmount, err := k.ForceCloseLong(ctx, *position, pool, position.LeveragedLpAmount) diff --git a/x/leveragelp/keeper/msg_server_open.go b/x/leveragelp/keeper/msg_server_open.go index 176fff5c1..bd6018676 100644 --- a/x/leveragelp/keeper/msg_server_open.go +++ b/x/leveragelp/keeper/msg_server_open.go @@ -20,7 +20,10 @@ func (k Keeper) Open(ctx sdk.Context, msg *types.MsgOpen) (*types.MsgOpenRespons } // Check if it is the same direction position for the same trader. - if position := k.CheckSamePosition(ctx, msg); position != nil { + if position, err := k.CheckSamePosition(ctx, msg); position != nil { + if err != nil { + return nil, err + } response, err := k.OpenConsolidate(ctx, position, msg) if err != nil { return nil, err diff --git a/x/leveragelp/keeper/position.go b/x/leveragelp/keeper/position.go index 30b03547e..5d7921fe8 100644 --- a/x/leveragelp/keeper/position.go +++ b/x/leveragelp/keeper/position.go @@ -258,7 +258,7 @@ func (k Keeper) GetPositionHealth(ctx sdk.Context, position types.Position) (sdk baseCurrency, found := k.assetProfileKeeper.GetUsdcDenom(ctx) if !found { - return sdk.Dec{}, errorsmod.Wrapf(assetprofiletypes.ErrAssetProfileNotFound, "asset %s not found", ptypes.BaseCurrency) + return sdk.ZeroDec(), errorsmod.Wrapf(assetprofiletypes.ErrAssetProfileNotFound, "asset %s not found", ptypes.BaseCurrency) } leveragedLpAmount := sdk.ZeroInt() @@ -270,7 +270,7 @@ func (k Keeper) GetPositionHealth(ctx sdk.Context, position types.Position) (sdk exitCoinsAfterFee, _, err := k.amm.ExitPoolEst(ctx, position.GetAmmPoolId(), leveragedLpAmount, baseCurrency) if err != nil { - return sdk.Dec{}, err + return sdk.ZeroDec(), err } exitAmountAfterFee := exitCoinsAfterFee.AmountOf(baseCurrency) @@ -322,3 +322,66 @@ func (k Keeper) DeleteLegacyPosition(ctx sdk.Context, positionAddress string, id store.Delete(key) return nil } + +func (k Keeper) MigrateData(ctx sdk.Context) { + iterator := k.GetPositionIterator(ctx) + defer func(iterator sdk.Iterator) { + err := iterator.Close() + if err != nil { + panic(err) + } + }(iterator) + + for ; iterator.Valid(); iterator.Next() { + var position types.Position + bytesValue := iterator.Value() + err := k.cdc.Unmarshal(bytesValue, &position) + if err == nil { + leveragedLpAmount := sdk.ZeroInt() + commitments := k.commKeeper.GetCommitments(ctx, position.GetPositionAddress()) + + for _, commitment := range commitments.CommittedTokens { + leveragedLpAmount = leveragedLpAmount.Add(commitment.Amount) + } + pool, found := k.GetPool(ctx, position.AmmPoolId) + if found { + pool.LeveragedLpAmount = pool.LeveragedLpAmount.Add(leveragedLpAmount) + pool.Health = k.CalculatePoolHealth(ctx, &pool) + k.SetPool(ctx, pool) + } + + // Repay any balance, delete position + debt := k.stableKeeper.UpdateInterestAndGetDebt(ctx, position.GetPositionAddress()) + repayAmount := debt.GetTotalLiablities() + + // Check if position has enough coins to repay else repay partial + bal := k.bankKeeper.GetBalance(ctx, position.GetPositionAddress(), position.Collateral.Denom) + userAmount := sdk.ZeroInt() + if bal.Amount.LT(repayAmount) { + repayAmount = bal.Amount + } else { + userAmount = bal.Amount.Sub(repayAmount) + } + + if repayAmount.IsPositive() { + k.stableKeeper.Repay(ctx, position.GetPositionAddress(), sdk.NewCoin(position.Collateral.Denom, repayAmount)) + } else { + userAmount = bal.Amount + } + + positionOwner := sdk.MustAccAddressFromBech32(position.Address) + if userAmount.IsPositive() { + k.bankKeeper.SendCoins(ctx, position.GetPositionAddress(), positionOwner, sdk.Coins{sdk.NewCoin(position.Collateral.Denom, userAmount)}) + } + + if leveragedLpAmount.IsZero() { + // Repay any balance, delete position + k.DestroyPosition(ctx, positionOwner, position.Id) + } else { + // Repay any balance and update position value + position.LeveragedLpAmount = leveragedLpAmount + k.SetPosition(ctx, &position) + } + } + } +} diff --git a/x/leveragelp/keeper/position_close.go b/x/leveragelp/keeper/position_close.go index 42d7486af..71c5ea46a 100644 --- a/x/leveragelp/keeper/position_close.go +++ b/x/leveragelp/keeper/position_close.go @@ -36,9 +36,13 @@ func (k Keeper) ForceCloseLong(ctx sdk.Context, position types.Position, pool ty userAmount = exitCoinsAfterExitFee[0].Amount.Sub(repayAmount) } - err = k.stableKeeper.Repay(ctx, position.GetPositionAddress(), sdk.NewCoin(position.Collateral.Denom, repayAmount)) - if err != nil { - return sdk.ZeroInt(), err + if repayAmount.IsPositive() { + err = k.stableKeeper.Repay(ctx, position.GetPositionAddress(), sdk.NewCoin(position.Collateral.Denom, repayAmount)) + if err != nil { + return sdk.ZeroInt(), err + } + } else { + userAmount = bal.Amount } positionOwner := sdk.MustAccAddressFromBech32(position.Address) @@ -65,10 +69,8 @@ func (k Keeper) ForceCloseLong(ctx sdk.Context, position types.Position, pool ty // Update leveragedLpAmount position.LeveragedLpAmount = position.LeveragedLpAmount.Sub(lpAmount) if position.LeveragedLpAmount.IsZero() { - err = k.masterchefKeeper.ClaimRewards(ctx, position.GetPositionAddress(), []uint64{position.AmmPoolId}, positionOwner) - if err != nil { - return sdk.ZeroInt(), err - } + // As we have already exited the pool, we need to delete the position + k.masterchefKeeper.ClaimRewards(ctx, position.GetPositionAddress(), []uint64{position.AmmPoolId}, positionOwner) err = k.DestroyPosition(ctx, positionOwner, position.Id) if err != nil { return sdk.ZeroInt(), err @@ -76,10 +78,9 @@ func (k Keeper) ForceCloseLong(ctx sdk.Context, position types.Position, pool ty } else { // Update position health positionHealth, err := k.GetPositionHealth(ctx, position) - if err != nil { - return sdk.ZeroInt(), err + if err == nil { + position.PositionHealth = positionHealth } - position.PositionHealth = positionHealth // Update Liabilities debt = k.stableKeeper.UpdateInterestAndGetDebt(ctx, position.GetPositionAddress()) diff --git a/x/leveragelp/keeper/query_position.go b/x/leveragelp/keeper/query_position.go index 4564f4c7e..17199d61d 100644 --- a/x/leveragelp/keeper/query_position.go +++ b/x/leveragelp/keeper/query_position.go @@ -2,7 +2,7 @@ package keeper import ( "context" - + sdk "github.com/cosmos/cosmos-sdk/types" ammtypes "github.com/elys-network/elys/x/amm/types" "github.com/elys-network/elys/x/leveragelp/types" @@ -24,8 +24,8 @@ func (k Keeper) Position(goCtx context.Context, req *types.PositionRequest) (*ty var positions = []*types.Position{} positions = append(positions, &position) - - updatedLeveragePosition, err := k.GetLeverageLpUpdatedLeverage(ctx, positions) + + updatedLeveragePosition, err := k.GetLeverageLpUpdatedLeverage(ctx, positions) if err != nil { return nil, err } diff --git a/x/leveragelp/keeper/utils.go b/x/leveragelp/keeper/utils.go index 5c126f123..3e2530f78 100644 --- a/x/leveragelp/keeper/utils.go +++ b/x/leveragelp/keeper/utils.go @@ -20,18 +20,18 @@ func (k Keeper) CheckUserAuthorization(ctx sdk.Context, msg *types.MsgOpen) erro return nil } -func (k Keeper) CheckSamePosition(ctx sdk.Context, msg *types.MsgOpen) *types.Position { +func (k Keeper) CheckSamePosition(ctx sdk.Context, msg *types.MsgOpen) (*types.Position, error) { positions, _, err := k.GetPositionsForAddress(ctx, sdk.MustAccAddressFromBech32(msg.Creator), &query.PageRequest{}) if err != nil { - return nil + return nil, err } for _, position := range positions { if position.Position.AmmPoolId == msg.AmmPoolId && position.Position.Collateral.Denom == msg.CollateralAsset { - return position.Position + return position.Position, nil } } - return nil + return nil, nil } func (k Keeper) CheckPoolHealth(ctx sdk.Context, poolId uint64) error { diff --git a/x/leveragelp/keeper/utils_test.go b/x/leveragelp/keeper/utils_test.go index f40b7c11a..afc544d75 100644 --- a/x/leveragelp/keeper/utils_test.go +++ b/x/leveragelp/keeper/utils_test.go @@ -52,7 +52,7 @@ func (suite KeeperTestSuite) TestCheckSameAssets() { } // Expect no error - position = k.CheckSamePosition(suite.ctx, msg) + position, _ = k.CheckSamePosition(suite.ctx, msg) suite.Require().NotNil(position) } diff --git a/x/leveragelp/migrations/v10_migration.go b/x/leveragelp/migrations/v10_migration.go new file mode 100644 index 000000000..258b5a524 --- /dev/null +++ b/x/leveragelp/migrations/v10_migration.go @@ -0,0 +1,17 @@ +package migrations + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (m Migrator) V10Migration(ctx sdk.Context) error { + pools := m.keeper.GetAllPools(ctx) + // Reset pools + for _, pool := range pools { + pool.LeveragedLpAmount = sdk.NewInt(0) + m.keeper.SetPool(ctx, pool) + } + + m.keeper.MigrateData(ctx) + return nil +} diff --git a/x/leveragelp/module.go b/x/leveragelp/module.go index ae3a7e76e..3bda7c00e 100644 --- a/x/leveragelp/module.go +++ b/x/leveragelp/module.go @@ -117,7 +117,7 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) types.RegisterQueryServer(cfg.QueryServer(), am.keeper) m := migrations.NewMigrator(am.keeper) - err := cfg.RegisterMigration(types.ModuleName, 8, m.V9Migration) + err := cfg.RegisterMigration(types.ModuleName, 9, m.V10Migration) if err != nil { panic(err) } @@ -144,7 +144,7 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw } // ConsensusVersion is a sequence number for state-breaking change of the module. It should be incremented on each consensus-breaking change introduced by the module. To avoid wrong/empty versions, the initial version should be set to 1 -func (AppModule) ConsensusVersion() uint64 { return 9 } +func (AppModule) ConsensusVersion() uint64 { return 10 } // BeginBlock contains the logic that is automatically triggered at the beginning of each block func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { diff --git a/x/leveragelp/types/params.go b/x/leveragelp/types/params.go index c7410c913..6c594dcce 100644 --- a/x/leveragelp/types/params.go +++ b/x/leveragelp/types/params.go @@ -39,7 +39,7 @@ func NewParams() Params { SafetyFactor: sdk.NewDecWithPrec(11, 1), // 1.1 WhitelistingEnabled: false, FallbackEnabled: true, - NumberPerBlock: 1000, + NumberPerBlock: (int64)(1000), } }