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(vpool): Add check for maximum leverage for open positions #793

Merged
merged 12 commits into from
Aug 9, 2022
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

* [#796](https://github.com/NibiruChain/nibiru/pull/796) - fix bug that caused that epochKeeper was nil when running epoch hook from Perp module
* [#793](https://github.com/NibiruChain/nibiru/pull/793) - add a vpool parameter to limit leverage in open position

## [v0.12.0](https://github.com/NibiruChain/nibiru/releases/tag/v0.12.0) - 2022-08-03

Expand Down
6 changes: 6 additions & 0 deletions proto/vpool/v1/gov.proto
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,10 @@ message CreatePoolProposal {
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];

// max_leverage
string max_leverage = 10 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
}
6 changes: 6 additions & 0 deletions proto/vpool/v1/vpool.proto
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,10 @@ message Pool {
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];

// max_leverage
string max_leverage = 8 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
}
2 changes: 2 additions & 0 deletions x/perp/keeper/calc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func TestCalcRemainMarginWithFundingPayment(t *testing.T) {
/* fluctuationLimit */ sdk.MustNewDecFromStr("1.0"), // 100%
/* maxOracleSpreadRatio */ sdk.MustNewDecFromStr("1.0"), // 100%
/* maintenanceMarginRatio */ sdk.MustNewDecFromStr("0.0625"),
/* maxLeverage */ sdk.MustNewDecFromStr("15"),
)
premiumFractions := []sdk.Dec{sdk.ZeroDec()} // fPayment -> 0
require.True(t, vpoolKeeper.ExistsPool(ctx, pair))
Expand Down Expand Up @@ -102,6 +103,7 @@ func TestCalcRemainMarginWithFundingPayment(t *testing.T) {
/* fluctuationLimit */ sdk.MustNewDecFromStr("1.0"), // 100%
/* maxOracleSpreadRatio */ sdk.MustNewDecFromStr("1.0"), // 100%
/* maintenanceMarginRatio */ sdk.MustNewDecFromStr("0.0625"),
/* maxLeverage */ sdk.MustNewDecFromStr("15"),
)
premiumFractions := []sdk.Dec{
sdk.MustNewDecFromStr("0.25"),
Expand Down
5 changes: 5 additions & 0 deletions x/perp/keeper/clearing_house.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ func (k Keeper) OpenPosition(
// - Checks that the VPool exists.
// - Checks that quote asset is not zero.
// - Checks that leverage is not zero.
// - Checks that leverage is below requirement.
//
func (k Keeper) checkOpenPositionRequirements(ctx sdk.Context, pair common.AssetPair, quoteAssetAmount sdk.Int, leverage sdk.Dec) error {
if err := k.requireVpool(ctx, pair); err != nil {
Expand All @@ -108,6 +109,10 @@ func (k Keeper) checkOpenPositionRequirements(ctx sdk.Context, pair common.Asset
return types.ErrLeverageIsZero
}

if leverage.GT(k.VpoolKeeper.GetMaxLeverage(ctx, pair)) {
return types.ErrLeverageIsTooHigh
}

return nil
}

Expand Down
9 changes: 6 additions & 3 deletions x/perp/keeper/clearing_house_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ func TestOpenPositionSuccess(t *testing.T) {
/* fluctuationLimit */ sdk.MustNewDecFromStr("0.1"),
/* maxOracleSpreadRatio */ sdk.OneDec(),
/* maintenanceMarginRatio */ sdk.MustNewDecFromStr("0.0625"),
/* maxLeverage */ sdk.MustNewDecFromStr("15"),
)
nibiruApp.PerpKeeper.PairMetadataState(ctx).Set(&types.PairMetadata{
Pair: common.PairBTCStable,
Expand Down Expand Up @@ -406,17 +407,17 @@ func TestOpenPositionError(t *testing.T) {
margin: sdk.NewInt(100),
leverage: sdk.NewDec(100),
baseLimit: sdk.NewDec(11_000),
expectedErr: types.ErrMarginRatioTooLow,
expectedErr: types.ErrLeverageIsTooHigh,
},
{
name: "leverage amount is too high - BUY",
traderFunds: sdk.NewCoins(sdk.NewInt64Coin(common.DenomStable, 1020)),
initialPosition: nil,
side: types.Side_BUY,
margin: sdk.NewInt(100),
leverage: sdk.NewDec(100),
leverage: sdk.NewDec(16),
baseLimit: sdk.NewDec(0),
expectedErr: types.ErrMarginRatioTooLow,
expectedErr: types.ErrLeverageIsTooHigh,
},
{
name: "new long position over fluctuation limit",
Expand Down Expand Up @@ -464,6 +465,7 @@ func TestOpenPositionError(t *testing.T) {
/* fluctuationLimit */ sdk.MustNewDecFromStr("0.1"),
/* maxOracleSpreadRatio */ sdk.OneDec(),
/* maintenanceMarginRatio */ sdk.MustNewDecFromStr("0.0625"),
/* maxLeverage */ sdk.MustNewDecFromStr("15"),
)
nibiruApp.PerpKeeper.PairMetadataState(ctx).Set(&types.PairMetadata{
Pair: common.PairBTCStable,
Expand Down Expand Up @@ -530,6 +532,7 @@ func TestOpenPositionInvalidPair(t *testing.T) {
sdk.MustNewDecFromStr("0.1"), // 0.9 ratio
sdk.MustNewDecFromStr("0.1"),
/* maintenanceMarginRatio */ sdk.MustNewDecFromStr("0.0625"),
/* maxLeverage */ sdk.MustNewDecFromStr("15"),
)
nibiruApp.PricefeedKeeper.ActivePairsStore().Set(ctx, pair, true)

Expand Down
1 change: 1 addition & 0 deletions x/perp/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ func TestQueryPosition(t *testing.T) {
/* fluctuationLimitRatio */ sdk.OneDec(),
/* maxOracleSpreadRatio */ sdk.OneDec(),
/* maintenanceMarginRatio */ sdk.MustNewDecFromStr("0.0625"),
/* maxLeverage */ sdk.MustNewDecFromStr("15"),
)
perpKeeper.PairMetadataState(ctx).Set(&types.PairMetadata{
Pair: common.PairBTCStable,
Expand Down
2 changes: 2 additions & 0 deletions x/perp/keeper/liquidate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func TestExecuteFullLiquidation(t *testing.T) {
/* fluctuationLimitRatio */ sdk.MustNewDecFromStr("1"),
/* maxOracleSpreadRatio */ sdk.MustNewDecFromStr("0.1"),
/* maintenanceMarginRatio */ sdk.MustNewDecFromStr("0.0625"),
/* maxLeverage */ sdk.MustNewDecFromStr("15"),
)
require.True(t, vpoolKeeper.ExistsPool(ctx, tokenPair))
nibiruApp.PricefeedKeeper.ActivePairsStore().Set(ctx, tokenPair, true)
Expand Down Expand Up @@ -262,6 +263,7 @@ func TestExecutePartialLiquidation(t *testing.T) {
/* fluctuationLimitRatio */ sdk.MustNewDecFromStr("1"),
/* maxOracleSpreadRatio */ sdk.MustNewDecFromStr("0.1"),
/* maintenanceMarginRatio */ sdk.MustNewDecFromStr("0.0625"),
/* maxLeverage */ sdk.MustNewDecFromStr("15"),
)
nibiruApp.PricefeedKeeper.ActivePairsStore().Set(ctx, tokenPair, true)
require.True(t, vpoolKeeper.ExistsPool(ctx, tokenPair))
Expand Down
3 changes: 3 additions & 0 deletions x/perp/keeper/margin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ func TestAddMarginSuccess(t *testing.T) {
/* fluctuationLimitRatio */ sdk.MustNewDecFromStr("0.1"), // 0.1 ratio
/* maxOracleSpreadRatio */ sdk.OneDec(), // 100%
/* maintenanceMarginRatio */ sdk.MustNewDecFromStr("0.0625"),
/* maxLeverage */ sdk.MustNewDecFromStr("15"),
)
require.True(t, vpoolKeeper.ExistsPool(ctx, common.PairBTCStable))

Expand Down Expand Up @@ -141,6 +142,7 @@ func TestRemoveMargin(t *testing.T) {
/* fluctuationLimit */ sdk.MustNewDecFromStr("1.0"), // 100%
/* maxOracleSpreadRatio */ sdk.MustNewDecFromStr("1.0"), // 100%
/* maintenanceMarginRatio */ sdk.MustNewDecFromStr("0.0625"),
/* maxLeverage */ sdk.MustNewDecFromStr("15"),
)

removeAmt := sdk.NewInt(5)
Expand Down Expand Up @@ -171,6 +173,7 @@ func TestRemoveMargin(t *testing.T) {
/* fluctuationLimit */ sdk.MustNewDecFromStr("1.0"), // 0.9 ratio
/* maxOracleSpreadRatio */ sdk.MustNewDecFromStr("0.4"), // 0.9 ratio
/* maintenanceMarginRatio */ sdk.MustNewDecFromStr("0.0625"),
/* maxLeverage */ sdk.MustNewDecFromStr("15"),
)
require.True(t, vpoolKeeper.ExistsPool(ctx, pair))

Expand Down
27 changes: 25 additions & 2 deletions x/perp/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ func TestMsgServerAddMargin(t *testing.T) {
/* fluctuationLimitRatio */ sdk.OneDec(),
/* maxOracleSpreadRatio */ sdk.OneDec(),
/* maintenanceMarginRatio */ sdk.MustNewDecFromStr("0.0625"),
/* maxLeverage */ sdk.MustNewDecFromStr("15"),
)
app.PerpKeeper.PairMetadataState(ctx).Set(&types.PairMetadata{
Pair: common.PairBTCStable,
Expand Down Expand Up @@ -200,6 +201,7 @@ func TestMsgServerRemoveMargin(t *testing.T) {
/* fluctuationLimitRatio */ sdk.OneDec(),
/* maxOracleSpreadRatio */ sdk.OneDec(),
/* maintenanceMarginRatio */ sdk.MustNewDecFromStr("0.0625"),
/* maxLeverage */ sdk.MustNewDecFromStr("15"),
)
app.PerpKeeper.PairMetadataState(ctx).Set(&types.PairMetadata{
Pair: common.PairBTCStable,
Expand Down Expand Up @@ -276,7 +278,17 @@ func TestMsgServerOpenPosition(t *testing.T) {
msgServer := keeper.NewMsgServerImpl(app.PerpKeeper)

t.Log("create vpool")
app.VpoolKeeper.CreatePool(ctx, common.PairBTCStable, sdk.OneDec(), sdk.NewDec(1_000_000), sdk.NewDec(1_000_000), sdk.OneDec(), sdk.OneDec(), sdk.MustNewDecFromStr("0.0625"))
app.VpoolKeeper.CreatePool(
ctx,
common.PairBTCStable,
sdk.OneDec(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nit) would be nice to add argument comments for the other parameters too.

sdk.NewDec(1_000_000),
sdk.NewDec(1_000_000),
sdk.OneDec(),
sdk.OneDec(),
sdk.MustNewDecFromStr("0.0625"),
/* maxLeverage */ sdk.MustNewDecFromStr("15"),
)
app.PerpKeeper.PairMetadataState(ctx).Set(&types.PairMetadata{
Pair: common.PairBTCStable,
CumulativePremiumFractions: []sdk.Dec{sdk.ZeroDec()},
Expand Down Expand Up @@ -360,6 +372,7 @@ func TestMsgServerClosePosition(t *testing.T) {
/* fluctuationLimitRatio */ sdk.OneDec(),
/* maxOracleSpreadRatio */ sdk.OneDec(),
/* maintenanceMarginRatio */ sdk.MustNewDecFromStr("0.0625"),
/* maxLeverage */ sdk.MustNewDecFromStr("15"),
)
app.PerpKeeper.PairMetadataState(ctx).Set(&types.PairMetadata{
Pair: common.PairBTCStable,
Expand Down Expand Up @@ -447,7 +460,17 @@ func TestMsgServerLiquidate(t *testing.T) {
msgServer := keeper.NewMsgServerImpl(app.PerpKeeper)

t.Log("create vpool")
app.VpoolKeeper.CreatePool(ctx, common.PairBTCStable, sdk.OneDec(), sdk.NewDec(1_000_000), sdk.NewDec(1_000_000), sdk.OneDec(), sdk.OneDec(), sdk.MustNewDecFromStr("0.0625"))
app.VpoolKeeper.CreatePool(
ctx,
common.PairBTCStable,
sdk.OneDec(),
sdk.NewDec(1_000_000),
sdk.NewDec(1_000_000),
sdk.OneDec(),
sdk.OneDec(),
sdk.MustNewDecFromStr("0.0625"),
/* maxLeverage */ sdk.MustNewDecFromStr("15"),
)
app.PerpKeeper.PairMetadataState(ctx).Set(&types.PairMetadata{
Pair: common.PairBTCStable,
CumulativePremiumFractions: []sdk.Dec{sdk.ZeroDec()},
Expand Down
1 change: 1 addition & 0 deletions x/perp/keeper/perp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ func TestKeeperClosePosition(t *testing.T) {
/*fluctuationLimitRatio*/ sdk.MustNewDecFromStr("0.1"),
/*maxOracleSpreadRatio*/ sdk.MustNewDecFromStr("0.1"),
/* maintenanceMarginRatio */ sdk.MustNewDecFromStr("0.0625"),
/* maxLeverage */ sdk.MustNewDecFromStr("15"),
)
require.True(t, vpoolKeeper.ExistsPool(ctx, pair))
nibiruApp.PricefeedKeeper.ActivePairsStore().Set(ctx, pair, true)
Expand Down
1 change: 1 addition & 0 deletions x/perp/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ type VpoolKeeper interface {

IsOverSpreadLimit(ctx sdk.Context, pair common.AssetPair) bool
GetMaintenanceMarginRatio(ctx sdk.Context, pair common.AssetPair) sdk.Dec
GetMaxLeverage(ctx sdk.Context, pair common.AssetPair) sdk.Dec
// ExistsPool returns true if pool exists, false if not.
ExistsPool(ctx sdk.Context, pair common.AssetPair) bool
GetSettlementPrice(ctx sdk.Context, pair common.AssetPair) (sdk.Dec, error)
Expand Down
1 change: 1 addition & 0 deletions x/perp/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var (
ErrQuoteAmountIsZero = sdkerrors.Register(ModuleName, 8, "quote amount cannot be zero")
ErrLeverageIsZero = sdkerrors.Register(ModuleName, 9, "leverage cannot be zero")
ErrMarginRatioTooLow = sdkerrors.Register(ModuleName, 10, "margin ratio did not meet maintenance margin ratio")
ErrLeverageIsTooHigh = sdkerrors.Register(ModuleName, 11, "leverage cannot be higher than vpool parameter")
)

func ZeroPosition(ctx sdk.Context, tokenPair common.AssetPair, traderAddr sdk.AccAddress) *Position {
Expand Down
14 changes: 14 additions & 0 deletions x/testutil/mock/perp_interfaces.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions x/vpool/client/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ func (s IntegrationTestSuite) TestX_CmdAddVpool() {
FluctuationLimitRatio: proposal.FluctuationLimitRatio,
MaxOracleSpreadRatio: proposal.MaxOracleSpreadRatio,
MaintenanceMarginRatio: proposal.MaintenanceMarginRatio,
MaxLeverage: proposal.MaxLeverage,
}, pool)
found = true
}
Expand Down
1 change: 1 addition & 0 deletions x/vpool/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState)
vp.FluctuationLimitRatio,
vp.MaxOracleSpreadRatio,
vp.MaintenanceMarginRatio,
vp.MaxLeverage,
)
}
}
Expand Down
2 changes: 2 additions & 0 deletions x/vpool/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func TestGenesis(t *testing.T) {
FluctuationLimitRatio: sdk.MustNewDecFromStr("0.20"),
MaxOracleSpreadRatio: sdk.MustNewDecFromStr("0.20"),
MaintenanceMarginRatio: sdk.MustNewDecFromStr("0.0625"),
MaxLeverage: sdk.MustNewDecFromStr("15"),
},
{
Pair: common.MustNewAssetPair("ETH:NUSD"),
Expand All @@ -32,6 +33,7 @@ func TestGenesis(t *testing.T) {
FluctuationLimitRatio: sdk.MustNewDecFromStr("0.30"),
MaxOracleSpreadRatio: sdk.MustNewDecFromStr("0.30"),
MaintenanceMarginRatio: sdk.MustNewDecFromStr("0.0625"),
MaxLeverage: sdk.MustNewDecFromStr("15"),
},
}

Expand Down
1 change: 1 addition & 0 deletions x/vpool/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func NewCreatePoolProposalHandler(k keeper.Keeper) govtypes.Handler {
m.FluctuationLimitRatio,
m.MaxOracleSpreadRatio,
m.MaintenanceMarginRatio,
m.MaxLeverage,
)
return nil
default:
Expand Down
19 changes: 19 additions & 0 deletions x/vpool/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,3 +345,22 @@ func (k Keeper) GetMaintenanceMarginRatio(ctx sdk.Context, pair common.AssetPair

return pool.MaintenanceMarginRatio
}

/**
GetMaxLeverage returns the maximum leverage required to open a position in the pool.

args:
- ctx: the cosmos-sdk context
- pair: the asset pair

ret:
- sdk.Dec: The maintenance margin ratio for the pool
*/
func (k Keeper) GetMaxLeverage(ctx sdk.Context, pair common.AssetPair) sdk.Dec {
pool, err := k.getPool(ctx, pair)
if err != nil {
panic(err)
}

return pool.MaxLeverage
}
Loading