Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(oracle): add timestamp to exchange rates #2254

Merged
merged 19 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
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
5 changes: 2 additions & 3 deletions app/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,8 @@ func IntegrationTestNetworkConfig() network.Config {
// execute ballot voting and thus clear out previous exchange rates, since we
// are not running a price-feeder.
oracleGenState.Params.VotePeriod = 1000
oracleGenState.ExchangeRates = append(oracleGenState.ExchangeRates, oracletypes.NewExchangeRateTuple(
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved
params.DisplayDenom, sdk.MustNewDecFromStr("34.21"),
))
oracleGenState.ExchangeRates = append(oracleGenState.ExchangeRates, oracletypes.NewDenomExchangeRate(
params.DisplayDenom, sdk.MustNewDecFromStr("34.21"), time.Now()))
// Set mock historic medians to satisfy leverage module's 24 median requirement
for i := 1; i <= 24; i++ {
median := oracletypes.Price{
Expand Down
23 changes: 8 additions & 15 deletions proto/umee/oracle/v1/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,15 @@ option (gogoproto.goproto_getters_all) = false;

// GenesisState defines the oracle module's genesis state.
message GenesisState {
Params params = 1 [(gogoproto.nullable) = false];
repeated FeederDelegation feeder_delegations = 2
[(gogoproto.nullable) = false];
// TODO: need to update this to save data with timestamp
repeated ExchangeRateTuple exchange_rates = 3 [
(gogoproto.castrepeated) = "ExchangeRateTuples",
(gogoproto.nullable) = false
];
Params params = 1 [(gogoproto.nullable) = false];
repeated FeederDelegation feeder_delegations = 2 [(gogoproto.nullable) = false];
repeated DenomExchangeRate exchange_rates = 3 [(gogoproto.nullable) = false];
repeated MissCounter miss_counters = 4 [(gogoproto.nullable) = false];
repeated AggregateExchangeRatePrevote aggregate_exchange_rate_prevotes = 5
[(gogoproto.nullable) = false];
repeated AggregateExchangeRateVote aggregate_exchange_rate_votes = 6
[(gogoproto.nullable) = false];
repeated Price medians = 7 [(gogoproto.nullable) = false];
repeated Price historic_prices = 8 [(gogoproto.nullable) = false];
repeated Price medianDeviations = 9 [(gogoproto.nullable) = false];
repeated AggregateExchangeRatePrevote aggregate_exchange_rate_prevotes = 5 [(gogoproto.nullable) = false];
repeated AggregateExchangeRateVote aggregate_exchange_rate_votes = 6 [(gogoproto.nullable) = false];
repeated Price medians = 7 [(gogoproto.nullable) = false];
repeated Price historic_prices = 8 [(gogoproto.nullable) = false];
repeated Price medianDeviations = 9 [(gogoproto.nullable) = false];
// Historic Avg Counter params
AvgCounterParams avg_counter_params = 10 [
(gogoproto.moretags) = "yaml:\"avg_counter_params\"",
Expand Down
8 changes: 4 additions & 4 deletions proto/umee/oracle/v1/oracle.proto
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,12 @@ message AvgCounter {
google.protobuf.Timestamp start = 3 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
}

// ExchangeRate stores exchange rate with timestamp
message ExchangeRate {
// DenomExchangeRate stores exchange rate with timestamp
message DenomExchangeRate {
option (gogoproto.equal) = false;
option (gogoproto.goproto_stringer) = false;

string rate = 1 [
string denom = 1 [(gogoproto.moretags) = "yaml:\"denom\""];
gsk967 marked this conversation as resolved.
Show resolved Hide resolved
string rate = 2 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
Expand Down
1 change: 1 addition & 0 deletions x/oracle/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ func CalcPrices(ctx sdk.Context, params types.Params, k keeper.Keeper) error {
if err != nil {
return err
}
// save the exchange rate to store with denom and timestamp
k.SetExchangeRateWithEvent(ctx, denom, exchangeRate)

if k.IsPeriodLastBlock(ctx, params.HistoricStampPeriod) {
Expand Down
8 changes: 5 additions & 3 deletions x/oracle/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,9 @@ func (s *IntegrationTestSuite) TestEndBlockerVoteThreshold() {
for _, denom := range app.OracleKeeper.AcceptList(ctx) {
rate, err := app.OracleKeeper.GetExchangeRate(ctx, denom.SymbolDenom)
s.Require().NoError(err)
s.Require().Equal(types.ExchangeRate{Rate: sdk.OneDec(), Timestamp: ctx.BlockTime()},
s.Require().Equal(types.ExchangeRate{
Rate: sdk.OneDec(),
Timestamp: ctx.BlockTime()},
rate)
}

Expand Down Expand Up @@ -237,8 +239,8 @@ func (s *IntegrationTestSuite) TestEndBlockerVoteThreshold() {
rate, err := app.OracleKeeper.GetExchangeRate(ctx, "umee")
s.Require().NoError(err)
s.Require().Equal(types.ExchangeRate{Rate: sdk.OneDec(), Timestamp: ctx.BlockTime()}, rate)
rate, err = app.OracleKeeper.GetExchangeRate(ctx, "atom")
s.Require().ErrorIs(err, types.ErrUnknownDenom.Wrap("atom"))
rate, err = app.OracleKeeper.GetExchangeRate(ctx, "ATOM")
s.Require().ErrorIs(err, types.ErrUnknownDenom.Wrap("ATOM"))
s.Require().Equal(types.ExchangeRate{}, rate)
}

Expand Down
13 changes: 5 additions & 8 deletions x/oracle/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package oracle

import (
"fmt"
"time"

gsk967 marked this conversation as resolved.
Show resolved Hide resolved
sdk "github.com/cosmos/cosmos-sdk/types"

Expand All @@ -23,7 +24,7 @@ func InitGenesis(ctx sdk.Context, keeper keeper.Keeper, genState types.GenesisSt
}

for _, ex := range genState.ExchangeRates {
keeper.SetExchangeRate(ctx, ex.Denom, ex.ExchangeRate)
keeper.SetExchangeRateWithTimestamp(ctx, ex.Denom, ex.Rate, ex.Timestamp)
}

for _, mc := range genState.MissCounters {
Expand Down Expand Up @@ -91,13 +92,9 @@ func ExportGenesis(ctx sdk.Context, keeper keeper.Keeper) *types.GenesisState {
return false
})

exchangeRates := []types.ExchangeRateTuple{}
keeper.IterateExchangeRates(ctx, func(denom string, rate sdk.Dec) (stop bool) {
exchangeRates = append(exchangeRates, types.ExchangeRateTuple{
Denom: denom,
ExchangeRate: rate,
})

exchangeRates := []types.DenomExchangeRate{}
keeper.IterateExchangeRates(ctx, func(denom string, er sdk.Dec, t time.Time) (stop bool) {
exchangeRates = append(exchangeRates, types.NewDenomExchangeRate(denom, er, t))
return false
})

Expand Down
18 changes: 10 additions & 8 deletions x/oracle/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@ func (s *IntegrationTestSuite) TestGenesis_InitGenesis() {
"valid",
types.GenesisState{
Params: types.DefaultParams(),
ExchangeRates: types.ExchangeRateTuples{
types.ExchangeRateTuple{
Denom: denom,
ExchangeRate: exchangeRate,
ExchangeRates: []types.DenomExchangeRate{
{
Denom: denom,
Rate: exchangeRate,
Timestamp: ctx.BlockTime(),
},
},
HistoricPrices: types.Prices{
Expand Down Expand Up @@ -152,10 +153,11 @@ func (s *IntegrationTestSuite) TestGenesis_ExportGenesis() {
ValidatorAddress: umeevaloperAddr,
},
}
exchangeRateTuples := types.ExchangeRateTuples{
types.ExchangeRateTuple{
Denom: upperDenom,
ExchangeRate: exchangeRate,
exchangeRateTuples := []types.DenomExchangeRate{
{
Denom: upperDenom,
Rate: exchangeRate,
Timestamp: ctx.BlockTime(),
},
}
missCounters := []types.MissCounter{
Expand Down
7 changes: 4 additions & 3 deletions x/oracle/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strings"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
"google.golang.org/grpc/codes"
Expand Down Expand Up @@ -64,8 +65,8 @@ func (q querier) ExchangeRates(

exchangeRates = exchangeRates.Add(sdk.NewDecCoinFromDec(req.Denom, exchangeRate.Rate))
} else {
q.IterateExchangeRates(ctx, func(denom string, rate sdk.Dec) (stop bool) {
exchangeRates = exchangeRates.Add(sdk.NewDecCoinFromDec(denom, rate))
q.IterateExchangeRates(ctx, func(denom string, exgRate sdk.Dec, _ time.Time) (stop bool) {
exchangeRates = exchangeRates.Add(sdk.NewDecCoinFromDec(denom, exgRate))
gsk967 marked this conversation as resolved.
Show resolved Hide resolved
return false
})
}
Expand All @@ -85,7 +86,7 @@ func (q querier) ActiveExchangeRates(
ctx := sdk.UnwrapSDKContext(goCtx)

denoms := []string{}
q.IterateExchangeRates(ctx, func(denom string, _ sdk.Dec) (stop bool) {
q.IterateExchangeRates(ctx, func(denom string, _ sdk.Dec, _ time.Time) (stop bool) {
denoms = append(denoms, denom)
return false
})
Expand Down
7 changes: 6 additions & 1 deletion x/oracle/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,13 @@ func (s *IntegrationTestSuite) TestQuerier_AggregatePrevotesAppendVotes() {
}

func (s *IntegrationTestSuite) TestQuerier_AggregateVotesAppendVotes() {
exgRateTuples := types.ExchangeRateTuples{}

for _, er := range types.DefaultGenesisState().ExchangeRates {
exgRateTuples = append(exgRateTuples, types.ExchangeRateTuple{Denom: er.Denom, ExchangeRate: er.Rate})
}
s.app.OracleKeeper.SetAggregateExchangeRateVote(s.ctx, valAddr, types.NewAggregateExchangeRateVote(
types.DefaultGenesisState().ExchangeRates,
exgRateTuples,
valAddr,
))

Expand Down
33 changes: 20 additions & 13 deletions x/oracle/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper

import (
"fmt"
"time"

"github.com/cosmos/cosmos-sdk/codec"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
Expand Down Expand Up @@ -110,39 +111,45 @@ func (k Keeper) GetExchangeRateBase(ctx sdk.Context, denom string) (sdk.Dec, err
return exchangeRate.Rate.Quo(powerReduction), nil
}

// SetExchangeRate sets the consensus exchange rate of USD denominated in the
// denom asset to the store.
func (k Keeper) SetExchangeRate(ctx sdk.Context, denom string, rate sdk.Dec) {
// SetExchangeRateWithTimestamp sets the consensus exchange rate of USD denominated in the
// denom asset to the store with a timestamp specified instead of using ctx
func (k Keeper) SetExchangeRateWithTimestamp(ctx sdk.Context, denom string, rate sdk.Dec, t time.Time) {
key := types.KeyExchangeRate(denom)
val := types.ExchangeRate{Rate: rate, Timestamp: ctx.BlockTime()}
err := store.SetValue(ctx.KVStore(k.storeKey), key, &val, "exchange_rate")
val := types.ExchangeRate{Rate: rate, Timestamp: t}
err := store.SetValue[*types.ExchangeRate](ctx.KVStore(k.storeKey), key, &val, "exchange_rate")
util.Panic(err)
}

// SetExchangeRate sets the consensus exchange rate of USD denominated in the
// denom asset to the store using timestamp from ctx
func (k Keeper) SetExchangeRate(ctx sdk.Context, denom string, rate sdk.Dec) {
k.SetExchangeRateWithTimestamp(ctx, denom, rate, ctx.BlockTime())
}

// SetExchangeRateWithEvent sets an consensus
// exchange rate to the store with ABCI event
func (k Keeper) SetExchangeRateWithEvent(ctx sdk.Context, denom string, exchangeRate sdk.Dec) {
k.SetExchangeRate(ctx, denom, exchangeRate)
func (k Keeper) SetExchangeRateWithEvent(ctx sdk.Context, denom string, rate sdk.Dec) {
k.SetExchangeRateWithTimestamp(ctx, denom, rate, ctx.BlockTime())
sdkutil.Emit(&ctx, &types.EventSetFxRate{
Denom: denom, Rate: exchangeRate,
Denom: denom, Rate: rate,
})
}

// IterateExchangeRates iterates over all USD rates in the store.
// TODO: handler should use ExchangeRate type rather than Dec
func (k Keeper) IterateExchangeRates(ctx sdk.Context, handler func(string, sdk.Dec) bool) {
func (k Keeper) IterateExchangeRates(ctx sdk.Context, handler func(string, sdk.Dec, time.Time) bool) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, types.KeyPrefixExchangeRate)
defer iter.Close()
prefixLen := len(types.KeyPrefixExchangeRate)

for ; iter.Valid(); iter.Next() {
key := iter.Key()
denom := string(key[prefixLen : len(key)-1]) // -1 to remove the null suffix
var er types.ExchangeRate
k.cdc.MustUnmarshal(iter.Value(), &er)
var exgRate types.ExchangeRate
err := exgRate.Unmarshal(iter.Value())
toteki marked this conversation as resolved.
Show resolved Hide resolved
util.Panic(err)

if handler(denom, er.Rate) {
if handler(denom, exgRate.Rate, exgRate.Timestamp) {
break
}
}
Expand Down
2 changes: 1 addition & 1 deletion x/oracle/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ func (s *IntegrationTestSuite) TestGetExchangeRate_Valid() {
s.Require().NoError(err)
s.Require().Equal(rate, expected)

s.app.OracleKeeper.SetExchangeRate(s.ctx, strings.ToLower(displayDenom), sdk.OneDec())
s.app.OracleKeeper.SetExchangeRate(s.ctx, displayDenom, sdk.OneDec())
rate, err = s.app.OracleKeeper.GetExchangeRate(s.ctx, displayDenom)
s.Require().NoError(err)
s.Require().Equal(rate, expected)
Expand Down
7 changes: 4 additions & 3 deletions x/oracle/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (s *IntegrationTestSuite) TestMsgServer_AggregateExchangeRateVote() {
acceptList := s.app.OracleKeeper.GetParams(ctx).AcceptList
var acceptListFlat []string
for _, v := range acceptList {
acceptListFlat = append(acceptListFlat, v.SymbolDenom)
acceptListFlat = append(acceptListFlat, strings.ToUpper(v.SymbolDenom))
}

// No existing prevote
Expand All @@ -122,7 +122,7 @@ func (s *IntegrationTestSuite) TestMsgServer_AggregateExchangeRateVote() {
vote, err := s.app.OracleKeeper.GetAggregateExchangeRateVote(ctx, valAddr)
s.Require().Nil(err)
for _, v := range vote.ExchangeRateTuples {
s.Require().Contains(acceptListFlat, strings.ToLower(v.Denom))
s.Require().Contains(acceptListFlat, v.Denom)
}

// Valid, but with an exchange rate which isn't in AcceptList
Expand All @@ -132,12 +132,13 @@ func (s *IntegrationTestSuite) TestMsgServer_AggregateExchangeRateVote() {
types.NewAggregateExchangeRatePrevote(
hashInvalidRate, valAddr, 1,
))

_, err = s.msgServer.AggregateExchangeRateVote(sdk.WrapSDKContext(ctx), voteMsgInvalidRate)
s.Require().NoError(err)
vote, err = s.app.OracleKeeper.GetAggregateExchangeRateVote(ctx, valAddr)
s.Require().NoError(err)
for _, v := range vote.ExchangeRateTuples {
s.Require().Contains(acceptListFlat, strings.ToLower(v.Denom))
s.Require().Contains(acceptListFlat, v.Denom)
}
}

Expand Down
22 changes: 11 additions & 11 deletions x/oracle/types/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ import (
)

const (
UmeeDenom string = appparams.BondDenom
UmeeSymbol string = "umee"
UmeeExponent = uint32(6)
USDDenom string = "USD"
BlocksPerMinute = uint64(10)
BlocksPerHour = BlocksPerMinute * 60
BlocksPerDay = BlocksPerHour * 24
BlocksPerWeek = BlocksPerDay * 7
BlocksPerMonth = BlocksPerDay * 30
BlocksPerYear = BlocksPerDay * 365
MicroUnit = int64(1e6)
UmeeDenom = appparams.BondDenom
UmeeSymbol = "umee"
UmeeExponent = uint32(6)
USDDenom = "USD"
BlocksPerMinute = uint64(10)
BlocksPerHour = BlocksPerMinute * 60
BlocksPerDay = BlocksPerHour * 24
BlocksPerWeek = BlocksPerDay * 7
BlocksPerMonth = BlocksPerDay * 30
BlocksPerYear = BlocksPerDay * 365
MicroUnit = int64(1e6)
)

type (
Expand Down
49 changes: 49 additions & 0 deletions x/oracle/types/exchange_rates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package types

import (
"encoding/json"
time "time"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// NewDenomExchangeRate creates a DenomExchangeRate instance
func NewDenomExchangeRate(denom string, exchangeRate sdk.Dec, t time.Time) DenomExchangeRate {
return DenomExchangeRate{
Denom: denom,
Rate: exchangeRate,
Timestamp: t,
}
}

func (v DenomExchangeRate) String() string {
bz, _ := json.Marshal(v)
return string(bz)
}

// ExchangeRate is type for storing rate and timestamp of denom into store without denom.
type ExchangeRate struct {
Rate sdk.Dec `json:"rate"`
Timestamp time.Time `json:"timestamp"`
}

// Marshal implements store.Marshalable.
func (d *ExchangeRate) Marshal() ([]byte, error) {
return json.Marshal(d)
}
gsk967 marked this conversation as resolved.
Show resolved Hide resolved
gsk967 marked this conversation as resolved.
Show resolved Hide resolved

func (d *ExchangeRate) String() string {
out, _ := json.Marshal(d)
return string(out)
}

// MarshalTo implements store.Marshalable.
func (ExchangeRate) MarshalTo(_ []byte) (int, error) {
panic("unimplemented")
}

// Unmarshal implements store.Marshalable.
func (d *ExchangeRate) Unmarshal(data []byte) error {
err := json.Unmarshal(data, d)
return err
}
Loading
Loading