feat: add pricefeed spec

This commit is contained in:
Kevin Davis 2020-05-07 11:11:10 -04:00
parent 8ef03e4181
commit c6d287a5b3
10 changed files with 173 additions and 30 deletions

View File

@ -9,15 +9,15 @@ import (
// EndBlocker updates the current pricefeed // EndBlocker updates the current pricefeed
func EndBlocker(ctx sdk.Context, k Keeper) { func EndBlocker(ctx sdk.Context, k Keeper) {
// Update the current price of each asset. // Update the current price of each asset.
for _, a := range k.GetMarkets(ctx) { for _, market := range k.GetMarkets(ctx) {
if a.Active { if market.Active {
err := k.SetCurrentPrices(ctx, a.MarketID) err := k.SetCurrentPrices(ctx, market.MarketID)
if err != nil { if err != nil {
// In the event of failure, emit an event. // In the event of failure, emit an event.
ctx.EventManager().EmitEvent( ctx.EventManager().EmitEvent(
sdk.NewEvent( sdk.NewEvent(
EventTypeNoValidPrices, EventTypeNoValidPrices,
sdk.NewAttribute(AttributeKeyPriceUpdateFailed, fmt.Sprintf("%s", a.MarketID)), sdk.NewAttribute(AttributeMarketID, fmt.Sprintf("%s", market.MarketID)),
), ),
) )
continue continue

View File

@ -20,7 +20,6 @@ const (
AttributeMarketPrice = types.AttributeMarketPrice AttributeMarketPrice = types.AttributeMarketPrice
AttributeOracle = types.AttributeOracle AttributeOracle = types.AttributeOracle
AttributeExpiry = types.AttributeExpiry AttributeExpiry = types.AttributeExpiry
AttributeKeyPriceUpdateFailed = types.AttributeKeyPriceUpdateFailed
ModuleName = types.ModuleName ModuleName = types.ModuleName
StoreKey = types.StoreKey StoreKey = types.StoreKey
RouterKey = types.RouterKey RouterKey = types.RouterKey

View File

@ -0,0 +1,3 @@
# Concepts
Prices can be posted by any account which is added as an oracle. Oracles are specific to each market and can be updated via param change proposals. When an oracle posts a price, they submit a message to the blockchain that contains the current price for that market and a time when that price should be considered expired. If an oracle posts a new price, that price becomes the current price for that oracle, regardless of the previous price's expiry. A group of prices posted by a set of oracles for a particular market are referred to as 'raw prices' and the current median price of all valid oracle prices is referred to as the 'current price'. Each block, the current price for each market is determined by calculating the median of the raw prices.

View File

@ -0,0 +1,44 @@
# State
## Parameters and genesis state
`Paramaters` determine which markets are tracked by the pricefeed and which oracles are authorized to post prices for a given market. There is only one active parameter set at any given time. Updates to parameters can be made via on-chain parameter update proposals.
```go
// Params params for pricefeed. Can be altered via governance
type Params struct {
Markets Markets `json:"markets" yaml:"markets"` // Array containing the markets supported by the pricefeed
}
// Market an asset in the pricefeed
type Market struct {
MarketID string `json:"market_id" yaml:"market_id"`
BaseAsset string `json:"base_asset" yaml:"base_asset"`
QuoteAsset string `json:"quote_asset" yaml:"quote_asset"`
Oracles []sdk.AccAddress `json:"oracles" yaml:"oracles"`
Active bool `json:"active" yaml:"active"`
}
type Markets []Market
```
`GenesisState` defines the state that must be persisted when the blockchain stops/stars in order for the normal function of the pricefeed to resume.
```go
// GenesisState - pricefeed state that must be provided at genesis
type GenesisState struct {
Params Params `json:"params" yaml:"params"`
PostedPrices []PostedPrice `json:"posted_prices" yaml:"posted_prices"`
}
// PostedPrice price for market posted by a specific oracle
type PostedPrice struct {
MarketID string `json:"market_id" yaml:"market_id"`
OracleAddress sdk.AccAddress `json:"oracle_address" yaml:"oracle_address"`
Price sdk.Dec `json:"price" yaml:"price"`
Expiry time.Time `json:"expiry" yaml:"expiry"`
}
type PostedPrices []PostedPrice
```

View File

@ -0,0 +1,20 @@
# Messages
## Posting Prices
An authorized oraclef for a particular market can post the current price for that market using the `MsgPostPrice` type.
```go
// MsgPostPrice struct representing a posted price message.
// Used by oracles to input prices to the pricefeed
type MsgPostPrice struct {
From sdk.AccAddress `json:"from" yaml:"from"` // client that sent in this address
MarketID string `json:"market_id" yaml:"market_id"` // asset code used by exchanges/api
Price sdk.Dec `json:"price" yaml:"price"` // price in decimal (max precision 18)
Expiry time.Time `json:"expiry" yaml:"expiry"` // expiry time
}
```
### State Modifications
* Update the raw price for the oracle for this market. This replaces any previous price for that oracle.

View File

@ -0,0 +1,22 @@
# Events
The `x/pricefeed` module emits the following events:
## MsgPostPrice
| Type | Attribute Key | Attribute Value |
|----------------------|---------------|------------------|
| oracle_updated_price | market_id | {market ID} |
| oracle_updated_price | oracle | {oracle} |
| oracle_updated_price | market_price | {price} |
| oracle_updated_price | expiry | {expiry} |
| message | module | pricefeed |
| message | sender | {sender address} |
## BeginBlock
| Type | Attribute Key | Attribute Value |
|----------------------|-----------------|-----------------|
| market_price_updated | market_id | {market ID} |
| market_price_updated | market_price | {price} |
| no_valid_prices | market_id | {market ID} |

View File

@ -0,0 +1,17 @@
# Parameters
The pricefeed module has the following parameters:
| Key | Type | Example | Description |
|------------|----------------|---------------|--------------------------------------------------|
| Markets | array (Market) | [{see below}] | array of params for each market in the pricefeed |
Each `Market` has the following parameters
| Key | Type | Example | Description |
|------------|--------------------|--------------------------|----------------------------------------------------------------|
| MarketID | string | "bnb:usd" | identifier for the market -- **must** be unique across markets |
| BaseAsset | string | "bnb" | the base asset for the market pair |
| QuoteAsset | string | "usd" | the quote asset for the market pair |
| Oracles | array (AccAddress) | ["kava1...", "kava1..."] | addresses which can post prices for the market |
| Active | bool | true | flag to disable oracle interactions with the module |

View File

@ -0,0 +1,26 @@
# Begin Block
At the start of each block, the current price is calculated as the median of all raw prices for each market. The logic is as follows:
```go
// EndBlocker updates the current pricefeed
func EndBlocker(ctx sdk.Context, k Keeper) {
// Update the current price of each asset.
for _, market := range k.GetMarkets(ctx) {
if market.Active {
err := k.SetCurrentPrices(ctx, market.MarketID)
if err != nil {
// In the event of failure, emit an event.
ctx.EventManager().EmitEvent(
sdk.NewEvent(
EventTypeNoValidPrices,
sdk.NewAttribute(AttributeMarketID, fmt.Sprintf("%s", market.MarketID)),
),
)
continue
}
}
}
return
}
```

View File

@ -0,0 +1,13 @@
# `pricefeed`
<!-- TOC -->
1. **[Concepts](01_concepts.md)**
2. **[State](02_state.md)**
3. **[Messages](03_messages.md)**
4. **[Events](04_events.md)**
5. **[Params](05_params.md)**
6. **[BeginBlock](06_begin_block.md)**
## Abstract
`x/pricefeed` is an implementation of a Cosmos SDK Module that handles the posting of prices for various markets by a group of whitelisted oracles. At the beginning of each block, the median price of all oracle posted prices is determined for each market and stored.

View File

@ -11,5 +11,4 @@ const (
AttributeMarketPrice = "market_price" AttributeMarketPrice = "market_price"
AttributeOracle = "oracle" AttributeOracle = "oracle"
AttributeExpiry = "expiry" AttributeExpiry = "expiry"
AttributeKeyPriceUpdateFailed = "price_update_failed"
) )