Skip to content

Commit

Permalink
Merge pull request #11618 from vegaprotocol/amm-capped-futures
Browse files Browse the repository at this point in the history
fix: prevent AMM's from being created outside of range on capped futu…
  • Loading branch information
jeremyletang authored Aug 28, 2024
2 parents 8031219 + 8d3be58 commit ecd9fe4
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 19 deletions.
1 change: 1 addition & 0 deletions core/execution/common/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
)
34 changes: 24 additions & 10 deletions core/execution/future/market.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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
}

Expand Down Expand Up @@ -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
}
}
Expand Down
105 changes: 96 additions & 9 deletions core/execution/future/market_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
}

0 comments on commit ecd9fe4

Please sign in to comment.