Skip to content

Commit

Permalink
feat: Update oracle endblocker for historic pricing (#1580)
Browse files Browse the repository at this point in the history
* Update oracle endblocker for historic pricing

* Stamp prices and set median after updating exchange rate

* Empty-Commit

* Update endblocker based on keeper updates

* Integrate keeper and param changes

* Fix comment

* Comment out median calc and setting in endblocker

* Make historacle keeper methods only called in experimental mode in oracle endblocker

* Add abci_test
  • Loading branch information
rbajollari authored Nov 23, 2022
1 parent 3953226 commit f468a4c
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 17 deletions.
27 changes: 26 additions & 1 deletion x/oracle/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func isPeriodLastBlock(ctx sdk.Context, blocksPerPeriod uint64) bool {
}

// EndBlocker is called at the end of every block
func EndBlocker(ctx sdk.Context, k keeper.Keeper) error {
func EndBlocker(ctx sdk.Context, k keeper.Keeper, experimental bool) error {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker)

params := k.GetParams(ctx)
Expand All @@ -41,6 +41,11 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) error {

k.ClearExchangeRates(ctx)

if isPeriodLastBlock(ctx, params.MedianPeriod) && experimental {
k.ClearMedians(ctx)
k.ClearMedianDeviations(ctx)
}

// NOTE: it filters out inactive or jailed validators
ballotDenomSlice := k.OrganizeBallotByDenom(ctx, validatorClaimMap)

Expand All @@ -56,6 +61,18 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) error {
if err = k.SetExchangeRateWithEvent(ctx, ballotDenom.Denom, exchangeRate); err != nil {
return err
}

if experimental {
// Stamp rate every stamp period if asset is set to have historic stats tracked
if isPeriodLastBlock(ctx, params.StampPeriod) && params.HistoricAcceptList.Contains(ballotDenom.Denom) {
k.AddHistoricPrice(ctx, ballotDenom.Denom, exchangeRate)
}

// Set median price every median period if asset is set to have historic stats tracked
if isPeriodLastBlock(ctx, params.MedianPeriod) && params.HistoricAcceptList.Contains(ballotDenom.Denom) {
k.CalcAndSetMedian(ctx, ballotDenom.Denom)
}
}
}

// update miss counting & slashing
Expand Down Expand Up @@ -91,6 +108,14 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) error {
k.SlashAndResetMissCounters(ctx)
}

// Prune historic prices every prune period
if isPeriodLastBlock(ctx, params.PrunePeriod) && experimental {
pruneBlock := uint64(ctx.BlockHeight()) - params.PrunePeriod
for _, v := range params.HistoricAcceptList {
k.DeleteHistoricPrice(ctx, v.String(), pruneBlock)
}
}

return nil
}

Expand Down
109 changes: 109 additions & 0 deletions x/oracle/abci_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package oracle_test

import (
"fmt"
"testing"

"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/cosmos/cosmos-sdk/x/staking/teststaking"
"github.com/stretchr/testify/suite"
"github.com/tendermint/tendermint/crypto/secp256k1"
tmrand "github.com/tendermint/tendermint/libs/rand"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"

umeeapp "github.com/umee-network/umee/v3/app"
appparams "github.com/umee-network/umee/v3/app/params"
"github.com/umee-network/umee/v3/x/oracle"
"github.com/umee-network/umee/v3/x/oracle/types"
)

const (
displayDenom string = appparams.DisplayDenom
bondDenom string = appparams.BondDenom
)

type IntegrationTestSuite struct {
suite.Suite

ctx sdk.Context
app *umeeapp.UmeeApp
}

const (
initialPower = int64(10000000000)
)

func (s *IntegrationTestSuite) SetupTest() {
require := s.Require()
isCheckTx := false
app := umeeapp.Setup(s.T(), isCheckTx, 1)
ctx := app.BaseApp.NewContext(isCheckTx, tmproto.Header{
ChainID: fmt.Sprintf("test-chain-%s", tmrand.Str(4)),
Height: int64(types.DefaultMedianPeriod) - 1,
})

oracle.InitGenesis(ctx, app.OracleKeeper, *types.DefaultGenesisState())

sh := teststaking.NewHelper(s.T(), ctx, *app.StakingKeeper)
sh.Denom = bondDenom
amt := sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction)

// mint and send coins to validators
require.NoError(app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, initCoins))
require.NoError(app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr, initCoins))
require.NoError(app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, initCoins))
require.NoError(app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr2, initCoins))

sh.CreateValidator(valAddr, valPubKey, amt, true)
sh.CreateValidator(valAddr2, valPubKey2, amt, true)

staking.EndBlocker(ctx, *app.StakingKeeper)

s.app = app
s.ctx = ctx
}

// Test addresses
var (
valPubKeys = simapp.CreateTestPubKeys(2)

valPubKey = valPubKeys[0]
pubKey = secp256k1.GenPrivKey().PubKey()
addr = sdk.AccAddress(pubKey.Address())
valAddr = sdk.ValAddress(pubKey.Address())

valPubKey2 = valPubKeys[1]
pubKey2 = secp256k1.GenPrivKey().PubKey()
addr2 = sdk.AccAddress(pubKey2.Address())
valAddr2 = sdk.ValAddress(pubKey2.Address())

initTokens = sdk.TokensFromConsensusPower(initialPower, sdk.DefaultPowerReduction)
initCoins = sdk.NewCoins(sdk.NewCoin(bondDenom, initTokens))
)

func (s *IntegrationTestSuite) TestEndblockerExperimentalFlag() {
app, ctx := s.app, s.ctx

// add historic price and calcSet median stats
app.OracleKeeper.AddHistoricPrice(s.ctx, displayDenom, sdk.MustNewDecFromStr("1.0"))
app.OracleKeeper.CalcAndSetMedian(s.ctx, displayDenom)

// with experimental flag off median stats don't get cleared
oracle.EndBlocker(ctx, app.OracleKeeper, false)
median, err := app.OracleKeeper.GetMedian(s.ctx, displayDenom)
s.Require().NoError(err)
s.Require().Equal(sdk.MustNewDecFromStr("1.0"), median)

// with experimental flag on median stats get cleared
oracle.EndBlocker(ctx, app.OracleKeeper, true)
median, err = app.OracleKeeper.GetMedian(s.ctx, displayDenom)
s.Require().Error(err)
s.Require().Equal(sdk.ZeroDec(), median)
}

func TestOracleTestSuite(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}
26 changes: 23 additions & 3 deletions x/oracle/keeper/historic_price.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,7 @@ func (k Keeper) DeleteHistoricPrice(
store.Delete(types.KeyHistoricPrice(denom, blockNum))
}

// DeleteMedian deletes a given denom's median price in the last prune
// period since a given block.
// DeleteMedian deletes a given denom's median price.
func (k Keeper) DeleteMedian(
ctx sdk.Context,
denom string,
Expand All @@ -231,11 +230,32 @@ func (k Keeper) DeleteMedian(
}

// DeleteMedianDeviation deletes a given denom's standard deviation around
// its median price in the last prune period since a given block.
// its median price.
func (k Keeper) DeleteMedianDeviation(
ctx sdk.Context,
denom string,
) {
store := ctx.KVStore(k.storeKey)
store.Delete(types.KeyMedianDeviation(denom))
}

// ClearMedians iterates through all medians in the store and deletes them.
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())
}
}

// ClearMedianDeviations iterates through all median deviations in the store
// and deletes them.
func (k Keeper) ClearMedianDeviations(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, types.KeyPrefixMedianDeviation)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
store.Delete(iter.Key())
}
}
9 changes: 5 additions & 4 deletions x/oracle/keeper/historic_price_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ func (s *IntegrationTestSuite) TestSetHistoraclePricing() {

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

for i, exchangeRate := range exchangeRates {
// update blockheight
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + int64(i))

app.OracleKeeper.AddHistoricPrice(ctx, displayDenom, sdk.MustNewDecFromStr(exchangeRate))
app.OracleKeeper.CalcAndSetMedian(ctx, displayDenom)
}

// set and check median and median standard deviation
Expand Down
2 changes: 1 addition & 1 deletion x/oracle/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {}
// EndBlock executes all ABCI EndBlock logic respective to the x/oracle module.
// It returns no validator updates.
func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
if err := EndBlocker(ctx, am.keeper); err != nil {
if err := EndBlocker(ctx, am.keeper, am.experimental); err != nil {
panic(err)
}

Expand Down
4 changes: 4 additions & 0 deletions x/oracle/types/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ func (p Params) Validate() error {
return fmt.Errorf("oracle parameter MedianPeriod must be greater than or equal with StampPeriod")
}

if p.StampPeriod%p.VotePeriod != 0 || p.MedianPeriod%p.VotePeriod != 0 || p.PrunePeriod%p.VotePeriod != 0 {
return fmt.Errorf("oracle parameters StampPeriod, MedianPeriod, and PrunePeriod must be exact multiples of VotePeiod")
}

for _, denom := range p.AcceptList {
if len(denom.BaseDenom) == 0 {
return fmt.Errorf("oracle parameter AcceptList Denom must have BaseDenom")
Expand Down
29 changes: 21 additions & 8 deletions x/oracle/types/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,21 +241,34 @@ func TestParamsEqual(t *testing.T) {
err = p10.Validate()
require.Error(t, err)

// empty name
// StampPeriod, MedianPeriod, PrunePeriod are multiples of VotePeriod
p11 := DefaultParams()
p11.AcceptList[0].BaseDenom = ""
p11.AcceptList[0].SymbolDenom = "ATOM"
p11.StampPeriod = 10
p11.VotePeriod = 3
err = p11.Validate()
require.Error(t, err)
p11.MedianPeriod = 10
err = p11.Validate()
require.Error(t, err)
p11.PrunePeriod = 10
err = p11.Validate()
require.Error(t, err)

// empty
// empty name
p12 := DefaultParams()
p12.AcceptList[0].BaseDenom = "uatom"
p12.AcceptList[0].SymbolDenom = ""
p12.AcceptList[0].BaseDenom = ""
p12.AcceptList[0].SymbolDenom = "ATOM"
err = p12.Validate()
require.Error(t, err)

// empty
p13 := DefaultParams()
require.NotNil(t, p13.ParamSetPairs())
require.NotNil(t, p13.String())
p13.AcceptList[0].BaseDenom = "uatom"
p13.AcceptList[0].SymbolDenom = ""
err = p13.Validate()
require.Error(t, err)

p14 := DefaultParams()
require.NotNil(t, p14.ParamSetPairs())
require.NotNil(t, p14.String())
}

0 comments on commit f468a4c

Please sign in to comment.