Skip to content

Commit

Permalink
feat: add oracle pair to market object
Browse files Browse the repository at this point in the history
  • Loading branch information
matthiasmatt committed Dec 12, 2023
1 parent 8d4ee29 commit c806ddf
Show file tree
Hide file tree
Showing 17 changed files with 232 additions and 90 deletions.
8 changes: 8 additions & 0 deletions proto/nibiru/perp/v2/state.proto
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ message Market {
(gogoproto.stdduration) = true,
(gogoproto.nullable) = false
];

// the pair of the oracle that is used to determine the index price
// for the market
string oracle_pair = 15 [
(gogoproto.customtype) =
"github.com/NibiruChain/nibiru/x/common/asset.Pair",
(gogoproto.nullable) = false
];
}

// MarketLastVersion is used to store the last version of the market
Expand Down
1 change: 1 addition & 0 deletions wasmbinding/bindings/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ type MarketParams struct {
MaxFundingRate sdk.Dec `json:"max_funding_rate,omitempty"`
// amount of time to look back for TWAP calculations
TwapLookbackWindow sdkmath.Int `json:"twap_lookback_window"`
OraclePair string `json:"oracle_pair"`
}

type NoOp struct{}
1 change: 1 addition & 0 deletions wasmbinding/exec_perp.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func (exec *ExecutorPerp) CreateMarket(
MaxFundingRate: mp.MaxFundingRate,
TwapLookbackWindow: time.Duration(mp.TwapLookbackWindow.Int64()),
PrepaidBadDebt: sdk.NewCoin(pair.QuoteDenom(), sdk.ZeroInt()),
OraclePair: pair,
}
}

Expand Down
4 changes: 4 additions & 0 deletions x/common/asset/pair.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ func (pair Pair) QuoteDenom() string {

// Validate performs a basic validation of the market params
func (pair Pair) Validate() error {
if len(pair) == 0 {
return ErrInvalidTokenPair.Wrap("oracle pair is empty")
}

split := strings.Split(pair.String(), ":")
if len(split) != 2 {
return ErrInvalidTokenPair.Wrap(pair.String())
Expand Down
6 changes: 6 additions & 0 deletions x/common/testutil/genesis/perp_genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func AddPerpV2Genesis(gen app.GenesisState) app.GenesisState {
asset.Registry.Pair(denoms.BTC, denoms.NUSD): {
Market: perpv2types.Market{
Pair: asset.NewPair(denoms.BTC, denoms.NUSD),
OraclePair: asset.NewPair(denoms.BTC, denoms.USD),
Version: 1,
Enabled: true,
MaintenanceMarginRatio: sdk.MustNewDecFromStr("0.04"),
Expand Down Expand Up @@ -46,6 +47,7 @@ func AddPerpV2Genesis(gen app.GenesisState) app.GenesisState {
asset.Registry.Pair(denoms.ATOM, denoms.NUSD): {
Market: perpv2types.Market{
Pair: asset.NewPair(denoms.ATOM, denoms.NUSD),
OraclePair: asset.NewPair(denoms.ATOM, denoms.USD),
Enabled: true,
Version: 1,
MaintenanceMarginRatio: sdk.MustNewDecFromStr("0.0625"),
Expand Down Expand Up @@ -74,6 +76,7 @@ func AddPerpV2Genesis(gen app.GenesisState) app.GenesisState {
asset.Registry.Pair(denoms.OSMO, denoms.NUSD): {
Market: perpv2types.Market{
Pair: asset.NewPair(denoms.OSMO, denoms.NUSD),
OraclePair: asset.NewPair(denoms.OSMO, denoms.USD),
Enabled: true,
Version: 1,
MaintenanceMarginRatio: sdk.MustNewDecFromStr("0.0625"),
Expand Down Expand Up @@ -134,6 +137,7 @@ var START_MARKETS = map[asset.Pair]perpv2types.AmmMarket{
asset.Registry.Pair(denoms.ETH, denoms.NUSD): {
Market: perpv2types.Market{
Pair: asset.Registry.Pair(denoms.ETH, denoms.NUSD),
OraclePair: asset.Registry.Pair(denoms.ETH, denoms.USD),
Enabled: true,
Version: 1,
MaintenanceMarginRatio: sdk.MustNewDecFromStr("0.0625"),
Expand Down Expand Up @@ -162,6 +166,7 @@ var START_MARKETS = map[asset.Pair]perpv2types.AmmMarket{
asset.Registry.Pair(denoms.NIBI, denoms.NUSD): {
Market: perpv2types.Market{
Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD),
OraclePair: asset.Registry.Pair(denoms.NIBI, denoms.USD),
Enabled: true,
Version: 1,
MaintenanceMarginRatio: sdk.MustNewDecFromStr("0.04"),
Expand Down Expand Up @@ -194,6 +199,7 @@ func PerpV2Genesis() *perpv2types.GenesisState {
Markets: []perpv2types.Market{
{
Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD),
OraclePair: asset.Registry.Pair(denoms.BTC, denoms.USD),
Enabled: true,
MaintenanceMarginRatio: sdk.MustNewDecFromStr("0.04"),
MaxLeverage: sdk.MustNewDecFromStr("20"),
Expand Down
1 change: 1 addition & 0 deletions x/common/testutil/mock/perp_market.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ func TestMarket() *types.Market {
MaxFundingRate: sdk.NewDec(1),
TwapLookbackWindow: time.Minute * 30,
PrepaidBadDebt: sdk.NewInt64Coin(denoms.NUSD, 0),
OraclePair: asset.NewPair(denoms.BTC, denoms.USD),
}
}
11 changes: 11 additions & 0 deletions x/perp/v2/client/cli/gen_market.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
FlagMaintenenceMarginRatio = "mmr"
FlagMaxLeverage = "max-leverage"
FlagMaxFundingrate = "max-funding-rate"
FlagOraclePair = "oracle-pair"
)

var addMarketGenesisFlags = map[string]struct {
Expand All @@ -39,6 +40,7 @@ var addMarketGenesisFlags = map[string]struct {
FlagMaintenenceMarginRatio: {"0.0625", "maintenance margin ratio"},
FlagMaxLeverage: {"10", "maximum leverage for opening a position"},
FlagMaxFundingrate: {"0.01", "maximum funding rate for the market"},
FlagOraclePair: {"", "oracle pair identifier of the form 'base:quote'. E.g., ueth:uusd"},
}

// getCmdFlagSet returns a flag set and list of required flags for the command.
Expand Down Expand Up @@ -140,6 +142,9 @@ func newMarketFromFlags(flagSet *flag.FlagSet,
maxFundingRateStr, err := flagSet.GetString(FlagMaxFundingrate)
flagErrors = append(flagErrors, err)

oraclePairStr, err := flagSet.GetString(FlagOraclePair)
flagErrors = append(flagErrors, err)

for _, err := range flagErrors { // for brevity's sake
if err != nil {
return types.Market{}, types.AMM{}, err
Expand Down Expand Up @@ -171,6 +176,11 @@ func newMarketFromFlags(flagSet *flag.FlagSet,
return types.Market{}, types.AMM{}, err
}

oraclePair, err := asset.TryNewPair(oraclePairStr)
if err != nil {
return
}

priceMultiplier, err := sdk.NewDecFromStr(priceMultiplierStr)
if err != nil {
return types.Market{}, types.AMM{}, err
Expand All @@ -190,6 +200,7 @@ func newMarketFromFlags(flagSet *flag.FlagSet,
MaxFundingRate: maxFundingRate,
TwapLookbackWindow: time.Minute * 30,
PrepaidBadDebt: sdk.NewInt64Coin(pair.QuoteDenom(), 0),
OraclePair: oraclePair,
}
if err := market.Validate(); err != nil {
return types.Market{}, types.AMM{}, err
Expand Down
21 changes: 21 additions & 0 deletions x/perp/v2/client/cli/gen_market_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func TestAddMarketGenesisCmd(t *testing.T) {
maintainRatio string
maxLeverage string
maxFundingRate string
oraclePair string
expectError bool
}{
{
Expand All @@ -31,6 +32,7 @@ func TestAddMarketGenesisCmd(t *testing.T) {
maintainRatio: "1",
maxLeverage: "1",
maxFundingRate: "1",
oraclePair: "token0:token1",
expectError: true,
},
{
Expand All @@ -41,6 +43,7 @@ func TestAddMarketGenesisCmd(t *testing.T) {
maintainRatio: "1",
maxLeverage: "1",
maxFundingRate: "1",
oraclePair: "token0:token1",
expectError: true,
},
{
Expand All @@ -51,6 +54,7 @@ func TestAddMarketGenesisCmd(t *testing.T) {
maintainRatio: "1",
maxLeverage: "1",
maxFundingRate: "1",
oraclePair: "token0:token1",
expectError: true,
},
{
Expand All @@ -61,6 +65,7 @@ func TestAddMarketGenesisCmd(t *testing.T) {
maintainRatio: "0.1",
maxLeverage: "0",
maxFundingRate: "0",
oraclePair: "token0:token1",
expectError: true,
},
{
Expand All @@ -71,6 +76,7 @@ func TestAddMarketGenesisCmd(t *testing.T) {
maintainRatio: "0.1",
maxLeverage: "1",
maxFundingRate: "1",
oraclePair: "token0:token1",
expectError: true,
},
{
Expand All @@ -81,6 +87,7 @@ func TestAddMarketGenesisCmd(t *testing.T) {
maintainRatio: "0.1",
maxLeverage: "1",
maxFundingRate: "1",
oraclePair: "token0:token1",
expectError: true,
},
{
Expand All @@ -91,6 +98,18 @@ func TestAddMarketGenesisCmd(t *testing.T) {
maintainRatio: "0.1",
maxLeverage: "10",
maxFundingRate: "-1",
oraclePair: "token0:token1",
expectError: true,
},
{
name: "negative max funding rate",
pairName: "token0:token1",
sqrtDepth: "100",
priceMultiplier: "1",
maintainRatio: "0.1",
maxLeverage: "10",
maxFundingRate: "-1",
oraclePair: "invalidPair",
expectError: true,
},
{
Expand All @@ -101,6 +120,7 @@ func TestAddMarketGenesisCmd(t *testing.T) {
maintainRatio: "0.1",
maxLeverage: "10",
maxFundingRate: "10",
oraclePair: "token0:token1",
expectError: false,
},
}
Expand All @@ -117,6 +137,7 @@ func TestAddMarketGenesisCmd(t *testing.T) {
fmt.Sprintf("--%s=%s", cli.FlagMaintenenceMarginRatio, tc.maintainRatio),
fmt.Sprintf("--%s=%s", cli.FlagMaxLeverage, tc.maxLeverage),
fmt.Sprintf("--%s=%s", cli.FlagMaxFundingrate, tc.maxFundingRate),
fmt.Sprintf("--%s=%s", cli.FlagOraclePair, tc.oraclePair),
fmt.Sprintf("--%s=home", flags.FlagHome),
})

Expand Down
10 changes: 10 additions & 0 deletions x/perp/v2/keeper/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ func TestCreateMarket(t *testing.T) {
})
require.ErrorContains(t, err, "maintenance margin ratio ratio must be 0 <= ratio <= 1")

// Error because of invalid oracle pair
market = perptypes.DefaultMarket(pair).WithOraclePair("random")
err = admin.CreateMarket(ctx, keeper.ArgsCreateMarket{
Pair: pair,
PriceMultiplier: amm.PriceMultiplier,
SqrtDepth: amm.SqrtDepth,
Market: &market, // Invalid maintenance ratio
})
require.ErrorContains(t, err, "err when validating oracle pair random: invalid token pair")

// Error because of invalid amm
err = admin.CreateMarket(ctx, keeper.ArgsCreateMarket{
Pair: pair,
Expand Down
2 changes: 1 addition & 1 deletion x/perp/v2/keeper/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func (k Keeper) AfterEpochEnd(ctx sdk.Context, epochIdentifier string, number ui
return
}

indexTwap, err := k.OracleKeeper.GetExchangeRateTwap(ctx, market.Pair)
indexTwap, err := k.OracleKeeper.GetExchangeRateTwap(ctx, market.OraclePair)
if err != nil {
ctx.Logger().Error("failed to fetch twap index price", "market.Pair", market.Pair, "error", err)
continue
Expand Down
17 changes: 9 additions & 8 deletions x/perp/v2/keeper/hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
)

func TestAfterEpochEnd(t *testing.T) {
pairBtcUsd := asset.Registry.Pair(denoms.BTC, denoms.USD)
pairBtcUsdc := asset.Registry.Pair(denoms.BTC, denoms.USDC)
startTime := time.Now()

Expand All @@ -25,7 +26,7 @@ func TestAfterEpochEnd(t *testing.T) {
Given(
CreateCustomMarket(pairBtcUsdc, WithEnabled(true)),
SetBlockTime(startTime),
InsertOraclePriceSnapshot(pairBtcUsdc, startTime.Add(15*time.Minute), sdk.MustNewDecFromStr("5.8")),
InsertOraclePriceSnapshot(pairBtcUsd, startTime.Add(15*time.Minute), sdk.MustNewDecFromStr("5.8")),
StartEpoch(epochtypes.ThirtyMinuteEpochID),
).
When(
Expand All @@ -39,7 +40,7 @@ func TestAfterEpochEnd(t *testing.T) {
Given(
CreateCustomMarket(pairBtcUsdc, WithEnabled(true)),
SetBlockTime(startTime),
InsertOraclePriceSnapshot(pairBtcUsdc, startTime.Add(15*time.Minute), sdk.MustNewDecFromStr("0.52")),
InsertOraclePriceSnapshot(pairBtcUsd, startTime.Add(15*time.Minute), sdk.MustNewDecFromStr("0.52")),
StartEpoch(epochtypes.ThirtyMinuteEpochID),
).
When(
Expand All @@ -53,7 +54,7 @@ func TestAfterEpochEnd(t *testing.T) {
Given(
CreateCustomMarket(pairBtcUsdc, WithEnabled(true), WithMaxFundingRate(sdk.MustNewDecFromStr("0.001"))),
SetBlockTime(startTime),
InsertOraclePriceSnapshot(pairBtcUsdc, startTime.Add(15*time.Minute), sdk.MustNewDecFromStr("5.8")),
InsertOraclePriceSnapshot(pairBtcUsd, startTime.Add(15*time.Minute), sdk.MustNewDecFromStr("5.8")),
StartEpoch(epochtypes.ThirtyMinuteEpochID),
).
When(
Expand All @@ -67,7 +68,7 @@ func TestAfterEpochEnd(t *testing.T) {
Given(
CreateCustomMarket(pairBtcUsdc, WithEnabled(true), WithMaxFundingRate(sdk.MustNewDecFromStr("0.001"))),
SetBlockTime(startTime),
InsertOraclePriceSnapshot(pairBtcUsdc, startTime.Add(15*time.Minute), sdk.MustNewDecFromStr("0.52")),
InsertOraclePriceSnapshot(pairBtcUsd, startTime.Add(15*time.Minute), sdk.MustNewDecFromStr("0.52")),
StartEpoch(epochtypes.ThirtyMinuteEpochID),
).
When(
Expand All @@ -81,7 +82,7 @@ func TestAfterEpochEnd(t *testing.T) {
Given(
CreateCustomMarket(pairBtcUsdc, WithEnabled(true)),
SetBlockTime(startTime),
InsertOraclePriceSnapshot(pairBtcUsdc, startTime.Add(15*time.Minute), sdk.OneDec()),
InsertOraclePriceSnapshot(pairBtcUsd, startTime.Add(15*time.Minute), sdk.OneDec()),
StartEpoch(epochtypes.ThirtyMinuteEpochID),
).
When(
Expand Down Expand Up @@ -109,7 +110,7 @@ func TestAfterEpochEnd(t *testing.T) {
CreateCustomMarket(pairBtcUsdc, WithEnabled(true)),
SetBlockTime(startTime),
StartEpoch(epochtypes.ThirtyMinuteEpochID),
InsertOraclePriceSnapshot(pairBtcUsdc, startTime.Add(15*time.Minute), sdk.ZeroDec()),
InsertOraclePriceSnapshot(pairBtcUsd, startTime.Add(15*time.Minute), sdk.ZeroDec()),
).
When(
MoveToNextBlockWithDuration(30 * time.Minute),
Expand All @@ -124,7 +125,7 @@ func TestAfterEpochEnd(t *testing.T) {
CloseMarket(pairBtcUsdc),
SetBlockTime(startTime),
StartEpoch(epochtypes.ThirtyMinuteEpochID),
InsertOraclePriceSnapshot(pairBtcUsdc, startTime.Add(15*time.Minute), sdk.NewDec(2)),
InsertOraclePriceSnapshot(pairBtcUsd, startTime.Add(15*time.Minute), sdk.NewDec(2)),
).
When(
MoveToNextBlockWithDuration(30 * time.Minute),
Expand All @@ -139,7 +140,7 @@ func TestAfterEpochEnd(t *testing.T) {
CloseMarket(pairBtcUsdc),
SetBlockTime(startTime),
StartEpoch(epochtypes.DayEpochID),
InsertOraclePriceSnapshot(pairBtcUsdc, startTime.Add(15*time.Minute), sdk.NewDec(2)),
InsertOraclePriceSnapshot(pairBtcUsd, startTime.Add(15*time.Minute), sdk.NewDec(2)),
).
When(
MoveToNextBlockWithDuration(30 * time.Minute),
Expand Down
6 changes: 3 additions & 3 deletions x/perp/v2/module/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate {
}

var indexTwap sdk.Dec
indexTwap, err = k.OracleKeeper.GetExchangeRateTwap(ctx, amm.Pair)
indexTwap, err = k.OracleKeeper.GetExchangeRateTwap(ctx, market.OraclePair)
if err != nil {
k.Logger(ctx).Error("failed to fetch twap index price", "market.Pair", market.Pair, "error", err)
k.Logger(ctx).Error("failed to fetch twap index price", "market.Pair", market.Pair, "market.OraclePair", market.OraclePair, "error", err)
indexTwap = sdk.OneDec().Neg()
}

if indexTwap.IsNil() {
k.Logger(ctx).Error("index price is zero", "market.Pair", market.Pair)
k.Logger(ctx).Error("index price is zero", "market.Pair", market.Pair, "market.OraclePair", market.OraclePair)
continue
}

Expand Down
2 changes: 2 additions & 0 deletions x/perp/v2/types/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/NibiruChain/nibiru/x/common/asset"
"github.com/NibiruChain/nibiru/x/common/denoms"
epochstypes "github.com/NibiruChain/nibiru/x/epochs/types"

"github.com/cosmos/cosmos-sdk/codec"
Expand Down Expand Up @@ -64,6 +65,7 @@ func DefaultMarket(pair asset.Pair) Market {
PrepaidBadDebt: sdk.NewCoin(TestingCollateralDenomNUSD, sdk.ZeroInt()),
MaintenanceMarginRatio: sdk.MustNewDecFromStr("0.0625"),
MaxLeverage: sdk.NewDec(10),
OraclePair: asset.NewPair(pair.BaseDenom(), denoms.USD),
}
}

Expand Down
Loading

0 comments on commit c806ddf

Please sign in to comment.