From 33624ed8cfa4b8bd16ab027c0980555f138487a2 Mon Sep 17 00:00:00 2001 From: Adam Moser <63419657+toteki@users.noreply.github.com> Date: Mon, 2 Oct 2023 20:19:21 -0600 Subject: [PATCH] feat: recent price mode support (#2267) * price mode support * cl# * fix tests * change query behavior * update test * add emulated price outage to mock oracle * test++ * test++ * adjust price behavior for account summary query * message behavior tests * lint * comment * market summary uses price mdoe query * fix oracle behavior (stop clearing prices) * tests++ * tests++ * warning++ --- CHANGELOG.md | 7 +- proto/umee/leverage/v1/query.proto | 15 ++- swagger/swagger.yaml | 147 ++++++++++++++++++++-- x/leverage/client/tests/tests.go | 3 +- x/leverage/keeper/errors.go | 1 + x/leverage/keeper/grpc_query.go | 43 ++++--- x/leverage/keeper/grpc_query_test.go | 83 ++++++++++--- x/leverage/keeper/msg_server_test.go | 151 ++++++++++++++++++++++- x/leverage/keeper/oracle.go | 31 +++-- x/leverage/keeper/oracle_test.go | 15 ++- x/leverage/keeper/suite_test.go | 7 ++ x/leverage/simulation/operations_test.go | 7 +- x/leverage/types/errors.go | 1 + x/leverage/types/oracle.go | 36 +++++- x/leverage/types/oracle_test.go | 58 +++++++++ x/leverage/types/query.pb.go | 151 ++++++++++++----------- x/oracle/abci.go | 2 - x/oracle/abci_test.go | 45 ++++--- 18 files changed, 642 insertions(+), 161 deletions(-) create mode 100644 x/leverage/types/oracle_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 11e477c7bc..40642aed51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,10 +46,15 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## v6.1.0-beta1 - 2023-09-29 +### API-Breaking + +- [2267](https://github.com/umee-network/umee/pull/2267) `BorrowLimit` field in QueryAccountSummaryResponse can be nil on missing borrow price (behavior now matches `LiquidationThreshold` field) + ### Improvements - [2261](https://github.com/umee-network/umee/pull/2261) Use go 1.21 -- [2263](https://github.com/umee-network/umee/pull/2263) Add spot price fields to account summary, and ensure all other fields use leverage logic prices. +- [2267](https://github.com/umee-network/umee/pull/2267) Leverage transactions accept spot prices up to 3 minutes old, and leverage queries use most recent spot price when required. +- [2263](https://github.com/umee-network/umee/pull/2263) Add spot price fields to account summary. - [2270](https://github.com/umee-network/umee/pull/2270) Increase free oracle tx limit to 200k gas. ### Features diff --git a/proto/umee/leverage/v1/query.proto b/proto/umee/leverage/v1/query.proto index 22c8782cb1..128f10aae1 100644 --- a/proto/umee/leverage/v1/query.proto +++ b/proto/umee/leverage/v1/query.proto @@ -286,28 +286,31 @@ message QueryAccountSummaryResponse { // Borrow Limit is the maximum Borrowed Value the account is allowed to reach through direct borrowing. // The lower of spot or historic price for each collateral token is used when calculating borrow limits. // Computation skips collateral which is missing an oracle price, potentially resulting in a lower borrow - // limit than if prices were all available. + // limit than if prices were all available. Will be null if an oracle price required for computation is + // missing. string borrow_limit = 4 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", - (gogoproto.nullable) = false + (gogoproto.nullable) = true ]; // Liquidation Threshold is the Borrowed Value at which the account becomes eligible for liquidation. - // Will be null if an oracle price required for computation is missing. + // Computation skips borrows which are missing an oracle price, potentially resulting in a lower borrow + // limit than if prices were all available. Will be null if an oracle price required for computation is + // missing. string liquidation_threshold = 5 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = true ]; - // Spot Supplied Value is supplied value but always uses spot prices. + // Spot Supplied Value is supplied value but always uses the most recent available spot prices. string spot_supplied_value = 6 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false ]; - // Spot Collateral Value is collateral value but always uses spot prices. + // Spot Collateral Value is collateral value but always uses the most recent available spot prices. string spot_collateral_value = 7 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false ]; - // Spot Borrowed Value is borrowed value but always uses spot prices. + // Spot Borrowed Value is borrowed value but always uses the most recent available spot prices. string spot_borrowed_value = 8 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false diff --git a/swagger/swagger.yaml b/swagger/swagger.yaml index 2bf7926026..6a7b98a30b 100644 --- a/swagger/swagger.yaml +++ b/swagger/swagger.yaml @@ -175,30 +175,38 @@ paths: Computation skips collateral which is missing an oracle price, potentially resulting in a lower borrow - limit than if prices were all available. + limit than if prices were all available. Will be null if an + oracle price required for computation is + + missing. liquidation_threshold: type: string description: >- Liquidation Threshold is the Borrowed Value at which the account becomes eligible for liquidation. - Will be null if an oracle price required for computation is + Computation skips borrows which are missing an oracle price, + potentially resulting in a lower borrow + + limit than if prices were all available. Will be null if an + oracle price required for computation is + missing. spot_supplied_value: type: string description: >- - Spot Supplied Value is supplied value but always uses spot - prices. + Spot Supplied Value is supplied value but always uses the most + recent available spot prices. spot_collateral_value: type: string description: >- - Spot Collateral Value is collateral value but always uses spot - prices. + Spot Collateral Value is collateral value but always uses the + most recent available spot prices. spot_borrowed_value: type: string description: >- - Spot Borrowed Value is borrowed value but always uses spot - prices. + Spot Borrowed Value is borrowed value but always uses the most + recent available spot prices. description: >- QueryAccountSummaryResponse defines the response structure for the AccountSummary gRPC service handler. @@ -1809,6 +1817,71 @@ paths: type: string tags: - Query + /umee/oracle/v1/denoms/exg_rates_timestamp: + get: + summary: >- + ExgRatesWithTimestamp returns exchange rates of all denoms with + timestamp, + + or, if specified, returns a single denom + operationId: ExgRatesWithTimestamp + responses: + '200': + description: A successful response. + schema: + type: object + properties: + exg_rates: + type: array + items: + type: object + properties: + denom: + type: string + rate: + type: string + timestamp: + type: string + format: date-time + title: DenomExchangeRate stores exchange rate with timestamp + title: >- + exchange_rates defines a list of the exchange rate for all + whitelisted + + denoms with timestamp + description: |- + QueryExgRatesWithTimestampResponse is response type for the + Query/ExchangeRatesWithTimestamp RPC method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + parameters: + - name: denom + description: denom defines the denomination to query for. + in: query + required: false + type: string + tags: + - Query /umee/oracle/v1/params: get: summary: Params queries all parameters. @@ -4757,23 +4830,38 @@ definitions: Computation skips collateral which is missing an oracle price, potentially resulting in a lower borrow - limit than if prices were all available. + limit than if prices were all available. Will be null if an oracle + price required for computation is + + missing. liquidation_threshold: type: string description: >- Liquidation Threshold is the Borrowed Value at which the account becomes eligible for liquidation. - Will be null if an oracle price required for computation is missing. + Computation skips borrows which are missing an oracle price, + potentially resulting in a lower borrow + + limit than if prices were all available. Will be null if an oracle + price required for computation is + + missing. spot_supplied_value: type: string - description: Spot Supplied Value is supplied value but always uses spot prices. + description: >- + Spot Supplied Value is supplied value but always uses the most recent + available spot prices. spot_collateral_value: type: string - description: Spot Collateral Value is collateral value but always uses spot prices. + description: >- + Spot Collateral Value is collateral value but always uses the most + recent available spot prices. spot_borrowed_value: type: string - description: Spot Borrowed Value is borrowed value but always uses spot prices. + description: >- + Spot Borrowed Value is borrowed value but always uses the most recent + available spot prices. description: >- QueryAccountSummaryResponse defines the response structure for the AccountSummary gRPC service handler. @@ -5897,6 +5985,17 @@ definitions: type: integer format: int64 title: Denom - the object to hold configurations of each denom + umee.oracle.v1.DenomExchangeRate: + type: object + properties: + denom: + type: string + rate: + type: string + timestamp: + type: string + format: date-time + title: DenomExchangeRate stores exchange rate with timestamp umee.oracle.v1.ExchangeRateTuple: type: object properties: @@ -6152,6 +6251,28 @@ definitions: description: |- QueryExchangeRatesResponse is response type for the Query/ExchangeRates RPC method. + umee.oracle.v1.QueryExgRatesWithTimestampResponse: + type: object + properties: + exg_rates: + type: array + items: + type: object + properties: + denom: + type: string + rate: + type: string + timestamp: + type: string + format: date-time + title: DenomExchangeRate stores exchange rate with timestamp + title: |- + exchange_rates defines a list of the exchange rate for all whitelisted + denoms with timestamp + description: |- + QueryExgRatesWithTimestampResponse is response type for the + Query/ExchangeRatesWithTimestamp RPC method. umee.oracle.v1.QueryFeederDelegationResponse: type: object properties: diff --git a/x/leverage/client/tests/tests.go b/x/leverage/client/tests/tests.go index b44ad6fa6c..1420923060 100644 --- a/x/leverage/client/tests/tests.go +++ b/x/leverage/client/tests/tests.go @@ -294,6 +294,7 @@ func (s *IntegrationTests) TestLeverageScenario() { } lt1 := sdk.MustNewDecFromStr("0.0089034946") + bl1 := sdk.MustNewDecFromStr("0.0085610525") nonzeroQueries := []itestsuite.TestQuery{ { @@ -337,7 +338,7 @@ func (s *IntegrationTests) TestLeverageScenario() { BorrowedValue: sdk.MustNewDecFromStr("0.00858671"), SpotBorrowedValue: sdk.MustNewDecFromStr("0.00858671"), // (1001 / 1000000) * 34.21 * 0.25 = 0.0085610525 - BorrowLimit: sdk.MustNewDecFromStr("0.0085610525"), + BorrowLimit: &bl1, // (1001 / 1000000) * 0.26 * 34.21 = 0.008903494600000000 LiquidationThreshold: <1, }, diff --git a/x/leverage/keeper/errors.go b/x/leverage/keeper/errors.go index 6a96a291de..0c7ced66aa 100644 --- a/x/leverage/keeper/errors.go +++ b/x/leverage/keeper/errors.go @@ -21,6 +21,7 @@ func nonOracleError(err error) bool { if errors.IsOf(err, leveragetypes.ErrInvalidOraclePrice, leveragetypes.ErrNoHistoricMedians, + leveragetypes.ErrExpiredOraclePrice, oracletypes.ErrUnknownDenom, ) { return false diff --git a/x/leverage/keeper/grpc_query.go b/x/leverage/keeper/grpc_query.go index 4ebdf9346e..6042f66c6f 100644 --- a/x/leverage/keeper/grpc_query.go +++ b/x/leverage/keeper/grpc_query.go @@ -163,8 +163,9 @@ func (q Querier) MarketSummary( AvailableCollateralize: availableCollateralize, } - // Oracle prices in response will be nil if it is unavailable - oraclePrice, _, oracleErr := q.Keeper.TokenPrice(ctx, req.Denom, types.PriceModeSpot) + // Oracle price in response will be nil if the oracle module has no price at all, but will instead + // show the most recent price if one existed. + oraclePrice, _, oracleErr := q.Keeper.TokenPrice(ctx, req.Denom, types.PriceModeQuery) if oracleErr == nil { resp.OraclePrice = &oraclePrice } else { @@ -237,32 +238,42 @@ func (q Querier) AccountSummary( collateral := q.Keeper.GetBorrowerCollateral(ctx, addr) borrowed := q.Keeper.GetBorrowerBorrows(ctx, addr) - // the following spot price calculations skip assets missing prices, but otherwise always - // use the most up to date prices - spotSuppliedValue, err := q.Keeper.VisibleTokenValue(ctx, supplied, types.PriceModeSpot) + // the following price calculations use the most recent prices if spot prices are missing + lastSuppliedValue, err := q.Keeper.VisibleTokenValue(ctx, supplied, types.PriceModeQuery) if err != nil { return nil, err } - spotBorrowedValue, err := q.Keeper.VisibleTokenValue(ctx, borrowed, types.PriceModeSpot) + lastBorrowedValue, err := q.Keeper.VisibleTokenValue(ctx, borrowed, types.PriceModeQuery) if err != nil { return nil, err } - spotCollateralValue, err := q.Keeper.VisibleCollateralValue(ctx, collateral, types.PriceModeSpot) + lastCollateralValue, err := q.Keeper.VisibleCollateralValue(ctx, collateral, types.PriceModeQuery) if err != nil { return nil, err } - // this supplied value uses leverage-like prices: the lower of spot or historic price for supplied tokens - suppliedValue, err := q.Keeper.VisibleTokenValue(ctx, supplied, types.PriceModeLow) + // these use leverage-like prices: the lower of spot or historic price for supplied tokens and higher for borrowed. + // unlike transactions, this query will use expired prices instead of skipping them. + suppliedValue, err := q.Keeper.VisibleTokenValue(ctx, supplied, types.PriceModeQueryLow) + if err != nil { + return nil, err + } + collateralValue, err := q.Keeper.VisibleCollateralValue(ctx, collateral, types.PriceModeQueryLow) + if err != nil { + return nil, err + } + borrowedValue, err := q.Keeper.VisibleTokenValue(ctx, borrowed, types.PriceModeQueryHigh) if err != nil { return nil, err } resp := &types.QueryAccountSummaryResponse{ SuppliedValue: suppliedValue, - SpotSuppliedValue: spotSuppliedValue, - SpotCollateralValue: spotCollateralValue, - SpotBorrowedValue: spotBorrowedValue, + CollateralValue: collateralValue, + BorrowedValue: borrowedValue, + SpotSuppliedValue: lastSuppliedValue, + SpotCollateralValue: lastCollateralValue, + SpotBorrowedValue: lastBorrowedValue, } // values computed from position use the same prices found in leverage logic: @@ -275,9 +286,9 @@ func (q Querier) AccountSummary( return nil, err } if err == nil { - resp.BorrowedValue = ap.BorrowedValue() - resp.CollateralValue = ap.CollateralValue() - resp.BorrowLimit = ap.Limit() + // on missing borrow price, borrow limit is nil + borrowLimit := ap.Limit() + resp.BorrowLimit = &borrowLimit } // liquidation threshold shown here as it is used in leverage logic: using spot prices. @@ -288,7 +299,7 @@ func (q Querier) AccountSummary( return nil, err } if err == nil { - // on an error here, simply skip setting the response field + // on missing collateral price, liquidation threshold is nil liquidationThreshold := ap.Limit() resp.LiquidationThreshold = &liquidationThreshold } diff --git a/x/leverage/keeper/grpc_query_test.go b/x/leverage/keeper/grpc_query_test.go index 3661dff18b..41057ba60c 100644 --- a/x/leverage/keeper/grpc_query_test.go +++ b/x/leverage/keeper/grpc_query_test.go @@ -25,7 +25,7 @@ func (s *IntegrationTestSuite) TestQuerier_RegisteredTokens() { "valid: get the all registered tokens", "", types.QueryRegisteredTokens{}, - 7, + leverage_initial_registry_length, }, { "valid: get the registered token info by base_denom", @@ -43,7 +43,7 @@ func (s *IntegrationTestSuite) TestQuerier_RegisteredTokens() { for _, tc := range tests { s.Run(tc.name, func() { - resp, err := s.queryClient.RegisteredTokens(ctx.Context(), &tc.req) + resp, err := s.queryClient.RegisteredTokens(ctx, &tc.req) if tc.errMsg == "" { assert.NilError(s.T(), err) assert.Equal(s.T(), tc.expectedTokens, len(resp.Registry)) @@ -57,7 +57,7 @@ func (s *IntegrationTestSuite) TestQuerier_RegisteredTokens() { func (s *IntegrationTestSuite) TestQuerier_Params() { ctx, require := s.ctx, s.Require() - resp, err := s.queryClient.Params(ctx.Context(), &types.QueryParams{}) + resp, err := s.queryClient.Params(ctx, &types.QueryParams{}) require.NoError(err) require.Equal(fixtures.Params(), resp.Params) } @@ -107,7 +107,7 @@ func (s *IntegrationTestSuite) TestQuerier_AccountBalances() { s.supply(addr, coin.New(umeeDenom, 1000)) s.collateralize(addr, coin.New("u/"+umeeDenom, 1000)) - resp, err := s.queryClient.AccountBalances(ctx.Context(), &types.QueryAccountBalances{Address: addr.String()}) + resp, err := s.queryClient.AccountBalances(ctx, &types.QueryAccountBalances{Address: addr.String()}) require.NoError(err) expected := types.QueryAccountBalancesResponse{ @@ -131,10 +131,11 @@ func (s *IntegrationTestSuite) TestQuerier_AccountSummary() { s.supply(addr, coin.New(umeeDenom, 1000_000000)) s.collateralize(addr, coin.New("u/"+umeeDenom, 1000_000000)) - resp, err := s.queryClient.AccountSummary(ctx.Context(), &types.QueryAccountSummary{Address: addr.String()}) + resp, err := s.queryClient.AccountSummary(ctx, &types.QueryAccountSummary{Address: addr.String()}) require.NoError(err) - lt := sdk.MustNewDecFromStr("1094.600000000000000000") + lt := sdk.MustNewDecFromStr("1094.6") + bl := sdk.MustNewDecFromStr("1052.5") expected := types.QueryAccountSummaryResponse{ // This result is umee's oracle exchange rate from // from .Reset() in x/leverage/keeper/oracle_test.go @@ -150,11 +151,57 @@ func (s *IntegrationTestSuite) TestQuerier_AccountSummary() { BorrowedValue: sdk.ZeroDec(), SpotBorrowedValue: sdk.ZeroDec(), // (1000) * 4.21 * 0.25 = 1052.5 - BorrowLimit: sdk.MustNewDecFromStr("1052.5"), + BorrowLimit: &bl, // (1000) * 4.21 * 0.26 = 1094.6 LiquidationThreshold: <, } + require.Equal(expected, *resp) + + // creates account which has supplied and collateralized 1000 OUTAGE and 500 PAIRED (these are $1, 6 exponent tokens) + addr = s.newAccount(coin.New(outageDenom, 1000_000000), coin.New(pairedDenom, 500_000000)) + s.supply(addr, coin.New(outageDenom, 1000_000000), coin.New(pairedDenom, 500_000000)) + s.collateralize(addr, coin.Utoken(outageDenom, 1000_000000), coin.Utoken(pairedDenom, 500_000000)) + + resp, err = s.queryClient.AccountSummary(ctx, &types.QueryAccountSummary{Address: addr.String()}) + require.NoError(err) + bl = sdk.MustNewDecFromStr("125") + expected = types.QueryAccountSummaryResponse{ + // Price outage should have no effect on value fields + SuppliedValue: sdk.MustNewDecFromStr("1500"), + SpotSuppliedValue: sdk.MustNewDecFromStr("1500"), + CollateralValue: sdk.MustNewDecFromStr("1500"), + SpotCollateralValue: sdk.MustNewDecFromStr("1500"), + // Nothing borrowed + BorrowedValue: sdk.ZeroDec(), + SpotBorrowedValue: sdk.ZeroDec(), + BorrowLimit: &bl, + LiquidationThreshold: nil, // missing collateral price: no threshold can be displayed + } + require.Equal(expected, *resp) + + // creates account which has supplied and collateralized 1000 OUTAGE and 500 PAIRED (these are $1, 6 exponent tokens) + addr = s.newAccount(coin.New(outageDenom, 1000_000000), coin.New(pairedDenom, 500_000000)) + s.supply(addr, coin.New(outageDenom, 1000_000000), coin.New(pairedDenom, 500_000000)) + s.collateralize(addr, coin.Utoken(outageDenom, 1000_000000), coin.Utoken(pairedDenom, 500_000000)) + // also borrow some PAIRED normally + s.borrow(addr, coin.New(pairedDenom, 100_000000)) + // and force-borrow (cannot normally because due to missing price) some OUTAGE + s.forceBorrow(addr, coin.New(outageDenom, 200_000000)) + resp, err = s.queryClient.AccountSummary(ctx, &types.QueryAccountSummary{Address: addr.String()}) + require.NoError(err) + expected = types.QueryAccountSummaryResponse{ + // Both prices should show up in spot fields and query fields. + SuppliedValue: sdk.MustNewDecFromStr("1500"), + SpotSuppliedValue: sdk.MustNewDecFromStr("1500"), + CollateralValue: sdk.MustNewDecFromStr("1500"), + SpotCollateralValue: sdk.MustNewDecFromStr("1500"), + // Borrowed 1/5 of collateral values + BorrowedValue: sdk.MustNewDecFromStr("300"), + SpotBorrowedValue: sdk.MustNewDecFromStr("300"), + BorrowLimit: nil, // missing borrow price: no borrow limit can be displayed + LiquidationThreshold: nil, // missing collateral price: no threshold can be displayed + } require.Equal(expected, *resp) } @@ -175,7 +222,7 @@ func (s *IntegrationTestSuite) TestQuerier_Inspect() { s.collateralize(addr3, coin.New("u/"+umeeDenom, 600_000000)) s.borrow(addr3, coin.New(umeeDenom, 15_000000)) - resp, err := s.queryClient.Inspect(ctx.Context(), &types.QueryInspect{}) + resp, err := s.queryClient.Inspect(ctx, &types.QueryInspect{}) require.NoError(err) expected := types.QueryInspectResponse{ @@ -224,7 +271,7 @@ func (s *IntegrationTestSuite) TestQuerier_Inspect() { func (s *IntegrationTestSuite) TestQuerier_LiquidationTargets() { ctx, require := s.ctx, s.Require() - resp, err := s.queryClient.LiquidationTargets(ctx.Context(), &types.QueryLiquidationTargets{}) + resp, err := s.queryClient.LiquidationTargets(ctx, &types.QueryLiquidationTargets{}) require.NoError(err) expected := types.QueryLiquidationTargetsResponse{ @@ -237,7 +284,7 @@ func (s *IntegrationTestSuite) TestQuerier_LiquidationTargets() { func (s *IntegrationTestSuite) TestQuerier_BadDebts() { ctx, require := s.ctx, s.Require() - resp, err := s.queryClient.BadDebts(ctx.Context(), &types.QueryBadDebts{}) + resp, err := s.queryClient.BadDebts(ctx, &types.QueryBadDebts{}) require.NoError(err) expected := types.QueryBadDebtsResponse{ @@ -255,7 +302,7 @@ func (s *IntegrationTestSuite) TestQuerier_MaxWithdraw() { s.supply(addr, coin.New(umeeDenom, 1000_000000)) s.collateralize(addr, coin.New("u/"+umeeDenom, 1000_000000)) - resp, err := s.queryClient.MaxWithdraw(ctx.Context(), &types.QueryMaxWithdraw{ + resp, err := s.queryClient.MaxWithdraw(ctx, &types.QueryMaxWithdraw{ Address: addr.String(), Denom: umeeDenom, }) @@ -268,7 +315,7 @@ func (s *IntegrationTestSuite) TestQuerier_MaxWithdraw() { require.Equal(expected, *resp) // Also test all-denoms - resp, err = s.queryClient.MaxWithdraw(ctx.Context(), &types.QueryMaxWithdraw{ + resp, err = s.queryClient.MaxWithdraw(ctx, &types.QueryMaxWithdraw{ Address: addr.String(), }) require.NoError(err) @@ -282,7 +329,7 @@ func (s *IntegrationTestSuite) TestQuerier_MaxWithdraw() { // borrow 100 UMEE for non-trivial query s.borrow(addr, coin.New(umeeDenom, 100_000000)) - resp, err = s.queryClient.MaxWithdraw(ctx.Context(), &types.QueryMaxWithdraw{ + resp, err = s.queryClient.MaxWithdraw(ctx, &types.QueryMaxWithdraw{ Address: addr.String(), Denom: umeeDenom, }) @@ -295,7 +342,7 @@ func (s *IntegrationTestSuite) TestQuerier_MaxWithdraw() { require.Equal(expected, *resp) // Also test all-denoms - resp, err = s.queryClient.MaxWithdraw(ctx.Context(), &types.QueryMaxWithdraw{ + resp, err = s.queryClient.MaxWithdraw(ctx, &types.QueryMaxWithdraw{ Address: addr.String(), }) require.NoError(err) @@ -315,7 +362,7 @@ func (s *IntegrationTestSuite) TestQuerier_MaxBorrow() { s.supply(addr, coin.New(umeeDenom, 1000_000000)) s.collateralize(addr, coin.New("u/"+umeeDenom, 1000_000000)) - resp, err := s.queryClient.MaxBorrow(ctx.Context(), &types.QueryMaxBorrow{ + resp, err := s.queryClient.MaxBorrow(ctx, &types.QueryMaxBorrow{ Address: addr.String(), Denom: umeeDenom, }) @@ -327,7 +374,7 @@ func (s *IntegrationTestSuite) TestQuerier_MaxBorrow() { require.Equal(expected, *resp) // Also test all-denoms - resp, err = s.queryClient.MaxBorrow(ctx.Context(), &types.QueryMaxBorrow{ + resp, err = s.queryClient.MaxBorrow(ctx, &types.QueryMaxBorrow{ Address: addr.String(), }) require.NoError(err) @@ -340,7 +387,7 @@ func (s *IntegrationTestSuite) TestQuerier_MaxBorrow() { // borrow 100 UMEE for non-trivial query s.borrow(addr, coin.New(umeeDenom, 100_000000)) - resp, err = s.queryClient.MaxBorrow(ctx.Context(), &types.QueryMaxBorrow{ + resp, err = s.queryClient.MaxBorrow(ctx, &types.QueryMaxBorrow{ Address: addr.String(), Denom: umeeDenom, }) @@ -352,7 +399,7 @@ func (s *IntegrationTestSuite) TestQuerier_MaxBorrow() { require.Equal(expected, *resp) // Also test all-denoms - resp, err = s.queryClient.MaxBorrow(ctx.Context(), &types.QueryMaxBorrow{ + resp, err = s.queryClient.MaxBorrow(ctx, &types.QueryMaxBorrow{ Address: addr.String(), }) require.NoError(err) diff --git a/x/leverage/keeper/msg_server_test.go b/x/leverage/keeper/msg_server_test.go index df9a8981fc..ee874ab73e 100644 --- a/x/leverage/keeper/msg_server_test.go +++ b/x/leverage/keeper/msg_server_test.go @@ -55,9 +55,9 @@ func (s *IntegrationTestSuite) TestAddTokensToRegistry() { }, }, "", - 8, + leverage_initial_registry_length + 1, // 1 new token added this case }, { - "regisering new token with existed symbol denom", + "registering new token with existing symbol denom", types.MsgGovUpdateRegistry{ Authority: govAccAddr, Description: "", @@ -66,7 +66,7 @@ func (s *IntegrationTestSuite) TestAddTokensToRegistry() { }, }, "", - 9, + leverage_initial_registry_length + 2, // 1 token added this case, and 1 from previous cases }, } @@ -173,7 +173,7 @@ func (s *IntegrationTestSuite) TestUpdateRegistry() { s.Require().NoError(err) // no tokens should have been deleted tokens := s.app.LeverageKeeper.GetAllRegisteredTokens(s.ctx) - s.Require().Len(tokens, 7) + s.Require().Len(tokens, leverage_initial_registry_length) token, err := s.app.LeverageKeeper.GetTokenSettings(s.ctx, "uumee") s.Require().NoError(err) @@ -340,6 +340,9 @@ func (s *IntegrationTestSuite) TestMsgSupply() { // create a supplier that will exceed token's default MaxSupply whale := s.newAccount(coin.New(umeeDenom, 1_000_000_000000)) + // create and fund a supplier with 100 tokens suffering from price outage + outageSupplier := s.newAccount(coin.New(outageDenom, 100_000000)) + tcs := []testCase{ { "unregistered denom", @@ -389,6 +392,12 @@ func (s *IntegrationTestSuite) TestMsgSupply() { coin.New(umeeDenom, 1_000_000_000000), sdk.Coin{}, types.ErrMaxSupply, + }, { + "valid outage supply", + outageSupplier, + coin.New(outageDenom, 80_000000), + coin.New("u/"+outageDenom, 80_000000), + nil, }, } @@ -489,6 +498,16 @@ func (s *IntegrationTestSuite) TestMsgWithdraw() { s.borrow(stableUmeeBorrower, coin.New(umeeDenom, 30_000000)) // UMEE and STABLE have the same price but different collateral weights + // create and fund a supplier with 100 tokens suffering from price outage + outageSupplier := s.newAccount(coin.New(outageDenom, 100_000000)) + s.supply(outageSupplier, coin.New(outageDenom, 100_000000)) + + // create and fund a looped borrower suffering from price outage + outageBorrower := s.newAccount(coin.New(outageDenom, 100_000000)) + s.supply(outageBorrower, coin.New(outageDenom, 100_000000)) + s.collateralize(outageBorrower, coin.New("u/"+outageDenom, 50_000000)) + s.forceBorrow(outageBorrower, coin.New(outageDenom, 20_000000)) + tcs := []struct { msg string addr sdk.AccAddress @@ -570,6 +589,30 @@ func (s *IntegrationTestSuite) TestMsgWithdraw() { sdk.NewCoins(coin.New("u/"+pumpDenom, 20_000000)), coin.New(pumpDenom, 20_000000), nil, + }, { + "acceptable withdrawal (outage supplier)", + outageSupplier, + coin.New("u/"+outageDenom, 100_000000), + sdk.NewCoins(coin.New("u/"+outageDenom, 100_000000)), + nil, + coin.New(outageDenom, 100_000000), + nil, + }, { + "can withdraw wallet uTokens (outage borrower)", + outageBorrower, + coin.New("u/"+outageDenom, 50_000000), + sdk.NewCoins(coin.New("u/"+outageDenom, 50_000000)), + nil, + coin.New(outageDenom, 50_000000), + nil, + }, { + "cannot withdraw collateral (outage borrower)", + outageBorrower, + coin.New("u/"+outageDenom, 1_000000), + nil, + nil, + sdk.Coin{}, + types.ErrExpiredOraclePrice, }, { "borrow limit (undercollateralized under historic prices but ok with current prices)", dumpborrower, @@ -720,6 +763,17 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { s.borrow(specialBorrower, coin.New(daiDenom, 400000_000000_000000)) // PAIRED and DAI have the same price, 0.25 collateral weight, but 0.5 special pair weight + // create and fund a supplier with 100 tokens suffering from price outage + outageSupplier := s.newAccount(coin.New(outageDenom, 100_000000)) + s.supply(outageSupplier, coin.New(outageDenom, 100_000000)) + s.collateralize(outageSupplier, coin.New("u/"+outageDenom, 50_000000)) + + // create and fund a looped borrower suffering from price outage, with some uToken balance + outageBorrower := s.newAccount(coin.New(outageDenom, 100_000000)) + s.supply(outageBorrower, coin.New(outageDenom, 100_000000)) + s.collateralize(outageBorrower, coin.New("u/"+outageDenom, 50_000000)) + s.forceBorrow(outageBorrower, coin.New(outageDenom, 10_000000)) + zeroUmee := coin.Zero(umeeDenom) zeroUUmee := coin.New("u/"+umeeDenom, 0) tcs := []struct { @@ -812,6 +866,24 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { coin.New(pairedDenom, 200000), nil, }, + { + "max withdraw with price outage (but no borrows)", + outageSupplier, + outageDenom, + coin.New("u/"+outageDenom, 100_000000), + coin.New("u/"+outageDenom, 50_000000), + coin.New(outageDenom, 100_000000), + nil, + }, + { + "max withdraw with price outage and borrow", + outageBorrower, + outageDenom, + coin.New("u/"+outageDenom, 50_000000), + coin.Zero("u/" + outageDenom), + coin.New(outageDenom, 50_000000), + nil, + }, } for _, tc := range tcs { @@ -1480,6 +1552,16 @@ func (s *IntegrationTestSuite) TestMsgBorrow() { s.borrow(specialBorrower, coin.New(daiDenom, 200000_000000_000000)) // PAIRED and DAI have the same price, 0.25 collateral weight, but 0.5 special pair weight + // create and fund a supplier with 100 tokens suffering from price outage + outageSupplier := s.newAccount(coin.New(outageDenom, 100_000000)) + s.supply(outageSupplier, coin.New(outageDenom, 100_000000)) + s.collateralize(outageSupplier, coin.New("u/"+outageDenom, 50_000000)) + + // create and fund a supplier with 100 tokens suffering from price outage, but also some atom collateral + atomOutageSupplier := s.newAccount(coin.New(outageDenom, 100_000000), coin.New(atomDenom, 100_000000)) + s.supply(atomOutageSupplier, coin.New(outageDenom, 100_000000), coin.New(atomDenom, 100_000000)) + s.collateralize(atomOutageSupplier, coin.New("u/"+outageDenom, 50_000000), coin.New("u/"+atomDenom, 50_000000)) + tcs := []testCase{ { "uToken", @@ -1566,6 +1648,21 @@ func (s *IntegrationTestSuite) TestMsgBorrow() { specialBorrower, coin.New(daiDenom, 50000_000000_000000), types.ErrUndercollateralized, + }, { + "price outage", + outageSupplier, + coin.New(outageDenom, 5_000000), + types.ErrExpiredOraclePrice, + }, { + "price outage, but sufficient priced collateral", + atomOutageSupplier, + coin.New(atomDenom, 5_000000), + nil, + }, { + "price outage, but sufficient priced collateral, but borrowing outage asset", + atomOutageSupplier, + coin.New(outageDenom, 5_000000), + types.ErrExpiredOraclePrice, }, } @@ -1654,6 +1751,22 @@ func (s *IntegrationTestSuite) TestMsgMaxBorrow() { s.borrow(stableUmeeBorrower, coin.New(umeeDenom, 30_000000)) // UMEE and STABLE have the same price but different collateral weights + // create and fund a supplier with 100 tokens suffering from price outage + outageSupplier := s.newAccount(coin.New(outageDenom, 100_000000)) + s.supply(outageSupplier, coin.New(outageDenom, 100_000000)) + s.collateralize(outageSupplier, coin.New("u/"+outageDenom, 50_000000)) + + // create and fund a supplier with 100 tokens suffering from price outage, but also some umee collateral + umeeOutageSupplier := s.newAccount(coin.New(outageDenom, 100_000000), coin.New(umeeDenom, 100_000000)) + s.supply(umeeOutageSupplier, coin.New(outageDenom, 100_000000), coin.New(umeeDenom, 100_000000)) + s.collateralize(umeeOutageSupplier, coin.New("u/"+outageDenom, 50_000000), coin.New("u/"+umeeDenom, 50_000000)) + + // create an umee supplier with an existing borrow of an asset with a price outage + outageBorrower := s.newAccount(coin.New(umeeDenom, 100_000000)) + s.supply(outageBorrower, coin.New(umeeDenom, 100_000000)) + s.collateralize(outageBorrower, coin.New("u/"+umeeDenom, 50_000000)) + s.forceBorrow(outageBorrower, coin.New(outageDenom, 1_000000)) + tcs := []struct { msg string addr sdk.AccAddress @@ -1700,6 +1813,36 @@ func (s *IntegrationTestSuite) TestMsgMaxBorrow() { stableUmeeBorrower, coin.New(umeeDenom, 20_000000), nil, + }, { + "outage supplier tries to borrow umee", + outageSupplier, + coin.Zero(umeeDenom), + nil, + }, { + "outage supplier tries to borrow outage asset", + outageSupplier, + coin.Zero(outageDenom), + nil, + }, { + "umee + outage supplier tries to borrow umee", + umeeOutageSupplier, + coin.New(umeeDenom, 12_500000), + nil, + }, { + "umee + outage supplier tries to borrow outage asset", + umeeOutageSupplier, + coin.Zero(outageDenom), + nil, + }, { + "outage borrower tries to borrow umee", + outageBorrower, + coin.Zero(umeeDenom), + nil, + }, { + "outage borrower tries to borrow outage asset", + outageBorrower, + coin.Zero(outageDenom), + nil, }, } diff --git a/x/leverage/keeper/oracle.go b/x/leverage/keeper/oracle.go index 7173c5cc43..2277e84851 100644 --- a/x/leverage/keeper/oracle.go +++ b/x/leverage/keeper/oracle.go @@ -12,6 +12,9 @@ import ( oracletypes "github.com/umee-network/umee/v6/x/oracle/types" ) +// TODO: parameterize this +const MaxSpotPriceAge = 180 // 180 seconds = 3 minutes + var ten = sdk.MustNewDecFromStr("10") // TokenPrice returns the USD value of a token's symbol denom, e.g. `UMEE` (rather than `uumee`). @@ -26,9 +29,10 @@ func (k Keeper) TokenPrice(ctx sdk.Context, baseDenom string, mode types.PriceMo return sdk.ZeroDec(), t.Exponent, types.ErrBlacklisted } - // if a token is exempt from historic pricing, all price modes return Spot price + // if a token is exempt from historic pricing, all price modes ignore historic prices + // and use spot prices instead, sometimes also allowing expired prices. if t.HistoricMedians == 0 { - mode = types.PriceModeSpot + mode = mode.IgnoreHistoric() } var price, historicPrice sdk.Dec @@ -39,9 +43,20 @@ func (k Keeper) TokenPrice(ctx sdk.Context, baseDenom string, mode types.PriceMo if err != nil { return sdk.ZeroDec(), t.Exponent, errors.Wrap(err, "oracle") } + if !mode.AllowsExpired() { + // with the exception of account summary queries, require spot prices to be recent + moduleTime := k.getLastInterestTime(ctx) + priceTime := spotPrice.Timestamp.Unix() + priceAge := moduleTime - priceTime + if priceAge < 0 || priceAge > MaxSpotPriceAge { + return sdk.ZeroDec(), t.Exponent, types.ErrExpiredOraclePrice.Wrapf( + "price: %d, module: %d", priceTime, moduleTime) + } + } } - if mode != types.PriceModeSpot { - // historic price is required for modes other than spot + + if mode != types.PriceModeSpot && mode != types.PriceModeQuery { + // historic price is required for modes other than spot and query var numStamps uint32 historicPrice, numStamps, err = k.oracleKeeper.MedianOfHistoricMedians( ctx, strings.ToUpper(t.SymbolDenom), uint64(t.HistoricMedians)) @@ -57,16 +72,14 @@ func (k Keeper) TokenPrice(ctx sdk.Context, baseDenom string, mode types.PriceMo } } - // TODO: need to use spotPrice.Timestamp to make a decision about the price - switch mode { - case types.PriceModeSpot: + case types.PriceModeSpot, types.PriceModeQuery: price = spotPrice.Rate case types.PriceModeHistoric: price = historicPrice - case types.PriceModeHigh: + case types.PriceModeHigh, types.PriceModeQueryHigh: price = sdk.MaxDec(spotPrice.Rate, historicPrice) - case types.PriceModeLow: + case types.PriceModeLow, types.PriceModeQueryLow: price = sdk.MinDec(spotPrice.Rate, historicPrice) default: return sdk.ZeroDec(), t.Exponent, types.ErrInvalidPriceMode.Wrapf("%d", mode) diff --git a/x/leverage/keeper/oracle_test.go b/x/leverage/keeper/oracle_test.go index 3cdcdd225b..7383f3b560 100644 --- a/x/leverage/keeper/oracle_test.go +++ b/x/leverage/keeper/oracle_test.go @@ -2,11 +2,13 @@ package keeper_test import ( "strings" + "time" sdk "github.com/cosmos/cosmos-sdk/types" appparams "github.com/umee-network/umee/v6/app/params" "github.com/umee-network/umee/v6/util/coin" + "github.com/umee-network/umee/v6/x/leverage/keeper" "github.com/umee-network/umee/v6/x/leverage/types" oracletypes "github.com/umee-network/umee/v6/x/oracle/types" ) @@ -43,15 +45,20 @@ func (m *mockOracleKeeper) MedianOfHistoricMedians(ctx sdk.Context, denom string return p, uint32(numStamps), nil } -func (m *mockOracleKeeper) GetExchangeRate(_ sdk.Context, denom string) (oracletypes.ExchangeRate, error) { +func (m *mockOracleKeeper) GetExchangeRate(ctx sdk.Context, denom string) (oracletypes.ExchangeRate, error) { p, ok := m.symbolExchangeRates[denom] if !ok { // This error matches oracle behavior on missing asset price return oracletypes.ExchangeRate{}, oracletypes.ErrUnknownDenom.Wrap(denom) } - // TODO: add timestamp - return oracletypes.ExchangeRate{Rate: p}, nil + // Emulates completely up to date prices + t := ctx.BlockTime() + if denom == "OUTAGE" { + // except for one denom, whose most recent price is twice as old as leverage logic allows + t = t.Add(-2 * time.Second * keeper.MaxSpotPriceAge) + } + return oracletypes.ExchangeRate{Rate: p, Timestamp: t}, nil } // Clear clears a denom from the mock oracle, simulating an outage. @@ -70,6 +77,7 @@ func (m *mockOracleKeeper) Reset() { "PUMP": sdk.MustNewDecFromStr("2.00"), // A token which has recently doubled in price "STABLE": sdk.MustNewDecFromStr("4.21"), // Same price as umee "PAIRED": sdk.MustNewDecFromStr("1.00"), + "OUTAGE": sdk.MustNewDecFromStr("1.00"), } m.historicExchangeRates = map[string]sdk.Dec{ "UMEE": sdk.MustNewDecFromStr("4.21"), @@ -79,6 +87,7 @@ func (m *mockOracleKeeper) Reset() { "PUMP": sdk.MustNewDecFromStr("1.00"), "STABLE": sdk.MustNewDecFromStr("4.21"), "PAIRED": sdk.MustNewDecFromStr("1.00"), + "OUTAGE": sdk.MustNewDecFromStr("1.00"), } } diff --git a/x/leverage/keeper/suite_test.go b/x/leverage/keeper/suite_test.go index 41f2c19e99..cd34c9bf40 100644 --- a/x/leverage/keeper/suite_test.go +++ b/x/leverage/keeper/suite_test.go @@ -29,8 +29,11 @@ const ( dumpDenom = "udump" stableDenom = "stable" pairedDenom = "upaired" + outageDenom = "uoutage" ) +var leverage_initial_registry_length = 0 + type IntegrationTestSuite struct { suite.Suite @@ -87,12 +90,16 @@ func (s *IntegrationTestSuite) SetupTest() { require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, newToken(pumpDenom, "PUMP", 6))) // additional token for special pairs require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, newToken(pairedDenom, "PAIRED", 6))) + require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, newToken(outageDenom, "OUTAGE", 6))) // additional tokens for borrow factor testing stable := newToken(stableDenom, "STABLE", 6) stable.CollateralWeight = sdk.MustNewDecFromStr("0.8") stable.LiquidationThreshold = sdk.MustNewDecFromStr("0.9") require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, stable)) + // set the initial token registry length used in update registry tests + leverage_initial_registry_length = len(app.LeverageKeeper.GetAllRegisteredTokens(ctx)) + // override DefaultGenesis params with some special asset pairs app.LeverageKeeper.SetSpecialAssetPair(ctx, types.SpecialAssetPair{ diff --git a/x/leverage/simulation/operations_test.go b/x/leverage/simulation/operations_test.go index dd7d631f92..6d9b0254e4 100644 --- a/x/leverage/simulation/operations_test.go +++ b/x/leverage/simulation/operations_test.go @@ -33,8 +33,11 @@ type SimTestSuite struct { func (s *SimTestSuite) SetupTest() { checkTx := false app := umeeapp.Setup(s.T()) - ctx := app.NewContext(checkTx, tmproto.Header{Time: time.Now()}) - leverage.InitGenesis(ctx, app.LeverageKeeper, *types.DefaultGenesis()) + t := time.Now() + ctx := app.NewContext(checkTx, tmproto.Header{Time: t}) + genesis := *types.DefaultGenesis() + genesis.LastInterestTime = t.Unix() + leverage.InitGenesis(ctx, app.LeverageKeeper, genesis) // Use default umee token for sim tests s.Require().NoError(app.LeverageKeeper.SetTokenSettings(ctx, fixtures.Token("uumee", "UMEE", 6))) diff --git a/x/leverage/types/errors.go b/x/leverage/types/errors.go index 01d2ee0595..39dc32ca82 100644 --- a/x/leverage/types/errors.go +++ b/x/leverage/types/errors.go @@ -40,6 +40,7 @@ var ( ErrUndercollateralized = errors.Register(ModuleName, 402, "borrow positions are undercollateralized") ErrLiquidationIneligible = errors.Register(ModuleName, 403, "borrower not eligible for liquidation") ErrNoHistoricMedians = errors.Register(ModuleName, 405, "insufficient historic medians available") + ErrExpiredOraclePrice = errors.Register(ModuleName, 406, "no recent oracle price") // 5XX = Market Conditions ErrLendingPoolInsufficient = errors.Register(ModuleName, 500, "lending pool insufficient") diff --git a/x/leverage/types/oracle.go b/x/leverage/types/oracle.go index afed86226c..d9b9f25b55 100644 --- a/x/leverage/types/oracle.go +++ b/x/leverage/types/oracle.go @@ -4,12 +4,46 @@ package types type PriceMode uint64 const ( - // Spot mode requests the most recent prices from oracle + // Spot mode requests the most recent prices from oracle, unless they are too old. PriceModeSpot PriceMode = iota + // Query mode requests the most recent price, regardless of price age. + // These potentially expired prices should only be used for queries, not transactions. + PriceModeQuery // Historic mode requests the median of the most recent historic medians PriceModeHistoric // High mode uses the higher of either Spot or Historic prices PriceModeHigh + // QueryHigh mode uses the higher of either Spot or Historic prices, allowing expired spot prices. + // These potentially expired prices should only be used for queries, not transactions. + PriceModeQueryHigh // Low mode uses the lower of either Spot or Historic prices PriceModeLow + // QueryLow mode uses the lower of either Spot or Historic prices, allowing expired spot prices. + // These potentially expired prices should only be used for queries, not transactions. + PriceModeQueryLow ) + +// IgnoreHistoric transforms a price mode in a way that uses spot prices instead of historic. This +// happens when historic prices would normally be used by a token has zero required historic medians. +func (mode PriceMode) IgnoreHistoric() PriceMode { + switch mode { + case PriceModeHigh, PriceModeLow, PriceModeHistoric: + // historic modes default to spot prices + return PriceModeSpot + case PriceModeQueryHigh, PriceModeQueryLow: + // historic-enabled queries allow expired prices as well + return PriceModeQuery + } + // other modes are unmodified + return mode +} + +// AllowsExpired returns true if a price mode allows expired spot prices to be used. +// Price modes with potentially expired prices should only be used for queries, not transactions. +func (mode PriceMode) AllowsExpired() bool { + switch mode { + case PriceModeQuery, PriceModeQueryHigh, PriceModeQueryLow: + return true + } + return false +} diff --git a/x/leverage/types/oracle_test.go b/x/leverage/types/oracle_test.go new file mode 100644 index 0000000000..5d6d83b7e7 --- /dev/null +++ b/x/leverage/types/oracle_test.go @@ -0,0 +1,58 @@ +package types_test + +import ( + "testing" + + "gotest.tools/v3/assert" + + "github.com/umee-network/umee/v6/x/leverage/types" +) + +func TestPriceModes(t *testing.T) { + tcs := []struct { + mode types.PriceMode + withoutHistoric types.PriceMode + allowsExpired bool + }{ + { + types.PriceModeSpot, + types.PriceModeSpot, + false, + }, { + types.PriceModeQuery, + types.PriceModeQuery, + true, + }, { + types.PriceModeHistoric, + types.PriceModeSpot, + false, + }, { + types.PriceModeHigh, + types.PriceModeSpot, + false, + }, { + types.PriceModeLow, + types.PriceModeSpot, + false, + }, { + types.PriceModeQueryHigh, + types.PriceModeQuery, + true, + }, { + types.PriceModeQueryLow, + types.PriceModeQuery, + true, + }, + } + + for _, tc := range tcs { + assert.Equal(t, + tc.mode.IgnoreHistoric(), + tc.withoutHistoric, + ) + assert.Equal(t, + tc.allowsExpired, + tc.mode.AllowsExpired(), + ) + } +} diff --git a/x/leverage/types/query.pb.go b/x/leverage/types/query.pb.go index 909a18e447..27e9f5ed8f 100644 --- a/x/leverage/types/query.pb.go +++ b/x/leverage/types/query.pb.go @@ -519,16 +519,19 @@ type QueryAccountSummaryResponse struct { // Borrow Limit is the maximum Borrowed Value the account is allowed to reach through direct borrowing. // The lower of spot or historic price for each collateral token is used when calculating borrow limits. // Computation skips collateral which is missing an oracle price, potentially resulting in a lower borrow - // limit than if prices were all available. - BorrowLimit github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,4,opt,name=borrow_limit,json=borrowLimit,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"borrow_limit"` + // limit than if prices were all available. Will be null if an oracle price required for computation is + // missing. + BorrowLimit *github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,4,opt,name=borrow_limit,json=borrowLimit,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"borrow_limit,omitempty"` // Liquidation Threshold is the Borrowed Value at which the account becomes eligible for liquidation. - // Will be null if an oracle price required for computation is missing. + // Computation skips borrows which are missing an oracle price, potentially resulting in a lower borrow + // limit than if prices were all available. Will be null if an oracle price required for computation is + // missing. LiquidationThreshold *github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,5,opt,name=liquidation_threshold,json=liquidationThreshold,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"liquidation_threshold,omitempty"` - // Spot Supplied Value is supplied value but always uses spot prices. + // Spot Supplied Value is supplied value but always uses the most recent available spot prices. SpotSuppliedValue github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,6,opt,name=spot_supplied_value,json=spotSuppliedValue,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"spot_supplied_value"` - // Spot Collateral Value is collateral value but always uses spot prices. + // Spot Collateral Value is collateral value but always uses the most recent available spot prices. SpotCollateralValue github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,7,opt,name=spot_collateral_value,json=spotCollateralValue,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"spot_collateral_value"` - // Spot Borrowed Value is borrowed value but always uses spot prices. + // Spot Borrowed Value is borrowed value but always uses the most recent available spot prices. SpotBorrowedValue github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,8,opt,name=spot_borrowed_value,json=spotBorrowedValue,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"spot_borrowed_value"` } @@ -1268,61 +1271,61 @@ var fileDescriptor_1e8137dcabb0ccc7 = []byte{ 0x01, 0x70, 0xa2, 0xe3, 0xcb, 0xce, 0x55, 0x39, 0x42, 0xf9, 0x94, 0x81, 0xb0, 0xc0, 0x1e, 0xbb, 0x47, 0x02, 0xcf, 0xe7, 0x02, 0x5e, 0x1d, 0xe3, 0x08, 0xe8, 0xa7, 0xb0, 0x12, 0xb9, 0x43, 0x02, 0x2f, 0xe4, 0x63, 0x1c, 0xa1, 0x08, 0xd8, 0xc7, 0xb0, 0x2c, 0xcb, 0xb3, 0x63, 0xf7, 0xed, 0x50, - 0x76, 0x2c, 0x27, 0x05, 0x2d, 0x09, 0x8c, 0x47, 0x0c, 0x02, 0x59, 0x70, 0x41, 0x24, 0x66, 0xde, - 0xf2, 0xb7, 0xc3, 0xfd, 0x80, 0xd0, 0x7d, 0xcf, 0xe9, 0xca, 0xee, 0xe4, 0xa4, 0xa1, 0xbb, 0x1e, - 0x03, 0xfb, 0x24, 0xc2, 0x42, 0x5f, 0xc2, 0x79, 0xea, 0x7b, 0x61, 0x7b, 0x62, 0x17, 0x0b, 0xb9, - 0xe8, 0x9f, 0x63, 0x50, 0x4f, 0x12, 0x3b, 0xd9, 0x81, 0x0b, 0x1c, 0x3f, 0xb5, 0x9d, 0x8b, 0xb9, - 0x2c, 0x70, 0xb2, 0x0f, 0x26, 0xb6, 0x34, 0x5a, 0xc3, 0xc4, 0xbe, 0x16, 0xf3, 0xaf, 0xa1, 0x15, - 0xdf, 0x5b, 0xe3, 0x32, 0x5c, 0xe2, 0x31, 0xf0, 0x28, 0xe6, 0x40, 0x1c, 0xf4, 0x48, 0x48, 0x8d, - 0xef, 0x42, 0x2d, 0x63, 0x4a, 0x85, 0x88, 0x0e, 0x8b, 0xa1, 0xf8, 0xc4, 0x33, 0xd6, 0x92, 0x19, - 0x0d, 0x8d, 0x55, 0x28, 0x73, 0xe5, 0x16, 0xee, 0xee, 0x92, 0x4e, 0x48, 0x0d, 0x53, 0xde, 0x7c, - 0xa2, 0x0f, 0xb1, 0x9b, 0x4b, 0x02, 0x83, 0xe5, 0x87, 0xd4, 0xb5, 0x41, 0x2a, 0xc9, 0xdb, 0x82, - 0x32, 0xd2, 0x82, 0x35, 0x79, 0x05, 0x38, 0x54, 0xd5, 0x27, 0x33, 0xde, 0xc7, 0xf7, 0x88, 0xf9, - 0xf8, 0x3d, 0xe2, 0xdf, 0x1a, 0xe8, 0x93, 0x20, 0x8a, 0x1b, 0x81, 0x45, 0x51, 0x94, 0xe9, 0x69, - 0x64, 0xe4, 0x08, 0x1b, 0x59, 0x50, 0x08, 0x85, 0x95, 0x53, 0x48, 0xc6, 0x12, 0xda, 0xf8, 0x21, - 0xac, 0x44, 0xeb, 0x94, 0x7d, 0xc0, 0x49, 0x5d, 0xf5, 0x35, 0x5c, 0x4c, 0x22, 0x28, 0x3f, 0x8d, - 0x17, 0xa0, 0x9d, 0xde, 0x02, 0x7e, 0xa5, 0xc1, 0x32, 0xb7, 0xff, 0xd0, 0xa5, 0x3e, 0xb1, 0x42, - 0x56, 0x9b, 0xc5, 0x7d, 0x4e, 0xd2, 0x97, 0x23, 0x76, 0xb1, 0x53, 0x25, 0x87, 0x2d, 0x40, 0x8b, - 0x75, 0xc7, 0xd5, 0x44, 0xed, 0x5b, 0xe0, 0xb3, 0xf1, 0x72, 0x75, 0x11, 0x0a, 0x5d, 0x76, 0x71, - 0x0a, 0x78, 0x96, 0xd3, 0x4c, 0x39, 0x42, 0x6b, 0xb0, 0xe0, 0x84, 0x43, 0x9e, 0x9e, 0x34, 0x93, - 0xfd, 0x54, 0xf5, 0x46, 0xb2, 0x91, 0x45, 0xe4, 0x98, 0x7a, 0xf3, 0x85, 0x6c, 0x19, 0xa4, 0x82, - 0x72, 0xde, 0x2e, 0xc8, 0x1b, 0x0f, 0x51, 0x37, 0xe7, 0x7a, 0x3a, 0x04, 0x92, 0x66, 0x64, 0x24, - 0x8c, 0x15, 0x0d, 0x2c, 0x8b, 0x59, 0x52, 0x4e, 0x19, 0x69, 0x29, 0x9f, 0x04, 0xf2, 0xc9, 0x62, - 0x56, 0x1b, 0x4a, 0xcf, 0xf8, 0x93, 0x06, 0x2b, 0xb3, 0xae, 0x16, 0xdd, 0x83, 0x22, 0x76, 0xb1, - 0x33, 0xa2, 0x36, 0xe5, 0x9b, 0x50, 0xda, 0xa9, 0xa4, 0x0d, 0x9a, 0x36, 0x3d, 0x78, 0xe8, 0x7e, - 0xe5, 0x99, 0x4a, 0x16, 0x7d, 0x08, 0x45, 0xdf, 0xa3, 0x36, 0xcb, 0x36, 0x7c, 0x7b, 0x4a, 0x3b, - 0x57, 0xd3, 0x7a, 0xbb, 0xc4, 0x52, 0xed, 0x93, 0x12, 0x47, 0x08, 0xce, 0xd8, 0xee, 0x57, 0x9e, - 0xa8, 0x4f, 0x26, 0xff, 0x6d, 0x7c, 0x09, 0xc5, 0xc8, 0x08, 0x3b, 0x17, 0x51, 0xf2, 0xe3, 0x6c, - 0x35, 0x53, 0x8d, 0x51, 0x1d, 0x4a, 0xb1, 0x3c, 0x27, 0x8f, 0x4d, 0xfc, 0x13, 0x8b, 0x89, 0x4f, - 0x55, 0x4d, 0xd5, 0x4c, 0x31, 0x30, 0xfe, 0xa3, 0x41, 0x29, 0xc6, 0x06, 0x3d, 0x4f, 0x9c, 0x2f, - 0xb1, 0x9b, 0x1b, 0x53, 0xa3, 0x61, 0x97, 0x58, 0x3c, 0x20, 0x3e, 0x90, 0x01, 0xf1, 0xfe, 0x6c, - 0x49, 0x3c, 0xdd, 0x61, 0xf5, 0x13, 0xc7, 0xfd, 0x94, 0x0c, 0x2a, 0x13, 0x3b, 0x2f, 0xcb, 0x70, - 0x96, 0x9f, 0x34, 0xe4, 0x43, 0x41, 0x3c, 0x72, 0xa1, 0x29, 0x5b, 0x14, 0x7b, 0x35, 0xab, 0x6c, - 0x1e, 0x3b, 0x1d, 0x9d, 0x51, 0xa3, 0xfe, 0xcd, 0x9f, 0xff, 0xf5, 0xdb, 0xf9, 0x0a, 0xd2, 0x9b, - 0xa9, 0xa7, 0x3d, 0xf1, 0x7c, 0x86, 0x7e, 0xaf, 0xc1, 0x5a, 0xea, 0xe9, 0xec, 0x56, 0x06, 0xfa, - 0xa4, 0x60, 0xa5, 0x39, 0xa3, 0xa0, 0x22, 0xf4, 0x3e, 0x27, 0xb4, 0x89, 0xae, 0xa7, 0x09, 0x05, - 0x4a, 0xa7, 0x2d, 0xd2, 0x13, 0xfa, 0xb5, 0x06, 0xe5, 0xe4, 0x13, 0xda, 0x8d, 0x0c, 0x7b, 0x09, - 0xa9, 0xca, 0x77, 0x66, 0x91, 0x52, 0x94, 0xb6, 0x38, 0x25, 0x03, 0xd5, 0xd3, 0x94, 0xa8, 0x50, - 0x68, 0x63, 0x61, 0x9d, 0xf1, 0x49, 0x3e, 0xa4, 0x65, 0xf1, 0x49, 0x48, 0x65, 0xf2, 0x99, 0xfa, - 0xce, 0x76, 0x1c, 0x9f, 0x3e, 0x57, 0x68, 0x53, 0x69, 0xfd, 0x77, 0x1a, 0xac, 0x4e, 0xde, 0x96, - 0x6e, 0x66, 0xd8, 0x9a, 0x90, 0xab, 0x34, 0x66, 0x93, 0x53, 0xac, 0x6e, 0x73, 0x56, 0x37, 0x90, - 0x91, 0x66, 0x85, 0x85, 0x4a, 0xbb, 0x13, 0x71, 0x78, 0xa9, 0xc1, 0xca, 0xc4, 0x9d, 0x61, 0xf3, - 0x78, 0x73, 0x91, 0xa7, 0xb6, 0x67, 0x12, 0x53, 0xa4, 0xde, 0xe3, 0xa4, 0xae, 0xa3, 0x6b, 0xd9, - 0xa4, 0x22, 0x5f, 0xfd, 0x41, 0x03, 0x94, 0x6e, 0xbb, 0xd0, 0x7b, 0x19, 0x06, 0xd3, 0xa2, 0x95, - 0xbb, 0x33, 0x8b, 0x2a, 0x7e, 0xdb, 0x9c, 0xdf, 0x2d, 0xb4, 0x99, 0xe6, 0x97, 0xe8, 0xd5, 0x25, - 0x99, 0x11, 0x14, 0xa3, 0x5e, 0x0e, 0xd5, 0x32, 0xac, 0x45, 0x02, 0x95, 0x5b, 0xef, 0x10, 0x50, - 0x24, 0xae, 0x73, 0x12, 0x57, 0xd1, 0x95, 0x34, 0x89, 0x0e, 0xee, 0xb6, 0xbb, 0xdc, 0xdc, 0x2f, - 0x35, 0x28, 0xc5, 0x7b, 0x3e, 0x23, 0xf3, 0xc8, 0x2a, 0x99, 0xca, 0xed, 0x77, 0xcb, 0x28, 0x12, - 0x37, 0x39, 0x89, 0x3a, 0xaa, 0x4e, 0x3b, 0xd4, 0x87, 0xea, 0xc9, 0x04, 0x7d, 0x0d, 0x4b, 0xe3, - 0x6e, 0xaa, 0x9e, 0x6d, 0x40, 0x48, 0x54, 0xb6, 0xde, 0x25, 0xa1, 0x08, 0xdc, 0xe0, 0x04, 0xaa, - 0x68, 0x63, 0x3a, 0x01, 0x91, 0x8b, 0x51, 0x08, 0x8b, 0x51, 0x2b, 0x54, 0xcd, 0x80, 0x96, 0xf3, - 0x95, 0x9b, 0xc7, 0xcf, 0x2b, 0xc3, 0xd7, 0xb8, 0xe1, 0x2b, 0xe8, 0x72, 0xda, 0xb0, 0x2d, 0x4d, - 0xbd, 0x4c, 0x77, 0x01, 0x9b, 0xc7, 0xa3, 0x4b, 0xb1, 0xcc, 0x78, 0x99, 0xde, 0xb2, 0x1c, 0x17, - 0x2f, 0x92, 0xcb, 0xb6, 0x8c, 0x9b, 0xd6, 0xc7, 0xaf, 0xfe, 0x59, 0x9d, 0x7b, 0xf5, 0xba, 0xaa, - 0x7d, 0xfb, 0xba, 0xaa, 0xfd, 0xe3, 0x75, 0x55, 0xfb, 0xcd, 0x9b, 0xea, 0xdc, 0xb7, 0x6f, 0xaa, - 0x73, 0x7f, 0x79, 0x53, 0x9d, 0xfb, 0xec, 0x4e, 0xac, 0xd0, 0x31, 0xa8, 0x6d, 0x97, 0x84, 0x2f, - 0xbc, 0xe0, 0x40, 0xe0, 0x0e, 0xef, 0x35, 0x0f, 0xc7, 0xe0, 0xbc, 0xec, 0x75, 0x0a, 0xfc, 0x7f, - 0x41, 0x1f, 0xfc, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x92, 0x47, 0xd1, 0xd0, 0xd2, 0x1a, 0x00, 0x00, + 0x76, 0x2c, 0x27, 0x6e, 0x86, 0x04, 0xc6, 0x23, 0x06, 0x81, 0x2c, 0xb8, 0x20, 0x12, 0x33, 0x6f, + 0xf9, 0xdb, 0xe1, 0x7e, 0x40, 0xe8, 0xbe, 0xe7, 0x74, 0x65, 0x77, 0x72, 0x52, 0xec, 0xf5, 0x18, + 0xd8, 0x27, 0x11, 0x16, 0xfa, 0x12, 0xce, 0x53, 0xdf, 0x0b, 0xdb, 0x13, 0xbb, 0x58, 0xc8, 0xe5, + 0x93, 0x73, 0x0c, 0xea, 0x49, 0x62, 0x27, 0x3b, 0x70, 0x81, 0xe3, 0xa7, 0xb6, 0x73, 0x31, 0x97, + 0x05, 0x4e, 0xf6, 0xc1, 0xc4, 0x96, 0x46, 0x6b, 0x98, 0xd8, 0xd7, 0x62, 0xfe, 0x35, 0xb4, 0xe2, + 0x7b, 0x6b, 0x5c, 0x86, 0x4b, 0x3c, 0x06, 0x1e, 0xc5, 0x1c, 0x88, 0x83, 0x1e, 0x09, 0xa9, 0xf1, + 0x5d, 0xa8, 0x65, 0x4c, 0xa9, 0x10, 0xd1, 0x61, 0x31, 0x14, 0x9f, 0x78, 0xc6, 0x5a, 0x32, 0xa3, + 0xa1, 0xb1, 0x0a, 0x65, 0xae, 0xdc, 0xc2, 0xdd, 0x5d, 0xd2, 0x09, 0xa9, 0x61, 0xca, 0x9b, 0x4f, + 0xf4, 0x21, 0x76, 0x73, 0x49, 0x60, 0xb0, 0xfc, 0x90, 0xba, 0x36, 0x48, 0x25, 0x79, 0x5b, 0x50, + 0x46, 0x5a, 0xb0, 0x26, 0xaf, 0x00, 0x87, 0xaa, 0xfa, 0x64, 0xc6, 0xfb, 0xf8, 0x1e, 0x31, 0x1f, + 0xbf, 0x47, 0xfc, 0x5b, 0x03, 0x7d, 0x12, 0x44, 0x71, 0x23, 0xb0, 0x28, 0x8a, 0x32, 0x3d, 0x8d, + 0x8c, 0x1c, 0x61, 0x23, 0x0b, 0x0a, 0xa1, 0xb0, 0x72, 0x0a, 0xc9, 0x58, 0x42, 0x1b, 0x3f, 0x84, + 0x95, 0x68, 0x9d, 0xb2, 0x0f, 0x38, 0xa9, 0xab, 0xbe, 0x86, 0x8b, 0x49, 0x04, 0xe5, 0xa7, 0xf1, + 0x02, 0xb4, 0xd3, 0x5b, 0xc0, 0xaf, 0x34, 0x58, 0xe6, 0xf6, 0x1f, 0xba, 0xd4, 0x27, 0x56, 0xc8, + 0x6a, 0xb3, 0xb8, 0xcf, 0x49, 0xfa, 0x72, 0xc4, 0x2e, 0x76, 0xaa, 0xe4, 0xb0, 0x05, 0x68, 0xb1, + 0xee, 0xb8, 0x9a, 0xa8, 0x7d, 0x0b, 0x7c, 0x36, 0x5e, 0xae, 0x2e, 0x42, 0xa1, 0xcb, 0x2e, 0x4e, + 0x01, 0xcf, 0x72, 0x9a, 0x29, 0x47, 0x68, 0x0d, 0x16, 0x9c, 0x70, 0xc8, 0xd3, 0x93, 0x66, 0xb2, + 0x9f, 0xaa, 0xde, 0x48, 0x36, 0xb2, 0x88, 0x1c, 0x53, 0x6f, 0xbe, 0x90, 0x2d, 0x83, 0x54, 0x50, + 0xce, 0xdb, 0x05, 0x79, 0xe3, 0x21, 0xea, 0xe6, 0x5c, 0x4f, 0x87, 0x40, 0xd2, 0x8c, 0x8c, 0x84, + 0xb1, 0xa2, 0x81, 0x65, 0x31, 0x4b, 0xca, 0x29, 0x23, 0x2d, 0xe5, 0x93, 0x40, 0x3e, 0x59, 0xcc, + 0x6a, 0x43, 0xe9, 0x19, 0x7f, 0xd2, 0x60, 0x65, 0xd6, 0xd5, 0xa2, 0x7b, 0x50, 0xc4, 0x2e, 0x76, + 0x46, 0xd4, 0xa6, 0x7c, 0x13, 0x4a, 0x3b, 0x95, 0xb4, 0x41, 0xd3, 0xa6, 0x07, 0x0f, 0xdd, 0xaf, + 0x3c, 0x53, 0xc9, 0xa2, 0x0f, 0xa1, 0xe8, 0x7b, 0xd4, 0x66, 0xd9, 0x86, 0x6f, 0x4f, 0x69, 0xe7, + 0x6a, 0x5a, 0x6f, 0x97, 0x58, 0xaa, 0x7d, 0x52, 0xe2, 0x08, 0xc1, 0x19, 0xdb, 0xfd, 0xca, 0x13, + 0xf5, 0xc9, 0xe4, 0xbf, 0x8d, 0x2f, 0xa1, 0x18, 0x19, 0x61, 0xe7, 0x22, 0x4a, 0x7e, 0x9c, 0xad, + 0x66, 0xaa, 0x31, 0xaa, 0x43, 0x29, 0x96, 0xe7, 0xe4, 0xb1, 0x89, 0x7f, 0x62, 0x31, 0xf1, 0xa9, + 0xaa, 0xa9, 0x9a, 0x29, 0x06, 0xc6, 0x7f, 0x34, 0x28, 0xc5, 0xd8, 0xa0, 0xe7, 0x89, 0xf3, 0x25, + 0x76, 0x73, 0x63, 0x6a, 0x34, 0xec, 0x12, 0x8b, 0x07, 0xc4, 0x07, 0x32, 0x20, 0xde, 0x9f, 0x2d, + 0x89, 0xa7, 0x3b, 0xac, 0x7e, 0xe2, 0xb8, 0x9f, 0x92, 0x41, 0x65, 0x62, 0xe7, 0x65, 0x19, 0xce, + 0xf2, 0x93, 0x86, 0x7c, 0x28, 0x88, 0x47, 0x2e, 0x34, 0x65, 0x8b, 0x62, 0xaf, 0x66, 0x95, 0xcd, + 0x63, 0xa7, 0xa3, 0x33, 0x6a, 0xd4, 0xbf, 0xf9, 0xf3, 0xbf, 0x7e, 0x3b, 0x5f, 0x41, 0x7a, 0x33, + 0xf5, 0xb4, 0x27, 0x9e, 0xcf, 0xd0, 0xef, 0x35, 0x58, 0x4b, 0x3d, 0x9d, 0xdd, 0xca, 0x40, 0x9f, + 0x14, 0xac, 0x34, 0x67, 0x14, 0x54, 0x84, 0xde, 0xe7, 0x84, 0x36, 0xd1, 0xf5, 0x34, 0xa1, 0x40, + 0xe9, 0xb4, 0x45, 0x7a, 0x42, 0xbf, 0xd6, 0xa0, 0x9c, 0x7c, 0x42, 0xbb, 0x91, 0x61, 0x2f, 0x21, + 0x55, 0xf9, 0xce, 0x2c, 0x52, 0x8a, 0xd2, 0x16, 0xa7, 0x64, 0xa0, 0x7a, 0x9a, 0x12, 0x15, 0x0a, + 0x6d, 0x2c, 0xac, 0x33, 0x3e, 0xc9, 0x87, 0xb4, 0x2c, 0x3e, 0x09, 0xa9, 0x4c, 0x3e, 0x53, 0xdf, + 0xd9, 0x8e, 0xe3, 0xd3, 0xe7, 0x0a, 0x6d, 0x2a, 0xad, 0xff, 0x4e, 0x83, 0xd5, 0xc9, 0xdb, 0xd2, + 0xcd, 0x0c, 0x5b, 0x13, 0x72, 0x95, 0xc6, 0x6c, 0x72, 0x8a, 0xd5, 0x6d, 0xce, 0xea, 0x06, 0x32, + 0xd2, 0xac, 0xb0, 0x50, 0x69, 0x77, 0x22, 0x0e, 0x2f, 0x35, 0x58, 0x99, 0xb8, 0x33, 0x6c, 0x1e, + 0x6f, 0x2e, 0xf2, 0xd4, 0xf6, 0x4c, 0x62, 0x8a, 0xd4, 0x7b, 0x9c, 0xd4, 0x75, 0x74, 0x2d, 0x9b, + 0x54, 0xe4, 0xab, 0x3f, 0x68, 0x80, 0xd2, 0x6d, 0x17, 0x7a, 0x2f, 0xc3, 0x60, 0x5a, 0xb4, 0x72, + 0x77, 0x66, 0x51, 0xc5, 0x6f, 0x9b, 0xf3, 0xbb, 0x85, 0x36, 0xd3, 0xfc, 0x12, 0xbd, 0xba, 0x24, + 0x33, 0x82, 0x62, 0xd4, 0xcb, 0xa1, 0x5a, 0x86, 0xb5, 0x48, 0xa0, 0x72, 0xeb, 0x1d, 0x02, 0x8a, + 0xc4, 0x75, 0x4e, 0xe2, 0x2a, 0xba, 0x92, 0x26, 0xd1, 0xc1, 0xdd, 0x76, 0x97, 0x9b, 0xfb, 0xa5, + 0x06, 0xa5, 0x78, 0xcf, 0x67, 0x64, 0x1e, 0x59, 0x25, 0x53, 0xb9, 0xfd, 0x6e, 0x19, 0x45, 0xe2, + 0x26, 0x27, 0x51, 0x47, 0xd5, 0x69, 0x87, 0xfa, 0x50, 0x3d, 0x99, 0xa0, 0xaf, 0x61, 0x69, 0xdc, + 0x4d, 0xd5, 0xb3, 0x0d, 0x08, 0x89, 0xca, 0xd6, 0xbb, 0x24, 0x14, 0x81, 0x1b, 0x9c, 0x40, 0x15, + 0x6d, 0x4c, 0x27, 0x20, 0x72, 0x31, 0x0a, 0x61, 0x31, 0x6a, 0x85, 0xaa, 0x19, 0xd0, 0x72, 0xbe, + 0x72, 0xf3, 0xf8, 0x79, 0x65, 0xf8, 0x1a, 0x37, 0x7c, 0x05, 0x5d, 0x4e, 0x1b, 0xb6, 0xa5, 0xa9, + 0x97, 0xe9, 0x2e, 0x60, 0xf3, 0x78, 0x74, 0x29, 0x96, 0x19, 0x2f, 0xd3, 0x5b, 0x96, 0xe3, 0xe2, + 0x45, 0x72, 0xd9, 0x96, 0x71, 0xd3, 0xfa, 0xf8, 0xd5, 0x3f, 0xab, 0x73, 0xaf, 0x5e, 0x57, 0xb5, + 0x6f, 0x5f, 0x57, 0xb5, 0x7f, 0xbc, 0xae, 0x6a, 0xbf, 0x79, 0x53, 0x9d, 0xfb, 0xf6, 0x4d, 0x75, + 0xee, 0x2f, 0x6f, 0xaa, 0x73, 0x9f, 0xdd, 0x89, 0x15, 0x3a, 0x06, 0xb5, 0xed, 0x92, 0xf0, 0x85, + 0x17, 0x1c, 0x08, 0xdc, 0xe1, 0xbd, 0xe6, 0xe1, 0x18, 0x9c, 0x97, 0xbd, 0x4e, 0x81, 0xff, 0x2f, + 0xe8, 0x83, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x75, 0x15, 0x86, 0x50, 0xd2, 0x1a, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -2468,16 +2471,18 @@ func (m *QueryAccountSummaryResponse) MarshalToSizedBuffer(dAtA []byte) (int, er i-- dAtA[i] = 0x2a } - { - size := m.BorrowLimit.Size() - i -= size - if _, err := m.BorrowLimit.MarshalTo(dAtA[i:]); err != nil { - return 0, err + if m.BorrowLimit != nil { + { + size := m.BorrowLimit.Size() + i -= size + if _, err := m.BorrowLimit.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) } - i = encodeVarintQuery(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 } - i-- - dAtA[i] = 0x22 { size := m.BorrowedValue.Size() i -= size @@ -3318,8 +3323,10 @@ func (m *QueryAccountSummaryResponse) Size() (n int) { n += 1 + l + sovQuery(uint64(l)) l = m.BorrowedValue.Size() n += 1 + l + sovQuery(uint64(l)) - l = m.BorrowLimit.Size() - n += 1 + l + sovQuery(uint64(l)) + if m.BorrowLimit != nil { + l = m.BorrowLimit.Size() + n += 1 + l + sovQuery(uint64(l)) + } if m.LiquidationThreshold != nil { l = m.LiquidationThreshold.Size() n += 1 + l + sovQuery(uint64(l)) @@ -5324,6 +5331,8 @@ func (m *QueryAccountSummaryResponse) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } + var v github_com_cosmos_cosmos_sdk_types.Dec + m.BorrowLimit = &v if err := m.BorrowLimit.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } diff --git a/x/oracle/abci.go b/x/oracle/abci.go index c59004bca1..9f366a438a 100644 --- a/x/oracle/abci.go +++ b/x/oracle/abci.go @@ -54,8 +54,6 @@ func CalcPrices(ctx sdk.Context, params types.Params, k keeper.Keeper) error { voteTargetDenoms = append(voteTargetDenoms, v.BaseDenom) } - k.ClearExchangeRates(ctx) - // NOTE: it filters out inactive or jailed validators // ballotDenomSlice is oracle votes of the symbol denoms, those are stored by AggregateExchangeRateVote ballotDenomSlice := k.OrganizeBallotByDenom(ctx, validatorClaimMap) diff --git a/x/oracle/abci_test.go b/x/oracle/abci_test.go index ca9e98dbfe..c1fcc8d954 100644 --- a/x/oracle/abci_test.go +++ b/x/oracle/abci_test.go @@ -102,10 +102,17 @@ var ( func (s *IntegrationTestSuite) TestEndBlockerVoteThreshold() { app, ctx := s.app, s.ctx - ctx = ctx.WithBlockHeight(1) + ctx = ctx.WithBlockHeight(1).WithBlockTime(time.Unix(1000, 0)) // block 1, t = 1000 preVoteBlockDiff := int64(app.OracleKeeper.VotePeriod(ctx) / 2) voteBlockDiff := int64(app.OracleKeeper.VotePeriod(ctx)/2 + 1) + advanceTime := func(oldCtx sdk.Context, blocks int64) sdk.Context { + return oldCtx.WithBlockHeight( + ctx.BlockHeight() + blocks).WithBlockTime( + time.Unix(ctx.BlockTime().Unix()+5*voteBlockDiff, 0), + ) // block number increased, and time advanced 5 seconds per block + } + var ( val1Tuples types.ExchangeRateTuples val2Tuples types.ExchangeRateTuples @@ -126,7 +133,9 @@ func (s *IntegrationTestSuite) TestEndBlockerVoteThreshold() { }) } - createVote := func(hash string, val sdk.ValAddress, rates types.ExchangeRateTuples, blockHeight uint64) (types.AggregateExchangeRatePrevote, types.AggregateExchangeRateVote) { + createVote := func(hash string, val sdk.ValAddress, rates types.ExchangeRateTuples, blockHeight uint64) ( + types.AggregateExchangeRatePrevote, types.AggregateExchangeRateVote, + ) { preVote := types.AggregateExchangeRatePrevote{ Hash: "hash1", Voter: val.String(), @@ -149,7 +158,7 @@ func (s *IntegrationTestSuite) TestEndBlockerVoteThreshold() { app.OracleKeeper.SetAggregateExchangeRatePrevote(ctx, valAddr3, val3PreVotes) oracle.EndBlocker(ctx, app.OracleKeeper) - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + voteBlockDiff) + ctx = advanceTime(ctx, voteBlockDiff) app.OracleKeeper.SetAggregateExchangeRateVote(ctx, valAddr1, val1Votes) app.OracleKeeper.SetAggregateExchangeRateVote(ctx, valAddr2, val2Votes) app.OracleKeeper.SetAggregateExchangeRateVote(ctx, valAddr3, val3Votes) @@ -160,33 +169,41 @@ func (s *IntegrationTestSuite) TestEndBlockerVoteThreshold() { s.Require().NoError(err) s.Require().Equal(types.ExchangeRate{ Rate: sdk.OneDec(), - Timestamp: ctx.BlockTime()}, - rate) + Timestamp: ctx.BlockTime(), + }, rate) } + // prices during next case will still have this old timestamp + expiredTime := ctx.BlockTime() + // Test: only val2 votes (has 39% vote power). // Total voting power per denom must be bigger or equal than 40% (see SetupTest). - // So if only val2 votes, we won't have any prices next block. - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + preVoteBlockDiff) + // So if only val2 votes, we won't update any prices next block. + // (prices will still exist with old timestamps) + ctx = advanceTime(ctx, preVoteBlockDiff) h = uint64(ctx.BlockHeight()) val2PreVotes.SubmitBlock = h app.OracleKeeper.SetAggregateExchangeRatePrevote(ctx, valAddr2, val2PreVotes) oracle.EndBlocker(ctx, app.OracleKeeper) - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + voteBlockDiff) + ctx = advanceTime(ctx, voteBlockDiff) app.OracleKeeper.SetAggregateExchangeRateVote(ctx, valAddr2, val2Votes) oracle.EndBlocker(ctx, app.OracleKeeper) for _, denom := range app.OracleKeeper.AcceptList(ctx) { rate, err := app.OracleKeeper.GetExchangeRate(ctx, denom.SymbolDenom) - s.Require().ErrorIs(err, types.ErrUnknownDenom.Wrap(denom.SymbolDenom)) - s.Require().Equal(types.ExchangeRate{}, rate) + // price must exist, but with old timestamp + s.Require().NoError(err) + s.Require().Equal(types.ExchangeRate{ + Rate: sdk.OneDec(), + Timestamp: expiredTime, + }, rate) } // Test: val2 and val3 votes. // now we will have 40% of the power, so now we should have prices - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + preVoteBlockDiff) + ctx = advanceTime(ctx, preVoteBlockDiff) h = uint64(ctx.BlockHeight()) val2PreVotes.SubmitBlock = h val3PreVotes.SubmitBlock = h @@ -195,7 +212,7 @@ func (s *IntegrationTestSuite) TestEndBlockerVoteThreshold() { app.OracleKeeper.SetAggregateExchangeRatePrevote(ctx, valAddr3, val3PreVotes) oracle.EndBlocker(ctx, app.OracleKeeper) - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + voteBlockDiff) + ctx = advanceTime(ctx, voteBlockDiff) app.OracleKeeper.SetAggregateExchangeRateVote(ctx, valAddr2, val2Votes) app.OracleKeeper.SetAggregateExchangeRateVote(ctx, valAddr3, val3Votes) oracle.EndBlocker(ctx, app.OracleKeeper) @@ -211,7 +228,7 @@ func (s *IntegrationTestSuite) TestEndBlockerVoteThreshold() { // Test: val1 and val2 vote again // umee has 69.9% power, and atom has 30%, so we should have price for umee, but not for atom - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + preVoteBlockDiff) + ctx = advanceTime(ctx, preVoteBlockDiff) h = uint64(ctx.BlockHeight()) val1PreVotes.SubmitBlock = h val2PreVotes.SubmitBlock = h @@ -233,7 +250,7 @@ func (s *IntegrationTestSuite) TestEndBlockerVoteThreshold() { app.OracleKeeper.SetAggregateExchangeRatePrevote(ctx, valAddr2, val2PreVotes) oracle.EndBlocker(ctx, app.OracleKeeper) - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + voteBlockDiff) + ctx = advanceTime(ctx, voteBlockDiff) app.OracleKeeper.SetAggregateExchangeRateVote(ctx, valAddr1, val1Votes) app.OracleKeeper.SetAggregateExchangeRateVote(ctx, valAddr2, val2Votes) oracle.EndBlocker(ctx, app.OracleKeeper)