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(WIP): adding timestamp to exchange rates of denoms #2243

Closed
wants to merge 8 commits into from
3 changes: 3 additions & 0 deletions proto/umee/oracle/v1/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ message GenesisState {
(gogoproto.moretags) = "yaml:\"avg_counter_params\"",
(gogoproto.nullable) = false
];
repeated ExchangeRatesWithTimestamp exchange_rates_timestamps = 11 [
(gogoproto.nullable) = false
];
}

// FeederDelegation is the address for where oracle feeder authority are
Expand Down
11 changes: 11 additions & 0 deletions proto/umee/oracle/v1/oracle.proto
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,17 @@ message AggregateExchangeRateVote {
string voter = 2 [(gogoproto.moretags) = "yaml:\"voter\""];
}

// ExchangeRatesWithTimestamp - store the exchange rate of denom with price timestamp
message ExchangeRatesWithTimestamp {
ExchangeRateTuple exchange_rate_tuples = 1 [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this repeated or single?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

single only

(gogoproto.moretags) = "yaml:\"exchange_rate_tuples\"",
(gogoproto.castrepeated) = "ExchangeRateTuples",
(gogoproto.nullable) = false
];
// Unix timestamp when the first price was aggregated in the counter
google.protobuf.Timestamp timestamp = 2 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
}

// ExchangeRateTuple - struct to store interpreted exchange rates data to store
message ExchangeRateTuple {
option (gogoproto.equal) = false;
Expand Down
18 changes: 18 additions & 0 deletions proto/umee/oracle/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,24 @@ service Query {
option (google.api.http).get =
"/umee/historacle/v1/avg_price/{denom}";
}

// ExgRateWithTimestamps returns exchange prices.
rpc ExgRateWithTimestamps(QueryExgRateWithTimestamps)
returns (QueryExgRateWithTimestampsResponse) {
option (google.api.http).get =
"/umee/historacle/v1/exg_rates";
}
}

// QueryExgRateWithTimestamps
message QueryExgRateWithTimestamps {
// denom defines the denomination to query for.
string denom = 1;
}

// QueryHistoricPricesResponse
message QueryExgRateWithTimestampsResponse {
repeated ExchangeRatesWithTimestamp exg_rates_timestamps = 1;
}

// QueryExchangeRates is the request type for the Query/ExchangeRate RPC
Expand Down
8 changes: 8 additions & 0 deletions x/oracle/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ func InitGenesis(ctx sdk.Context, keeper keeper.Keeper, genState types.GenesisSt
keeper.SetFeederDelegation(ctx, voter, feeder)
}

for _, ex := range genState.ExchangeRatesTimestamps {
keeper.SetExchangeRateWithTimestamp(ctx, ex.ExchangeRateTuples.Denom,
ex.ExchangeRateTuples.ExchangeRate, ex.Timestamp,
)
}

for _, ex := range genState.ExchangeRates {
keeper.SetExchangeRate(ctx, ex.Denom, ex.ExchangeRate)
}
Expand Down Expand Up @@ -133,6 +139,7 @@ func ExportGenesis(ctx sdk.Context, keeper keeper.Keeper) *types.GenesisState {
medianPrices := keeper.AllMedianPrices(ctx)
medianDeviationPrices := keeper.AllMedianDeviationPrices(ctx)
hacp := keeper.GetHistoricAvgCounterParams(ctx)
exgRatesWithTimestamps := keeper.ExgRatesWithTimestamp(ctx)

return types.NewGenesisState(
params,
Expand All @@ -145,5 +152,6 @@ func ExportGenesis(ctx sdk.Context, keeper keeper.Keeper) *types.GenesisState {
medianPrices,
medianDeviationPrices,
hacp,
exgRatesWithTimestamps,
)
}
3 changes: 3 additions & 0 deletions x/oracle/keeper/end_blocker.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ func (k *Keeper) PruneAllPrices(ctx sdk.Context) {
}
}
}

// Deleting the old exchange rates of denoms and keep latest rates
k.PruneExgRates(ctx, params.HistoricStampPeriod)
}

// IsPeriodLastBlock returns true if we are at the last block of the period
Expand Down
24 changes: 24 additions & 0 deletions x/oracle/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,30 @@ type querier struct {
Keeper
}

// ExgRateWithTimestamps implements types.QueryServer.
func (q querier) ExgRateWithTimestamps(goCtx context.Context, req *types.QueryExgRateWithTimestamps) (
*types.QueryExgRateWithTimestampsResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}
ctx := sdk.UnwrapSDKContext(goCtx)
exgRatesWithTimestamp := make([]*types.ExchangeRatesWithTimestamp, 0)
if len(req.Denom) != 0 {
q.IterateExgRatesWithTimestampForDenom(ctx, req.Denom, func(exgRate types.ExchangeRatesWithTimestamp) (stop bool) {
// exchangeRates = exchangeRates.Add(sdk.NewDecCoinFromDec(denom, rate))
exgRatesWithTimestamp = append(exgRatesWithTimestamp, &exgRate)
return false
})
} else {
q.IterateExchangeRatesWithTimestamp(ctx, func(exgRate types.ExchangeRatesWithTimestamp) (stop bool) {
// exchangeRates = exchangeRates.Add(sdk.NewDecCoinFromDec(denom, rate))
exgRatesWithTimestamp = append(exgRatesWithTimestamp, &exgRate)
return false
})
}
return &types.QueryExgRateWithTimestampsResponse{ExgRatesTimestamps: exgRatesWithTimestamp}, nil
}

// NewQuerier returns an implementation of the oracle QueryServer interface
// for the provided Keeper.
func NewQuerier(keeper Keeper) types.QueryServer {
Expand Down
124 changes: 116 additions & 8 deletions x/oracle/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package keeper

import (
"fmt"
"sort"
"strings"
"time"

"github.com/cosmos/cosmos-sdk/codec"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
Expand All @@ -12,6 +14,7 @@ import (
gogotypes "github.com/gogo/protobuf/types"
"github.com/tendermint/tendermint/libs/log"

"github.com/umee-network/umee/v6/util"
"github.com/umee-network/umee/v6/util/sdkutil"
"github.com/umee-network/umee/v6/x/oracle/types"
)
Expand Down Expand Up @@ -73,17 +76,14 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger {
// GetExchangeRate gets the consensus exchange rate of USD denominated in the
// denom asset from the store.
func (k Keeper) GetExchangeRate(ctx sdk.Context, symbol string) (sdk.Dec, error) {
store := ctx.KVStore(k.storeKey)
symbol = strings.ToUpper(symbol)
b := store.Get(types.KeyExchangeRate(symbol))
if b == nil {

exgRates := k.ExgRatesWithTimestampForDenom(ctx, symbol)
if len(exgRates) == 0 {
return sdk.ZeroDec(), types.ErrUnknownDenom.Wrap(symbol)
}

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

return decProto.Dec, nil
// return latest exchange rate
return exgRates[0].ExchangeRateTuples.ExchangeRate, nil
}

// GetExchangeRateBase gets the consensus exchange rate of an asset
Expand Down Expand Up @@ -113,6 +113,113 @@ func (k Keeper) GetExchangeRateBase(ctx sdk.Context, denom string) (sdk.Dec, err
return exchangeRate.Quo(powerReduction), nil
}

// SetExchangeRateWithTimestamp
func (k Keeper) SetExchangeRateWithTimestamp(ctx sdk.Context, denom string, exchangeRate sdk.Dec, t time.Time) {
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshal(&types.ExchangeRatesWithTimestamp{
ExchangeRateTuples: types.ExchangeRateTuple{
Denom: denom,
ExchangeRate: exchangeRate,
},
Timestamp: t,
})
denom = strings.ToUpper(denom)
store.Set(types.KeyExchangeRateWithTimestamp(denom, t), bz)
}

// ExgRatesWithTimestamp returns all exchange rates with timestamps
func (k Keeper) ExgRatesWithTimestamp(ctx sdk.Context) []types.ExchangeRatesWithTimestamp {

exchangeRatesWithTimestamps := make([]types.ExchangeRatesWithTimestamp, 0)
k.IterateExchangeRatesWithTimestamp(ctx, func(exgRate types.ExchangeRatesWithTimestamp) (stop bool) {
exchangeRatesWithTimestamps = append(exchangeRatesWithTimestamps, exgRate)
return false
})

return exchangeRatesWithTimestamps
}

// ExgRatesWithTimestampForDenom returns exchange rates of given denom with timestamps
func (k Keeper) ExgRatesWithTimestampForDenom(ctx sdk.Context, denom string) []types.ExchangeRatesWithTimestamp {

exchangeRatesWithTimestamps := make([]types.ExchangeRatesWithTimestamp, 0)
k.IterateExgRatesWithTimestampForDenom(ctx, denom, func(exgRate types.ExchangeRatesWithTimestamp) (stop bool) {
// exchangeRates = exchangeRates.Add(sdk.NewDecCoinFromDec(denom, rate))
exchangeRatesWithTimestamps = append(exchangeRatesWithTimestamps, exgRate)
return false
})

sort.Slice(exchangeRatesWithTimestamps, func(i, j int) bool {
return exchangeRatesWithTimestamps[i].Timestamp.After(exchangeRatesWithTimestamps[j].Timestamp)
},
)

return exchangeRatesWithTimestamps
}

// IterateExchangeRates iterates over all USD rates in the store.
func (k Keeper) IterateExgRatesWithTimestampForDenom(ctx sdk.Context, denom string,
handler func(types.ExchangeRatesWithTimestamp) bool) {
store := ctx.KVStore(k.storeKey)
prefix := util.ConcatBytes(0, types.KeyPrefixExchangeRateWithTimeStamp, []byte(denom))
iter := sdk.KVStorePrefixIterator(store, prefix)
defer iter.Close()

for ; iter.Valid(); iter.Next() {
dp := types.ExchangeRatesWithTimestamp{}
k.cdc.MustUnmarshal(iter.Value(), &dp)

if handler(dp) {
break
}
}
}

// IterateExchangeRates iterates over all USD rates in the store.
func (k Keeper) IterateExchangeRatesWithTimestamp(ctx sdk.Context,
handler func(types.ExchangeRatesWithTimestamp) bool) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, types.KeyPrefixExchangeRateWithTimeStamp)
defer iter.Close()

for ; iter.Valid(); iter.Next() {
dp := types.ExchangeRatesWithTimestamp{}
k.cdc.MustUnmarshal(iter.Value(), &dp)

if handler(dp) {
break
}
}
}

// PruneExgRates will delete exg rates of denoms and keep only latest timestamp noOfRecords
func (k Keeper) PruneExgRates(ctx sdk.Context, noOfRecords uint64) {
exgRates := k.ExgRatesWithTimestamp(ctx)
exgRatesForDenom := make(map[string][]types.ExchangeRatesWithTimestamp, 0)
for _, er := range exgRates {
denom := er.ExchangeRateTuples.Denom
exgRatesForDenom[denom] = append(exgRatesForDenom[denom], er)
}

for _, v := range exgRatesForDenom {
if len(v) > int(noOfRecords) {
ers := v
// sort the list with descending order by timestamp
// only keep latest noOfRecords
sort.Slice(ers, func(i, j int) bool { return ers[i].Timestamp.After(ers[j].Timestamp) })
// exgRatesForDenom[k] = ers
for _, d := range ers[noOfRecords:] {
k.DeleteExgRateWithTimestamp(ctx, d.ExchangeRateTuples.Denom, d.Timestamp)
}
}
}
Comment on lines +202 to +213

Check warning

Code scanning / CodeQL

Iteration over map

Iteration over map may be a possible source of non-determinism
}

func (k Keeper) DeleteExgRateWithTimestamp(ctx sdk.Context, denom string, t time.Time) {
store := ctx.KVStore(k.storeKey)
store.Delete(types.KeyExchangeRateWithTimestamp(denom, t))
}

// 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, exchangeRate sdk.Dec) {
Expand All @@ -126,6 +233,7 @@ func (k Keeper) SetExchangeRate(ctx sdk.Context, denom string, exchangeRate sdk.
// 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)
k.SetExchangeRateWithTimestamp(ctx, denom, exchangeRate, ctx.BlockTime())
sdkutil.Emit(&ctx, &types.EventSetFxRate{
Denom: denom, Rate: exchangeRate,
})
Expand Down
3 changes: 3 additions & 0 deletions x/oracle/types/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func NewGenesisState(
medianPrices []Price,
medianDeviationPrices []Price,
acp AvgCounterParams,
exgRatesWithTimestamps []ExchangeRatesWithTimestamp,
) *GenesisState {
return &GenesisState{
Params: params,
Expand All @@ -31,6 +32,7 @@ func NewGenesisState(
Medians: medianPrices,
MedianDeviations: medianDeviationPrices,
AvgCounterParams: acp,
ExchangeRatesTimestamps: exgRatesWithTimestamps,
}
}

Expand All @@ -48,6 +50,7 @@ func DefaultGenesisState() *GenesisState {
Medians: []Price{},
MedianDeviations: []Price{},
AvgCounterParams: DefaultAvgCounterParams(),
ExchangeRatesTimestamps: []ExchangeRatesWithTimestamp{},
}
}

Expand Down
Loading