From 8d3be5807a758f27ae80290d6b41850f0e64f543 Mon Sep 17 00:00:00 2001 From: wwestgarth Date: Wed, 28 Aug 2024 09:54:46 +0100 Subject: [PATCH] fix: prevent AMM's from being created outside of range on capped future markets --- core/execution/common/errors.go | 1 + core/execution/future/market.go | 34 ++++++--- core/execution/future/market_test.go | 105 ++++++++++++++++++++++++--- 3 files changed, 121 insertions(+), 19 deletions(-) diff --git a/core/execution/common/errors.go b/core/execution/common/errors.go index 4c6aaeb0ccc..929501435f9 100644 --- a/core/execution/common/errors.go +++ b/core/execution/common/errors.go @@ -90,4 +90,5 @@ var ( ErrIsolatedMarginFullyCollateralised = errors.New("isolated margin not permitted on fully collateralised markets") // ErrSettlementDataOutOfRange is returned when a capped future receives settlement data that is outside of the acceptable range (either > max price, or neither 0 nor max for binary settlements). ErrSettlementDataOutOfRange = errors.New("settlement data is outside of the price cap") + ErrAMMBoundsOutsidePriceCap = errors.New("an AMM bound is outside of the price cap") ) diff --git a/core/execution/future/market.go b/core/execution/future/market.go index 5a0dc4f1bcc..06f60dc6222 100644 --- a/core/execution/future/market.go +++ b/core/execution/future/market.go @@ -5437,20 +5437,34 @@ func (m *Market) needsRebase(fairPrice *num.Uint) (bool, types.Side, *num.Uint) return false, types.SideUnspecified, nil } -func VerifyAMMBounds(baseParam *num.Uint, lowerParam *num.Uint, upperParam *num.Uint, priceFactor num.Decimal) error { - base, _ := num.UintFromDecimal(baseParam.ToDecimal().Mul(priceFactor)) - if lowerParam != nil { - lower, _ := num.UintFromDecimal(lowerParam.ToDecimal().Mul(priceFactor)) +func VerifyAMMBounds(params *types.ConcentratedLiquidityParameters, cap *num.Uint, priceFactor num.Decimal) error { + base, _ := num.UintFromDecimal(params.Base.ToDecimal().Mul(priceFactor)) + if cap != nil && base.GTE(cap) { + return common.ErrAMMBoundsOutsidePriceCap + } + + if params.LowerBound != nil { + lower, _ := num.UintFromDecimal(params.LowerBound.ToDecimal().Mul(priceFactor)) if lower.GTE(base) { - return fmt.Errorf(fmt.Sprintf("base (%s) as factored by market and asset decimals must be greater than lower bound (%s)", base.String(), lower.String())) + return fmt.Errorf("base (%s) as factored by market and asset decimals must be greater than lower bound (%s)", base.String(), lower.String()) + } + + if cap != nil && lower.GTE(cap) { + return common.ErrAMMBoundsOutsidePriceCap } } - if upperParam != nil { - upper, _ := num.UintFromDecimal(upperParam.ToDecimal().Mul(priceFactor)) + + if params.UpperBound != nil { + upper, _ := num.UintFromDecimal(params.UpperBound.ToDecimal().Mul(priceFactor)) if base.GTE(upper) { - return fmt.Errorf(fmt.Sprintf("upper bound (%s) as factored by market and asset decimals must be greater than base (%s)", upper.String(), base.String())) + return fmt.Errorf("upper bound (%s) as factored by market and asset decimals must be greater than base (%s)", upper.String(), base.String()) + } + + if cap != nil && upper.GTE(cap) { + return common.ErrAMMBoundsOutsidePriceCap } } + return nil } @@ -5464,7 +5478,7 @@ func (m *Market) SubmitAMM(ctx context.Context, submit *types.SubmitAMM, determi // create the AMM curves but do not confirm it with the engine var order *types.Order - if err := VerifyAMMBounds(submit.Parameters.Base, submit.Parameters.LowerBound, submit.Parameters.UpperBound, m.priceFactor); err != nil { + if err := VerifyAMMBounds(submit.Parameters, m.capMax, m.priceFactor); err != nil { return err } @@ -5545,7 +5559,7 @@ func (m *Market) AmendAMM(ctx context.Context, amend *types.AmendAMM, determinis defer func() { m.idgen = nil }() if amend.Parameters != nil { - if err := VerifyAMMBounds(amend.Parameters.Base, amend.Parameters.LowerBound, amend.Parameters.UpperBound, m.priceFactor); err != nil { + if err := VerifyAMMBounds(amend.Parameters, m.capMax, m.priceFactor); err != nil { return err } } diff --git a/core/execution/future/market_test.go b/core/execution/future/market_test.go index e243bf1a3f9..446c6108ccc 100644 --- a/core/execution/future/market_test.go +++ b/core/execution/future/market_test.go @@ -6722,13 +6722,100 @@ func TestLiquidityFeeSettingsConstantFee(t *testing.T) { } func TestVerifyAMMBounds(t *testing.T) { - require.Equal(t, "base (8) as factored by market and asset decimals must be greater than lower bound (8)", future.VerifyAMMBounds(num.NewUint(85), num.NewUint(82), num.NewUint(88), num.NewDecimalFromFloat(0.1)).Error()) - require.Equal(t, "upper bound (8) as factored by market and asset decimals must be greater than base (8)", future.VerifyAMMBounds(num.NewUint(85), num.NewUint(78), num.NewUint(88), num.NewDecimalFromFloat(0.1)).Error()) - require.Equal(t, "base (8) as factored by market and asset decimals must be greater than lower bound (8)", future.VerifyAMMBounds(num.NewUint(85), num.NewUint(80), num.NewUint(90), num.NewDecimalFromFloat(0.1)).Error()) - require.NoError(t, future.VerifyAMMBounds(num.NewUint(85), num.NewUint(78), num.NewUint(90), num.NewDecimalFromFloat(0.1))) - - require.NoError(t, future.VerifyAMMBounds(num.NewUint(85), num.NewUint(82), num.NewUint(88), num.NewDecimalFromFloat(1.1))) - require.NoError(t, future.VerifyAMMBounds(num.NewUint(85), num.NewUint(78), num.NewUint(88), num.NewDecimalFromFloat(1.1))) - require.NoError(t, future.VerifyAMMBounds(num.NewUint(85), num.NewUint(80), num.NewUint(90), num.NewDecimalFromFloat(1.1))) - require.NoError(t, future.VerifyAMMBounds(num.NewUint(85), num.NewUint(78), num.NewUint(90), num.NewDecimalFromFloat(1.1))) + tests := []struct { + name string + params *types.ConcentratedLiquidityParameters + maxCap *num.Uint + priceFactor num.Decimal + expectedErr error + }{ + { + name: "normal valid bounds", + params: &types.ConcentratedLiquidityParameters{ + LowerBound: num.NewUint(82), + Base: num.NewUint(85), + UpperBound: num.NewUint(88), + }, + priceFactor: num.NewDecimalFromFloat(1.1), + }, + { + name: "lower greater than base with fewer decimals", + params: &types.ConcentratedLiquidityParameters{ + LowerBound: num.NewUint(80), + Base: num.NewUint(85), + UpperBound: num.NewUint(90), + }, + priceFactor: num.NewDecimalFromFloat(0.1), + expectedErr: fmt.Errorf("base (8) as factored by market and asset decimals must be greater than lower bound (8)"), + }, + { + name: "base greater than base with fewer decimals", + params: &types.ConcentratedLiquidityParameters{ + LowerBound: num.NewUint(80), + Base: num.NewUint(85), + UpperBound: num.NewUint(88), + }, + priceFactor: num.NewDecimalFromFloat(0.1), + expectedErr: fmt.Errorf("base (8) as factored by market and asset decimals must be greater than lower bound (8)"), + }, + { + name: "both bounds too close with fewer decimals", + params: &types.ConcentratedLiquidityParameters{ + LowerBound: num.NewUint(82), + Base: num.NewUint(85), + UpperBound: num.NewUint(88), + }, + priceFactor: num.NewDecimalFromFloat(0.1), + expectedErr: fmt.Errorf("base (8) as factored by market and asset decimals must be greater than lower bound (8)"), + }, + { + name: "upper bound higher than cap", + params: &types.ConcentratedLiquidityParameters{ + LowerBound: num.NewUint(82), + Base: num.NewUint(85), + UpperBound: num.NewUint(88), + }, + priceFactor: num.NewDecimalFromFloat(1), + maxCap: num.NewUint(86), + expectedErr: common.ErrAMMBoundsOutsidePriceCap, + }, + { + name: "upper bound equal cap", + params: &types.ConcentratedLiquidityParameters{ + LowerBound: num.NewUint(82), + Base: num.NewUint(85), + UpperBound: num.NewUint(88), + }, + priceFactor: num.NewDecimalFromFloat(1), + maxCap: num.NewUint(88), + expectedErr: common.ErrAMMBoundsOutsidePriceCap, + }, + { + name: "base higher than cap", + params: &types.ConcentratedLiquidityParameters{ + LowerBound: num.NewUint(82), + Base: num.NewUint(100), + }, + priceFactor: num.NewDecimalFromFloat(1), + maxCap: num.NewUint(86), + expectedErr: common.ErrAMMBoundsOutsidePriceCap, + }, + { + name: "base equal cap", + params: &types.ConcentratedLiquidityParameters{ + LowerBound: num.NewUint(82), + Base: num.NewUint(88), + }, + priceFactor: num.NewDecimalFromFloat(1), + maxCap: num.NewUint(88), + expectedErr: common.ErrAMMBoundsOutsidePriceCap, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := future.VerifyAMMBounds(tt.params, tt.maxCap, tt.priceFactor) + assert.Equal(t, tt.expectedErr, err) + }) + } }