Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: increase price calc precision for high exponent assets #1633

Merged
merged 16 commits into from
Dec 2, 2022
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ Ref: https://keepachangelog.com/en/1.0.0/

## [Unreleased]

### Features

- [1633](https://github.com/umee-network/umee/pull/1633) MarketSummary query now displays symbol price instead of base price for readability.

### Fixes

- [1633](https://github.com/umee-network/umee/pull/1633) Increases price calculation precision for high exponent assets.

## [v3.2.0](https://github.com/umee-network/umee/releases/tag/v3.2.0) - 2022-11-25

Since `umeed v3.2` there is a new runtime dependency: `libwasmvm.x86_64.so v1.1.1` is required.
Expand Down
2 changes: 1 addition & 1 deletion proto/umee/leverage/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ message QueryMarketSummaryResponse {
string symbol_denom = 1;
// Exponent is the power of ten required to get from base denom to symbol denom. For example, an exponent of 6 means 10^6 uumee = 1 UMEE.
uint32 exponent = 2;
// Oracle Price is the current USD value of a base token. Exponent must be applied to reach the price from symbol_denom. For example, a price of $0.000001 for 1 uumee is equivalent to $1.00 for 1 UMEE. Oracle price is nil when the oracle is down.
// Oracle Price is the current USD value of a token. Oracle price is nil when the oracle is down.
string oracle_price = 3 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = true
Expand Down
189 changes: 180 additions & 9 deletions swagger/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -311,10 +311,8 @@ paths:
oracle_price:
type: string
description: >-
Oracle Price is the current USD value of a base token.
Exponent must be applied to reach the price from symbol_denom.
For example, a price of $0.000001 for 1 uumee is equivalent to
$1.00 for 1 UMEE. Oracle price is nil when the oracle is down.
Oracle Price is the current USD value of a token. Oracle price
is nil when the oracle is down.
uToken_exchange_rate:
type: string
description: >-
Expand Down Expand Up @@ -862,9 +860,77 @@ paths:
format: byte
tags:
- Query
/umee/historacle/v1/denoms/median_deviations:
get:
summary: |-
MedianDeviations returns median deviations of all denoms,
or, if specified, returns a single median deviation
operationId: MedianDeviations
responses:
'200':
description: A successful response.
schema:
type: object
properties:
medianDeviations:
type: array
items:
type: object
properties:
denom:
type: string
amount:
type: string
description: >-
DecCoin defines a token with a denomination and a decimal
amount.


NOTE: The amount field is an Dec which implements the custom
method

signatures required by gogoproto.
description: >-
medians defines a list of the median deviations for all
stamped denoms.
description: |-
QueryMedianDeviationsResponse is response type for the
Query/MedianDeviations 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/historacle/v1/denoms/medians:
get:
summary: Medians returns medians of all denoms
summary: |-
Medians returns medians of all denoms,
or, if specified, returns a single median
operationId: Medians
responses:
'200':
Expand Down Expand Up @@ -1098,6 +1164,38 @@ paths:
we want

to keep a record of our set of exchange rates.
median_period:
type: string
format: uint64
description: >-
Median Period represents the amount blocks we will wait
between

calculating the median and standard deviation of the
median of

historic prices in the last Prune Period.
historic_accept_list:
type: array
items:
type: object
properties:
base_denom:
type: string
symbol_denom:
type: string
exponent:
type: integer
format: int64
title: Denom - the object to hold configurations of each denom
description: >-
Historic Asset List is a list of assets which will use the
historic

price stamping protection methodology (mainly
manipulatable assets).

Any assets not on this list will not be stamped.
description: >-
QueryParamsResponse is the response type for the Query/Params RPC
method.
Expand Down Expand Up @@ -1797,10 +1895,8 @@ definitions:
oracle_price:
type: string
description: >-
Oracle Price is the current USD value of a base token. Exponent must
be applied to reach the price from symbol_denom. For example, a price
of $0.000001 for 1 uumee is equivalent to $1.00 for 1 UMEE. Oracle
price is nil when the oracle is down.
Oracle Price is the current USD value of a token. Oracle price is nil
when the oracle is down.
uToken_exchange_rate:
type: string
description: >-
Expand Down Expand Up @@ -2515,6 +2611,30 @@ definitions:
description: |-
Prune Period represents the maximum amount of blocks which we want
to keep a record of our set of exchange rates.
median_period:
type: string
format: uint64
description: |-
Median Period represents the amount blocks we will wait between
calculating the median and standard deviation of the median of
historic prices in the last Prune Period.
historic_accept_list:
type: array
items:
type: object
properties:
base_denom:
type: string
symbol_denom:
type: string
exponent:
type: integer
format: int64
title: Denom - the object to hold configurations of each denom
description: |-
Historic Asset List is a list of assets which will use the historic
price stamping protection methodology (mainly manipulatable assets).
Any assets not on this list will not be stamped.
description: Params defines the parameters for the oracle module.
umee.oracle.v1.QueryActiveExchangeRatesResponse:
type: object
Expand Down Expand Up @@ -2688,6 +2808,29 @@ definitions:
description: |-
QueryFeederDelegationResponse is response type for the
Query/FeederDelegation RPC method.
umee.oracle.v1.QueryMedianDeviationsResponse:
type: object
properties:
medianDeviations:
type: array
items:
type: object
properties:
denom:
type: string
amount:
type: string
description: |-
DecCoin defines a token with a denomination and a decimal amount.

NOTE: The amount field is an Dec which implements the custom method
signatures required by gogoproto.
description: >-
medians defines a list of the median deviations for all stamped
denoms.
description: |-
QueryMedianDeviationsResponse is response type for the
Query/MedianDeviations RPC method.
umee.oracle.v1.QueryMediansResponse:
type: object
properties:
Expand Down Expand Up @@ -2768,6 +2911,34 @@ definitions:
description: |-
Prune Period represents the maximum amount of blocks which we want
to keep a record of our set of exchange rates.
median_period:
type: string
format: uint64
description: |-
Median Period represents the amount blocks we will wait between
calculating the median and standard deviation of the median of
historic prices in the last Prune Period.
historic_accept_list:
type: array
items:
type: object
properties:
base_denom:
type: string
symbol_denom:
type: string
exponent:
type: integer
format: int64
title: Denom - the object to hold configurations of each denom
description: >-
Historic Asset List is a list of assets which will use the
historic

price stamping protection methodology (mainly manipulatable
assets).

Any assets not on this list will not be stamped.
description: QueryParamsResponse is the response type for the Query/Params RPC method.
umee.oracle.v1.QuerySlashWindowResponse:
type: object
Expand Down
4 changes: 2 additions & 2 deletions x/leverage/client/tests/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (s *IntegrationTestSuite) TestInvalidQueries() {
func (s *IntegrationTestSuite) TestLeverageScenario() {
val := s.network.Validators[0]

oraclePrice := sdk.MustNewDecFromStr("0.00003421")
oracleSymbolPrice := sdk.MustNewDecFromStr("34.21")

initialQueries := []testQuery{
{
Expand Down Expand Up @@ -105,7 +105,7 @@ func (s *IntegrationTestSuite) TestLeverageScenario() {
&types.QueryMarketSummaryResponse{
SymbolDenom: "UMEE",
Exponent: 6,
OraclePrice: &oraclePrice,
OraclePrice: &oracleSymbolPrice,
UTokenExchangeRate: sdk.OneDec(),
// Borrow rate * (1 - ReserveFactor - OracleRewardFactor)
// 1.50 * (1 - 0.10 - 0.01) = 0.89 * 1.5 = 1.335
Expand Down
6 changes: 4 additions & 2 deletions x/leverage/fixtures/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ import (
const (
// AtomDenom is an ibc denom to be used as ATOM's BaseDenom during testing. Matches mainnet.
AtomDenom = "ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9"
// DaiDenom is an ibc denom to be used as DAI's BaseDenom during testing. Matches mainnet.
DaiDenom = "ibc/C86651B4D30C1739BF8B061E36F4473A0C9D60380B52D01E56A6874037A5D060"
)

// Token returns a valid token
func Token(base, symbol string) types.Token {
func Token(base, symbol string, exponent uint32) types.Token {
return types.Token{
BaseDenom: base,
SymbolDenom: symbol,
Exponent: 6,
Exponent: exponent,
ReserveFactor: sdk.MustNewDecFromStr("0.2"),
CollateralWeight: sdk.MustNewDecFromStr("0.25"),
LiquidationThreshold: sdk.MustNewDecFromStr("0.25"),
Expand Down
2 changes: 1 addition & 1 deletion x/leverage/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ func (q Querier) MarketSummary(
}

// Oracle price in response will be nil if it is unavailable
if oraclePrice, oracleErr := q.Keeper.TokenPrice(ctx, req.Denom); oracleErr == nil {
if oraclePrice, _, oracleErr := q.Keeper.TokenDefaultDenomPrice(ctx, req.Denom); oracleErr == nil {
resp.OraclePrice = &oraclePrice
}

Expand Down
6 changes: 3 additions & 3 deletions x/leverage/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func (s *IntegrationTestSuite) TestQuerier_RegisteredTokens() {

resp, err := s.queryClient.RegisteredTokens(ctx.Context(), &types.QueryRegisteredTokens{})
require.NoError(err)
require.Len(resp.Registry, 2, "token registry length")
require.Len(resp.Registry, 3, "token registry length")
toteki marked this conversation as resolved.
Show resolved Hide resolved
}

func (s *IntegrationTestSuite) TestQuerier_Params() {
Expand All @@ -36,12 +36,12 @@ func (s *IntegrationTestSuite) TestQuerier_MarketSummary() {
resp, err := s.queryClient.MarketSummary(context.Background(), req)
require.NoError(err)

oraclePrice := sdk.MustNewDecFromStr("0.00000421")
oracleSymbolPrice := sdk.MustNewDecFromStr("4.21")

expected := types.QueryMarketSummaryResponse{
SymbolDenom: "UMEE",
Exponent: 6,
OraclePrice: &oraclePrice,
OraclePrice: &oracleSymbolPrice,
UTokenExchangeRate: sdk.OneDec(),
Supply_APY: sdk.MustNewDecFromStr("1.2008"),
Borrow_APY: sdk.MustNewDecFromStr("1.52"),
Expand Down
10 changes: 5 additions & 5 deletions x/leverage/keeper/iter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func (s *IntegrationTestSuite) TestGetEligibleLiquidationTargets_OneAddrOneAsset
require.Equal([]sdk.AccAddress{}, zeroAddresses)

// Note: Setting umee liquidation threshold to 0.05 to make the user eligible to liquidation
umeeToken := newToken("uumee", "UMEE")
umeeToken := newToken("uumee", "UMEE", 6)
umeeToken.CollateralWeight = sdk.MustNewDecFromStr("0.05")
umeeToken.LiquidationThreshold = sdk.MustNewDecFromStr("0.05")

Expand Down Expand Up @@ -58,14 +58,14 @@ func (s *IntegrationTestSuite) TestGetEligibleLiquidationTargets_OneAddrTwoAsset
s.borrow(addr, coin(atomDenom, 4_000000))

// Note: Setting umee liquidation threshold to 0.05 to make the user eligible for liquidation
umeeToken := newToken("uumee", "UMEE")
umeeToken := newToken("uumee", "UMEE", 6)
umeeToken.CollateralWeight = sdk.MustNewDecFromStr("0.05")
umeeToken.LiquidationThreshold = sdk.MustNewDecFromStr("0.05")

require.NoError(app.LeverageKeeper.SetTokenSettings(s.ctx, umeeToken))

// Note: Setting atom collateral weight to 0.01 to make the user eligible for liquidation
atomIBCToken := newToken(atomDenom, "ATOM")
atomIBCToken := newToken(atomDenom, "ATOM", 6)
atomIBCToken.CollateralWeight = sdk.MustNewDecFromStr("0.01")
atomIBCToken.LiquidationThreshold = sdk.MustNewDecFromStr("0.01")

Expand Down Expand Up @@ -100,14 +100,14 @@ func (s *IntegrationTestSuite) TestGetEligibleLiquidationTargets_TwoAddr() {
s.borrow(addr2, coin(atomDenom, 24))

// Note: Setting umee liquidation threshold to 0.05 to make the first supplier eligible for liquidation
umeeToken := newToken("uumee", "UMEE")
umeeToken := newToken("uumee", "UMEE", 6)
umeeToken.CollateralWeight = sdk.MustNewDecFromStr("0.05")
umeeToken.LiquidationThreshold = sdk.MustNewDecFromStr("0.05")

require.NoError(app.LeverageKeeper.SetTokenSettings(s.ctx, umeeToken))

// Note: Setting atom collateral weight to 0.01 to make the second supplier eligible for liquidation
atomIBCToken := newToken(atomDenom, "ATOM")
atomIBCToken := newToken(atomDenom, "ATOM", 6)
atomIBCToken.CollateralWeight = sdk.MustNewDecFromStr("0.01")
atomIBCToken.LiquidationThreshold = sdk.MustNewDecFromStr("0.01")

Expand Down
6 changes: 3 additions & 3 deletions x/leverage/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,9 +372,9 @@ func (k Keeper) Decollateralize(ctx sdk.Context, borrowerAddr sdk.AccAddress, uT
// Because partial liquidation is possible and exchange rates vary, Liquidate returns the actual amount of
// tokens repaid, collateral liquidated, and base tokens or uTokens rewarded.
func (k Keeper) Liquidate(
ctx sdk.Context, liquidatorAddr, borrowerAddr sdk.AccAddress, maxRepay sdk.Coin, rewardDenom string,
ctx sdk.Context, liquidatorAddr, borrowerAddr sdk.AccAddress, requestedRepay sdk.Coin, rewardDenom string,
) (repaid sdk.Coin, liquidated sdk.Coin, reward sdk.Coin, err error) {
if err := k.validateAcceptedAsset(ctx, maxRepay); err != nil {
if err := k.validateAcceptedAsset(ctx, requestedRepay); err != nil {
return sdk.Coin{}, sdk.Coin{}, sdk.Coin{}, err
}

Expand All @@ -393,7 +393,7 @@ func (k Keeper) Liquidate(
ctx,
liquidatorAddr,
borrowerAddr,
maxRepay,
requestedRepay,
rewardDenom,
directLiquidation,
)
Expand Down
Loading