mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 08:15:19 +00:00
Merge branch 'kd-propose-params' into ro-remove-mock-from-tests
This commit is contained in:
commit
688b7830c0
@ -152,7 +152,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
|||||||
auctionSubspace := app.paramsKeeper.Subspace(auction.DefaultParamspace)
|
auctionSubspace := app.paramsKeeper.Subspace(auction.DefaultParamspace)
|
||||||
cdpSubspace := app.paramsKeeper.Subspace(cdp.DefaultParamspace)
|
cdpSubspace := app.paramsKeeper.Subspace(cdp.DefaultParamspace)
|
||||||
liquidatorSubspace := app.paramsKeeper.Subspace(liquidator.DefaultParamspace)
|
liquidatorSubspace := app.paramsKeeper.Subspace(liquidator.DefaultParamspace)
|
||||||
pricefeedSubspace := app.paramsKeeper.Subspace(pricefeed.DefaultParamspace).WithKeyTable(pricefeed.ParamKeyTable()) // TODO why do other modules not need this?
|
pricefeedSubspace := app.paramsKeeper.Subspace(pricefeed.DefaultParamspace)
|
||||||
|
|
||||||
// add keepers
|
// add keepers
|
||||||
app.accountKeeper = auth.NewAccountKeeper(
|
app.accountKeeper = auth.NewAccountKeeper(
|
||||||
|
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 h1:yb+E8HGzFnO0YwLS6OCBIAVWtN8KfCYoKeO9mgAmQN0=
|
||||||
github.com/cosmos/cosmos-sdk v0.34.4-0.20191010193331-18de630d0ae1/go.mod h1:IGBhkbOK1ebLqMWjtgo99zUxWHsA5IOb6N9CI8nHs0Y=
|
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.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 h1:Iwin12wRQtyZhH6FV3ykFcdGNlYEzoeR0jN8Vn+JWsI=
|
||||||
github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y=
|
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=
|
github.com/cosmos/ledger-cosmos-go v0.10.3 h1:Qhi5yTR5Pg1CaTpd00pxlGwNl4sFRdtK1J96OTjeFFc=
|
||||||
|
@ -2,6 +2,7 @@ package cdp_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/simapp"
|
"github.com/cosmos/cosmos-sdk/simapp"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
@ -21,8 +22,8 @@ func TestApp_CreateModifyDeleteCDP(t *testing.T) {
|
|||||||
tApp.InitializeFromGenesisStates(
|
tApp.InitializeFromGenesisStates(
|
||||||
tApp.NewAuthGenStateFromAccounts(addrs, []sdk.Coins{cs(c("xrp", 100))}),
|
tApp.NewAuthGenStateFromAccounts(addrs, []sdk.Coins{cs(c("xrp", 100))}),
|
||||||
)
|
)
|
||||||
// check balance
|
|
||||||
ctx := tApp.NewContext(false, abci.Header{})
|
ctx := tApp.NewContext(false, abci.Header{})
|
||||||
|
// check balance
|
||||||
tApp.CheckBalance(t, ctx, testAddr, cs(c("xrp", 100)))
|
tApp.CheckBalance(t, ctx, testAddr, cs(c("xrp", 100)))
|
||||||
|
|
||||||
// setup cdp keeper
|
// setup cdp keeper
|
||||||
@ -40,17 +41,20 @@ func TestApp_CreateModifyDeleteCDP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
keeper.SetParams(ctx, params)
|
keeper.SetParams(ctx, params)
|
||||||
keeper.SetGlobalDebt(ctx, sdk.NewInt(0))
|
keeper.SetGlobalDebt(ctx, sdk.NewInt(0))
|
||||||
// setup pricefeed
|
pricefeedKeeper := tApp.GetPriceFeedKeeper()
|
||||||
pfKeeper := tApp.GetPriceFeedKeeper()
|
ap := pricefeed.Params{
|
||||||
ap := pricefeed.AssetParams{
|
Markets: []pricefeed.Market{
|
||||||
Assets: []pricefeed.Asset{pricefeed.Asset{AssetCode: "xrp", Description: ""}},
|
pricefeed.Market{
|
||||||
|
MarketID: "xrp", BaseAsset: "xrp",
|
||||||
|
QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
pfKeeper.SetAssetParams(ctx, ap)
|
pricefeedKeeper.SetParams(ctx, ap)
|
||||||
pfKeeper.SetPrice(
|
pricefeedKeeper.SetPrice(
|
||||||
ctx, sdk.AccAddress{}, "xrp",
|
ctx, sdk.AccAddress{}, "xrp",
|
||||||
sdk.MustNewDecFromStr("1.00"),
|
sdk.MustNewDecFromStr("1.00"),
|
||||||
sdk.NewInt(10))
|
time.Unix(9999999999, 0)) // some deterministic future date
|
||||||
pfKeeper.SetCurrentPrices(ctx)
|
pricefeedKeeper.SetCurrentPrices(ctx, "xrp")
|
||||||
tApp.EndBlock(abci.RequestEndBlock{})
|
tApp.EndBlock(abci.RequestEndBlock{})
|
||||||
tApp.Commit()
|
tApp.Commit()
|
||||||
|
|
||||||
|
@ -9,9 +9,9 @@ import (
|
|||||||
func InitGenesis(ctx sdk.Context, k Keeper, pk PricefeedKeeper, data GenesisState) {
|
func InitGenesis(ctx sdk.Context, k Keeper, pk PricefeedKeeper, data GenesisState) {
|
||||||
// validate denoms - check that any collaterals in the CdpParams are in the pricefeed, pricefeed needs to initgenesis before cdp
|
// validate denoms - check that any collaterals in the CdpParams are in the pricefeed, pricefeed needs to initgenesis before cdp
|
||||||
collateralMap := make(map[string]int)
|
collateralMap := make(map[string]int)
|
||||||
ap := pk.GetAssetParams(ctx)
|
ap := pk.GetParams(ctx)
|
||||||
for _, a := range ap.Assets {
|
for _, m := range ap.Markets {
|
||||||
collateralMap[a.AssetCode] = 1
|
collateralMap[m.MarketID] = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, col := range data.Params.CollateralParams {
|
for _, col := range data.Params.CollateralParams {
|
||||||
|
@ -3,6 +3,7 @@ package keeper_test
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -105,22 +106,24 @@ func TestKeeper_ModifyCDP(t *testing.T) {
|
|||||||
keeper := tApp.GetCDPKeeper()
|
keeper := tApp.GetCDPKeeper()
|
||||||
keeper.SetParams(ctx, defaultParamsSingle())
|
keeper.SetParams(ctx, defaultParamsSingle())
|
||||||
pricefeedKeeper := tApp.GetPriceFeedKeeper()
|
pricefeedKeeper := tApp.GetPriceFeedKeeper()
|
||||||
ap := pricefeed.AssetParams{
|
ap := pricefeed.Params{
|
||||||
Assets: []pricefeed.Asset{
|
Markets: []pricefeed.Market{
|
||||||
pricefeed.Asset{AssetCode: "xrp", Description: ""},
|
pricefeed.Market{
|
||||||
|
MarketID: "xrp", BaseAsset: "xrp",
|
||||||
|
QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
pricefeedKeeper.SetAssetParams(ctx, ap)
|
pricefeedKeeper.SetParams(ctx, ap)
|
||||||
_, err := pricefeedKeeper.SetPrice(
|
_, err := pricefeedKeeper.SetPrice(
|
||||||
ctx, ownerAddr, "xrp",
|
ctx, ownerAddr, "xrp",
|
||||||
sdk.MustNewDecFromStr(tc.price),
|
sdk.MustNewDecFromStr(tc.price),
|
||||||
sdk.NewInt(ctx.BlockHeight()+10))
|
time.Unix(9999999999, 0)) // some deterministic future date
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log("test context height", ctx.BlockHeight())
|
t.Log("test context height", ctx.BlockHeight())
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.Log(tc.name)
|
t.Log(tc.name)
|
||||||
}
|
}
|
||||||
err = pricefeedKeeper.SetCurrentPrices(ctx)
|
err = pricefeedKeeper.SetCurrentPrices(ctx, "xrp")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log("test context height", ctx.BlockHeight())
|
t.Log("test context height", ctx.BlockHeight())
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
@ -139,9 +142,6 @@ func TestKeeper_ModifyCDP(t *testing.T) {
|
|||||||
|
|
||||||
// get new state for verification
|
// get new state for verification
|
||||||
actualCDP, found := keeper.GetCDP(ctx, tc.args.owner, tc.args.collateralDenom)
|
actualCDP, found := keeper.GetCDP(ctx, tc.args.owner, tc.args.collateralDenom)
|
||||||
if tc.name == "removeTooMuchCollateral" {
|
|
||||||
t.Log(actualCDP.String())
|
|
||||||
}
|
|
||||||
// check for err
|
// check for err
|
||||||
if tc.expectPass {
|
if tc.expectPass {
|
||||||
require.NoError(t, err, fmt.Sprint(err))
|
require.NoError(t, err, fmt.Sprint(err))
|
||||||
@ -180,17 +180,19 @@ func TestKeeper_PartialSeizeCDP(t *testing.T) {
|
|||||||
|
|
||||||
// setup pricefeed
|
// setup pricefeed
|
||||||
pricefeedKeeper := tApp.GetPriceFeedKeeper()
|
pricefeedKeeper := tApp.GetPriceFeedKeeper()
|
||||||
ap := pricefeed.AssetParams{
|
ap := pricefeed.Params{
|
||||||
Assets: []pricefeed.Asset{
|
Markets: []pricefeed.Market{
|
||||||
pricefeed.Asset{AssetCode: collateral, Description: ""},
|
pricefeed.Market{
|
||||||
|
MarketID: "xrp", BaseAsset: collateral,
|
||||||
|
QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
pricefeedKeeper.SetAssetParams(ctx, ap)
|
pricefeedKeeper.SetParams(ctx, ap)
|
||||||
pricefeedKeeper.SetPrice(
|
pricefeedKeeper.SetPrice(
|
||||||
ctx, sdk.AccAddress{}, collateral,
|
ctx, sdk.AccAddress{}, collateral,
|
||||||
sdk.MustNewDecFromStr("1.00"),
|
sdk.MustNewDecFromStr("1.00"),
|
||||||
i(10))
|
time.Unix(9999999999, 0)) // some deterministic future date
|
||||||
require.NoError(t, pricefeedKeeper.SetCurrentPrices(ctx))
|
pricefeedKeeper.SetCurrentPrices(ctx, collateral)
|
||||||
|
|
||||||
// Create CDP
|
// Create CDP
|
||||||
keeper.SetParams(ctx, defaultParamsSingle())
|
keeper.SetParams(ctx, defaultParamsSingle())
|
||||||
@ -200,8 +202,8 @@ func TestKeeper_PartialSeizeCDP(t *testing.T) {
|
|||||||
pricefeedKeeper.SetPrice(
|
pricefeedKeeper.SetPrice(
|
||||||
ctx, sdk.AccAddress{}, collateral,
|
ctx, sdk.AccAddress{}, collateral,
|
||||||
sdk.MustNewDecFromStr("0.90"),
|
sdk.MustNewDecFromStr("0.90"),
|
||||||
i(10))
|
time.Unix(9999999999, 0)) // some deterministic future date
|
||||||
require.NoError(t, pricefeedKeeper.SetCurrentPrices(ctx))
|
pricefeedKeeper.SetCurrentPrices(ctx, collateral)
|
||||||
|
|
||||||
// Seize entire CDP
|
// Seize entire CDP
|
||||||
err = keeper.PartialSeizeCDP(ctx, testAddr, collateral, i(10), i(5))
|
err = keeper.PartialSeizeCDP(ctx, testAddr, collateral, i(10), i(5))
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
pftypes "github.com/kava-labs/kava/x/pricefeed/types"
|
pftypes "github.com/kava-labs/kava/x/pricefeed/types"
|
||||||
)
|
)
|
||||||
@ -14,9 +16,9 @@ type BankKeeper interface {
|
|||||||
|
|
||||||
type PricefeedKeeper interface {
|
type PricefeedKeeper interface {
|
||||||
GetCurrentPrice(sdk.Context, string) pftypes.CurrentPrice
|
GetCurrentPrice(sdk.Context, string) pftypes.CurrentPrice
|
||||||
GetAssetParams(sdk.Context) pftypes.AssetParams
|
GetParams(sdk.Context) pftypes.Params
|
||||||
// These are used for testing TODO replace mockApp with keeper in tests to remove these
|
// These are used for testing TODO replace mockApp with keeper in tests to remove these
|
||||||
SetAssetParams(sdk.Context, pftypes.AssetParams)
|
SetParams(sdk.Context, pftypes.Params)
|
||||||
SetPrice(sdk.Context, sdk.AccAddress, string, sdk.Dec, sdk.Int) (pftypes.PostedPrice, sdk.Error)
|
SetPrice(sdk.Context, sdk.AccAddress, string, sdk.Dec, time.Time) (pftypes.PostedPrice, sdk.Error)
|
||||||
SetCurrentPrices(sdk.Context) sdk.Error
|
SetCurrentPrices(sdk.Context, string) sdk.Error
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GenesisState is the state that must be provided at genesis.
|
// GenesisState is the state that must be provided at genesis.
|
||||||
// TODO What is globaldebt and is is separate from the global debt limit in CdpParams
|
// TODO What is globaldebt and is is separate from the global debt limit in CdpParams
|
||||||
@ -19,7 +17,6 @@ type GenesisState struct {
|
|||||||
func DefaultGenesisState() GenesisState {
|
func DefaultGenesisState() GenesisState {
|
||||||
return GenesisState{
|
return GenesisState{
|
||||||
Params: DefaultParams(),
|
Params: DefaultParams(),
|
||||||
GlobalDebt: sdk.ZeroInt(),
|
|
||||||
CDPs: CDPs{},
|
CDPs: CDPs{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,12 @@ package keeper
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/mock"
|
"github.com/cosmos/cosmos-sdk/x/mock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
tmtime "github.com/tendermint/tendermint/types/time"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/x/cdp"
|
"github.com/kava-labs/kava/x/cdp"
|
||||||
"github.com/kava-labs/kava/x/liquidator/types"
|
"github.com/kava-labs/kava/x/liquidator/types"
|
||||||
@ -19,8 +21,8 @@ func TestKeeper_SeizeAndStartCollateralAuction(t *testing.T) {
|
|||||||
_, addrs := mock.GeneratePrivKeyAddressPairs(1)
|
_, addrs := mock.GeneratePrivKeyAddressPairs(1)
|
||||||
|
|
||||||
pricefeed.InitGenesis(ctx, k.pricefeedKeeper, pricefeedGenesis())
|
pricefeed.InitGenesis(ctx, k.pricefeedKeeper, pricefeedGenesis())
|
||||||
k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("8000.00"), i(999999999))
|
k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("8000.00"), tmtime.Now().Add(time.Hour*1))
|
||||||
k.pricefeedKeeper.SetCurrentPrices(ctx)
|
k.pricefeedKeeper.SetCurrentPrices(ctx, "btc")
|
||||||
cdp.InitGenesis(ctx, k.cdpKeeper, k.pricefeedKeeper, cdpDefaultGenesis())
|
cdp.InitGenesis(ctx, k.cdpKeeper, k.pricefeedKeeper, cdpDefaultGenesis())
|
||||||
dp := defaultParams()
|
dp := defaultParams()
|
||||||
k.liquidatorKeeper.SetParams(ctx, dp)
|
k.liquidatorKeeper.SetParams(ctx, dp)
|
||||||
@ -28,8 +30,8 @@ func TestKeeper_SeizeAndStartCollateralAuction(t *testing.T) {
|
|||||||
|
|
||||||
k.cdpKeeper.ModifyCDP(ctx, addrs[0], "btc", i(3), i(16000))
|
k.cdpKeeper.ModifyCDP(ctx, addrs[0], "btc", i(3), i(16000))
|
||||||
|
|
||||||
k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("7999.99"), i(999999999))
|
k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("7999.99"), tmtime.Now().Add(time.Hour*1))
|
||||||
k.pricefeedKeeper.SetCurrentPrices(ctx)
|
k.pricefeedKeeper.SetCurrentPrices(ctx, "btc")
|
||||||
|
|
||||||
// Run test function
|
// Run test function
|
||||||
auctionID, err := k.liquidatorKeeper.SeizeAndStartCollateralAuction(ctx, addrs[0], "btc")
|
auctionID, err := k.liquidatorKeeper.SeizeAndStartCollateralAuction(ctx, addrs[0], "btc")
|
||||||
@ -100,16 +102,16 @@ func TestKeeper_partialSeizeCDP(t *testing.T) {
|
|||||||
|
|
||||||
pricefeed.InitGenesis(ctx, k.pricefeedKeeper, pricefeedGenesis())
|
pricefeed.InitGenesis(ctx, k.pricefeedKeeper, pricefeedGenesis())
|
||||||
|
|
||||||
k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("8000.00"), i(999999999))
|
k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("8000.00"), tmtime.Now().Add(time.Hour*1))
|
||||||
k.pricefeedKeeper.SetCurrentPrices(ctx)
|
k.pricefeedKeeper.SetCurrentPrices(ctx, "btc")
|
||||||
k.bankKeeper.AddCoins(ctx, addrs[0], cs(c("btc", 100)))
|
k.bankKeeper.AddCoins(ctx, addrs[0], cs(c("btc", 100)))
|
||||||
cdp.InitGenesis(ctx, k.cdpKeeper, k.pricefeedKeeper, cdpDefaultGenesis())
|
cdp.InitGenesis(ctx, k.cdpKeeper, k.pricefeedKeeper, cdpDefaultGenesis())
|
||||||
k.liquidatorKeeper.SetParams(ctx, defaultParams())
|
k.liquidatorKeeper.SetParams(ctx, defaultParams())
|
||||||
|
|
||||||
k.cdpKeeper.ModifyCDP(ctx, addrs[0], "btc", i(3), i(16000))
|
k.cdpKeeper.ModifyCDP(ctx, addrs[0], "btc", i(3), i(16000))
|
||||||
|
|
||||||
k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("7999.99"), i(999999999))
|
k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("7999.99"), tmtime.Now().Add(time.Hour*1))
|
||||||
k.pricefeedKeeper.SetCurrentPrices(ctx)
|
k.pricefeedKeeper.SetCurrentPrices(ctx, "btc")
|
||||||
|
|
||||||
// Run test function
|
// Run test function
|
||||||
err := k.liquidatorKeeper.partialSeizeCDP(ctx, addrs[0], "btc", i(2), i(10000))
|
err := k.liquidatorKeeper.partialSeizeCDP(ctx, addrs[0], "btc", i(2), i(10000))
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package keeper
|
package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
"github.com/cosmos/cosmos-sdk/store"
|
"github.com/cosmos/cosmos-sdk/store"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
@ -9,6 +11,7 @@ import (
|
|||||||
"github.com/cosmos/cosmos-sdk/x/params"
|
"github.com/cosmos/cosmos-sdk/x/params"
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
tmtime "github.com/tendermint/tendermint/types/time"
|
||||||
dbm "github.com/tendermint/tm-db"
|
dbm "github.com/tendermint/tm-db"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/x/auction"
|
"github.com/kava-labs/kava/x/auction"
|
||||||
@ -75,7 +78,7 @@ func setupTestKeepers() (sdk.Context, keepers) {
|
|||||||
bank.DefaultCodespace,
|
bank.DefaultCodespace,
|
||||||
blacklistedAddrs,
|
blacklistedAddrs,
|
||||||
)
|
)
|
||||||
pricefeedKeeper := pricefeed.NewKeeper(cdc, keyPriceFeed, paramsKeeper.Subspace(pricefeed.DefaultParamspace).WithKeyTable(pricefeed.ParamKeyTable()), pricefeed.DefaultCodespace)
|
pricefeedKeeper := pricefeed.NewKeeper(cdc, keyPriceFeed, paramsKeeper.Subspace(pricefeed.DefaultParamspace), pricefeed.DefaultCodespace)
|
||||||
cdpKeeper := cdp.NewKeeper(
|
cdpKeeper := cdp.NewKeeper(
|
||||||
cdc,
|
cdc,
|
||||||
keyCDP,
|
keyCDP,
|
||||||
@ -150,20 +153,18 @@ func cdpDefaultGenesis() cdp.GenesisState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func pricefeedGenesis() pricefeed.GenesisState {
|
func pricefeedGenesis() pricefeed.GenesisState {
|
||||||
ap := pricefeed.AssetParams{
|
ap := pricefeed.Params{
|
||||||
Assets: []pricefeed.Asset{
|
Markets: []pricefeed.Market{
|
||||||
pricefeed.Asset{AssetCode: "btc", Description: "a description"},
|
pricefeed.Market{MarketID: "btc", BaseAsset: "btc", QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
return pricefeed.GenesisState{
|
return pricefeed.GenesisState{
|
||||||
AssetParams: ap,
|
Params: ap,
|
||||||
OracleParams: pricefeed.DefaultOracleParams(),
|
|
||||||
PostedPrices: []pricefeed.PostedPrice{
|
PostedPrices: []pricefeed.PostedPrice{
|
||||||
pricefeed.PostedPrice{
|
pricefeed.PostedPrice{
|
||||||
AssetCode: "btc",
|
MarketID: "btc",
|
||||||
OracleAddress: "",
|
OracleAddress: sdk.AccAddress{},
|
||||||
Price: sdk.MustNewDecFromStr("8000.00"),
|
Price: sdk.MustNewDecFromStr("8000.00"),
|
||||||
Expiry: sdk.NewInt(999999999),
|
Expiry: tmtime.Now().Add(1 * time.Hour),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
28
x/pricefeed/abci.go
Normal file
28
x/pricefeed/abci.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package pricefeed
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
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.GetMarketParams(ctx) {
|
||||||
|
if a.Active {
|
||||||
|
err := k.SetCurrentPrices(ctx, a.MarketID)
|
||||||
|
if err != nil {
|
||||||
|
// In the event of failure, emit an event.
|
||||||
|
ctx.EventManager().EmitEvent(
|
||||||
|
sdk.NewEvent(
|
||||||
|
EventTypeNoValidPrices,
|
||||||
|
sdk.NewAttribute(AttributeKeyPriceUpdateFailed, fmt.Sprintf("%s", a.MarketID)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@ -2,9 +2,11 @@
|
|||||||
// autogenerated code using github.com/rigelrozanski/multitool
|
// autogenerated code using github.com/rigelrozanski/multitool
|
||||||
// aliases generated for the following subdirectories:
|
// aliases generated for the following subdirectories:
|
||||||
// ALIASGEN: github.com/kava-labs/kava/x/pricefeed/types/
|
// ALIASGEN: github.com/kava-labs/kava/x/pricefeed/types/
|
||||||
|
// ALIASGEN: github.com/kava-labs/kava/x/pricefeed/keeper/
|
||||||
package pricefeed
|
package pricefeed
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/kava-labs/kava/x/pricefeed/keeper"
|
||||||
"github.com/kava-labs/kava/x/pricefeed/types"
|
"github.com/kava-labs/kava/x/pricefeed/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,9 +17,17 @@ const (
|
|||||||
CodeInvalidPrice = types.CodeInvalidPrice
|
CodeInvalidPrice = types.CodeInvalidPrice
|
||||||
CodeInvalidAsset = types.CodeInvalidAsset
|
CodeInvalidAsset = types.CodeInvalidAsset
|
||||||
CodeInvalidOracle = types.CodeInvalidOracle
|
CodeInvalidOracle = types.CodeInvalidOracle
|
||||||
|
EventTypeNoValidPrices = types.EventTypeNoValidPrices
|
||||||
|
AttributeKeyPriceUpdateFailed = types.AttributeKeyPriceUpdateFailed
|
||||||
ModuleName = types.ModuleName
|
ModuleName = types.ModuleName
|
||||||
StoreKey = types.StoreKey
|
StoreKey = types.StoreKey
|
||||||
RouterKey = types.RouterKey
|
RouterKey = types.RouterKey
|
||||||
|
QuerierRoute = types.QuerierRoute
|
||||||
|
DefaultParamspace = types.DefaultParamspace
|
||||||
|
RawPriceFeedPrefix = types.RawPriceFeedPrefix
|
||||||
|
CurrentPricePrefix = types.CurrentPricePrefix
|
||||||
|
AssetPrefix = types.AssetPrefix
|
||||||
|
OraclePrefix = types.OraclePrefix
|
||||||
TypeMsgPostPrice = types.TypeMsgPostPrice
|
TypeMsgPostPrice = types.TypeMsgPostPrice
|
||||||
QueryCurrentPrice = types.QueryCurrentPrice
|
QueryCurrentPrice = types.QueryCurrentPrice
|
||||||
QueryRawPrices = types.QueryRawPrices
|
QueryRawPrices = types.QueryRawPrices
|
||||||
@ -37,28 +47,29 @@ var (
|
|||||||
ValidateGenesis = types.ValidateGenesis
|
ValidateGenesis = types.ValidateGenesis
|
||||||
NewMsgPostPrice = types.NewMsgPostPrice
|
NewMsgPostPrice = types.NewMsgPostPrice
|
||||||
ParamKeyTable = types.ParamKeyTable
|
ParamKeyTable = types.ParamKeyTable
|
||||||
NewAssetParams = types.NewAssetParams
|
NewParams = types.NewParams
|
||||||
DefaultAssetParams = types.DefaultAssetParams
|
DefaultParams = types.DefaultParams
|
||||||
NewOracleParams = types.NewOracleParams
|
NewKeeper = keeper.NewKeeper
|
||||||
DefaultOracleParams = types.DefaultOracleParams
|
NewQuerier = keeper.NewQuerier
|
||||||
|
|
||||||
// variable aliases
|
// variable aliases
|
||||||
ModuleCdc = types.ModuleCdc
|
ModuleCdc = types.ModuleCdc
|
||||||
ParamStoreKeyOracles = types.ParamStoreKeyOracles
|
KeyMarkets = types.KeyMarkets
|
||||||
ParamStoreKeyAssets = types.ParamStoreKeyAssets
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
GenesisState = types.GenesisState
|
Market = types.Market
|
||||||
MsgPostPrice = types.MsgPostPrice
|
Markets = types.Markets
|
||||||
AssetParams = types.AssetParams
|
|
||||||
OracleParams = types.OracleParams
|
|
||||||
ParamSubspace = types.ParamSubspace
|
|
||||||
QueryRawPricesResp = types.QueryRawPricesResp
|
|
||||||
QueryAssetsResp = types.QueryAssetsResp
|
|
||||||
Asset = types.Asset
|
|
||||||
Oracle = types.Oracle
|
Oracle = types.Oracle
|
||||||
|
Oracles = types.Oracles
|
||||||
CurrentPrice = types.CurrentPrice
|
CurrentPrice = types.CurrentPrice
|
||||||
PostedPrice = types.PostedPrice
|
PostedPrice = types.PostedPrice
|
||||||
SortDecs = types.SortDecs
|
SortDecs = types.SortDecs
|
||||||
|
GenesisState = types.GenesisState
|
||||||
|
MsgPostPrice = types.MsgPostPrice
|
||||||
|
Params = types.Params
|
||||||
|
ParamSubspace = types.ParamSubspace
|
||||||
|
QueryRawPricesResp = types.QueryRawPricesResp
|
||||||
|
QueryAssetsResp = types.QueryAssetsResp
|
||||||
|
Keeper = keeper.Keeper
|
||||||
)
|
)
|
||||||
|
@ -2,6 +2,7 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
@ -12,6 +13,7 @@ import (
|
|||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
|
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
|
||||||
"github.com/kava-labs/kava/x/pricefeed/types"
|
"github.com/kava-labs/kava/x/pricefeed/types"
|
||||||
|
tmtime "github.com/tendermint/tendermint/types/time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetTxCmd returns the transaction commands for this module
|
// GetTxCmd returns the transaction commands for this module
|
||||||
@ -47,11 +49,13 @@ func GetCmdPostPrice(cdc *codec.Codec) *cobra.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
expiry, ok := sdk.NewIntFromString(args[2])
|
expiryInt, ok := sdk.NewIntFromString(args[2])
|
||||||
if !ok {
|
if !ok {
|
||||||
fmt.Printf("invalid expiry - %s \n", args[2])
|
fmt.Printf("invalid expiry - %s \n", args[2])
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
expiry := tmtime.Canonical(time.Unix(expiryInt.Int64(), 0))
|
||||||
|
|
||||||
msg := types.NewMsgPostPrice(cliCtx.GetFromAddress(), args[0], price, expiry)
|
msg := types.NewMsgPostPrice(cliCtx.GetFromAddress(), args[0], price, expiry)
|
||||||
err = msg.ValidateBasic()
|
err = msg.ValidateBasic()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -3,6 +3,7 @@ package rest
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/context"
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
@ -10,6 +11,7 @@ import (
|
|||||||
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
|
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/kava-labs/kava/x/pricefeed/types"
|
"github.com/kava-labs/kava/x/pricefeed/types"
|
||||||
|
tmtime "github.com/tendermint/tendermint/types/time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -57,11 +59,12 @@ func postPriceHandler(cliCtx context.CLIContext) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
expiry, ok := sdk.NewIntFromString(req.Expiry)
|
expiryInt, ok := sdk.NewIntFromString(req.Expiry)
|
||||||
if !ok {
|
if !ok {
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, "invalid expiry")
|
rest.WriteErrorResponse(w, http.StatusBadRequest, "invalid expiry")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
expiry := tmtime.Canonical(time.Unix(expiryInt.Int64(), 0))
|
||||||
|
|
||||||
// create the message
|
// create the message
|
||||||
msg := types.NewMsgPostPrice(addr, req.AssetCode, price, expiry)
|
msg := types.NewMsgPostPrice(addr, req.AssetCode, price, expiry)
|
||||||
|
@ -8,24 +8,21 @@ import (
|
|||||||
func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) {
|
func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) {
|
||||||
|
|
||||||
// Set the assets and oracles from params
|
// Set the assets and oracles from params
|
||||||
keeper.SetAssetParams(ctx, data.AssetParams)
|
keeper.SetParams(ctx, data.Params)
|
||||||
keeper.SetOracleParams(ctx, data.OracleParams)
|
|
||||||
|
|
||||||
// Iterate through the posted prices and set them in the store
|
// Iterate through the posted prices and set them in the store
|
||||||
for _, pp := range data.PostedPrices {
|
for _, pp := range data.PostedPrices {
|
||||||
addr, err := sdk.AccAddressFromBech32(pp.OracleAddress)
|
_, err := keeper.SetPrice(ctx, pp.OracleAddress, pp.MarketID, pp.Price, pp.Expiry)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
_, err = keeper.SetPrice(ctx, addr, pp.AssetCode, pp.Price, pp.Expiry)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the current price (if any) based on what's now in the store
|
// Set the current price (if any) based on what's now in the store
|
||||||
if err := keeper.SetCurrentPrices(ctx); err != nil {
|
for _, a := range data.Params.Markets {
|
||||||
panic(err)
|
if a.Active {
|
||||||
|
_ = keeper.SetCurrentPrices(ctx, a.MarketID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,18 +30,16 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) {
|
|||||||
func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState {
|
func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState {
|
||||||
|
|
||||||
// Get the params for assets and oracles
|
// Get the params for assets and oracles
|
||||||
assetParams := keeper.GetAssetParams(ctx)
|
params := keeper.GetParams(ctx)
|
||||||
oracleParams := keeper.GetOracleParams(ctx)
|
|
||||||
|
|
||||||
var postedPrices []PostedPrice
|
var postedPrices []PostedPrice
|
||||||
for _, asset := range keeper.GetAssets(ctx) {
|
for _, asset := range keeper.GetMarketParams(ctx) {
|
||||||
pp := keeper.GetRawPrices(ctx, asset.AssetCode)
|
pp := keeper.GetRawPrices(ctx, asset.MarketID)
|
||||||
postedPrices = append(postedPrices, pp...)
|
postedPrices = append(postedPrices, pp...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return GenesisState{
|
return GenesisState{
|
||||||
AssetParams: assetParams,
|
Params: params,
|
||||||
OracleParams: oracleParams,
|
|
||||||
PostedPrices: postedPrices,
|
PostedPrices: postedPrices,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,22 +29,10 @@ func HandleMsgPostPrice(
|
|||||||
msg MsgPostPrice) sdk.Result {
|
msg MsgPostPrice) sdk.Result {
|
||||||
|
|
||||||
// TODO cleanup message validation and errors
|
// TODO cleanup message validation and errors
|
||||||
err := k.ValidatePostPrice(ctx, msg)
|
_, err := k.GetOracle(ctx, msg.AssetCode, msg.From)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err.Result()
|
return ErrInvalidOracle(k.Codespace()).Result()
|
||||||
}
|
}
|
||||||
k.SetPrice(ctx, msg.From, msg.AssetCode, msg.Price, msg.Expiry)
|
k.SetPrice(ctx, msg.From, msg.AssetCode, msg.Price, msg.Expiry)
|
||||||
return sdk.Result{}
|
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(
|
|
||||||
cdc *codec.Codec, storeKey sdk.StoreKey, 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
|
|
||||||
}
|
|
168
x/pricefeed/keeper/keeper.go
Normal file
168
x/pricefeed/keeper/keeper.go
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
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(
|
||||||
|
cdc *codec.Codec, storeKey sdk.StoreKey, 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,
|
||||||
|
marketID 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, marketID)
|
||||||
|
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{
|
||||||
|
MarketID: marketID, OracleAddress: oracle,
|
||||||
|
Price: price, Expiry: expiry}
|
||||||
|
} else {
|
||||||
|
prices = append(prices, types.PostedPrice{
|
||||||
|
MarketID: marketID, OracleAddress: oracle,
|
||||||
|
Price: price, Expiry: expiry})
|
||||||
|
index = len(prices) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
store.Set(
|
||||||
|
[]byte(types.RawPriceFeedPrefix+marketID), 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, marketID string) sdk.Error {
|
||||||
|
_, ok := k.GetMarket(ctx, marketID)
|
||||||
|
if !ok {
|
||||||
|
return types.ErrInvalidAsset(k.codespace)
|
||||||
|
}
|
||||||
|
prices := k.GetRawPrices(ctx, marketID)
|
||||||
|
var notExpiredPrices []types.CurrentPrice
|
||||||
|
// filter out expired prices
|
||||||
|
for _, v := range prices {
|
||||||
|
if v.Expiry.After(ctx.BlockTime()) {
|
||||||
|
notExpiredPrices = append(notExpiredPrices, types.CurrentPrice{
|
||||||
|
MarketID: v.MarketID,
|
||||||
|
Price: v.Price,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
medianPrice, err := k.CalculateMedianPrice(ctx, notExpiredPrices)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
store := ctx.KVStore(k.storeKey)
|
||||||
|
currentPrice := types.CurrentPrice{
|
||||||
|
MarketID: marketID,
|
||||||
|
Price: medianPrice,
|
||||||
|
}
|
||||||
|
store.Set(
|
||||||
|
[]byte(types.CurrentPricePrefix+marketID), k.cdc.MustMarshalBinaryBare(currentPrice),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalculateMedianPrice calculates the median prices for the input prices.
|
||||||
|
func (k Keeper) CalculateMedianPrice(ctx sdk.Context, prices []types.CurrentPrice) (sdk.Dec, sdk.Error) {
|
||||||
|
l := len(prices)
|
||||||
|
|
||||||
|
if l == 0 {
|
||||||
|
// Error if there are no valid prices in the raw pricefeed
|
||||||
|
return sdk.Dec{}, types.ErrNoValidPrice(k.codespace)
|
||||||
|
} else if l == 1 {
|
||||||
|
// Return immediately if there's only one price
|
||||||
|
return prices[0].Price, nil
|
||||||
|
} else {
|
||||||
|
// sort the prices
|
||||||
|
sort.Slice(prices, func(i, j int) bool {
|
||||||
|
return prices[i].Price.LT(prices[j].Price)
|
||||||
|
})
|
||||||
|
// 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])
|
||||||
|
return median, nil
|
||||||
|
}
|
||||||
|
// for odd numbers of prices, return the middle element
|
||||||
|
return prices[l/2].Price, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k Keeper) calculateMeanPrice(ctx sdk.Context, prices []types.CurrentPrice) sdk.Dec {
|
||||||
|
sum := prices[0].Price.Add(prices[1].Price)
|
||||||
|
mean := sum.Quo(sdk.NewDec(2))
|
||||||
|
return mean
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCurrentPrice fetches the current median price of all oracles for a specific asset
|
||||||
|
func (k Keeper) GetCurrentPrice(ctx sdk.Context, marketID string) types.CurrentPrice {
|
||||||
|
store := ctx.KVStore(k.storeKey)
|
||||||
|
bz := store.Get([]byte(types.CurrentPricePrefix + marketID))
|
||||||
|
// 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, marketID string) []types.PostedPrice {
|
||||||
|
store := ctx.KVStore(k.storeKey)
|
||||||
|
bz := store.Get([]byte(types.RawPriceFeedPrefix + marketID))
|
||||||
|
var prices []types.PostedPrice
|
||||||
|
k.cdc.MustUnmarshalBinaryBare(bz, &prices)
|
||||||
|
return prices
|
||||||
|
}
|
||||||
|
|
||||||
|
// Codespace return the codespace for the keeper
|
||||||
|
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_SetGetMarket tests adding markets to the pricefeed, getting markets from the store
|
||||||
|
func TestKeeper_SetGetMarket(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)
|
||||||
|
|
||||||
|
mp := types.Params{
|
||||||
|
Markets: types.Markets{
|
||||||
|
types.Market{MarketID: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
helper.keeper.SetParams(ctx, mp)
|
||||||
|
markets := helper.keeper.GetMarketParams(ctx)
|
||||||
|
require.Equal(t, len(markets), 1)
|
||||||
|
require.Equal(t, markets[0].MarketID, "tstusd")
|
||||||
|
|
||||||
|
_, found := helper.keeper.GetMarket(ctx, "tstusd")
|
||||||
|
require.Equal(t, found, true)
|
||||||
|
|
||||||
|
mp = types.Params{
|
||||||
|
Markets: types.Markets{
|
||||||
|
types.Market{MarketID: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true},
|
||||||
|
types.Market{MarketID: "tst2usd", BaseAsset: "tst2", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
helper.keeper.SetParams(ctx, mp)
|
||||||
|
markets = helper.keeper.GetMarketParams(ctx)
|
||||||
|
require.Equal(t, len(markets), 2)
|
||||||
|
require.Equal(t, markets[0].MarketID, "tstusd")
|
||||||
|
require.Equal(t, markets[1].MarketID, "tst2usd")
|
||||||
|
|
||||||
|
_, found = helper.keeper.GetMarket(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)
|
||||||
|
mp := types.Params{
|
||||||
|
Markets: types.Markets{
|
||||||
|
types.Market{MarketID: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
helper.keeper.SetParams(ctx, mp)
|
||||||
|
// 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)
|
||||||
|
mp := types.Params{
|
||||||
|
Markets: types.Markets{
|
||||||
|
types.Market{MarketID: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
helper.keeper.SetParams(ctx, mp)
|
||||||
|
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.GetMarketParams(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetParams updates params in the store
|
||||||
|
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
|
||||||
|
k.paramstore.SetParamSet(ctx, ¶ms)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMarketParams get asset params from store
|
||||||
|
func (k Keeper) GetMarketParams(ctx sdk.Context) types.Markets {
|
||||||
|
var markets types.Markets
|
||||||
|
k.paramstore.Get(ctx, types.KeyMarkets, &markets)
|
||||||
|
return markets
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOracles returns the oracles in the pricefeed store
|
||||||
|
func (k Keeper) GetOracles(ctx sdk.Context, marketID string) (types.Oracles, error) {
|
||||||
|
|
||||||
|
for _, m := range k.GetMarketParams(ctx) {
|
||||||
|
if marketID == m.MarketID {
|
||||||
|
return m.Oracles, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return types.Oracles{}, fmt.Errorf("asset %s not found", marketID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOracle returns the oracle from the store or an error if not found
|
||||||
|
func (k Keeper) GetOracle(ctx sdk.Context, marketID string, address sdk.AccAddress) (types.Oracle, error) {
|
||||||
|
oracles, err := k.GetOracles(ctx, marketID)
|
||||||
|
if err != nil {
|
||||||
|
return types.Oracle{}, fmt.Errorf("asset %s not found", marketID)
|
||||||
|
}
|
||||||
|
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, marketID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMarket returns the market if it is in the pricefeed system
|
||||||
|
func (k Keeper) GetMarket(ctx sdk.Context, marketID string) (types.Market, bool) {
|
||||||
|
markets := k.GetMarketParams(ctx)
|
||||||
|
|
||||||
|
for i := range markets {
|
||||||
|
if markets[i].MarketID == marketID {
|
||||||
|
return markets[i], true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return types.Market{}, false
|
||||||
|
|
||||||
|
}
|
@ -1,21 +1,23 @@
|
|||||||
package pricefeed
|
package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/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
|
// NewQuerier is the module level router for state queries
|
||||||
func NewQuerier(keeper Keeper) sdk.Querier {
|
func NewQuerier(keeper Keeper) sdk.Querier {
|
||||||
return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) {
|
return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) {
|
||||||
switch path[0] {
|
switch path[0] {
|
||||||
case QueryCurrentPrice:
|
case types.QueryCurrentPrice:
|
||||||
return queryCurrentPrice(ctx, path[1:], req, keeper)
|
return queryCurrentPrice(ctx, path[1:], req, keeper)
|
||||||
case QueryRawPrices:
|
case types.QueryRawPrices:
|
||||||
return queryRawPrices(ctx, path[1:], req, keeper)
|
return queryRawPrices(ctx, path[1:], req, keeper)
|
||||||
case QueryAssets:
|
case types.QueryAssets:
|
||||||
return queryAssets(ctx, req, keeper)
|
return queryAssets(ctx, req, keeper)
|
||||||
default:
|
default:
|
||||||
return nil, sdk.ErrUnknownRequest("unknown pricefeed query endpoint")
|
return nil, sdk.ErrUnknownRequest("unknown pricefeed query endpoint")
|
||||||
@ -25,12 +27,12 @@ func NewQuerier(keeper Keeper) sdk.Querier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func queryCurrentPrice(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
|
func queryCurrentPrice(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
|
||||||
assetCode := path[0]
|
marketID := path[0]
|
||||||
_, found := keeper.GetAsset(ctx, assetCode)
|
_, found := keeper.GetMarket(ctx, marketID)
|
||||||
if !found {
|
if !found {
|
||||||
return []byte{}, sdk.ErrUnknownRequest("asset not found")
|
return []byte{}, sdk.ErrUnknownRequest("asset not found")
|
||||||
}
|
}
|
||||||
currentPrice := keeper.GetCurrentPrice(ctx, assetCode)
|
currentPrice := keeper.GetCurrentPrice(ctx, marketID)
|
||||||
|
|
||||||
bz, err2 := codec.MarshalJSONIndent(keeper.cdc, currentPrice)
|
bz, err2 := codec.MarshalJSONIndent(keeper.cdc, currentPrice)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
@ -41,13 +43,13 @@ 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) {
|
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]
|
marketID := path[0]
|
||||||
_, found := keeper.GetAsset(ctx, assetCode)
|
_, found := keeper.GetMarket(ctx, marketID)
|
||||||
if !found {
|
if !found {
|
||||||
return []byte{}, sdk.ErrUnknownRequest("asset not found")
|
return []byte{}, sdk.ErrUnknownRequest("asset not found")
|
||||||
}
|
}
|
||||||
rawPrices := keeper.GetRawPrices(ctx, assetCode)
|
rawPrices := keeper.GetRawPrices(ctx, marketID)
|
||||||
for _, price := range rawPrices {
|
for _, price := range rawPrices {
|
||||||
priceList = append(priceList, price.String())
|
priceList = append(priceList, price.String())
|
||||||
}
|
}
|
||||||
@ -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) {
|
func queryAssets(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
|
||||||
var assetList QueryAssetsResp
|
var assetList types.QueryAssetsResp
|
||||||
assets := keeper.GetAssets(ctx)
|
assets := keeper.GetMarketParams(ctx)
|
||||||
for _, asset := range assets {
|
for _, asset := range assets {
|
||||||
assetList = append(assetList, asset.String())
|
assetList = append(assetList, asset.String())
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package pricefeed
|
package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@ -7,8 +7,9 @@ import (
|
|||||||
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
|
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
|
||||||
"github.com/cosmos/cosmos-sdk/x/mock"
|
"github.com/cosmos/cosmos-sdk/x/mock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
|
||||||
"github.com/tendermint/tendermint/crypto"
|
"github.com/tendermint/tendermint/crypto"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/x/pricefeed/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testHelper struct {
|
type testHelper struct {
|
||||||
@ -19,16 +20,12 @@ type testHelper struct {
|
|||||||
privKeys []crypto.PrivKey
|
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()
|
mApp := mock.NewApp()
|
||||||
RegisterCodec(mApp.Cdc)
|
types.RegisterCodec(mApp.Cdc)
|
||||||
keyPricefeed := sdk.NewKVStoreKey(StoreKey)
|
keyPricefeed := sdk.NewKVStoreKey(types.StoreKey)
|
||||||
pk := mApp.ParamsKeeper
|
pk := mApp.ParamsKeeper
|
||||||
keeper := NewKeeper(mApp.Cdc, keyPricefeed, pk.Subspace(DefaultParamspace).WithKeyTable(ParamKeyTable()), DefaultCodespace)
|
keeper := NewKeeper(mApp.Cdc, keyPricefeed, pk.Subspace(types.DefaultParamspace), types.DefaultCodespace)
|
||||||
|
|
||||||
// Register routes
|
|
||||||
mApp.Router().AddRoute(RouterKey, NewHandler(keeper))
|
|
||||||
mApp.SetEndBlocker(getEndBlocker(keeper))
|
|
||||||
|
|
||||||
require.NoError(t, mApp.CompleteSetup(keyPricefeed))
|
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)
|
mock.SetGenesis(mApp, genAccs)
|
||||||
return testHelper{mApp, keeper, addrs, pubKeys, privKeys}
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Market struct that represents 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 Oracles `json:"oracles" yaml:"oracles"`
|
||||||
|
Active bool `json:"active" yaml:"active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implement fmt.Stringer
|
||||||
|
func (a Market) String() string {
|
||||||
|
return fmt.Sprintf(`Asset:
|
||||||
|
Market ID: %s
|
||||||
|
Base Asset: %s
|
||||||
|
Quote Asset: %s
|
||||||
|
Oracles: %s
|
||||||
|
Active: %t`,
|
||||||
|
a.MarketID, a.BaseAsset, a.QuoteAsset, a.Oracles, a.Active)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Markets array type for oracle
|
||||||
|
type Markets []Market
|
||||||
|
|
||||||
|
// String implements fmt.Stringer
|
||||||
|
func (ms Markets) String() string {
|
||||||
|
out := "Markets:\n"
|
||||||
|
for _, m := range ms {
|
||||||
|
out += fmt.Sprintf("%s\n", m.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 {
|
||||||
|
MarketID string `json:"market_id" yaml:"market_id"`
|
||||||
|
Price sdk.Dec `json:"price" yaml:"price"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostedPrice struct represented a price for an asset 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// implement fmt.Stringer
|
||||||
|
func (cp CurrentPrice) String() string {
|
||||||
|
return strings.TrimSpace(fmt.Sprintf(`Market ID: %s
|
||||||
|
Price: %s`, cp.MarketID, cp.Price))
|
||||||
|
}
|
||||||
|
|
||||||
|
// implement fmt.Stringer
|
||||||
|
func (pp PostedPrice) String() string {
|
||||||
|
return strings.TrimSpace(fmt.Sprintf(`Market ID: %s
|
||||||
|
Oracle Address: %s
|
||||||
|
Price: %s
|
||||||
|
Expiry: %s`, pp.MarketID, 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]) }
|
8
x/pricefeed/types/events.go
Normal file
8
x/pricefeed/types/events.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
// Pricefeed module event types
|
||||||
|
const (
|
||||||
|
EventTypeNoValidPrices = "no_valid_prices"
|
||||||
|
|
||||||
|
AttributeKeyPriceUpdateFailed = "price_update_failed"
|
||||||
|
)
|
@ -2,21 +2,18 @@ package types
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenesisState - pricefeed state that must be provided at genesis
|
// GenesisState - pricefeed state that must be provided at genesis
|
||||||
type GenesisState struct {
|
type GenesisState struct {
|
||||||
AssetParams AssetParams `json:"asset_params" yaml:"asset_params"`
|
Params Params `json:"asset_params" yaml:"asset_params"`
|
||||||
OracleParams OracleParams `json:"oracle_params" yaml:"oracle_params"`
|
|
||||||
PostedPrices []PostedPrice `json:"posted_prices" yaml:"posted_prices"`
|
PostedPrices []PostedPrice `json:"posted_prices" yaml:"posted_prices"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGenesisState creates a new genesis state for the pricefeed module
|
// 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{
|
return GenesisState{
|
||||||
AssetParams: ap,
|
Params: p,
|
||||||
OracleParams: op,
|
|
||||||
PostedPrices: pp,
|
PostedPrices: pp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -24,8 +21,7 @@ func NewGenesisState(ap AssetParams, op OracleParams, pp []PostedPrice) GenesisS
|
|||||||
// DefaultGenesisState defines default GenesisState for pricefeed
|
// DefaultGenesisState defines default GenesisState for pricefeed
|
||||||
func DefaultGenesisState() GenesisState {
|
func DefaultGenesisState() GenesisState {
|
||||||
return NewGenesisState(
|
return NewGenesisState(
|
||||||
DefaultAssetParams(),
|
DefaultParams(),
|
||||||
DefaultOracleParams(),
|
|
||||||
[]PostedPrice{},
|
[]PostedPrice{},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -45,19 +41,9 @@ func (data GenesisState) IsEmpty() bool {
|
|||||||
// ValidateGenesis performs basic validation of genesis data returning an
|
// ValidateGenesis performs basic validation of genesis data returning an
|
||||||
// error for any failed validation criteria.
|
// error for any failed validation criteria.
|
||||||
func ValidateGenesis(data GenesisState) error {
|
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
|
if err := data.Params.Validate(); err != nil {
|
||||||
for _, oracle := range data.OracleParams.Oracles {
|
return err
|
||||||
if oracle.OracleAddress == "" {
|
|
||||||
return fmt.Errorf("invalid oracle: %s. missing oracle address", oracle.String())
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -9,4 +9,22 @@ const (
|
|||||||
|
|
||||||
// RouterKey Top level router key
|
// RouterKey Top level router key
|
||||||
RouterKey = ModuleName
|
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
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,7 +17,7 @@ type MsgPostPrice struct {
|
|||||||
From sdk.AccAddress // client that sent in this address
|
From sdk.AccAddress // client that sent in this address
|
||||||
AssetCode string // asset code used by exchanges/api
|
AssetCode string // asset code used by exchanges/api
|
||||||
Price sdk.Dec // price in decimal (max precision 18)
|
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
|
// NewMsgPostPrice creates a new post price msg
|
||||||
@ -23,7 +25,7 @@ func NewMsgPostPrice(
|
|||||||
from sdk.AccAddress,
|
from sdk.AccAddress,
|
||||||
assetCode string,
|
assetCode string,
|
||||||
price sdk.Dec,
|
price sdk.Dec,
|
||||||
expiry sdk.Int) MsgPostPrice {
|
expiry time.Time) MsgPostPrice {
|
||||||
return MsgPostPrice{
|
return MsgPostPrice{
|
||||||
From: from,
|
From: from,
|
||||||
AssetCode: assetCode,
|
AssetCode: assetCode,
|
||||||
@ -60,9 +62,6 @@ func (msg MsgPostPrice) ValidateBasic() sdk.Error {
|
|||||||
if msg.Price.LT(sdk.ZeroDec()) {
|
if msg.Price.LT(sdk.ZeroDec()) {
|
||||||
return sdk.ErrInternal("invalid (negative) price")
|
return sdk.ErrInternal("invalid (negative) price")
|
||||||
}
|
}
|
||||||
if msg.Expiry.LT(sdk.ZeroInt()) {
|
|
||||||
return sdk.ErrInternal("invalid (negative) expiry")
|
|
||||||
}
|
|
||||||
// TODO check coin denoms
|
// TODO check coin denoms
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
tmtime "github.com/tendermint/tendermint/types/time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMsgPlaceBid_ValidateBasic(t *testing.T) {
|
func TestMsgPlaceBid_ValidateBasic(t *testing.T) {
|
||||||
@ -13,8 +14,7 @@ func TestMsgPlaceBid_ValidateBasic(t *testing.T) {
|
|||||||
// OracleAddress: addr.String(),
|
// OracleAddress: addr.String(),
|
||||||
// }}
|
// }}
|
||||||
price, _ := sdk.NewDecFromStr("0.3005")
|
price, _ := sdk.NewDecFromStr("0.3005")
|
||||||
expiry, _ := sdk.NewIntFromString("10")
|
expiry := tmtime.Now()
|
||||||
negativeExpiry, _ := sdk.NewIntFromString("-3")
|
|
||||||
negativePrice, _ := sdk.NewDecFromStr("-3.05")
|
negativePrice, _ := sdk.NewDecFromStr("-3.05")
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@ -26,7 +26,6 @@ func TestMsgPlaceBid_ValidateBasic(t *testing.T) {
|
|||||||
{"emptyAddr", MsgPostPrice{sdk.AccAddress{}, "xrp", price, expiry}, false},
|
{"emptyAddr", MsgPostPrice{sdk.AccAddress{}, "xrp", price, expiry}, false},
|
||||||
{"emptyAsset", MsgPostPrice{addr, "", price, expiry}, false},
|
{"emptyAsset", MsgPostPrice{addr, "", price, expiry}, false},
|
||||||
{"negativePrice", MsgPostPrice{addr, "xrp", negativePrice, expiry}, false},
|
{"negativePrice", MsgPostPrice{addr, "xrp", negativePrice, expiry}, false},
|
||||||
{"negativeExpiry", MsgPostPrice{addr, "xrp", price, negativeExpiry}, false},
|
|
||||||
}
|
}
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
@ -5,77 +5,51 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
params "github.com/cosmos/cosmos-sdk/x/params/subspace"
|
"github.com/cosmos/cosmos-sdk/x/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Parameter store key
|
|
||||||
var (
|
var (
|
||||||
// ParamStoreKeyOracles Param store key for oracles
|
// KeyMarkets store key for markets
|
||||||
ParamStoreKeyOracles = []byte("oracles")
|
KeyMarkets = []byte("Markets")
|
||||||
// ParamStoreKeyAssets Param store key for assets
|
|
||||||
ParamStoreKeyAssets = []byte("assets")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParamKeyTable Key declaration for parameters
|
// ParamKeyTable Key declaration for parameters
|
||||||
func ParamKeyTable() params.KeyTable {
|
func ParamKeyTable() params.KeyTable {
|
||||||
return params.NewKeyTable(
|
return params.NewKeyTable().RegisterParamSet(&Params{})
|
||||||
ParamStoreKeyOracles, OracleParams{},
|
|
||||||
ParamStoreKeyAssets, AssetParams{},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssetParams params for assets. Can be altered via governance
|
// Params params for pricefeed. Can be altered via governance
|
||||||
type AssetParams struct {
|
type Params struct {
|
||||||
Assets []Asset `json:"assets,omitempty" yaml:"assets,omitempty"` // Array containing the assets supported by the pricefeed
|
Markets Markets `json:"markets" yaml:"markets"` // Array containing the markets supported by the pricefeed
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAssetParams creates a new AssetParams object
|
// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs
|
||||||
func NewAssetParams(assets []Asset) AssetParams {
|
// pairs of pricefeed module's parameters.
|
||||||
return AssetParams{
|
func (p Params) ParamSetPairs() params.ParamSetPairs {
|
||||||
Assets: assets,
|
return params.ParamSetPairs{
|
||||||
|
{Key: KeyMarkets, Value: &p.Markets},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultAssetParams default params for assets
|
// NewParams creates a new AssetParams object
|
||||||
func DefaultAssetParams() AssetParams {
|
func NewParams(markets Markets) Params {
|
||||||
return NewAssetParams([]Asset{})
|
return Params{
|
||||||
}
|
Markets: markets,
|
||||||
|
|
||||||
// implements fmt.stringer
|
|
||||||
func (ap AssetParams) String() string {
|
|
||||||
var assetListString []string
|
|
||||||
for _, asset := range ap.Assets {
|
|
||||||
assetListString = append(assetListString, asset.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
|
// DefaultParams default params for pricefeed
|
||||||
func DefaultOracleParams() OracleParams {
|
func DefaultParams() Params {
|
||||||
return NewOracleParams([]Oracle{})
|
return NewParams(Markets{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// implements fmt.stringer
|
// String implements fmt.stringer
|
||||||
func (op OracleParams) String() string {
|
func (p Params) String() string {
|
||||||
var oracleListString []string
|
out := "Params:\n"
|
||||||
for _, oracle := range op.Oracles {
|
for _, a := range p.Markets {
|
||||||
oracleListString = append(oracleListString, oracle.String())
|
out += a.String()
|
||||||
}
|
}
|
||||||
return strings.TrimSpace(fmt.Sprintf(`Oracle Params:
|
return strings.TrimSpace(out)
|
||||||
Oracles: %s\`, strings.Join(oracleListString, ", ")))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParamSubspace defines the expected Subspace interface for parameters
|
// ParamSubspace defines the expected Subspace interface for parameters
|
||||||
@ -83,3 +57,14 @@ type ParamSubspace interface {
|
|||||||
Get(ctx sdk.Context, key []byte, ptr interface{})
|
Get(ctx sdk.Context, key []byte, ptr interface{})
|
||||||
Set(ctx sdk.Context, key []byte, param 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.Markets {
|
||||||
|
if asset.MarketID == "" {
|
||||||
|
return fmt.Errorf("invalid market: %s. missing market ID", 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