mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-10-31 17:07:28 +00:00 
			
		
		
		
	feat: draft params for pricefeed
This commit is contained in:
		
							parent
							
								
									57a1532db4
								
							
						
					
					
						commit
						d5da161dd8
					
				
							
								
								
									
										1
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.sum
									
									
									
									
									
								
							| @ -43,6 +43,7 @@ github.com/cosmos/cosmos-sdk v0.34.4-0.20191010155330-64a27412505c/go.mod h1:Fxj | ||||
| github.com/cosmos/cosmos-sdk v0.34.4-0.20191010193331-18de630d0ae1 h1:yb+E8HGzFnO0YwLS6OCBIAVWtN8KfCYoKeO9mgAmQN0= | ||||
| github.com/cosmos/cosmos-sdk v0.34.4-0.20191010193331-18de630d0ae1/go.mod h1:IGBhkbOK1ebLqMWjtgo99zUxWHsA5IOb6N9CI8nHs0Y= | ||||
| github.com/cosmos/cosmos-sdk v0.37.1 h1:mz5W3Au32VIPPtrY65dheVYeVDSFfS3eSSmuIj+cXsI= | ||||
| github.com/cosmos/cosmos-sdk v0.37.4 h1:1ioXxkpiS+wOgaUbROeDIyuF7hciU5nti0TSyBmV2Ok= | ||||
| github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8 h1:Iwin12wRQtyZhH6FV3ykFcdGNlYEzoeR0jN8Vn+JWsI= | ||||
| github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= | ||||
| github.com/cosmos/ledger-cosmos-go v0.10.3 h1:Qhi5yTR5Pg1CaTpd00pxlGwNl4sFRdtK1J96OTjeFFc= | ||||
|  | ||||
							
								
								
									
										20
									
								
								x/pricefeed/abci.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								x/pricefeed/abci.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| package pricefeed | ||||
| 
 | ||||
| import ( | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| ) | ||||
| 
 | ||||
| // EndBlocker updates the current pricefeed
 | ||||
| func EndBlocker(ctx sdk.Context, k Keeper) { | ||||
| 	// Update the current price of each asset.
 | ||||
| 	for _, a := range k.GetAssetParams(ctx) { | ||||
| 		if a.Active { | ||||
| 			err := k.SetCurrentPrices(ctx, a.AssetCode) | ||||
| 			if err != nil { | ||||
| 				// TODO emit an event that price failed to update
 | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| @ -2,26 +2,34 @@ | ||||
| // autogenerated code using github.com/rigelrozanski/multitool
 | ||||
| // aliases generated for the following subdirectories:
 | ||||
| // ALIASGEN: github.com/kava-labs/kava/x/pricefeed/types/
 | ||||
| // ALIASGEN: github.com/kava-labs/kava/x/pricefeed/keeper/
 | ||||
| package pricefeed | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/kava-labs/kava/x/pricefeed/keeper" | ||||
| 	"github.com/kava-labs/kava/x/pricefeed/types" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	DefaultCodespace  = types.DefaultCodespace | ||||
| 	CodeEmptyInput    = types.CodeEmptyInput | ||||
| 	CodeExpired       = types.CodeExpired | ||||
| 	CodeInvalidPrice  = types.CodeInvalidPrice | ||||
| 	CodeInvalidAsset  = types.CodeInvalidAsset | ||||
| 	CodeInvalidOracle = types.CodeInvalidOracle | ||||
| 	ModuleName        = types.ModuleName | ||||
| 	StoreKey          = types.StoreKey | ||||
| 	RouterKey         = types.RouterKey | ||||
| 	TypeMsgPostPrice  = types.TypeMsgPostPrice | ||||
| 	QueryCurrentPrice = types.QueryCurrentPrice | ||||
| 	QueryRawPrices    = types.QueryRawPrices | ||||
| 	QueryAssets       = types.QueryAssets | ||||
| 	DefaultCodespace   = types.DefaultCodespace | ||||
| 	CodeEmptyInput     = types.CodeEmptyInput | ||||
| 	CodeExpired        = types.CodeExpired | ||||
| 	CodeInvalidPrice   = types.CodeInvalidPrice | ||||
| 	CodeInvalidAsset   = types.CodeInvalidAsset | ||||
| 	CodeInvalidOracle  = types.CodeInvalidOracle | ||||
| 	ModuleName         = types.ModuleName | ||||
| 	StoreKey           = types.StoreKey | ||||
| 	RouterKey          = types.RouterKey | ||||
| 	QuerierRoute       = types.QuerierRoute | ||||
| 	DefaultParamspace  = types.DefaultParamspace | ||||
| 	RawPriceFeedPrefix = types.RawPriceFeedPrefix | ||||
| 	CurrentPricePrefix = types.CurrentPricePrefix | ||||
| 	AssetPrefix        = types.AssetPrefix | ||||
| 	OraclePrefix       = types.OraclePrefix | ||||
| 	TypeMsgPostPrice   = types.TypeMsgPostPrice | ||||
| 	QueryCurrentPrice  = types.QueryCurrentPrice | ||||
| 	QueryRawPrices     = types.QueryRawPrices | ||||
| 	QueryAssets        = types.QueryAssets | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| @ -37,28 +45,29 @@ var ( | ||||
| 	ValidateGenesis     = types.ValidateGenesis | ||||
| 	NewMsgPostPrice     = types.NewMsgPostPrice | ||||
| 	ParamKeyTable       = types.ParamKeyTable | ||||
| 	NewAssetParams      = types.NewAssetParams | ||||
| 	DefaultAssetParams  = types.DefaultAssetParams | ||||
| 	NewOracleParams     = types.NewOracleParams | ||||
| 	DefaultOracleParams = types.DefaultOracleParams | ||||
| 	NewParams           = types.NewParams | ||||
| 	DefaultParams       = types.DefaultParams | ||||
| 	NewKeeper           = keeper.NewKeeper | ||||
| 	NewQuerier          = keeper.NewQuerier | ||||
| 
 | ||||
| 	// variable aliases
 | ||||
| 	ModuleCdc            = types.ModuleCdc | ||||
| 	ParamStoreKeyOracles = types.ParamStoreKeyOracles | ||||
| 	ParamStoreKeyAssets  = types.ParamStoreKeyAssets | ||||
| 	ModuleCdc = types.ModuleCdc | ||||
| 	KeyAssets = types.KeyAssets | ||||
| ) | ||||
| 
 | ||||
| type ( | ||||
| 	GenesisState       = types.GenesisState | ||||
| 	MsgPostPrice       = types.MsgPostPrice | ||||
| 	AssetParams        = types.AssetParams | ||||
| 	OracleParams       = types.OracleParams | ||||
| 	Params             = types.Params | ||||
| 	ParamSubspace      = types.ParamSubspace | ||||
| 	QueryRawPricesResp = types.QueryRawPricesResp | ||||
| 	QueryAssetsResp    = types.QueryAssetsResp | ||||
| 	Asset              = types.Asset | ||||
| 	Assets             = types.Assets | ||||
| 	Oracle             = types.Oracle | ||||
| 	Oracles            = types.Oracles | ||||
| 	CurrentPrice       = types.CurrentPrice | ||||
| 	PostedPrice        = types.PostedPrice | ||||
| 	SortDecs           = types.SortDecs | ||||
| 	Keeper             = keeper.Keeper | ||||
| ) | ||||
|  | ||||
| @ -2,6 +2,7 @@ package cli | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/spf13/cobra" | ||||
| 
 | ||||
| @ -12,6 +13,7 @@ import ( | ||||
| 	"github.com/cosmos/cosmos-sdk/x/auth" | ||||
| 	"github.com/cosmos/cosmos-sdk/x/auth/client/utils" | ||||
| 	"github.com/kava-labs/kava/x/pricefeed/types" | ||||
| 	tmtime "github.com/tendermint/tendermint/types/time" | ||||
| ) | ||||
| 
 | ||||
| // GetTxCmd returns the transaction commands for this module
 | ||||
| @ -47,11 +49,13 @@ func GetCmdPostPrice(cdc *codec.Codec) *cobra.Command { | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			expiry, ok := sdk.NewIntFromString(args[2]) | ||||
| 			expiryInt, ok := sdk.NewIntFromString(args[2]) | ||||
| 			if !ok { | ||||
| 				fmt.Printf("invalid expiry - %s \n", args[2]) | ||||
| 				return nil | ||||
| 			} | ||||
| 			expiry := tmtime.Canonical(time.Unix(expiryInt.Int64(), 0)) | ||||
| 
 | ||||
| 			msg := types.NewMsgPostPrice(cliCtx.GetFromAddress(), args[0], price, expiry) | ||||
| 			err = msg.ValidateBasic() | ||||
| 			if err != nil { | ||||
|  | ||||
| @ -3,6 +3,7 @@ package rest | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/cosmos/cosmos-sdk/client/context" | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| @ -10,6 +11,7 @@ import ( | ||||
| 	"github.com/cosmos/cosmos-sdk/x/auth/client/utils" | ||||
| 	"github.com/gorilla/mux" | ||||
| 	"github.com/kava-labs/kava/x/pricefeed/types" | ||||
| 	tmtime "github.com/tendermint/tendermint/types/time" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| @ -57,11 +59,12 @@ func postPriceHandler(cliCtx context.CLIContext) http.HandlerFunc { | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		expiry, ok := sdk.NewIntFromString(req.Expiry) | ||||
| 		expiryInt, ok := sdk.NewIntFromString(req.Expiry) | ||||
| 		if !ok { | ||||
| 			rest.WriteErrorResponse(w, http.StatusBadRequest, "invalid expiry") | ||||
| 			return | ||||
| 		} | ||||
| 		expiry := tmtime.Canonical(time.Unix(expiryInt.Int64(), 0)) | ||||
| 
 | ||||
| 		// create the message
 | ||||
| 		msg := types.NewMsgPostPrice(addr, req.AssetCode, price, expiry) | ||||
|  | ||||
| @ -4,29 +4,25 @@ import ( | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| // InitGenesis sets distribution information for genesis.
 | ||||
| func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { | ||||
| 
 | ||||
| 	// Set the assets and oracles from params
 | ||||
| 	keeper.SetAssetParams(ctx, data.AssetParams) | ||||
| 	keeper.SetOracleParams(ctx ,data.OracleParams) | ||||
| 	keeper.SetParams(ctx, data.Params) | ||||
| 
 | ||||
| 	// Iterate through the posted prices and set them in the store
 | ||||
| 	for _, pp := range data.PostedPrices { | ||||
| 		addr, err := sdk.AccAddressFromBech32(pp.OracleAddress) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		_, err = keeper.SetPrice(ctx, addr, pp.AssetCode, pp.Price, pp.Expiry) | ||||
| 		_, err := keeper.SetPrice(ctx, pp.OracleAddress, pp.AssetCode, pp.Price, pp.Expiry) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Set the current price (if any) based on what's now in the store
 | ||||
| 	if err := keeper.SetCurrentPrices(ctx); err != nil { | ||||
| 		panic(err) | ||||
| 	for _, a := range data.Params.Assets { | ||||
| 		if a.Active { | ||||
| 			_ = keeper.SetCurrentPrices(ctx, a.AssetCode) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -34,18 +30,16 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { | ||||
| func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { | ||||
| 
 | ||||
| 	// Get the params for assets and oracles
 | ||||
| 	assetParams := keeper.GetAssetParams(ctx) | ||||
| 	oracleParams := keeper.GetOracleParams(ctx) | ||||
| 	params := keeper.GetParams(ctx) | ||||
| 
 | ||||
| 	var postedPrices []PostedPrice | ||||
| 	for _, asset := range keeper.GetAssets(ctx) { | ||||
| 	for _, asset := range keeper.GetAssetParams(ctx) { | ||||
| 		pp := keeper.GetRawPrices(ctx, asset.AssetCode) | ||||
| 		postedPrices = append(postedPrices, pp...) | ||||
| 	} | ||||
| 
 | ||||
| 	return GenesisState{ | ||||
| 		AssetParams:  assetParams, | ||||
| 		OracleParams: oracleParams, | ||||
| 		Params:       params, | ||||
| 		PostedPrices: postedPrices, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -29,22 +29,10 @@ func HandleMsgPostPrice( | ||||
| 	msg MsgPostPrice) sdk.Result { | ||||
| 
 | ||||
| 	// TODO cleanup message validation and errors
 | ||||
| 	err := k.ValidatePostPrice(ctx, msg) | ||||
| 	_, err := k.GetOracle(ctx, msg.AssetCode, msg.From) | ||||
| 	if err != nil { | ||||
| 		return err.Result() | ||||
| 		return ErrInvalidOracle(k.Codespace()).Result() | ||||
| 	} | ||||
| 	k.SetPrice(ctx, msg.From, msg.AssetCode, msg.Price, msg.Expiry) | ||||
| 	return sdk.Result{} | ||||
| } | ||||
| 
 | ||||
| // EndBlocker updates the current pricefeed
 | ||||
| func EndBlocker(ctx sdk.Context, k Keeper) { | ||||
| 	// TODO val_state_change.go is relevant if we want to rotate the oracle set
 | ||||
| 
 | ||||
| 	// Running in the end blocker ensures that prices will update at most once per block,
 | ||||
| 	// which seems preferable to having state storage values change in response to multiple transactions
 | ||||
| 	// which occur during a block
 | ||||
| 	//TODO use an iterator and update the prices for all assets in the store
 | ||||
| 	k.SetCurrentPrices(ctx) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| @ -1,280 +0,0 @@ | ||||
| package pricefeed | ||||
| 
 | ||||
| import ( | ||||
| 	"sort" | ||||
| 
 | ||||
| 	"github.com/cosmos/cosmos-sdk/codec" | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| ) | ||||
| 
 | ||||
| // TODO refactor constants to app.go
 | ||||
| const ( | ||||
| 	// QuerierRoute is the querier route for gov
 | ||||
| 	QuerierRoute = ModuleName | ||||
| 
 | ||||
| 	// Parameter store default namestore
 | ||||
| 	DefaultParamspace = ModuleName | ||||
| 
 | ||||
| 	// Store prefix for the raw pricefeed of an asset
 | ||||
| 	RawPriceFeedPrefix = StoreKey + ":raw:" | ||||
| 
 | ||||
| 	// Store prefix for the current price of an asset
 | ||||
| 	CurrentPricePrefix = StoreKey + ":currentprice:" | ||||
| 
 | ||||
| 	// Store Prefix for the assets in the pricefeed system
 | ||||
| 	AssetPrefix = StoreKey + ":assets" | ||||
| 
 | ||||
| 	// OraclePrefix store prefix for the oracle accounts
 | ||||
| 	OraclePrefix = StoreKey + ":oracles" | ||||
| ) | ||||
| 
 | ||||
| // Keeper struct for pricefeed module
 | ||||
| type Keeper struct { | ||||
| 	// The reference to the Paramstore to get and set pricefeed specific params
 | ||||
| 	paramSpace ParamSubspace | ||||
| 	// The keys used to access the stores from Context
 | ||||
| 	storeKey sdk.StoreKey | ||||
| 	// Codec for binary encoding/decoding
 | ||||
| 	cdc *codec.Codec | ||||
| 	// Reserved codespace
 | ||||
| 	codespace sdk.CodespaceType | ||||
| } | ||||
| 
 | ||||
| // NewKeeper returns a new keeper for the pricefeed module. It handles:
 | ||||
| // - adding oracles
 | ||||
| // - adding/removing assets from the pricefeed
 | ||||
| func NewKeeper( | ||||
| 	storeKey sdk.StoreKey, cdc *codec.Codec, paramSpace ParamSubspace, codespace sdk.CodespaceType, | ||||
| ) Keeper { | ||||
| 	return Keeper{ | ||||
| 		paramSpace: paramSpace, | ||||
| 		storeKey:   storeKey, | ||||
| 		cdc:        cdc, | ||||
| 		codespace:  codespace, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // // AddOracle adds an Oracle to the store
 | ||||
| // func (k Keeper) AddOracle(ctx sdk.Context, address string) {
 | ||||
| 
 | ||||
| // 	oracles := k.GetOracles(ctx)
 | ||||
| // 	oracles = append(oracles, Oracle{OracleAddress: address})
 | ||||
| // 	store := ctx.KVStore(k.storeKey)
 | ||||
| // 	store.Set(
 | ||||
| // 		[]byte(OraclePrefix), k.cdc.MustMarshalBinaryBare(oracles),
 | ||||
| // 	)
 | ||||
| // }
 | ||||
| 
 | ||||
| // // AddAsset adds an asset to the store
 | ||||
| // func (k Keeper) AddAsset(
 | ||||
| // 	ctx sdk.Context,
 | ||||
| // 	assetCode string,
 | ||||
| // 	desc string,
 | ||||
| // ) {
 | ||||
| // 	assets := k.GetAssets(ctx)
 | ||||
| // 	assets = append(assets, Asset{AssetCode: assetCode, Description: desc})
 | ||||
| // 	store := ctx.KVStore(k.storeKey)
 | ||||
| // 	store.Set(
 | ||||
| // 		[]byte(AssetPrefix), k.cdc.MustMarshalBinaryBare(assets),
 | ||||
| // 	)
 | ||||
| // }
 | ||||
| 
 | ||||
| func (k Keeper) SetAssetParams(ctx sdk.Context, ap AssetParams) { | ||||
| 	k.paramSpace.Set(ctx, ParamStoreKeyAssets, &ap) | ||||
| } | ||||
| 
 | ||||
| func (k Keeper) SetOracleParams(ctx sdk.Context, op OracleParams) { | ||||
| 	k.paramSpace.Set(ctx, ParamStoreKeyOracles, &op) | ||||
| } | ||||
| 
 | ||||
| // SetPrice updates the posted price for a specific oracle
 | ||||
| func (k Keeper) SetPrice( | ||||
| 	ctx sdk.Context, | ||||
| 	oracle sdk.AccAddress, | ||||
| 	assetCode string, | ||||
| 	price sdk.Dec, | ||||
| 	expiry sdk.Int) (PostedPrice, sdk.Error) { | ||||
| 	// If the expiry is less than or equal to the current blockheight, we consider the price valid
 | ||||
| 	if expiry.GTE(sdk.NewInt(ctx.BlockHeight())) { | ||||
| 		store := ctx.KVStore(k.storeKey) | ||||
| 		prices := k.GetRawPrices(ctx, assetCode) | ||||
| 		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] = PostedPrice{AssetCode: assetCode, OracleAddress: oracle.String(), Price: price, Expiry: expiry} | ||||
| 		} else { | ||||
| 			prices = append(prices, PostedPrice{ | ||||
| 				assetCode, oracle.String(), price, expiry, | ||||
| 			}) | ||||
| 			index = len(prices) - 1 | ||||
| 		} | ||||
| 
 | ||||
| 		store.Set( | ||||
| 			[]byte(RawPriceFeedPrefix+assetCode), k.cdc.MustMarshalBinaryBare(prices), | ||||
| 		) | ||||
| 		return prices[index], nil | ||||
| 	} | ||||
| 	return PostedPrice{}, ErrExpired(k.codespace) | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // SetCurrentPrices updates the price of an asset to the meadian of all valid oracle inputs
 | ||||
| func (k Keeper) SetCurrentPrices(ctx sdk.Context) sdk.Error { | ||||
| 	assets := k.GetAssets(ctx) | ||||
| 	for _, v := range assets { | ||||
| 		assetCode := v.AssetCode | ||||
| 		prices := k.GetRawPrices(ctx, assetCode) | ||||
| 		var notExpiredPrices []CurrentPrice | ||||
| 		// filter out expired prices
 | ||||
| 		for _, v := range prices { | ||||
| 			if v.Expiry.GTE(sdk.NewInt(ctx.BlockHeight())) { | ||||
| 				notExpiredPrices = append(notExpiredPrices, CurrentPrice{ | ||||
| 					AssetCode: v.AssetCode, | ||||
| 					Price:     v.Price, | ||||
| 					Expiry:    v.Expiry, | ||||
| 				}) | ||||
| 			} | ||||
| 		} | ||||
| 		l := len(notExpiredPrices) | ||||
| 		var medianPrice sdk.Dec | ||||
| 		var expiry sdk.Int | ||||
| 		// TODO make threshold for acceptance (ie. require 51% of oracles to have posted valid prices
 | ||||
| 		if l == 0 { | ||||
| 			// Error if there are no valid prices in the raw pricefeed
 | ||||
| 			return ErrNoValidPrice(k.codespace) | ||||
| 		} else if l == 1 { | ||||
| 			// Return immediately if there's only one price
 | ||||
| 			medianPrice = notExpiredPrices[0].Price | ||||
| 			expiry = notExpiredPrices[0].Expiry | ||||
| 		} else { | ||||
| 			// sort the prices
 | ||||
| 			sort.Slice(notExpiredPrices, func(i, j int) bool { | ||||
| 				return notExpiredPrices[i].Price.LT(notExpiredPrices[j].Price) | ||||
| 			}) | ||||
| 			// If there's an even number of prices
 | ||||
| 			if l%2 == 0 { | ||||
| 				// TODO make sure this is safe.
 | ||||
| 				// Since it's a price and not a balance, division with precision loss is OK.
 | ||||
| 				price1 := notExpiredPrices[l/2-1].Price | ||||
| 				price2 := notExpiredPrices[l/2].Price | ||||
| 				sum := price1.Add(price2) | ||||
| 				divsor, _ := sdk.NewDecFromStr("2") | ||||
| 				medianPrice = sum.Quo(divsor) | ||||
| 				// TODO Check if safe, makes sense
 | ||||
| 				// Takes the average of the two expiries rounded down to the nearest Int.
 | ||||
| 				expiry = notExpiredPrices[l/2-1].Expiry.Add(notExpiredPrices[l/2].Expiry).Quo(sdk.NewInt(2)) | ||||
| 			} else { | ||||
| 				// integer division, so we'll get an integer back, rounded down
 | ||||
| 				medianPrice = notExpiredPrices[l/2].Price | ||||
| 				expiry = notExpiredPrices[l/2].Expiry | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		store := ctx.KVStore(k.storeKey) | ||||
| 		currentPrice := CurrentPrice{ | ||||
| 			AssetCode: assetCode, | ||||
| 			Price:     medianPrice, | ||||
| 			Expiry:    expiry, | ||||
| 		} | ||||
| 		store.Set( | ||||
| 			[]byte(CurrentPricePrefix+assetCode), k.cdc.MustMarshalBinaryBare(currentPrice), | ||||
| 		) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (k Keeper) GetOracleParams(ctx sdk.Context) OracleParams { | ||||
| 	var op OracleParams | ||||
| 	k.paramSpace.Get(ctx, ParamStoreKeyOracles, &op) | ||||
| 	return op | ||||
| } | ||||
| 
 | ||||
| // GetOracles returns the oracles in the pricefeed store
 | ||||
| func (k Keeper) GetOracles(ctx sdk.Context) []Oracle { | ||||
| 	var op OracleParams | ||||
| 	k.paramSpace.Get(ctx, ParamStoreKeyOracles, &op) | ||||
| 	return op.Oracles | ||||
| } | ||||
| 
 | ||||
| func (k Keeper) GetAssetParams(ctx sdk.Context) AssetParams { | ||||
| 	var ap AssetParams | ||||
| 	k.paramSpace.Get(ctx, ParamStoreKeyAssets, &ap) | ||||
| 	return ap | ||||
| } | ||||
| 
 | ||||
| // GetAssets returns the assets in the pricefeed store
 | ||||
| func (k Keeper) GetAssets(ctx sdk.Context) []Asset { | ||||
| 	var ap AssetParams | ||||
| 	k.paramSpace.Get(ctx, ParamStoreKeyAssets, &ap) | ||||
| 	return ap.Assets | ||||
| } | ||||
| 
 | ||||
| // GetAsset returns the asset if it is in the pricefeed system
 | ||||
| func (k Keeper) GetAsset(ctx sdk.Context, assetCode string) (Asset, bool) { | ||||
| 	assets := k.GetAssets(ctx) | ||||
| 
 | ||||
| 	for i := range assets { | ||||
| 		if assets[i].AssetCode == assetCode { | ||||
| 			return assets[i], true | ||||
| 		} | ||||
| 	} | ||||
| 	return Asset{}, false | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // GetOracle returns the oracle address as a string if it is in the pricefeed store
 | ||||
| func (k Keeper) GetOracle(ctx sdk.Context, oracle string) (Oracle, bool) { | ||||
| 	oracles := k.GetOracles(ctx) | ||||
| 
 | ||||
| 	for i := range oracles { | ||||
| 		if oracles[i].OracleAddress == oracle { | ||||
| 			return oracles[i], true | ||||
| 		} | ||||
| 	} | ||||
| 	return Oracle{}, false | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // GetCurrentPrice fetches the current median price of all oracles for a specific asset
 | ||||
| func (k Keeper) GetCurrentPrice(ctx sdk.Context, assetCode string) CurrentPrice { | ||||
| 	store := ctx.KVStore(k.storeKey) | ||||
| 	bz := store.Get([]byte(CurrentPricePrefix + assetCode)) | ||||
| 	// TODO panic or return error if not found
 | ||||
| 	var price CurrentPrice | ||||
| 	k.cdc.MustUnmarshalBinaryBare(bz, &price) | ||||
| 	return price | ||||
| } | ||||
| 
 | ||||
| // GetRawPrices fetches the set of all prices posted by oracles for an asset
 | ||||
| func (k Keeper) GetRawPrices(ctx sdk.Context, assetCode string) []PostedPrice { | ||||
| 	store := ctx.KVStore(k.storeKey) | ||||
| 	bz := store.Get([]byte(RawPriceFeedPrefix + assetCode)) | ||||
| 	var prices []PostedPrice | ||||
| 	k.cdc.MustUnmarshalBinaryBare(bz, &prices) | ||||
| 	return prices | ||||
| } | ||||
| 
 | ||||
| // ValidatePostPrice makes sure the person posting the price is an oracle
 | ||||
| func (k Keeper) ValidatePostPrice(ctx sdk.Context, msg MsgPostPrice) sdk.Error { | ||||
| 	// TODO implement this
 | ||||
| 
 | ||||
| 	_, assetFound := k.GetAsset(ctx, msg.AssetCode) | ||||
| 	if !assetFound { | ||||
| 		return ErrInvalidAsset(k.codespace) | ||||
| 	} | ||||
| 	_, oracleFound := k.GetOracle(ctx, msg.From.String()) | ||||
| 	if !oracleFound { | ||||
| 		return ErrInvalidOracle(k.codespace) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										160
									
								
								x/pricefeed/keeper/keeper.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								x/pricefeed/keeper/keeper.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,160 @@ | ||||
| package keeper | ||||
| 
 | ||||
| import ( | ||||
| 	"sort" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/cosmos/cosmos-sdk/codec" | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 	"github.com/cosmos/cosmos-sdk/x/params" | ||||
| 
 | ||||
| 	"github.com/kava-labs/kava/x/pricefeed/types" | ||||
| ) | ||||
| 
 | ||||
| // Keeper struct for pricefeed module
 | ||||
| type Keeper struct { | ||||
| 	// The keys used to access the stores from Context
 | ||||
| 	storeKey sdk.StoreKey | ||||
| 	// Codec for binary encoding/decoding
 | ||||
| 	cdc *codec.Codec | ||||
| 	// The reference to the Paramstore to get and set pricefeed specific params
 | ||||
| 	paramstore params.Subspace | ||||
| 	// Reserved codespace
 | ||||
| 	codespace sdk.CodespaceType | ||||
| } | ||||
| 
 | ||||
| // NewKeeper returns a new keeper for the pricefeed module. It handles:
 | ||||
| // - adding oracles
 | ||||
| // - adding/removing assets from the pricefeed
 | ||||
| func NewKeeper( | ||||
| 	storeKey sdk.StoreKey, cdc *codec.Codec, paramstore params.Subspace, codespace sdk.CodespaceType, | ||||
| ) Keeper { | ||||
| 	return Keeper{ | ||||
| 		paramstore: paramstore.WithKeyTable(types.ParamKeyTable()), | ||||
| 		storeKey:   storeKey, | ||||
| 		cdc:        cdc, | ||||
| 		codespace:  codespace, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // SetPrice updates the posted price for a specific oracle
 | ||||
| func (k Keeper) SetPrice( | ||||
| 	ctx sdk.Context, | ||||
| 	oracle sdk.AccAddress, | ||||
| 	assetCode string, | ||||
| 	price sdk.Dec, | ||||
| 	expiry time.Time) (types.PostedPrice, sdk.Error) { | ||||
| 	// If the expiry is less than or equal to the current blockheight, we consider the price valid
 | ||||
| 	if expiry.After(ctx.BlockTime()) { | ||||
| 		store := ctx.KVStore(k.storeKey) | ||||
| 		prices := k.GetRawPrices(ctx, assetCode) | ||||
| 		var index int | ||||
| 		found := false | ||||
| 		for i := range prices { | ||||
| 			if prices[i].OracleAddress.Equals(oracle) { | ||||
| 				index = i | ||||
| 				found = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		// set the price for that particular oracle
 | ||||
| 		if found { | ||||
| 			prices[index] = types.PostedPrice{ | ||||
| 				AssetCode: assetCode, OracleAddress: oracle, | ||||
| 				Price: price, Expiry: expiry} | ||||
| 		} else { | ||||
| 			prices = append(prices, types.PostedPrice{ | ||||
| 				AssetCode: assetCode, OracleAddress: oracle, | ||||
| 				Price: price, Expiry: expiry}) | ||||
| 			index = len(prices) - 1 | ||||
| 		} | ||||
| 
 | ||||
| 		store.Set( | ||||
| 			[]byte(types.RawPriceFeedPrefix+assetCode), k.cdc.MustMarshalBinaryBare(prices), | ||||
| 		) | ||||
| 		return prices[index], nil | ||||
| 	} | ||||
| 	return types.PostedPrice{}, types.ErrExpired(k.codespace) | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // SetCurrentPrices updates the price of an asset to the meadian of all valid oracle inputs
 | ||||
| func (k Keeper) SetCurrentPrices(ctx sdk.Context, assetCode string) sdk.Error { | ||||
| 	_, ok := k.GetAsset(ctx, assetCode) | ||||
| 	if !ok { | ||||
| 		return types.ErrInvalidAsset(k.codespace) | ||||
| 	} | ||||
| 	prices := k.GetRawPrices(ctx, assetCode) | ||||
| 	var notExpiredPrices []types.CurrentPrice | ||||
| 	// filter out expired prices
 | ||||
| 	for _, v := range prices { | ||||
| 		if v.Expiry.After(ctx.BlockTime()) { | ||||
| 			notExpiredPrices = append(notExpiredPrices, types.CurrentPrice{ | ||||
| 				AssetCode: v.AssetCode, | ||||
| 				Price:     v.Price, | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| 	l := len(notExpiredPrices) | ||||
| 	var medianPrice sdk.Dec | ||||
| 	// TODO make threshold for acceptance (ie. require 51% of oracles to have posted valid prices
 | ||||
| 	if l == 0 { | ||||
| 		// Error if there are no valid prices in the raw pricefeed
 | ||||
| 		return types.ErrNoValidPrice(k.codespace) | ||||
| 	} else if l == 1 { | ||||
| 		// Return immediately if there's only one price
 | ||||
| 		medianPrice = notExpiredPrices[0].Price | ||||
| 	} else { | ||||
| 		// sort the prices
 | ||||
| 		sort.Slice(notExpiredPrices, func(i, j int) bool { | ||||
| 			return notExpiredPrices[i].Price.LT(notExpiredPrices[j].Price) | ||||
| 		}) | ||||
| 		// If there's an even number of prices
 | ||||
| 		if l%2 == 0 { | ||||
| 			// TODO make sure this is safe.
 | ||||
| 			// Since it's a price and not a balance, division with precision loss is OK.
 | ||||
| 			price1 := notExpiredPrices[l/2-1].Price | ||||
| 			price2 := notExpiredPrices[l/2].Price | ||||
| 			sum := price1.Add(price2) | ||||
| 			divsor, _ := sdk.NewDecFromStr("2") | ||||
| 			medianPrice = sum.Quo(divsor) | ||||
| 		} else { | ||||
| 			// integer division, so we'll get an integer back, rounded down
 | ||||
| 			medianPrice = notExpiredPrices[l/2].Price | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	store := ctx.KVStore(k.storeKey) | ||||
| 	currentPrice := types.CurrentPrice{ | ||||
| 		AssetCode: assetCode, | ||||
| 		Price:     medianPrice, | ||||
| 	} | ||||
| 	store.Set( | ||||
| 		[]byte(types.CurrentPricePrefix+assetCode), k.cdc.MustMarshalBinaryBare(currentPrice), | ||||
| 	) | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // GetCurrentPrice fetches the current median price of all oracles for a specific asset
 | ||||
| func (k Keeper) GetCurrentPrice(ctx sdk.Context, assetCode string) types.CurrentPrice { | ||||
| 	store := ctx.KVStore(k.storeKey) | ||||
| 	bz := store.Get([]byte(types.CurrentPricePrefix + assetCode)) | ||||
| 	// TODO panic or return error if not found
 | ||||
| 	var price types.CurrentPrice | ||||
| 	k.cdc.MustUnmarshalBinaryBare(bz, &price) | ||||
| 	return price | ||||
| } | ||||
| 
 | ||||
| // GetRawPrices fetches the set of all prices posted by oracles for an asset
 | ||||
| func (k Keeper) GetRawPrices(ctx sdk.Context, assetCode string) []types.PostedPrice { | ||||
| 	store := ctx.KVStore(k.storeKey) | ||||
| 	bz := store.Get([]byte(types.RawPriceFeedPrefix + assetCode)) | ||||
| 	var prices []types.PostedPrice | ||||
| 	k.cdc.MustUnmarshalBinaryBare(bz, &prices) | ||||
| 	return prices | ||||
| } | ||||
| 
 | ||||
| func (k Keeper) Codespace() sdk.CodespaceType { | ||||
| 	return k.codespace | ||||
| } | ||||
							
								
								
									
										141
									
								
								x/pricefeed/keeper/keeper_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								x/pricefeed/keeper/keeper_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,141 @@ | ||||
| package keeper | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 	"time" | ||||
| 
 | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| 	abci "github.com/tendermint/tendermint/abci/types" | ||||
| 	tmtime "github.com/tendermint/tendermint/types/time" | ||||
| 
 | ||||
| 	"github.com/kava-labs/kava/x/pricefeed/types" | ||||
| ) | ||||
| 
 | ||||
| // TestKeeper_SetGetAsset tests adding assets to the pricefeed, getting assets from the store
 | ||||
| func TestKeeper_SetGetAsset(t *testing.T) { | ||||
| 	helper := getMockApp(t, 0, types.GenesisState{}, nil) | ||||
| 	header := abci.Header{ | ||||
| 		Height: helper.mApp.LastBlockHeight() + 1, | ||||
| 		Time:   tmtime.Now()} | ||||
| 	helper.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) | ||||
| 	ctx := helper.mApp.BaseApp.NewContext(false, header) | ||||
| 
 | ||||
| 	ap := types.Params{ | ||||
| 		Assets: []types.Asset{ | ||||
| 			types.Asset{AssetCode: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true}, | ||||
| 		}, | ||||
| 	} | ||||
| 	helper.keeper.SetParams(ctx, ap) | ||||
| 	assets := helper.keeper.GetAssetParams(ctx) | ||||
| 	require.Equal(t, len(assets), 1) | ||||
| 	require.Equal(t, assets[0].AssetCode, "tstusd") | ||||
| 
 | ||||
| 	_, found := helper.keeper.GetAsset(ctx, "tstusd") | ||||
| 	require.Equal(t, found, true) | ||||
| 
 | ||||
| 	ap = types.Params{ | ||||
| 		Assets: []types.Asset{ | ||||
| 			types.Asset{AssetCode: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true}, | ||||
| 			types.Asset{AssetCode: "tst2usd", BaseAsset: "tst2", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true}, | ||||
| 		}, | ||||
| 	} | ||||
| 	helper.keeper.SetParams(ctx, ap) | ||||
| 	assets = helper.keeper.GetAssetParams(ctx) | ||||
| 	require.Equal(t, len(assets), 2) | ||||
| 	require.Equal(t, assets[0].AssetCode, "tstusd") | ||||
| 	require.Equal(t, assets[1].AssetCode, "tst2usd") | ||||
| 
 | ||||
| 	_, found = helper.keeper.GetAsset(ctx, "nan") | ||||
| 	require.Equal(t, found, false) | ||||
| } | ||||
| 
 | ||||
| // TestKeeper_GetSetPrice Test Posting the price by an oracle
 | ||||
| func TestKeeper_GetSetPrice(t *testing.T) { | ||||
| 	helper := getMockApp(t, 2, types.GenesisState{}, nil) | ||||
| 	header := abci.Header{ | ||||
| 		Height: helper.mApp.LastBlockHeight() + 1, | ||||
| 		Time:   tmtime.Now()} | ||||
| 	helper.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) | ||||
| 	ctx := helper.mApp.BaseApp.NewContext(false, header) | ||||
| 	ap := types.Params{ | ||||
| 		Assets: []types.Asset{ | ||||
| 			types.Asset{AssetCode: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true}, | ||||
| 		}, | ||||
| 	} | ||||
| 	helper.keeper.SetParams(ctx, ap) | ||||
| 	// Set price by oracle 1
 | ||||
| 	_, err := helper.keeper.SetPrice( | ||||
| 		ctx, helper.addrs[0], "tstusd", | ||||
| 		sdk.MustNewDecFromStr("0.33"), | ||||
| 		header.Time.Add(1*time.Hour)) | ||||
| 	require.NoError(t, err) | ||||
| 	// Get raw prices
 | ||||
| 	rawPrices := helper.keeper.GetRawPrices(ctx, "tstusd") | ||||
| 	require.Equal(t, len(rawPrices), 1) | ||||
| 	require.Equal(t, rawPrices[0].Price.Equal(sdk.MustNewDecFromStr("0.33")), true) | ||||
| 	// Set price by oracle 2
 | ||||
| 	_, err = helper.keeper.SetPrice( | ||||
| 		ctx, helper.addrs[1], "tstusd", | ||||
| 		sdk.MustNewDecFromStr("0.35"), | ||||
| 		header.Time.Add(time.Hour*1)) | ||||
| 	require.NoError(t, err) | ||||
| 
 | ||||
| 	rawPrices = helper.keeper.GetRawPrices(ctx, "tstusd") | ||||
| 	require.Equal(t, len(rawPrices), 2) | ||||
| 	require.Equal(t, rawPrices[1].Price.Equal(sdk.MustNewDecFromStr("0.35")), true) | ||||
| 
 | ||||
| 	// Update Price by Oracle 1
 | ||||
| 	_, err = helper.keeper.SetPrice( | ||||
| 		ctx, helper.addrs[0], "tstusd", | ||||
| 		sdk.MustNewDecFromStr("0.37"), | ||||
| 		header.Time.Add(time.Hour*1)) | ||||
| 	require.NoError(t, err) | ||||
| 	rawPrices = helper.keeper.GetRawPrices(ctx, "tstusd") | ||||
| 	require.Equal(t, rawPrices[0].Price.Equal(sdk.MustNewDecFromStr("0.37")), true) | ||||
| } | ||||
| 
 | ||||
| // TestKeeper_GetSetCurrentPrice Test Setting the median price of an Asset
 | ||||
| func TestKeeper_GetSetCurrentPrice(t *testing.T) { | ||||
| 	helper := getMockApp(t, 4, types.GenesisState{}, nil) | ||||
| 	header := abci.Header{ | ||||
| 		Height: helper.mApp.LastBlockHeight() + 1, | ||||
| 		Time:   tmtime.Now()} | ||||
| 	helper.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) | ||||
| 	ctx := helper.mApp.BaseApp.NewContext(false, header) | ||||
| 	ap := types.Params{ | ||||
| 		Assets: []types.Asset{ | ||||
| 			types.Asset{AssetCode: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true}, | ||||
| 		}, | ||||
| 	} | ||||
| 	helper.keeper.SetParams(ctx, ap) | ||||
| 	helper.keeper.SetPrice( | ||||
| 		ctx, helper.addrs[0], "tstusd", | ||||
| 		sdk.MustNewDecFromStr("0.33"), | ||||
| 		header.Time.Add(time.Hour*1)) | ||||
| 	helper.keeper.SetPrice( | ||||
| 		ctx, helper.addrs[1], "tstusd", | ||||
| 		sdk.MustNewDecFromStr("0.35"), | ||||
| 		header.Time.Add(time.Hour*1)) | ||||
| 	helper.keeper.SetPrice( | ||||
| 		ctx, helper.addrs[2], "tstusd", | ||||
| 		sdk.MustNewDecFromStr("0.34"), | ||||
| 		header.Time.Add(time.Hour*1)) | ||||
| 	// Set current price
 | ||||
| 	err := helper.keeper.SetCurrentPrices(ctx, "tstusd") | ||||
| 	require.NoError(t, err) | ||||
| 	// Get Current price
 | ||||
| 	price := helper.keeper.GetCurrentPrice(ctx, "tstusd") | ||||
| 	require.Equal(t, price.Price.Equal(sdk.MustNewDecFromStr("0.34")), true) | ||||
| 
 | ||||
| 	// Even number of oracles
 | ||||
| 	helper.keeper.SetPrice( | ||||
| 		ctx, helper.addrs[3], "tstusd", | ||||
| 		sdk.MustNewDecFromStr("0.36"), | ||||
| 		header.Time.Add(time.Hour*1)) | ||||
| 	err = helper.keeper.SetCurrentPrices(ctx, "tstusd") | ||||
| 	require.NoError(t, err) | ||||
| 	price = helper.keeper.GetCurrentPrice(ctx, "tstusd") | ||||
| 	require.Equal(t, price.Price.Equal(sdk.MustNewDecFromStr("0.345")), true) | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										64
									
								
								x/pricefeed/keeper/params.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								x/pricefeed/keeper/params.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | ||||
| package keeper | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 
 | ||||
| 	"github.com/kava-labs/kava/x/pricefeed/types" | ||||
| ) | ||||
| 
 | ||||
| // GetParams gets params from the store
 | ||||
| func (k Keeper) GetParams(ctx sdk.Context) types.Params { | ||||
| 	return types.NewParams(k.GetAssetParams(ctx)) | ||||
| } | ||||
| 
 | ||||
| // SetParams updates params in the store
 | ||||
| func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { | ||||
| 	k.paramstore.SetParamSet(ctx, ¶ms) | ||||
| } | ||||
| 
 | ||||
| // GetAssetParams get asset params from store
 | ||||
| func (k Keeper) GetAssetParams(ctx sdk.Context) types.Assets { | ||||
| 	var assets types.Assets | ||||
| 	k.paramstore.Get(ctx, types.KeyAssets, &assets) | ||||
| 	return assets | ||||
| } | ||||
| 
 | ||||
| // GetOracles returns the oracles in the pricefeed store
 | ||||
| func (k Keeper) GetOracles(ctx sdk.Context, assetCode string) (types.Oracles, error) { | ||||
| 
 | ||||
| 	for _, a := range k.GetAssetParams(ctx) { | ||||
| 		if assetCode == a.AssetCode { | ||||
| 			return a.Oracles, nil | ||||
| 		} | ||||
| 	} | ||||
| 	return types.Oracles{}, fmt.Errorf("asset %s not found", assetCode) | ||||
| } | ||||
| 
 | ||||
| // GetOracle returns the oracle from the store or an error if not found
 | ||||
| func (k Keeper) GetOracle(ctx sdk.Context, assetCode string, address sdk.AccAddress) (types.Oracle, error) { | ||||
| 	oracles, err := k.GetOracles(ctx, assetCode) | ||||
| 	if err != nil { | ||||
| 		return types.Oracle{}, fmt.Errorf("asset %s not found", assetCode) | ||||
| 	} | ||||
| 	for _, o := range oracles { | ||||
| 		if address.Equals(o.Address) { | ||||
| 			return o, nil | ||||
| 		} | ||||
| 	} | ||||
| 	return types.Oracle{}, fmt.Errorf("oracle %s not found for asset %s", address, assetCode) | ||||
| } | ||||
| 
 | ||||
| // GetAsset returns the asset if it is in the pricefeed system
 | ||||
| func (k Keeper) GetAsset(ctx sdk.Context, assetCode string) (types.Asset, bool) { | ||||
| 	assets := k.GetAssetParams(ctx) | ||||
| 
 | ||||
| 	for i := range assets { | ||||
| 		if assets[i].AssetCode == assetCode { | ||||
| 			return assets[i], true | ||||
| 		} | ||||
| 	} | ||||
| 	return types.Asset{}, false | ||||
| 
 | ||||
| } | ||||
| @ -1,21 +1,23 @@ | ||||
| package pricefeed | ||||
| package keeper | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/cosmos/cosmos-sdk/codec" | ||||
| 
 | ||||
| 	abci "github.com/tendermint/tendermint/abci/types" | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 	abci "github.com/tendermint/tendermint/abci/types" | ||||
| 
 | ||||
| 	"github.com/kava-labs/kava/x/pricefeed/types" | ||||
| ) | ||||
| 
 | ||||
| // NewQuerier is the module level router for state queries
 | ||||
| func NewQuerier(keeper Keeper) sdk.Querier { | ||||
| 	return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { | ||||
| 		switch path[0] { | ||||
| 		case QueryCurrentPrice: | ||||
| 		case types.QueryCurrentPrice: | ||||
| 			return queryCurrentPrice(ctx, path[1:], req, keeper) | ||||
| 		case QueryRawPrices: | ||||
| 		case types.QueryRawPrices: | ||||
| 			return queryRawPrices(ctx, path[1:], req, keeper) | ||||
| 		case QueryAssets: | ||||
| 		case types.QueryAssets: | ||||
| 			return queryAssets(ctx, req, keeper) | ||||
| 		default: | ||||
| 			return nil, sdk.ErrUnknownRequest("unknown pricefeed query endpoint") | ||||
| @ -41,7 +43,7 @@ func queryCurrentPrice(ctx sdk.Context, path []string, req abci.RequestQuery, ke | ||||
| } | ||||
| 
 | ||||
| func queryRawPrices(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { | ||||
| 	var priceList QueryRawPricesResp | ||||
| 	var priceList types.QueryRawPricesResp | ||||
| 	assetCode := path[0] | ||||
| 	_, found := keeper.GetAsset(ctx, assetCode) | ||||
| 	if !found { | ||||
| @ -60,8 +62,8 @@ func queryRawPrices(ctx sdk.Context, path []string, req abci.RequestQuery, keepe | ||||
| } | ||||
| 
 | ||||
| func queryAssets(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { | ||||
| 	var assetList QueryAssetsResp | ||||
| 	assets := keeper.GetAssets(ctx) | ||||
| 	var assetList types.QueryAssetsResp | ||||
| 	assets := keeper.GetAssetParams(ctx) | ||||
| 	for _, asset := range assets { | ||||
| 		assetList = append(assetList, asset.String()) | ||||
| 	} | ||||
| @ -1,4 +1,4 @@ | ||||
| package pricefeed | ||||
| package keeper | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| @ -7,8 +7,9 @@ import ( | ||||
| 	authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" | ||||
| 	"github.com/cosmos/cosmos-sdk/x/mock" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| 	abci "github.com/tendermint/tendermint/abci/types" | ||||
| 	"github.com/tendermint/tendermint/crypto" | ||||
| 
 | ||||
| 	"github.com/kava-labs/kava/x/pricefeed/types" | ||||
| ) | ||||
| 
 | ||||
| type testHelper struct { | ||||
| @ -19,16 +20,12 @@ type testHelper struct { | ||||
| 	privKeys []crypto.PrivKey | ||||
| } | ||||
| 
 | ||||
| func getMockApp(t *testing.T, numGenAccs int, genState GenesisState, genAccs []authexported.Account) testHelper { | ||||
| func getMockApp(t *testing.T, numGenAccs int, genState types.GenesisState, genAccs []authexported.Account) testHelper { | ||||
| 	mApp := mock.NewApp() | ||||
| 	RegisterCodec(mApp.Cdc) | ||||
| 	keyPricefeed := sdk.NewKVStoreKey(StoreKey) | ||||
| 	types.RegisterCodec(mApp.Cdc) | ||||
| 	keyPricefeed := sdk.NewKVStoreKey(types.StoreKey) | ||||
| 	pk := mApp.ParamsKeeper | ||||
| 	keeper := NewKeeper(keyPricefeed, mApp.Cdc, pk.Subspace(DefaultParamspace).WithKeyTable(ParamKeyTable()), DefaultCodespace) | ||||
| 
 | ||||
| 	// Register routes
 | ||||
| 	mApp.Router().AddRoute(RouterKey, NewHandler(keeper)) | ||||
| 	mApp.SetEndBlocker(getEndBlocker(keeper)) | ||||
| 	keeper := NewKeeper(keyPricefeed, mApp.Cdc, pk.Subspace(types.DefaultParamspace), types.DefaultCodespace) | ||||
| 
 | ||||
| 	require.NoError(t, mApp.CompleteSetup(keyPricefeed)) | ||||
| 
 | ||||
| @ -47,11 +44,3 @@ func getMockApp(t *testing.T, numGenAccs int, genState GenesisState, genAccs []a | ||||
| 	mock.SetGenesis(mApp, genAccs) | ||||
| 	return testHelper{mApp, keeper, addrs, pubKeys, privKeys} | ||||
| } | ||||
| 
 | ||||
| // gov and staking endblocker
 | ||||
| func getEndBlocker(keeper Keeper) sdk.EndBlocker { | ||||
| 	return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { | ||||
| 		EndBlocker(ctx, keeper) | ||||
| 		return abci.ResponseEndBlock{} | ||||
| 	} | ||||
| } | ||||
| @ -1,124 +0,0 @@ | ||||
| package pricefeed | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 
 | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| 	abci "github.com/tendermint/tendermint/abci/types" | ||||
| ) | ||||
| 
 | ||||
| // TestKeeper_SetGetAsset tests adding assets to the pricefeed, getting assets from the store
 | ||||
| func TestKeeper_SetGetAsset(t *testing.T) { | ||||
| 	helper := getMockApp(t, 0, GenesisState{}, nil) | ||||
| 	header := abci.Header{Height: helper.mApp.LastBlockHeight() + 1} | ||||
| 	helper.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) | ||||
| 	ctx := helper.mApp.BaseApp.NewContext(false, abci.Header{}) | ||||
| 	ap := AssetParams{ | ||||
| 		Assets: []Asset{Asset{AssetCode: "tst", Description: "the future of finance"}}, | ||||
| 	} | ||||
| 	helper.keeper.SetAssetParams(ctx, ap) | ||||
| 	assets := helper.keeper.GetAssets(ctx) | ||||
| 	require.Equal(t, len(assets), 1) | ||||
| 	require.Equal(t, assets[0].AssetCode, "tst") | ||||
| 
 | ||||
| 	_, found := helper.keeper.GetAsset(ctx, "tst") | ||||
| 	require.Equal(t, found, true) | ||||
| 
 | ||||
| 	ap = AssetParams{ | ||||
| 		Assets: []Asset{ | ||||
| 			Asset{AssetCode: "tst", Description: "the future of finance"}, | ||||
| 			Asset{AssetCode: "tst2", Description: "the future of finance"}}, | ||||
| 	} | ||||
| 	helper.keeper.SetAssetParams(ctx, ap) | ||||
| 	assets = helper.keeper.GetAssets(ctx) | ||||
| 	require.Equal(t, len(assets), 2) | ||||
| 	require.Equal(t, assets[0].AssetCode, "tst") | ||||
| 	require.Equal(t, assets[1].AssetCode, "tst2") | ||||
| 
 | ||||
| 	_, found = helper.keeper.GetAsset(ctx, "nan") | ||||
| 	require.Equal(t, found, false) | ||||
| } | ||||
| 
 | ||||
| // TestKeeper_GetSetPrice Test Posting the price by an oracle
 | ||||
| func TestKeeper_GetSetPrice(t *testing.T) { | ||||
| 	helper := getMockApp(t, 2, GenesisState{}, nil) | ||||
| 	header := abci.Header{Height: helper.mApp.LastBlockHeight() + 1} | ||||
| 	helper.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) | ||||
| 	ctx := helper.mApp.BaseApp.NewContext(false, abci.Header{}) | ||||
| 	ap := AssetParams{ | ||||
| 		Assets: []Asset{Asset{AssetCode: "tst", Description: "the future of finance"}}, | ||||
| 	} | ||||
| 	helper.keeper.SetAssetParams(ctx, ap) | ||||
| 	// Set price by oracle 1
 | ||||
| 	_, err := helper.keeper.SetPrice( | ||||
| 		ctx, helper.addrs[0], "tst", | ||||
| 		sdk.MustNewDecFromStr("0.33"), | ||||
| 		sdk.NewInt(10)) | ||||
| 	require.NoError(t, err) | ||||
| 	// Get raw prices
 | ||||
| 	rawPrices := helper.keeper.GetRawPrices(ctx, "tst") | ||||
| 	require.Equal(t, len(rawPrices), 1) | ||||
| 	require.Equal(t, rawPrices[0].Price.Equal(sdk.MustNewDecFromStr("0.33")), true) | ||||
| 	// Set price by oracle 2
 | ||||
| 	_, err = helper.keeper.SetPrice( | ||||
| 		ctx, helper.addrs[1], "tst", | ||||
| 		sdk.MustNewDecFromStr("0.35"), | ||||
| 		sdk.NewInt(10)) | ||||
| 	require.NoError(t, err) | ||||
| 
 | ||||
| 	rawPrices = helper.keeper.GetRawPrices(ctx, "tst") | ||||
| 	require.Equal(t, len(rawPrices), 2) | ||||
| 	require.Equal(t, rawPrices[1].Price.Equal(sdk.MustNewDecFromStr("0.35")), true) | ||||
| 
 | ||||
| 	// Update Price by Oracle 1
 | ||||
| 	_, err = helper.keeper.SetPrice( | ||||
| 		ctx, helper.addrs[0], "tst", | ||||
| 		sdk.MustNewDecFromStr("0.37"), | ||||
| 		sdk.NewInt(10)) | ||||
| 	require.NoError(t, err) | ||||
| 	rawPrices = helper.keeper.GetRawPrices(ctx, "tst") | ||||
| 	require.Equal(t, rawPrices[0].Price.Equal(sdk.MustNewDecFromStr("0.37")), true) | ||||
| } | ||||
| 
 | ||||
| // TestKeeper_GetSetCurrentPrice Test Setting the median price of an Asset
 | ||||
| func TestKeeper_GetSetCurrentPrice(t *testing.T) { | ||||
| 	helper := getMockApp(t, 4, GenesisState{}, nil) | ||||
| 	header := abci.Header{Height: helper.mApp.LastBlockHeight() + 1} | ||||
| 	helper.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) | ||||
| 	ctx := helper.mApp.BaseApp.NewContext(false, abci.Header{}) | ||||
| 	// Odd number of oracles
 | ||||
| 	ap := AssetParams{ | ||||
| 		Assets: []Asset{Asset{AssetCode: "tst", Description: "the future of finance"}}, | ||||
| 	} | ||||
| 	helper.keeper.SetAssetParams(ctx, ap) | ||||
| 	helper.keeper.SetPrice( | ||||
| 		ctx, helper.addrs[0], "tst", | ||||
| 		sdk.MustNewDecFromStr("0.33"), | ||||
| 		sdk.NewInt(10)) | ||||
| 	helper.keeper.SetPrice( | ||||
| 		ctx, helper.addrs[1], "tst", | ||||
| 		sdk.MustNewDecFromStr("0.35"), | ||||
| 		sdk.NewInt(10)) | ||||
| 	helper.keeper.SetPrice( | ||||
| 		ctx, helper.addrs[2], "tst", | ||||
| 		sdk.MustNewDecFromStr("0.34"), | ||||
| 		sdk.NewInt(10)) | ||||
| 	// Set current price
 | ||||
| 	err := helper.keeper.SetCurrentPrices(ctx) | ||||
| 	require.NoError(t, err) | ||||
| 	// Get Current price
 | ||||
| 	price := helper.keeper.GetCurrentPrice(ctx, "tst") | ||||
| 	require.Equal(t, price.Price.Equal(sdk.MustNewDecFromStr("0.34")), true) | ||||
| 
 | ||||
| 	// Even number of oracles
 | ||||
| 	helper.keeper.SetPrice( | ||||
| 		ctx, helper.addrs[3], "tst", | ||||
| 		sdk.MustNewDecFromStr("0.36"), | ||||
| 		sdk.NewInt(10)) | ||||
| 	err = helper.keeper.SetCurrentPrices(ctx) | ||||
| 	require.NoError(t, err) | ||||
| 	price = helper.keeper.GetCurrentPrice(ctx, "tst") | ||||
| 	require.Equal(t, price.Price.Equal(sdk.MustNewDecFromStr("0.345")), true) | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										98
									
								
								x/pricefeed/types/asset.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								x/pricefeed/types/asset.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,98 @@ | ||||
| package types | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| ) | ||||
| 
 | ||||
| // Asset struct that represents an asset in the pricefeed
 | ||||
| type Asset struct { | ||||
| 	AssetCode  string  `json:"asset_code" yaml:"asset_code"` | ||||
| 	BaseAsset  string  `json:"base_asset" yaml:"base_asset"` | ||||
| 	QuoteAsset string  `json:"quote_asset" yaml:"quote_asset"` | ||||
| 	Oracles    Oracles `json:"oracles" yaml:"oracles"` | ||||
| 	Active     bool    `json:"active" yaml:"active"` | ||||
| } | ||||
| 
 | ||||
| // implement fmt.Stringer
 | ||||
| func (a Asset) String() string { | ||||
| 	return fmt.Sprintf(`Asset: | ||||
| 	Asset Code: %s | ||||
| 	Base Asset: %s | ||||
| 	Quote Asset: %s | ||||
| 	Oracles: %s | ||||
| 	Active: %t`, | ||||
| 		a.AssetCode, a.BaseAsset, a.QuoteAsset, a.Oracles, a.Active) | ||||
| } | ||||
| 
 | ||||
| // Assets array type for oracle
 | ||||
| type Assets []Asset | ||||
| 
 | ||||
| // String implements fmt.Stringer
 | ||||
| func (as Assets) String() string { | ||||
| 	out := "Assets:\n" | ||||
| 	for _, a := range as { | ||||
| 		out += fmt.Sprintf("%s\n", a.String()) | ||||
| 	} | ||||
| 	return strings.TrimSpace(out) | ||||
| } | ||||
| 
 | ||||
| // Oracle struct that documents which address an oracle is using
 | ||||
| type Oracle struct { | ||||
| 	Address sdk.AccAddress `json:"address" yaml:"address"` | ||||
| } | ||||
| 
 | ||||
| // String implements fmt.Stringer
 | ||||
| func (o Oracle) String() string { | ||||
| 	return fmt.Sprintf(`Address: %s`, o.Address) | ||||
| } | ||||
| 
 | ||||
| // Oracles array type for oracle
 | ||||
| type Oracles []Oracle | ||||
| 
 | ||||
| // String implements fmt.Stringer
 | ||||
| func (os Oracles) String() string { | ||||
| 	out := "Oracles:\n" | ||||
| 	for _, o := range os { | ||||
| 		out += fmt.Sprintf("%s\n", o.String()) | ||||
| 	} | ||||
| 	return strings.TrimSpace(out) | ||||
| } | ||||
| 
 | ||||
| // CurrentPrice struct that contains the metadata of a current price for a particular asset in the pricefeed module.
 | ||||
| type CurrentPrice struct { | ||||
| 	AssetCode string  `json:"asset_code" yaml:"asset_code"` | ||||
| 	Price     sdk.Dec `json:"price" yaml:"price"` | ||||
| } | ||||
| 
 | ||||
| // PostedPrice struct represented a price for an asset posted by a specific oracle
 | ||||
| type PostedPrice struct { | ||||
| 	AssetCode     string         `json:"asset_code" yaml:"asset_code"` | ||||
| 	OracleAddress sdk.AccAddress `json:"oracle_address" yaml:"oracle_address"` | ||||
| 	Price         sdk.Dec        `json:"price" yaml:"price"` | ||||
| 	Expiry        time.Time      `json:"expiry" yaml:"expiry"` | ||||
| } | ||||
| 
 | ||||
| // implement fmt.Stringer
 | ||||
| func (cp CurrentPrice) String() string { | ||||
| 	return strings.TrimSpace(fmt.Sprintf(`AssetCode: %s | ||||
| Price: %s`, cp.AssetCode, cp.Price)) | ||||
| } | ||||
| 
 | ||||
| // implement fmt.Stringer
 | ||||
| func (pp PostedPrice) String() string { | ||||
| 	return strings.TrimSpace(fmt.Sprintf(`AssetCode: %s | ||||
| OracleAddress: %s | ||||
| Price: %s | ||||
| Expiry: %s`, pp.AssetCode, pp.OracleAddress, pp.Price, pp.Expiry)) | ||||
| } | ||||
| 
 | ||||
| // SortDecs provides the interface needed to sort sdk.Dec slices
 | ||||
| type SortDecs []sdk.Dec | ||||
| 
 | ||||
| func (a SortDecs) Len() int           { return len(a) } | ||||
| func (a SortDecs) Swap(i, j int)      { a[i], a[j] = a[j], a[i] } | ||||
| func (a SortDecs) Less(i, j int) bool { return a[i].LT(a[j]) } | ||||
| @ -2,21 +2,18 @@ package types | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| ) | ||||
| 
 | ||||
| // GenesisState - pricefeed state that must be provided at genesis
 | ||||
| type GenesisState struct { | ||||
| 	AssetParams  AssetParams   `json:"asset_params" yaml:"asset_params"` | ||||
| 	OracleParams OracleParams  `json:"oracle_params" yaml:"oracle_params"` | ||||
| 	Params       Params        `json:"asset_params" yaml:"asset_params"` | ||||
| 	PostedPrices []PostedPrice `json:"posted_prices" yaml:"posted_prices"` | ||||
| } | ||||
| 
 | ||||
| // NewGenesisState creates a new genesis state for the pricefeed module
 | ||||
| func NewGenesisState(ap AssetParams, op OracleParams, pp []PostedPrice) GenesisState { | ||||
| func NewGenesisState(p Params, pp []PostedPrice) GenesisState { | ||||
| 	return GenesisState{ | ||||
| 		AssetParams:  ap, | ||||
| 		OracleParams: op, | ||||
| 		Params:       p, | ||||
| 		PostedPrices: pp, | ||||
| 	} | ||||
| } | ||||
| @ -24,8 +21,7 @@ func NewGenesisState(ap AssetParams, op OracleParams, pp []PostedPrice) GenesisS | ||||
| // DefaultGenesisState defines default GenesisState for pricefeed
 | ||||
| func DefaultGenesisState() GenesisState { | ||||
| 	return NewGenesisState( | ||||
| 		DefaultAssetParams(), | ||||
| 		DefaultOracleParams(), | ||||
| 		DefaultParams(), | ||||
| 		[]PostedPrice{}, | ||||
| 	) | ||||
| } | ||||
| @ -45,19 +41,9 @@ func (data GenesisState) IsEmpty() bool { | ||||
| // ValidateGenesis performs basic validation of genesis data returning an
 | ||||
| // error for any failed validation criteria.
 | ||||
| func ValidateGenesis(data GenesisState) error { | ||||
| 	// iterate over assets and verify them
 | ||||
| 	for _, asset := range data.AssetParams.Assets { | ||||
| 		if asset.AssetCode == "" { | ||||
| 			return fmt.Errorf("invalid asset: %s. missing asset code", asset.String()) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// iterate over oracles and verify them
 | ||||
| 	for _, oracle := range data.OracleParams.Oracles { | ||||
| 		if oracle.OracleAddress == "" { | ||||
| 			return fmt.Errorf("invalid oracle: %s. missing oracle address", oracle.String()) | ||||
| 		} | ||||
| 	if err := data.Params.Validate(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @ -9,4 +9,22 @@ const ( | ||||
| 
 | ||||
| 	// RouterKey Top level router key
 | ||||
| 	RouterKey = ModuleName | ||||
| 
 | ||||
| 	// QuerierRoute is the querier route for gov
 | ||||
| 	QuerierRoute = ModuleName | ||||
| 
 | ||||
| 	// DefaultParamspace default namestore
 | ||||
| 	DefaultParamspace = ModuleName | ||||
| 
 | ||||
| 	// RawPriceFeedPrefix prefix for the raw pricefeed of an asset
 | ||||
| 	RawPriceFeedPrefix = StoreKey + ":raw:" | ||||
| 
 | ||||
| 	// CurrentPricePrefix prefix for the current price of an asset
 | ||||
| 	CurrentPricePrefix = StoreKey + ":currentprice:" | ||||
| 
 | ||||
| 	// AssetPrefix Prefix for the assets in the pricefeed system
 | ||||
| 	AssetPrefix = StoreKey + ":assets" | ||||
| 
 | ||||
| 	// OraclePrefix store prefix for the oracle accounts
 | ||||
| 	OraclePrefix = StoreKey + ":oracles" | ||||
| ) | ||||
|  | ||||
| @ -1,6 +1,8 @@ | ||||
| package types | ||||
| 
 | ||||
| import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| ) | ||||
| 
 | ||||
| @ -15,7 +17,7 @@ type MsgPostPrice struct { | ||||
| 	From      sdk.AccAddress // client that sent in this address
 | ||||
| 	AssetCode string         // asset code used by exchanges/api
 | ||||
| 	Price     sdk.Dec        // price in decimal (max precision 18)
 | ||||
| 	Expiry    sdk.Int        // block height
 | ||||
| 	Expiry    time.Time      // expiry time
 | ||||
| } | ||||
| 
 | ||||
| // NewMsgPostPrice creates a new post price msg
 | ||||
| @ -23,7 +25,7 @@ func NewMsgPostPrice( | ||||
| 	from sdk.AccAddress, | ||||
| 	assetCode string, | ||||
| 	price sdk.Dec, | ||||
| 	expiry sdk.Int) MsgPostPrice { | ||||
| 	expiry time.Time) MsgPostPrice { | ||||
| 	return MsgPostPrice{ | ||||
| 		From:      from, | ||||
| 		AssetCode: assetCode, | ||||
| @ -60,9 +62,6 @@ func (msg MsgPostPrice) ValidateBasic() sdk.Error { | ||||
| 	if msg.Price.LT(sdk.ZeroDec()) { | ||||
| 		return sdk.ErrInternal("invalid (negative) price") | ||||
| 	} | ||||
| 	if msg.Expiry.LT(sdk.ZeroInt()) { | ||||
| 		return sdk.ErrInternal("invalid (negative) expiry") | ||||
| 	} | ||||
| 	// TODO check coin denoms
 | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @ -5,6 +5,7 @@ import ( | ||||
| 
 | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| 	tmtime "github.com/tendermint/tendermint/types/time" | ||||
| ) | ||||
| 
 | ||||
| func TestMsgPlaceBid_ValidateBasic(t *testing.T) { | ||||
| @ -13,8 +14,7 @@ func TestMsgPlaceBid_ValidateBasic(t *testing.T) { | ||||
| 	// 	OracleAddress: addr.String(),
 | ||||
| 	// }}
 | ||||
| 	price, _ := sdk.NewDecFromStr("0.3005") | ||||
| 	expiry, _ := sdk.NewIntFromString("10") | ||||
| 	negativeExpiry, _ := sdk.NewIntFromString("-3") | ||||
| 	expiry := tmtime.Now() | ||||
| 	negativePrice, _ := sdk.NewDecFromStr("-3.05") | ||||
| 
 | ||||
| 	tests := []struct { | ||||
| @ -26,7 +26,6 @@ func TestMsgPlaceBid_ValidateBasic(t *testing.T) { | ||||
| 		{"emptyAddr", MsgPostPrice{sdk.AccAddress{}, "xrp", price, expiry}, false}, | ||||
| 		{"emptyAsset", MsgPostPrice{addr, "", price, expiry}, false}, | ||||
| 		{"negativePrice", MsgPostPrice{addr, "xrp", negativePrice, expiry}, false}, | ||||
| 		{"negativeExpiry", MsgPostPrice{addr, "xrp", price, negativeExpiry}, false}, | ||||
| 	} | ||||
| 	for _, tc := range tests { | ||||
| 		t.Run(tc.name, func(t *testing.T) { | ||||
|  | ||||
| @ -8,74 +8,48 @@ import ( | ||||
| 	params "github.com/cosmos/cosmos-sdk/x/params/subspace" | ||||
| ) | ||||
| 
 | ||||
| // Parameter store key
 | ||||
| var ( | ||||
| 	// ParamStoreKeyOracles Param store key for oracles
 | ||||
| 	ParamStoreKeyOracles = []byte("oracles") | ||||
| 	// ParamStoreKeyAssets Param store key for assets
 | ||||
| 	ParamStoreKeyAssets = []byte("assets") | ||||
| 	// KeyAssets store key for assets
 | ||||
| 	KeyAssets = []byte("assets") | ||||
| ) | ||||
| 
 | ||||
| // ParamKeyTable Key declaration for parameters
 | ||||
| func ParamKeyTable() params.KeyTable { | ||||
| 	return params.NewKeyTable( | ||||
| 		ParamStoreKeyOracles, OracleParams{}, | ||||
| 		ParamStoreKeyAssets, AssetParams{}, | ||||
| 	) | ||||
| 	return params.NewKeyTable().RegisterParamSet(&Params{}) | ||||
| } | ||||
| 
 | ||||
| // AssetParams params for assets. Can be altered via governance
 | ||||
| type AssetParams struct { | ||||
| 	Assets []Asset `json:"assets,omitempty" yaml:"assets,omitempty"` //  Array containing the assets supported by the pricefeed
 | ||||
| // Params params for pricefeed. Can be altered via governance
 | ||||
| type Params struct { | ||||
| 	Assets []Asset `json:"assets" yaml:"assets"` //  Array containing the assets supported by the pricefeed
 | ||||
| } | ||||
| 
 | ||||
| // NewAssetParams creates a new AssetParams object
 | ||||
| func NewAssetParams(assets []Asset) AssetParams { | ||||
| 	return AssetParams{ | ||||
| // ParamSetPairs implements the ParamSet interface and returns all the key/value pairs
 | ||||
| // pairs of pricefeed module's parameters.
 | ||||
| func (p Params) ParamSetPairs() params.ParamSetPairs { | ||||
| 	return params.ParamSetPairs{ | ||||
| 		{Key: KeyAssets, Value: &p.Assets}, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // NewParams creates a new AssetParams object
 | ||||
| func NewParams(assets []Asset) Params { | ||||
| 	return Params{ | ||||
| 		Assets: assets, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // DefaultAssetParams default params for assets
 | ||||
| func DefaultAssetParams() AssetParams { | ||||
| 	return NewAssetParams([]Asset{}) | ||||
| // DefaultParams default params for pricefeed
 | ||||
| func DefaultParams() Params { | ||||
| 	return NewParams(Assets{}) | ||||
| } | ||||
| 
 | ||||
| // implements fmt.stringer
 | ||||
| func (ap AssetParams) String() string { | ||||
| 	var assetListString []string | ||||
| 	for _, asset := range ap.Assets { | ||||
| 		assetListString = append(assetListString, asset.String()) | ||||
| // String implements fmt.stringer
 | ||||
| func (p Params) String() string { | ||||
| 	out := "Params:\n" | ||||
| 	for _, a := range p.Assets { | ||||
| 		out += a.String() | ||||
| 	} | ||||
| 	return strings.TrimSpace(fmt.Sprintf(`Asset Params: | ||||
| 	Assets:        %s\`, strings.Join(assetListString, ", "))) | ||||
| } | ||||
| 
 | ||||
| // OracleParams params for assets. Can be altered via governance
 | ||||
| type OracleParams struct { | ||||
| 	Oracles []Oracle `json:"oracles,omitempty" yaml:"oracles,omitempty"` //  Array containing the oracles supported by the pricefeed
 | ||||
| } | ||||
| 
 | ||||
| // NewOracleParams creates a new OracleParams object
 | ||||
| func NewOracleParams(oracles []Oracle) OracleParams { | ||||
| 	return OracleParams{ | ||||
| 		Oracles: oracles, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // DefaultOracleParams default params for assets
 | ||||
| func DefaultOracleParams() OracleParams { | ||||
| 	return NewOracleParams([]Oracle{}) | ||||
| } | ||||
| 
 | ||||
| // implements fmt.stringer
 | ||||
| func (op OracleParams) String() string { | ||||
| 	var oracleListString []string | ||||
| 	for _, oracle := range op.Oracles { | ||||
| 		oracleListString = append(oracleListString, oracle.String()) | ||||
| 	} | ||||
| 	return strings.TrimSpace(fmt.Sprintf(`Oracle Params: | ||||
|   Oracles:        %s\`, strings.Join(oracleListString, ", "))) | ||||
| 	return strings.TrimSpace(out) | ||||
| } | ||||
| 
 | ||||
| // ParamSubspace defines the expected Subspace interface for parameters
 | ||||
| @ -83,3 +57,14 @@ type ParamSubspace interface { | ||||
| 	Get(ctx sdk.Context, key []byte, ptr interface{}) | ||||
| 	Set(ctx sdk.Context, key []byte, param interface{}) | ||||
| } | ||||
| 
 | ||||
| // Validate ensure that params have valid values
 | ||||
| func (p Params) Validate() error { | ||||
| 	// iterate over assets and verify them
 | ||||
| 	for _, asset := range p.Assets { | ||||
| 		if asset.AssetCode == "" { | ||||
| 			return fmt.Errorf("invalid asset: %s. missing asset code", asset.String()) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @ -1,67 +0,0 @@ | ||||
| package types | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| ) | ||||
| 
 | ||||
| // Asset struct that represents an asset in the pricefeed
 | ||||
| type Asset struct { | ||||
| 	AssetCode   string `json:"asset_code"` | ||||
| 	Description string `json:"description"` | ||||
| } | ||||
| 
 | ||||
| // implement fmt.Stringer
 | ||||
| func (a Asset) String() string { | ||||
| 	return strings.TrimSpace(fmt.Sprintf(`AssetCode: %s | ||||
| Description: %s`, a.AssetCode, a.Description)) | ||||
| } | ||||
| 
 | ||||
| // Oracle struct that documents which address an oracle is using
 | ||||
| type Oracle struct { | ||||
| 	OracleAddress string `json:"oracle_address"` | ||||
| } | ||||
| 
 | ||||
| // implement fmt.Stringer
 | ||||
| func (o Oracle) String() string { | ||||
| 	return strings.TrimSpace(fmt.Sprintf(`OracleAddress: %s`, o.OracleAddress)) | ||||
| } | ||||
| 
 | ||||
| // CurrentPrice struct that contains the metadata of a current price for a particular asset in the pricefeed module.
 | ||||
| type CurrentPrice struct { | ||||
| 	AssetCode string  `json:"asset_code"` | ||||
| 	Price     sdk.Dec `json:"price"` | ||||
| 	Expiry    sdk.Int `json:"expiry"` | ||||
| } | ||||
| 
 | ||||
| // PostedPrice struct represented a price for an asset posted by a specific oracle
 | ||||
| type PostedPrice struct { | ||||
| 	AssetCode     string  `json:"asset_code"` | ||||
| 	OracleAddress string  `json:"oracle_address"` | ||||
| 	Price         sdk.Dec `json:"price"` | ||||
| 	Expiry        sdk.Int `json:"expiry"` | ||||
| } | ||||
| 
 | ||||
| // implement fmt.Stringer
 | ||||
| func (cp CurrentPrice) String() string { | ||||
| 	return strings.TrimSpace(fmt.Sprintf(`AssetCode: %s | ||||
| Price: %s | ||||
| Expiry: %s`, cp.AssetCode, cp.Price, cp.Expiry)) | ||||
| } | ||||
| 
 | ||||
| // implement fmt.Stringer
 | ||||
| func (pp PostedPrice) String() string { | ||||
| 	return strings.TrimSpace(fmt.Sprintf(`AssetCode: %s | ||||
| OracleAddress: %s | ||||
| Price: %s | ||||
| Expiry: %s`, pp.AssetCode, pp.OracleAddress, pp.Price, pp.Expiry)) | ||||
| } | ||||
| 
 | ||||
| // SortDecs provides the interface needed to sort sdk.Dec slices
 | ||||
| type SortDecs []sdk.Dec | ||||
| 
 | ||||
| func (a SortDecs) Len() int           { return len(a) } | ||||
| func (a SortDecs) Swap(i, j int)      { a[i], a[j] = a[j], a[i] } | ||||
| func (a SortDecs) Less(i, j int) bool { return a[i].LT(a[j]) } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Kevin Davis
						Kevin Davis