From af0c45479aca7291dbcc5cd5117ef937247a2826 Mon Sep 17 00:00:00 2001 From: toteki <63419657+toteki@users.noreply.github.com> Date: Wed, 27 Sep 2023 08:09:52 -0600 Subject: [PATCH] price mode support --- CHANGELOG.md | 1 + x/leverage/keeper/oracle.go | 27 ++++++++++++++++++++------- x/leverage/types/errors.go | 1 + x/leverage/types/oracle.go | 4 +++- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4065a56351..bf3dd420d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements - [2261](https://github.com/umee-network/umee/pull/2261) Use go 1.21 +- [22XX](https://github.com/umee-network/umee/pull/22XX) Leverage transactions accept spot prices up to 3 minutes old, and leverage queries use most recet spot price when required. ### Bug Fixes diff --git a/x/leverage/keeper/oracle.go b/x/leverage/keeper/oracle.go index 7173c5cc43..1d1f0db220 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 // 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,8 +29,9 @@ 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 t.HistoricMedians == 0 { + // if a token is exempt from historic pricing, all price modes return Spot price. + // price mode last is unaffected. + if t.HistoricMedians == 0 && mode != types.PriceModeLast { mode = types.PriceModeSpot } @@ -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 != types.PriceModeLast { + // unless price mode is last price, require spot price to be recent + blockTime := ctx.BlockTime().Unix() + priceTime := spotPrice.Timestamp.Unix() + priceAge := blockTime - priceTime + if priceAge < 0 || priceAge > maxSpotPriceAge { + return sdk.ZeroDec(), t.Exponent, types.ErrExpiredOraclePrice.Wrapf( + "price: %d, block: %d", priceTime, blockTime) + } + } } - if mode != types.PriceModeSpot { - // historic price is required for modes other than spot + + if mode != types.PriceModeSpot && mode != types.PriceModeLast { + // historic price is required for modes other than spot and last var numStamps uint32 historicPrice, numStamps, err = k.oracleKeeper.MedianOfHistoricMedians( ctx, strings.ToUpper(t.SymbolDenom), uint64(t.HistoricMedians)) @@ -57,10 +72,8 @@ 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.PriceModeLast: price = spotPrice.Rate case types.PriceModeHistoric: price = historicPrice 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..490cf3a163 100644 --- a/x/leverage/types/oracle.go +++ b/x/leverage/types/oracle.go @@ -4,8 +4,10 @@ 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 + // Last mode requests the most recent price, regardless of price age. + PriceModeLast // Historic mode requests the median of the most recent historic medians PriceModeHistoric // High mode uses the higher of either Spot or Historic prices