Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add oracle pair to market object #1705

Merged
merged 10 commits into from
Dec 13, 2023
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"`
}
matthiasmatt marked this conversation as resolved.
Show resolved Hide resolved

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,
matthiasmatt marked this conversation as resolved.
Show resolved Hide resolved
}
}

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")
matthiasmatt marked this conversation as resolved.
Show resolved Hide resolved
}

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
matthiasmatt marked this conversation as resolved.
Show resolved Hide resolved
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,
matthiasmatt marked this conversation as resolved.
Show resolved Hide resolved
},
{
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
matthiasmatt marked this conversation as resolved.
Show resolved Hide resolved
})
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
matthiasmatt marked this conversation as resolved.
Show resolved Hide resolved
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
Loading