Skip to content
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
8b6ee7d
Add keeper methods for historacle prices and medians
rbajollari Nov 4, 2022
b52ecac
Merge branch 'main' into ryan/historacle-keeper
rbajollari Nov 4, 2022
38bbb07
Add HistoraclePricing test
rbajollari Nov 7, 2022
d5951c0
Merge branch 'main' into ryan/historacle-keeper
rbajollari Nov 7, 2022
7c1d213
fix sim-non-determinism
rbajollari Nov 7, 2022
b8e0987
get rid of getstampperiodhistoricprices
rbajollari Nov 7, 2022
00245a7
Update x/oracle/keeper/params.go
rbajollari Nov 8, 2022
515d608
Update x/oracle/keeper/params.go
rbajollari Nov 8, 2022
b2ce838
Update x/oracle/keeper/historic_price.go
rbajollari Nov 8, 2022
cf36377
Update x/oracle/keeper/historic_price.go
rbajollari Nov 8, 2022
bb7879d
Update x/oracle/keeper/historic_price.go
rbajollari Nov 8, 2022
629e188
Update x/oracle/keeper/historic_price.go
rbajollari Nov 8, 2022
943a300
Update x/oracle/types/params.go
rbajollari Nov 8, 2022
790e150
Update x/oracle/keeper/historic_price.go
rbajollari Nov 8, 2022
fcb811d
PR suggestions
rbajollari Nov 8, 2022
cc0c293
Merge branch 'main' into ryan/historacle-keeper
rbajollari Nov 8, 2022
ea36bef
Merge branch 'main' into ryan/historacle-keeper
adamewozniak Nov 9, 2022
6abbd31
Merge branch 'main' into ryan/historacle-keeper
rbajollari Nov 9, 2022
af8c1e6
Add median deviation keeper
rbajollari Nov 9, 2022
b9f974d
gofmt
rbajollari Nov 9, 2022
b719546
Update proto with MedianPeriod and leaner HistoricPrice type
rbajollari Nov 9, 2022
b38c8bb
Merge branch 'main' into ryan/historacle-keeper
adamewozniak Nov 9, 2022
7514900
Add delete methods for median and median deviation
rbajollari Nov 9, 2022
676b37d
Merge branch 'main' into ryan/historacle-keeper
rbajollari Nov 10, 2022
6d6a69d
Update proto/umee/oracle/v1/oracle.proto
rbajollari Nov 10, 2022
c49a4f8
Update proto/umee/oracle/v1/oracle.proto
rbajollari Nov 10, 2022
e8262d4
Update x/oracle/keeper/historic_price.go
rbajollari Nov 10, 2022
8b84251
Update x/oracle/keeper/historic_price.go
rbajollari Nov 10, 2022
54b133e
Make historic price getter private and create error types for medians
rbajollari Nov 10, 2022
7a4dcf9
Merge branch 'main' into ryan/historacle-keeper
rbajollari Nov 10, 2022
85781b9
Add method for appending denom and block to key
rbajollari Nov 10, 2022
fe75545
Merge branch 'main' into ryan/historacle-keeper
rbajollari Nov 10, 2022
fbb76be
Empty-Commit
rbajollari Nov 10, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions proto/umee/oracle/v1/oracle.proto
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ message Params {
// Prune Period represents the maximum amount of blocks which we want
// to keep a record of our set of exchange rates.
uint64 prune_period = 10;
// 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.
uint64 median_period = 11;
}

// Denom - the object to hold configurations of each denom
Expand Down Expand Up @@ -107,9 +111,9 @@ message ExchangeRateTuple {

// HistoricPrice is an instance of a price "stamp"
message HistoricPrice {
ExchangeRateTuple exchange_rates = 1 [
(gogoproto.castrepeated) = "ExchangeRateTuples",
(gogoproto.nullable) = false
ExchangeRateTuple exchange_rate = 1 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
uint64 block_num = 2;
}
205 changes: 205 additions & 0 deletions x/oracle/keeper/historic_price.go
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))
}
50 changes: 50 additions & 0 deletions x/oracle/keeper/historic_price_test.go
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())
}
41 changes: 41 additions & 0 deletions x/oracle/keeper/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,47 @@ func (k Keeper) MinValidPerWindow(ctx sdk.Context) (res sdk.Dec) {
return
}

// StampPeriod returns the amount of blocks the oracle module waits
// between recording a set of prices.
func (k Keeper) StampPeriod(ctx sdk.Context) (res uint64) {
k.paramSpace.Get(ctx, types.KeyStampPeriod, &res)
return
}

// SetStampPeriod updates the amount of blocks the oracle module waits
// between recording a set of prices.
func (k Keeper) SetStampPeriod(ctx sdk.Context, stampPeriod uint64) {
k.paramSpace.Set(ctx, types.KeyStampPeriod, stampPeriod)
}

// PrunePeriod returns the max amount of blocks that a record of the set
// of exchanges is kept.
func (k Keeper) PrunePeriod(ctx sdk.Context) (res uint64) {
k.paramSpace.Get(ctx, types.KeyPrunePeriod, &res)
return
}

// SetPrunePeriod updates the max amount of blocks that a record of the set
// of exchanges is kept.
func (k Keeper) SetPrunePeriod(ctx sdk.Context, prunePeriod uint64) {
k.paramSpace.Set(ctx, types.KeyPrunePeriod, prunePeriod)
}

// MedianPeriod returns the amount blocks we will wait between calculating the
// median and standard deviation of the median of historic prices in the
// last Prune Period.
func (k Keeper) MedianPeriod(ctx sdk.Context) (res uint64) {
k.paramSpace.Get(ctx, types.KeyMedianPeriod, &res)
return
}

// MedianPeriod updates the amount blocks we will wait between calculating the
// median and standard deviation of the median of historic prices in the
// last Prune Period.
func (k Keeper) SetMedianPeriod(ctx sdk.Context, medianPeriod uint64) {
k.paramSpace.Set(ctx, types.KeyMedianPeriod, medianPeriod)
}

// GetParams returns the total set of oracle parameters.
func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) {
k.paramSpace.GetParamSet(ctx, &params)
Expand Down
39 changes: 39 additions & 0 deletions x/oracle/simulations/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ const (
slashFractionKey = "slash_fraction"
slashWindowKey = "slash_window"
minValidPerWindowKey = "min_valid_per_window"
stampPeriodKey = "stamp_period"
prunePeriodKey = "prune_period"
medianPeriodKey = "median_period"
)

// GenVotePeriod produces a randomized VotePeriod in the range of [5, 100]
Expand Down Expand Up @@ -56,6 +59,21 @@ func GenMinValidPerWindow(r *rand.Rand) sdk.Dec {
return sdk.ZeroDec().Add(sdk.NewDecWithPrec(int64(r.Intn(500)), 3))
}

// GenStampPeriod produces a randomized StampPeriod in the range of [100, 1000]
func GenStampPeriod(r *rand.Rand) uint64 {
return uint64(100 + r.Intn(1000))
}

// GenPrunePeriod produces a randomized PrunePeriod in the range of [10001, 100000]
func GenPrunePeriod(r *rand.Rand) uint64 {
return uint64(10001 + r.Intn(100000))
}

// GenMedianPeriod produces a randomized MedianPeriod in the range of [1001, 10000]
func GenMedianPeriod(r *rand.Rand) uint64 {
return uint64(1001 + r.Intn(10000))
}

// RandomizedGenState generates a random GenesisState for oracle
func RandomizedGenState(simState *module.SimulationState) {
var votePeriod uint64
Expand Down Expand Up @@ -100,6 +118,24 @@ func RandomizedGenState(simState *module.SimulationState) {
func(r *rand.Rand) { minValidPerWindow = GenMinValidPerWindow(r) },
)

var stampPeriod uint64
simState.AppParams.GetOrGenerate(
simState.Cdc, stampPeriodKey, &stampPeriod, simState.Rand,
func(r *rand.Rand) { stampPeriod = GenStampPeriod(r) },
)

var prunePeriod uint64
simState.AppParams.GetOrGenerate(
simState.Cdc, prunePeriodKey, &prunePeriod, simState.Rand,
func(r *rand.Rand) { prunePeriod = GenPrunePeriod(r) },
)

var medianPeriod uint64
simState.AppParams.GetOrGenerate(
simState.Cdc, medianPeriodKey, &medianPeriod, simState.Rand,
func(r *rand.Rand) { medianPeriod = GenMedianPeriod(r) },
)

oracleGenesis := types.NewGenesisState(
types.Params{
VotePeriod: votePeriod,
Expand All @@ -112,6 +148,9 @@ func RandomizedGenState(simState *module.SimulationState) {
SlashFraction: slashFraction,
SlashWindow: slashWindow,
MinValidPerWindow: minValidPerWindow,
StampPeriod: stampPeriod,
PrunePeriod: prunePeriod,
MedianPeriod: medianPeriod,
},
[]types.ExchangeRateTuple{},
[]types.FeederDelegation{},
Expand Down
Loading