Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
201 changes: 201 additions & 0 deletions x/oracle/keeper/historic_price.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package keeper

import (
"sort"
"strings"

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].ExchangeRates.ExchangeRate.BigInt().
Cmp(prices[j].ExchangeRates.ExchangeRate.BigInt()) > 0
})

var medianPrice sdk.Dec
if lenPrices%2 == 0 {
medianPrice = prices[lenPrices/2-1].ExchangeRates.ExchangeRate.
Add(prices[lenPrices/2].ExchangeRates.ExchangeRate).
QuoInt64(2)
} else {
medianPrice = prices[lenPrices/2].ExchangeRates.ExchangeRate
}

return medianPrice
}

// GetMedian returns the median price of a given denom.
func (k Keeper) GetMedian(
ctx sdk.Context,
denom string,
) (sdk.Dec, error) {
store := ctx.KVStore(k.storeKey)
denom = strings.ToUpper(denom)
bz := store.Get(types.GetMedianKey(denom))
if bz == nil {
return sdk.ZeroDec(), sdkerrors.Wrap(types.ErrUnknownDenom, denom)
}

decProto := sdk.DecProto{}
k.cdc.MustUnmarshal(bz, &decProto)

return decProto.Dec, nil
}

// setMedian uses all the historic prices of given denom to set the
// the median price of that denom in the store.
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})
denom = strings.ToUpper(denom)
store.Set(types.GetMedianKey(denom), bz)
}

// GetHistoricPrice returns the historic price of a denom at a given
// block.
func (k Keeper) GetHistoricPrice(
ctx sdk.Context,
denom string,
blockNum uint64,
) (types.HistoricPrice, error) {
store := ctx.KVStore(k.storeKey)
denom = strings.ToUpper(denom)
bz := store.Get(types.GetHistoricPriceKey(denom, blockNum))
if bz == nil {
return types.HistoricPrice{}, sdkerrors.Wrap(types.ErrUnknownDenom, denom)
}

var historicPrice types.HistoricPrice
k.cdc.MustUnmarshal(bz, &historicPrice)

return historicPrice, nil
}

// GetHistoricPrices returns all the historic prices of a given denom.
func (k Keeper) GetHistoricPrices(
ctx sdk.Context,
denom string,
) []types.HistoricPrice {
denom = strings.ToUpper(denom)
historicPrices := []types.HistoricPrice{}

k.IterateHistoricPrices(ctx, denom, func(exchangeRate sdk.Dec, blockNum uint64) bool {
historicPrices = append(historicPrices, types.HistoricPrice{
ExchangeRates: types.ExchangeRateTuple{
Denom: denom,
ExchangeRate: exchangeRate,
},
BlockNum: blockNum,
})

return false
})

return historicPrices
}

// IterateHistoricPrices iterates over historic prices of a given
// denom in the store.
func (k Keeper) IterateHistoricPrices(
ctx sdk.Context,
denom string,
handler func(sdk.Dec, uint64) bool,
) {
store := ctx.KVStore(k.storeKey)

iter := sdk.KVStorePrefixIterator(store, types.KeyPrefixHistoricPrice)
defer iter.Close()

for ; iter.Valid(); iter.Next() {
key := iter.Key()
historicPriceDenom := string(key[len(types.KeyPrefixExchangeRate) : len(key)-9])
if historicPriceDenom != denom {
continue
}
historicPrice := types.HistoricPrice{}

k.cdc.MustUnmarshal(iter.Value(), &historicPrice)
if handler(historicPrice.ExchangeRates.ExchangeRate, historicPrice.BlockNum) {
break
}
}
}

// AddHistoricPrice adds the historic price of a denom at the current
// block height when called to the store. Afterwards it will call
// setMedian to update the median price of the denom in the store.
func (k Keeper) AddHistoricPrice(
ctx sdk.Context,
denom string,
exchangeRate sdk.Dec,
) {
store := ctx.KVStore(k.storeKey)
exchangeRateTuple := types.ExchangeRateTuple{
Denom: denom,
ExchangeRate: exchangeRate,
}

block := uint64(ctx.BlockHeight())
bz := k.cdc.MustMarshal(&types.HistoricPrice{
ExchangeRates: exchangeRateTuple,
BlockNum: block,
})
denom = strings.ToUpper(denom)
store.Set(types.GetHistoricPriceKey(denom, block), bz)

// update median after adding new historic price
k.setMedian(ctx, denom)
}

// 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))

// update median after deleting historic price
k.setMedian(ctx, denom)
}

func (k Keeper) ClearHistoricPrices(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, types.KeyPrefixHistoricPrice)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
store.Delete(iter.Key())
}

// clear medians as well
k.clearMedians(ctx)
}

func (k Keeper) clearMedians(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, types.KeyPrefixMedian)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
store.Delete(iter.Key())
}
}
88 changes: 88 additions & 0 deletions x/oracle/keeper/historic_price_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
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())

app.OracleKeeper.AddHistoricPrice(ctx, displayDenom, rate)
historicPrice, err := app.OracleKeeper.GetHistoricPrice(ctx, displayDenom, uint64(ctx.BlockHeight()))
s.Require().NoError(err)
s.Require().Equal(historicPrice, types.HistoricPrice{
ExchangeRates: types.ExchangeRateTuple{
Denom: displayDenom,
ExchangeRate: sdk.OneDec(),
},
BlockNum: uint64(ctx.BlockHeight()),
})

// add multiple historic prices to store
exchangeRates := []string{"1.2", "1.1", "1.4"}
for _, exchangeRate := range exchangeRates {
// update blockheight
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)

// update exchange rate in store before updating historic price
newRate := sdk.OneDec().Mul(sdk.MustNewDecFromStr(exchangeRate))
app.OracleKeeper.SetExchangeRate(ctx, displayDenom, newRate)
rate, err = app.OracleKeeper.GetExchangeRate(ctx, displayDenom)
s.Require().NoError(err)
s.Require().Equal(rate, newRate)

app.OracleKeeper.AddHistoricPrice(ctx, displayDenom, rate)
historicPrice, err = app.OracleKeeper.GetHistoricPrice(ctx, displayDenom, uint64(ctx.BlockHeight()))
s.Require().NoError(err)
s.Require().Equal(historicPrice, types.HistoricPrice{
ExchangeRates: types.ExchangeRateTuple{
Denom: displayDenom,
ExchangeRate: newRate,
},
BlockNum: uint64(ctx.BlockHeight()),
})
}

// check all historic prices were set
historicPrices := app.OracleKeeper.GetHistoricPrices(ctx, displayDenom)
s.Require().NoError(err)
s.Require().Equal(len(historicPrices), 4)

// check median was set
median, err := app.OracleKeeper.GetMedian(ctx, displayDenom)
s.Require().NoError(err)
s.Require().Equal(median, sdk.MustNewDecFromStr("1.15"))

// delete first historic price and check median was updated
app.OracleKeeper.DeleteHistoricPrice(ctx, displayDenom, uint64(ctx.BlockHeight()-3))
s.Require().NoError(err)

median, err = app.OracleKeeper.GetMedian(ctx, displayDenom)
s.Require().NoError(err)
s.Require().Equal(median, sdk.MustNewDecFromStr("1.2"))

historicPrices = app.OracleKeeper.GetHistoricPrices(ctx, displayDenom)
s.Require().NoError(err)
s.Require().Equal(len(historicPrices), 3)

// check historic prices and medians get cleared
app.OracleKeeper.ClearHistoricPrices(ctx)
s.Require().NoError(err)

historicPrices = app.OracleKeeper.GetHistoricPrices(ctx, displayDenom)
s.Require().NoError(err)
s.Require().Equal(len(historicPrices), 0)

median, err = app.OracleKeeper.GetMedian(ctx, displayDenom)
s.Require().EqualError(err, sdkerrors.Wrap(types.ErrUnknownDenom, displayDenom).Error())
s.Require().Equal(median, sdk.ZeroDec())
}
26 changes: 26 additions & 0 deletions x/oracle/keeper/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,32 @@ func (k Keeper) MinValidPerWindow(ctx sdk.Context) (res sdk.Dec) {
return
}

// StampPeriod returns the amount of blocks the historacle 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 historacle 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)
}

// 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
26 changes: 26 additions & 0 deletions x/oracle/simulations/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const (
slashFractionKey = "slash_fraction"
slashWindowKey = "slash_window"
minValidPerWindowKey = "min_valid_per_window"
stampPeriodKey = "stamp_period"
prunePeriodKey = "prune_period"
)

// GenVotePeriod produces a randomized VotePeriod in the range of [5, 100]
Expand Down Expand Up @@ -56,6 +58,16 @@ 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, 10000]
func GenStampPeriod(r *rand.Rand) uint64 {
return uint64(100 + r.Intn(10000))
}

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

// RandomizedGenState generates a random GenesisState for oracle
func RandomizedGenState(simState *module.SimulationState) {
var votePeriod uint64
Expand Down Expand Up @@ -100,6 +112,18 @@ 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 = GenSlashWindow(r) },
)

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

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