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: pricefeed #639

Merged
merged 1 commit into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 0 additions & 1 deletion proto/ununifi/pricefeed/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ message MsgPostPrice {
(gogoproto.nullable) = false,
(gogoproto.stdtime) = true
];
cosmos.base.v1beta1.Coin deposit = 5 [(gogoproto.nullable) = false];
}

message MsgPostPriceResponse {}
2 changes: 1 addition & 1 deletion x/pricefeed/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ $ %s tx %s postprice uusdc:ubtc 24528.185864015486004064 60 --from myKeyName --
return err
}
expiry := now.Add(time.Second * time.Duration(expirySec))
msg := types.NewMsgPostPrice(clientCtx.GetFromAddress().String(), marketId, price, expiry, sdk.NewCoin("uusdc", sdk.NewInt(1000))) // TODO: deposit
msg := types.NewMsgPostPrice(clientCtx.GetFromAddress().String(), marketId, price, expiry)
if err := msg.ValidateBasic(); err != nil {
return err
}
Expand Down
16 changes: 6 additions & 10 deletions x/pricefeed/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,19 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState)
}
}
}
params := genState.Params
params := k.GetParams(ctx)

// Set the current price (if any) based on what's now in the store
for _, market := range params.Markets {
if !market.Active {
continue
}
rps, err := k.GetRawPrices(ctx, market.MarketId)
if err != nil {
panic(err)
}
rps := k.GetRawPrices(ctx, market.MarketId)

if len(rps) == 0 {
continue
}
err = k.SetCurrentPrices(ctx, market.MarketId)
err := k.SetCurrentPrices(ctx, market.MarketId)
if err != nil {
panic(err)
}
Expand All @@ -49,14 +47,12 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState)

// ExportGenesis returns the capability module's exported genesis.
func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState {
// Get the params for markets and oracles
params := k.GetParams(ctx)

var postedPrices []types.PostedPrice
for _, market := range k.GetMarkets(ctx) {
pp, err := k.GetRawPrices(ctx, market.MarketId)
if err != nil {
panic(err)
}
pp := k.GetRawPrices(ctx, market.MarketId)
postedPrices = append(postedPrices, pp...)
}
return types.NewGenesisState(params, postedPrices)
Expand Down
5 changes: 1 addition & 4 deletions x/pricefeed/keeper/grpc_query_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"

"github.com/UnUniFi/chain/x/pricefeed/types"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
Expand All @@ -15,11 +14,9 @@ func (k Keeper) Params(c context.Context, req *types.QueryParamsRequest) (*types
return nil, status.Error(codes.InvalidArgument, "invalid request")
}

var params types.Params
ctx := sdk.UnwrapSDKContext(c)

store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.ParamsKey))
k.cdc.MustUnmarshal(store.Get(types.KeyPrefix(types.ParamsKey)), &params)
params := k.GetParams(ctx)

return &types.QueryParamsResponse{Params: &params}, nil
}
6 changes: 1 addition & 5 deletions x/pricefeed/keeper/grpc_query_rawprice.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,9 @@ func (k Keeper) RawPriceAll(c context.Context, req *types.QueryAllRawPriceReques
return nil, status.Error(codes.InvalidArgument, "invalid request")
}

var prices types.PostedPrices
ctx := sdk.UnwrapSDKContext(c)

prices, err := k.GetRawPrices(ctx, req.MarketId)
if err != nil {
return nil, status.Error(codes.NotFound, "not found")
}
prices := k.GetRawPrices(ctx, req.MarketId)

return &types.QueryAllRawPriceResponse{Prices: prices}, nil
}
112 changes: 39 additions & 73 deletions x/pricefeed/keeper/keeper.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
package keeper

import (
"encoding/json"
"fmt"
"sort"
"time"

"github.com/cometbft/cometbft/libs/log"

errorsmod "cosmossdk.io/errors"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"

"github.com/UnUniFi/chain/x/pricefeed/types"
Expand Down Expand Up @@ -46,22 +44,6 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
}

func (k Keeper) ValidateAuthorityAndDeposit(ctx sdk.Context, marketId string, address sdk.AccAddress, deposit sdk.Coin) error {
params := k.GetParams(ctx)
if deposit.IsLT(params.DepositForPosting) {
k.bankKeeper.SendCoinsFromAccountToModule(ctx, address, types.ModuleName, sdk.NewCoins(deposit))
return sdkerrors.Wrapf(types.ErrInvalidOracle, "price deposit %s is less than minimum price deposit %s", deposit, params.DepositForPosting)
}

_, err := k.GetOracle(ctx, marketId, address)
if err != nil {
k.bankKeeper.SendCoinsFromAccountToModule(ctx, address, types.ModuleName, sdk.NewCoins(deposit))
return err
}

return nil
}

// SetPrice updates the posted price for a specific oracle
func (k Keeper) SetPrice(
ctx sdk.Context,
Expand All @@ -76,27 +58,8 @@ func (k Keeper) SetPrice(
}

store := ctx.KVStore(k.storeKey)
prices, err := k.GetRawPrices(ctx, marketID)
if err != nil {
return types.PostedPrice{}, err
}
var index int
found := false
for i := range prices {
if prices[i].OracleAddress == oracle.String() {
index = i
found = true
break
}
}

// set the price for that particular oracle
if found {
prices[index] = types.NewPostedPrice(marketID, oracle.String(), price, expiry)
} else {
prices = append(prices, types.NewPostedPrice(marketID, oracle.String(), price, expiry))
index = len(prices) - 1
}
newRawPrice := types.NewPostedPrice(marketID, oracle.String(), price, expiry)

// Emit an event containing the oracle's new price
ctx.EventManager().EmitEvent(
Expand All @@ -109,16 +72,16 @@ func (k Keeper) SetPrice(
),
)

bz, _ := json.Marshal(prices)
store.Set(types.RawPriceKeySuffix(marketID), bz)
return prices[index], nil
// Sets the raw price for a single oracle instead of an array of all oracle's raw prices
store.Set(types.RawPriceKey(marketID, oracle), k.cdc.MustMarshal(&newRawPrice))
return newRawPrice, nil
}

// SetCurrentPrices updates the price of an asset to the median of all valid oracle inputs
func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) error {
_, ok := k.GetMarket(ctx, marketID)
if !ok {
return sdkerrors.Wrap(types.ErrInvalidMarket, marketID)
return errorsmod.Wrap(types.ErrInvalidMarket, marketID)
}
// store current price
validPrevPrice := true
Expand All @@ -127,11 +90,9 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) error {
validPrevPrice = false
}

prices, err := k.GetRawPrices(ctx, marketID)
if err != nil {
return err
}
var notExpiredPrices types.CurrentPrices
prices := k.GetRawPrices(ctx, marketID)

var notExpiredPrices []types.CurrentPrice
// filter out expired prices
for _, v := range prices {
if v.Expiry.After(ctx.BlockTime()) {
Expand All @@ -143,12 +104,12 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) error {
// NOTE: The current price stored will continue storing the most recent (expired)
// price if this is not set.
// This zero's out the current price stored value for that market and ensures
// that Cdp methods that GetCurrentPrice will return error.
// that CDP methods that GetCurrentPrice will return error.
k.setCurrentPrice(ctx, marketID, types.CurrentPrice{})
return types.ErrNoValidPrice
}

medianPrice := k.CalculateMedianPrice(ctx, notExpiredPrices)
medianPrice := k.CalculateMedianPrice(notExpiredPrices)

// check case that market price was not set in genesis
if validPrevPrice && !medianPrice.Equal(prevPrice.Price) {
Expand All @@ -170,11 +131,11 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) error {

func (k Keeper) setCurrentPrice(ctx sdk.Context, marketID string, currentPrice types.CurrentPrice) {
store := ctx.KVStore(k.storeKey)
store.Set(types.CurrentPriceKeySuffix(marketID), k.cdc.MustMarshal(&currentPrice))
store.Set(types.CurrentPriceKey(marketID), k.cdc.MustMarshal(&currentPrice))
}

// CalculateMedianPrice calculates the median prices for the input prices.
func (k Keeper) CalculateMedianPrice(ctx sdk.Context, prices types.CurrentPrices) sdk.Dec {
func (k Keeper) CalculateMedianPrice(prices []types.CurrentPrice) sdk.Dec {
l := len(prices)

if l == 1 {
Expand All @@ -187,34 +148,32 @@ func (k Keeper) CalculateMedianPrice(ctx sdk.Context, prices types.CurrentPrices
})
// for even numbers of prices, the median is calculated as the mean of the two middle prices
if l%2 == 0 {
median := k.calculateMeanPrice(ctx, prices[l/2-1:l/2+1])
median := k.calculateMeanPrice(prices[l/2-1], prices[l/2])
return median
}
// for odd numbers of prices, return the middle element
return prices[l/2].Price

}

func (k Keeper) calculateMeanPrice(ctx sdk.Context, prices types.CurrentPrices) sdk.Dec {
sum := prices[0].Price.Add(prices[1].Price)
func (k Keeper) calculateMeanPrice(priceA, priceB types.CurrentPrice) sdk.Dec {
sum := priceA.Price.Add(priceB.Price)
mean := sum.Quo(sdk.NewDec(2))
return mean
}

// GetCurrentPrice fetches the current median price of all oracles for a specific market
func (k Keeper) GetCurrentPrice(ctx sdk.Context, marketID string) (types.CurrentPrice, error) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.CurrentPriceKeySuffix(marketID))
bz := store.Get(types.CurrentPriceKey(marketID))

if bz == nil {
return types.CurrentPrice{}, types.ErrNoValidPrice
}

var price types.CurrentPrice
err := k.cdc.Unmarshal(bz, &price)
if err != nil {
return types.CurrentPrice{}, err
}

if price.Price.Equal(sdk.ZeroDec()) {
return types.CurrentPrice{}, types.ErrNoValidPrice
}
Expand All @@ -223,8 +182,7 @@ func (k Keeper) GetCurrentPrice(ctx sdk.Context, marketID string) (types.Current

// IterateCurrentPrices iterates over all current price objects in the store and performs a callback function
func (k Keeper) IterateCurrentPrices(ctx sdk.Context, cb func(cp types.CurrentPrice) (stop bool)) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.CurrentPriceKey))
iterator := sdk.KVStorePrefixIterator(store, []byte{})
iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.CurrentPricePrefix)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var cp types.CurrentPrice
Expand All @@ -237,7 +195,7 @@ func (k Keeper) IterateCurrentPrices(ctx sdk.Context, cb func(cp types.CurrentPr

// GetCurrentPrices returns all current price objects from the store
func (k Keeper) GetCurrentPrices(ctx sdk.Context) types.CurrentPrices {
cps := types.CurrentPrices{}
var cps types.CurrentPrices
k.IterateCurrentPrices(ctx, func(cp types.CurrentPrice) (stop bool) {
cps = append(cps, cp)
return false
Expand All @@ -246,16 +204,24 @@ func (k Keeper) GetCurrentPrices(ctx sdk.Context) types.CurrentPrices {
}

// GetRawPrices fetches the set of all prices posted by oracles for an asset
func (k Keeper) GetRawPrices(ctx sdk.Context, marketID string) (types.PostedPrices, error) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.RawPriceKeySuffix(marketID))
if bz == nil {
return types.PostedPrices{}, nil
}
var prices types.PostedPrices
err := json.Unmarshal(bz, &prices)
if err != nil {
return types.PostedPrices{}, err
func (k Keeper) GetRawPrices(ctx sdk.Context, marketId string) types.PostedPrices {
var pps types.PostedPrices
k.IterateRawPricesByMarket(ctx, marketId, func(pp types.PostedPrice) (stop bool) {
pps = append(pps, pp)
return false
})
return pps
}

// IterateRawPrices iterates over all raw prices in the store and performs a callback function
func (k Keeper) IterateRawPricesByMarket(ctx sdk.Context, marketId string, cb func(record types.PostedPrice) (stop bool)) {
iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.RawPriceIteratorKey((marketId)))
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var record types.PostedPrice
k.cdc.MustUnmarshal(iterator.Value(), &record)
if cb(record) {
break
}
}
return prices, nil
}
Loading