-
Notifications
You must be signed in to change notification settings - Fork 161
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add keeper methods for historacle prices and medians (#1548)
* Add keeper methods for historacle prices and medians * Add HistoraclePricing test * fix sim-non-determinism * get rid of getstampperiodhistoricprices * Update x/oracle/keeper/params.go Co-authored-by: Robert Zaremba <robert@zaremba.ch> * Update x/oracle/keeper/params.go Co-authored-by: Robert Zaremba <robert@zaremba.ch> * Update x/oracle/keeper/historic_price.go Co-authored-by: Robert Zaremba <robert@zaremba.ch> * Update x/oracle/keeper/historic_price.go Co-authored-by: Robert Zaremba <robert@zaremba.ch> * Update x/oracle/keeper/historic_price.go Co-authored-by: Adam Wozniak <29418299+adamewozniak@users.noreply.github.com> * Update x/oracle/keeper/historic_price.go Co-authored-by: Adam Wozniak <29418299+adamewozniak@users.noreply.github.com> * Update x/oracle/types/params.go Co-authored-by: Adam Wozniak <29418299+adamewozniak@users.noreply.github.com> * Update x/oracle/keeper/historic_price.go Co-authored-by: Robert Zaremba <robert@zaremba.ch> * PR suggestions * Add median deviation keeper * gofmt * Update proto with MedianPeriod and leaner HistoricPrice type * Add delete methods for median and median deviation * Update proto/umee/oracle/v1/oracle.proto Co-authored-by: Adam Wozniak <29418299+adamewozniak@users.noreply.github.com> * Update proto/umee/oracle/v1/oracle.proto Co-authored-by: Adam Wozniak <29418299+adamewozniak@users.noreply.github.com> * Update x/oracle/keeper/historic_price.go Co-authored-by: Adam Wozniak <29418299+adamewozniak@users.noreply.github.com> * Update x/oracle/keeper/historic_price.go Co-authored-by: Adam Wozniak <29418299+adamewozniak@users.noreply.github.com> * Make historic price getter private and create error types for medians * Add method for appending denom and block to key * Empty-Commit Co-authored-by: Robert Zaremba <robert@zaremba.ch> Co-authored-by: Adam Wozniak <29418299+adamewozniak@users.noreply.github.com>
- Loading branch information
1 parent
acad48b
commit efd74a6
Showing
10 changed files
with
622 additions
and
75 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
package keeper | ||
|
||
import ( | ||
"fmt" | ||
"sort" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" | ||
|
||
"github.com/umee-network/umee/v3/x/oracle/types" | ||
) | ||
|
||
// median returns the median of a list of historic prices. | ||
func median(prices []types.HistoricPrice) sdk.Dec { | ||
lenPrices := len(prices) | ||
if lenPrices == 0 { | ||
return sdk.ZeroDec() | ||
} | ||
|
||
sort.Slice(prices, func(i, j int) bool { | ||
return prices[i].ExchangeRate.BigInt(). | ||
Cmp(prices[j].ExchangeRate.BigInt()) > 0 | ||
}) | ||
|
||
if lenPrices%2 == 0 { | ||
return prices[lenPrices/2-1].ExchangeRate. | ||
Add(prices[lenPrices/2].ExchangeRate). | ||
QuoInt64(2) | ||
} | ||
return prices[lenPrices/2].ExchangeRate | ||
} | ||
|
||
// medianDeviation returns the standard deviation around the | ||
// median of a list of prices. | ||
// medianDeviation = ∑((price - median)^2 / len(prices)) | ||
func medianDeviation(median sdk.Dec, prices []types.HistoricPrice) sdk.Dec { | ||
lenPrices := len(prices) | ||
medianDeviation := sdk.ZeroDec() | ||
|
||
for _, price := range prices { | ||
medianDeviation = medianDeviation.Add(price.ExchangeRate. | ||
Sub(median).Abs().Power(2). | ||
QuoInt64(int64(lenPrices))) | ||
} | ||
|
||
return medianDeviation | ||
} | ||
|
||
// GetMedian returns a given denom's median price in the last prune | ||
// period since a given block. | ||
func (k Keeper) GetMedian( | ||
ctx sdk.Context, | ||
denom string, | ||
blockNum uint64, | ||
) (sdk.Dec, error) { | ||
store := ctx.KVStore(k.storeKey) | ||
bz := store.Get(types.GetMedianKey(denom, blockNum)) | ||
if bz == nil { | ||
return sdk.ZeroDec(), sdkerrors.Wrap(types.ErrNoMedian, fmt.Sprintf("denom: %s block: %d", denom, blockNum)) | ||
} | ||
|
||
decProto := sdk.DecProto{} | ||
k.cdc.MustUnmarshal(bz, &decProto) | ||
|
||
return decProto.Dec, nil | ||
} | ||
|
||
// SetMedian uses all the historic prices of a given denom to calculate | ||
// its median price in the last prune period since the current block and | ||
// set it to the store. It will also call setMedianDeviation with the | ||
// calculated median. | ||
func (k Keeper) SetMedian( | ||
ctx sdk.Context, | ||
denom string, | ||
) { | ||
store := ctx.KVStore(k.storeKey) | ||
historicPrices := k.getHistoricPrices(ctx, denom) | ||
median := median(historicPrices) | ||
bz := k.cdc.MustMarshal(&sdk.DecProto{Dec: median}) | ||
store.Set(types.GetMedianKey(denom, uint64(ctx.BlockHeight())), bz) | ||
k.setMedianDeviation(ctx, denom, median, historicPrices) | ||
} | ||
|
||
// GetMedianDeviation returns a given denom's standard deviation around | ||
// its median price in the last prune period since a given block. | ||
func (k Keeper) GetMedianDeviation( | ||
ctx sdk.Context, | ||
denom string, | ||
blockNum uint64, | ||
) (sdk.Dec, error) { | ||
store := ctx.KVStore(k.storeKey) | ||
bz := store.Get(types.GetMedianDeviationKey(denom, blockNum)) | ||
if bz == nil { | ||
return sdk.ZeroDec(), sdkerrors.Wrap(types.ErrNoMedianDeviation, fmt.Sprintf("denom: %s block: %d", denom, blockNum)) | ||
} | ||
|
||
decProto := sdk.DecProto{} | ||
k.cdc.MustUnmarshal(bz, &decProto) | ||
|
||
return decProto.Dec, nil | ||
} | ||
|
||
// setMedianDeviation sets a given denom's standard deviation around | ||
// its median price in the last prune period since the current block. | ||
func (k Keeper) setMedianDeviation( | ||
ctx sdk.Context, | ||
denom string, | ||
median sdk.Dec, | ||
prices []types.HistoricPrice, | ||
) { | ||
store := ctx.KVStore(k.storeKey) | ||
medianDeviation := medianDeviation(median, prices) | ||
bz := k.cdc.MustMarshal(&sdk.DecProto{Dec: medianDeviation}) | ||
store.Set(types.GetMedianDeviationKey(denom, uint64(ctx.BlockHeight())), bz) | ||
} | ||
|
||
// getHistoricPrices returns all the historic prices of a given denom. | ||
func (k Keeper) getHistoricPrices( | ||
ctx sdk.Context, | ||
denom string, | ||
) []types.HistoricPrice { | ||
historicPrices := []types.HistoricPrice{} | ||
|
||
k.IterateHistoricPrices(ctx, denom, func(exchangeRate sdk.Dec, blockNum uint64) bool { | ||
historicPrices = append(historicPrices, types.HistoricPrice{ | ||
ExchangeRate: exchangeRate, | ||
BlockNum: blockNum, | ||
}) | ||
|
||
return false | ||
}) | ||
|
||
return historicPrices | ||
} | ||
|
||
// IterateHistoricPrices iterates over historic prices of a given | ||
// denom in the store. | ||
// Iterator stops when exhausting the source, or when the handler returns `true`. | ||
func (k Keeper) IterateHistoricPrices( | ||
ctx sdk.Context, | ||
denom string, | ||
handler func(sdk.Dec, uint64) bool, | ||
) { | ||
store := ctx.KVStore(k.storeKey) | ||
|
||
iter := sdk.KVStorePrefixIterator(store, append(types.KeyPrefixHistoricPrice, []byte(denom)...)) | ||
defer iter.Close() | ||
|
||
for ; iter.Valid(); iter.Next() { | ||
var historicPrice types.HistoricPrice | ||
k.cdc.MustUnmarshal(iter.Value(), &historicPrice) | ||
if handler(historicPrice.ExchangeRate, historicPrice.BlockNum) { | ||
break | ||
} | ||
} | ||
} | ||
|
||
// AddHistoricPrice adds the historic price of a denom at the current | ||
// block height. | ||
func (k Keeper) AddHistoricPrice( | ||
ctx sdk.Context, | ||
denom string, | ||
exchangeRate sdk.Dec, | ||
) { | ||
store := ctx.KVStore(k.storeKey) | ||
block := uint64(ctx.BlockHeight()) | ||
bz := k.cdc.MustMarshal(&types.HistoricPrice{ | ||
ExchangeRate: exchangeRate, | ||
BlockNum: block, | ||
}) | ||
store.Set(types.GetHistoricPriceKey(denom, block), bz) | ||
} | ||
|
||
// DeleteHistoricPrice deletes the historic price of a denom at a | ||
// given block. | ||
func (k Keeper) DeleteHistoricPrice( | ||
ctx sdk.Context, | ||
denom string, | ||
blockNum uint64, | ||
) { | ||
store := ctx.KVStore(k.storeKey) | ||
store.Delete(types.GetHistoricPriceKey(denom, blockNum)) | ||
} | ||
|
||
// DeleteMedian deletes a given denom's median price in the last prune | ||
// period since a given block. | ||
func (k Keeper) DeleteMedian( | ||
ctx sdk.Context, | ||
denom string, | ||
blockNum uint64, | ||
) { | ||
store := ctx.KVStore(k.storeKey) | ||
store.Delete(types.GetMedianKey(denom, blockNum)) | ||
} | ||
|
||
// DeleteMedianDeviation deletes a given denom's standard deviation around | ||
// its median price in the last prune period since a given block. | ||
func (k Keeper) DeleteMedianDeviation( | ||
ctx sdk.Context, | ||
denom string, | ||
blockNum uint64, | ||
) { | ||
store := ctx.KVStore(k.storeKey) | ||
store.Delete(types.GetMedianDeviationKey(denom, blockNum)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package keeper_test | ||
|
||
import ( | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" | ||
|
||
"github.com/umee-network/umee/v3/x/oracle/types" | ||
) | ||
|
||
func (s *IntegrationTestSuite) TestSetHistoraclePricing() { | ||
app, ctx := s.app, s.ctx | ||
|
||
// set exchange rate in store before adding a historic price | ||
app.OracleKeeper.SetExchangeRate(ctx, displayDenom, sdk.OneDec()) | ||
rate, err := app.OracleKeeper.GetExchangeRate(ctx, displayDenom) | ||
s.Require().NoError(err) | ||
s.Require().Equal(rate, sdk.OneDec()) | ||
|
||
// add multiple historic prices to store | ||
exchangeRates := []string{"1.0", "1.2", "1.1", "1.4"} | ||
for _, exchangeRate := range exchangeRates { | ||
app.OracleKeeper.AddHistoricPrice(ctx, displayDenom, sdk.MustNewDecFromStr(exchangeRate)) | ||
|
||
// update blockheight | ||
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) | ||
} | ||
|
||
// set and check median and median standard deviation | ||
app.OracleKeeper.SetMedian(ctx, displayDenom) | ||
median, err := app.OracleKeeper.GetMedian(ctx, displayDenom, uint64(ctx.BlockHeight())) | ||
s.Require().NoError(err) | ||
s.Require().Equal(median, sdk.MustNewDecFromStr("1.15")) | ||
|
||
medianDeviation, err := app.OracleKeeper.GetMedianDeviation(ctx, displayDenom, uint64(ctx.BlockHeight())) | ||
s.Require().NoError(err) | ||
s.Require().Equal(medianDeviation, sdk.MustNewDecFromStr("0.0225")) | ||
|
||
// delete first historic price, median, and median standard deviation | ||
app.OracleKeeper.DeleteHistoricPrice(ctx, displayDenom, uint64(ctx.BlockHeight()-3)) | ||
app.OracleKeeper.DeleteMedian(ctx, displayDenom, uint64(ctx.BlockHeight())) | ||
app.OracleKeeper.DeleteMedianDeviation(ctx, displayDenom, uint64(ctx.BlockHeight())) | ||
|
||
median, err = app.OracleKeeper.GetMedian(ctx, displayDenom, uint64(ctx.BlockHeight())) | ||
s.Require().Error(err, sdkerrors.Wrap(types.ErrUnknownDenom, displayDenom)) | ||
s.Require().Equal(median, sdk.ZeroDec()) | ||
|
||
medianDeviation, err = app.OracleKeeper.GetMedianDeviation(ctx, displayDenom, uint64(ctx.BlockHeight())) | ||
s.Require().Error(err, sdkerrors.Wrap(types.ErrUnknownDenom, displayDenom)) | ||
s.Require().Equal(median, sdk.ZeroDec()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.