From 6f54848aead04adaa368a9306765551abd70d8e8 Mon Sep 17 00:00:00 2001 From: atvanguard <3612498+atvanguard@users.noreply.github.com> Date: Wed, 13 Sep 2023 17:58:00 +0530 Subject: [PATCH] migrate oever tests to juror --- precompile/contracts/bibliophile/contract.go | 14 +- .../contracts/bibliophile/contract_test.go | 588 ------------------ precompile/contracts/bibliophile/orderbook.go | 108 ---- precompile/contracts/juror/contract_test.go | 381 +++++++++++- 4 files changed, 379 insertions(+), 712 deletions(-) diff --git a/precompile/contracts/bibliophile/contract.go b/precompile/contracts/bibliophile/contract.go index 07d899b217..98bff3b7b8 100644 --- a/precompile/contracts/bibliophile/contract.go +++ b/precompile/contracts/bibliophile/contract.go @@ -224,10 +224,8 @@ func validateLiquidationOrderAndDetermineFillPrice(accessibleState contract.Acce } // CUSTOM CODE STARTS HERE - output, err := ValidateLiquidationOrderAndDetermineFillPrice(accessibleState.GetStateDB(), &inputStruct) - if err != nil { - return nil, remainingGas, err - } + _ = inputStruct + output := big.NewInt(0) packedOutput, err := PackValidateLiquidationOrderAndDetermineFillPriceOutput(output) if err != nil { return nil, remainingGas, err @@ -274,11 +272,9 @@ func validateOrdersAndDetermineFillPrice(accessibleState contract.AccessibleStat } // CUSTOM CODE STARTS HERE - output, err := ValidateOrdersAndDetermineFillPrice(accessibleState.GetStateDB(), &inputStruct) - if err != nil { - return nil, remainingGas, err - } - packedOutput, err := PackValidateOrdersAndDetermineFillPriceOutput(*output) + _ = inputStruct + output := ValidateOrdersAndDetermineFillPriceOutput{} + packedOutput, err := PackValidateOrdersAndDetermineFillPriceOutput(output) if err != nil { return nil, remainingGas, err } diff --git a/precompile/contracts/bibliophile/contract_test.go b/precompile/contracts/bibliophile/contract_test.go index 6d488d9a90..16d8fe2ab2 100644 --- a/precompile/contracts/bibliophile/contract_test.go +++ b/precompile/contracts/bibliophile/contract_test.go @@ -5,14 +5,12 @@ package bibliophile import ( - "math/big" "testing" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/testutils" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -51,254 +49,6 @@ func TestRun(t *testing.T) { ReadOnly: false, ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, - "insufficient gas for validateLiquidationOrderAndDetermineFillPrice should fail": { - Caller: common.Address{1}, - InputFn: func(t testing.TB) []byte { - // CUSTOM CODE STARTS HERE - // populate test input here - testInput := ValidateLiquidationOrderAndDetermineFillPriceInput{ - Order: IHubbleBibliophileOrder{ - AmmIndex: big.NewInt(0), - Trader: trader, - BaseAssetQuantity: big.NewInt(0), - Price: big.NewInt(10), - Salt: big.NewInt(0), - ReduceOnly: false, - }, - FillAmount: big.NewInt(0), - } - input, err := PackValidateLiquidationOrderAndDetermineFillPrice(testInput) - require.NoError(t, err) - return input - }, - SuppliedGas: ValidateLiquidationOrderAndDetermineFillPriceGasCost - 1, - ReadOnly: false, - ExpectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "insufficient gas for validateOrdersAndDetermineFillPrice should fail": { - Caller: common.Address{1}, - InputFn: func(t testing.TB) []byte { - testInput := ValidateOrdersAndDetermineFillPriceInput{ - Orders: [2]IHubbleBibliophileOrder{ - IHubbleBibliophileOrder{ - AmmIndex: big.NewInt(0), - Trader: trader, - BaseAssetQuantity: big.NewInt(0), - Price: big.NewInt(10), - Salt: big.NewInt(0), - ReduceOnly: false, - }, - IHubbleBibliophileOrder{ - AmmIndex: big.NewInt(0), - Trader: trader, - BaseAssetQuantity: big.NewInt(0), - Price: big.NewInt(10), - Salt: big.NewInt(0), - ReduceOnly: false, - }, - }, - FillAmount: big.NewInt(0), - } - input, err := PackValidateOrdersAndDetermineFillPrice(testInput) - require.NoError(t, err) - return input - }, - SuppliedGas: ValidateOrdersAndDetermineFillPriceGasCost - 1, - ReadOnly: false, - ExpectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "ErrNotLongOrder_0": { - Caller: common.Address{1}, - InputFn: func(t testing.TB) []byte { - testInput := ValidateOrdersAndDetermineFillPriceInput{ - Orders: [2]IHubbleBibliophileOrder{ - IHubbleBibliophileOrder{ - AmmIndex: big.NewInt(0), - Trader: trader, - BaseAssetQuantity: big.NewInt(0), - Price: big.NewInt(10), - Salt: big.NewInt(0), - ReduceOnly: false, - }, - IHubbleBibliophileOrder{ - AmmIndex: big.NewInt(0), - Trader: trader, - BaseAssetQuantity: big.NewInt(0), - Price: big.NewInt(10), - Salt: big.NewInt(0), - ReduceOnly: false, - }, - }, - FillAmount: big.NewInt(0), - } - input, err := PackValidateOrdersAndDetermineFillPrice(testInput) - require.NoError(t, err) - return input - }, - SuppliedGas: ValidateOrdersAndDetermineFillPriceGasCost, - ReadOnly: false, - ExpectedErr: ErrNotLongOrder.Error(), - }, - "ErrNotLongOrder": { - Caller: common.Address{1}, - InputFn: func(t testing.TB) []byte { - testInput := ValidateOrdersAndDetermineFillPriceInput{ - Orders: [2]IHubbleBibliophileOrder{ - IHubbleBibliophileOrder{ - AmmIndex: big.NewInt(0), - Trader: trader, - BaseAssetQuantity: big.NewInt(-1), - Price: big.NewInt(10), - Salt: big.NewInt(0), - ReduceOnly: false, - }, - IHubbleBibliophileOrder{ - AmmIndex: big.NewInt(0), - Trader: trader, - BaseAssetQuantity: big.NewInt(0), - Price: big.NewInt(10), - Salt: big.NewInt(0), - ReduceOnly: false, - }, - }, - FillAmount: big.NewInt(0), - } - input, err := PackValidateOrdersAndDetermineFillPrice(testInput) - require.NoError(t, err) - return input - }, - SuppliedGas: ValidateOrdersAndDetermineFillPriceGasCost, - ReadOnly: false, - ExpectedErr: ErrNotLongOrder.Error(), - }, - "ErrNotShortOrder_0": { - Caller: common.Address{1}, - InputFn: func(t testing.TB) []byte { - testInput := ValidateOrdersAndDetermineFillPriceInput{ - Orders: [2]IHubbleBibliophileOrder{ - IHubbleBibliophileOrder{ - AmmIndex: big.NewInt(0), - Trader: trader, - BaseAssetQuantity: big.NewInt(1), - Price: big.NewInt(10), - Salt: big.NewInt(0), - ReduceOnly: false, - }, - IHubbleBibliophileOrder{ - AmmIndex: big.NewInt(0), - Trader: trader, - BaseAssetQuantity: big.NewInt(0), - Price: big.NewInt(10), - Salt: big.NewInt(0), - ReduceOnly: false, - }, - }, - FillAmount: big.NewInt(0), - } - input, err := PackValidateOrdersAndDetermineFillPrice(testInput) - require.NoError(t, err) - return input - }, - SuppliedGas: ValidateOrdersAndDetermineFillPriceGasCost, - ReadOnly: false, - ExpectedErr: ErrNotShortOrder.Error(), - }, - "ErrNotShortOrder": { - Caller: common.Address{1}, - InputFn: func(t testing.TB) []byte { - testInput := ValidateOrdersAndDetermineFillPriceInput{ - Orders: [2]IHubbleBibliophileOrder{ - IHubbleBibliophileOrder{ - AmmIndex: big.NewInt(0), - Trader: trader, - BaseAssetQuantity: big.NewInt(1), - Price: big.NewInt(10), - Salt: big.NewInt(0), - ReduceOnly: false, - }, - IHubbleBibliophileOrder{ - AmmIndex: big.NewInt(0), - Trader: trader, - BaseAssetQuantity: big.NewInt(1), - Price: big.NewInt(10), - Salt: big.NewInt(0), - ReduceOnly: false, - }, - }, - FillAmount: big.NewInt(0), - } - input, err := PackValidateOrdersAndDetermineFillPrice(testInput) - require.NoError(t, err) - return input - }, - SuppliedGas: ValidateOrdersAndDetermineFillPriceGasCost, - ReadOnly: false, - ExpectedErr: ErrNotShortOrder.Error(), - }, - "ErrNotSameAMM": { - Caller: common.Address{1}, - InputFn: func(t testing.TB) []byte { - testInput := ValidateOrdersAndDetermineFillPriceInput{ - Orders: [2]IHubbleBibliophileOrder{ - IHubbleBibliophileOrder{ - AmmIndex: big.NewInt(0), - Trader: trader, - BaseAssetQuantity: big.NewInt(1), - Price: big.NewInt(10), - Salt: big.NewInt(0), - ReduceOnly: false, - }, - IHubbleBibliophileOrder{ - AmmIndex: big.NewInt(1), - Trader: trader, - BaseAssetQuantity: big.NewInt(-1), - Price: big.NewInt(10), - Salt: big.NewInt(0), - ReduceOnly: false, - }, - }, - FillAmount: big.NewInt(0), - } - input, err := PackValidateOrdersAndDetermineFillPrice(testInput) - require.NoError(t, err) - return input - }, - SuppliedGas: ValidateOrdersAndDetermineFillPriceGasCost, - ReadOnly: false, - ExpectedErr: ErrNotSameAMM.Error(), - }, - "ErrNoMatch": { - Caller: common.Address{1}, - InputFn: func(t testing.TB) []byte { - testInput := ValidateOrdersAndDetermineFillPriceInput{ - Orders: [2]IHubbleBibliophileOrder{ - IHubbleBibliophileOrder{ - AmmIndex: big.NewInt(0), - Trader: trader, - BaseAssetQuantity: big.NewInt(1), - Price: big.NewInt(10), - Salt: big.NewInt(0), - ReduceOnly: false, - }, - IHubbleBibliophileOrder{ - AmmIndex: big.NewInt(0), - Trader: trader, - BaseAssetQuantity: big.NewInt(-1), - Price: big.NewInt(11), - Salt: big.NewInt(0), - ReduceOnly: false, - }, - }, - FillAmount: big.NewInt(0), - } - input, err := PackValidateOrdersAndDetermineFillPrice(testInput) - require.NoError(t, err) - return input - }, - SuppliedGas: ValidateOrdersAndDetermineFillPriceGasCost, - ReadOnly: false, - ExpectedErr: ErrNoMatch.Error(), - }, } // Run tests. for name, test := range tests { @@ -307,341 +57,3 @@ func TestRun(t *testing.T) { }) } } - -func TestDetermineFillPrice(t *testing.T) { - oraclePrice := multiply1e6(big.NewInt(20)) // $10 - spreadLimit := new(big.Int).Mul(big.NewInt(50), big.NewInt(1e4)) // 50% - upperbound := divide1e6(new(big.Int).Mul(oraclePrice, new(big.Int).Add(_1e6, spreadLimit))) // $10 - lowerbound := divide1e6(new(big.Int).Mul(oraclePrice, new(big.Int).Sub(_1e6, spreadLimit))) // $30 - Taker := uint8(0) - Maker := uint8(1) - - t.Run("long order came first", func(t *testing.T) { - blockPlaced0 := big.NewInt(69) - blockPlaced1 := big.NewInt(70) - t.Run("long price < lower bound", func(t *testing.T) { - t.Run("short price < long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, multiply1e6(big.NewInt(9)), multiply1e6(big.NewInt(8)), blockPlaced0, blockPlaced1) - assert.Nil(t, output) - assert.Equal(t, ErrTooLow, err) - }) - - t.Run("short price == long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, multiply1e6(big.NewInt(7)), multiply1e6(big.NewInt(7)), blockPlaced0, blockPlaced1) - assert.Nil(t, output) - assert.Equal(t, ErrTooLow, err) - }) - }) - - t.Run("long price == lower bound", func(t *testing.T) { - longPrice := lowerbound - t.Run("short price < long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, longPrice, new(big.Int).Sub(longPrice, big.NewInt(1)), blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{longPrice, Maker, Taker}, *output) - }) - - t.Run("short price == long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, longPrice, longPrice, blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{longPrice, Maker, Taker}, *output) - }) - }) - - t.Run("lowerbound < long price < oracle", func(t *testing.T) { - longPrice := multiply1e6(big.NewInt(15)) - t.Run("short price < long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, longPrice, new(big.Int).Sub(longPrice, big.NewInt(1)), blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{longPrice, Maker, Taker}, *output) - }) - - t.Run("short price == long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, longPrice, longPrice, blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{longPrice, Maker, Taker}, *output) - }) - }) - - t.Run("long price == oracle", func(t *testing.T) { - longPrice := oraclePrice - t.Run("short price < long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, longPrice, new(big.Int).Sub(longPrice, big.NewInt(1)), blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{longPrice, Maker, Taker}, *output) - }) - - t.Run("short price == long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, longPrice, longPrice, blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{longPrice, Maker, Taker}, *output) - }) - }) - - t.Run("oracle < long price < upper bound", func(t *testing.T) { - longPrice := multiply1e6(big.NewInt(25)) - t.Run("short price < long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, longPrice, new(big.Int).Sub(longPrice, big.NewInt(1)), blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{longPrice, Maker, Taker}, *output) - }) - - t.Run("short price == long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, longPrice, longPrice, blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{longPrice, Maker, Taker}, *output) - }) - }) - - t.Run("long price == upper bound", func(t *testing.T) { - longPrice := upperbound - t.Run("short price < long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, longPrice, new(big.Int).Sub(longPrice, big.NewInt(1)), blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{longPrice, Maker, Taker}, *output) - }) - - t.Run("short price == long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, longPrice, longPrice, blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{longPrice, Maker, Taker}, *output) - }) - }) - - t.Run("upper bound < long price", func(t *testing.T) { - longPrice := new(big.Int).Add(upperbound, big.NewInt(42)) - t.Run("upper < short price < long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, longPrice, new(big.Int).Add(upperbound, big.NewInt(1)), blockPlaced0, blockPlaced1) - assert.Nil(t, output) - assert.Equal(t, ErrTooHigh, err) - }) - - t.Run("upper == short price < long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, longPrice, upperbound, blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{upperbound, Maker, Taker}, *output) - }) - - t.Run("short price < upper", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, longPrice, new(big.Int).Sub(upperbound, big.NewInt(1)), blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{upperbound, Maker, Taker}, *output) - }) - - t.Run("short price < lower", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, longPrice, new(big.Int).Sub(lowerbound, big.NewInt(1)), blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{upperbound, Maker, Taker}, *output) - }) - }) - }) - - t.Run("short order came first and both came in same block", func(t *testing.T) { - blockPlaced0 := big.NewInt(70) - blockPlaced1 := big.NewInt(69) // short order came first - for i := 0; i < 2; i++ { - if i == 1 { - blockPlaced0 = blockPlaced1 // both orders came in same block - } - t.Run("short price < lower bound", func(t *testing.T) { - t.Run("short price < long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, multiply1e6(big.NewInt(9)), multiply1e6(big.NewInt(8)), blockPlaced0, blockPlaced1) - assert.Nil(t, output) - assert.Equal(t, ErrTooLow, err) - }) - - t.Run("short price == long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, multiply1e6(big.NewInt(7)), multiply1e6(big.NewInt(7)), blockPlaced0, blockPlaced1) - assert.Nil(t, output) - assert.Equal(t, ErrTooLow, err) - }) - }) - - t.Run("short price == lower bound", func(t *testing.T) { - shortPrice := lowerbound - t.Run("short price < long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, new(big.Int).Add(shortPrice, big.NewInt(67)), shortPrice, blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{shortPrice, Taker, Maker}, *output) - }) - - t.Run("short price == long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, shortPrice, shortPrice, blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{shortPrice, Taker, Maker}, *output) - }) - }) - - t.Run("lowerbound < short price < oracle", func(t *testing.T) { - shortPrice := multiply1e6(big.NewInt(15)) - t.Run("short price < long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, new(big.Int).Add(shortPrice, big.NewInt(58)), shortPrice, blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{shortPrice, Taker, Maker}, *output) - }) - - t.Run("short price == long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, shortPrice, shortPrice, blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{shortPrice, Taker, Maker}, *output) - }) - }) - - t.Run("short price == oracle", func(t *testing.T) { - shortPrice := oraclePrice - t.Run("short price < long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, new(big.Int).Add(shortPrice, big.NewInt(99)), shortPrice, blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{shortPrice, Taker, Maker}, *output) - }) - - t.Run("short price == long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, shortPrice, shortPrice, blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{shortPrice, Taker, Maker}, *output) - }) - }) - - t.Run("oracle < short price < upper bound", func(t *testing.T) { - shortPrice := multiply1e6(big.NewInt(25)) - t.Run("short price < long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, new(big.Int).Add(shortPrice, big.NewInt(453)), shortPrice, blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{shortPrice, Taker, Maker}, *output) - }) - - t.Run("short price == long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, shortPrice, shortPrice, blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{shortPrice, Taker, Maker}, *output) - }) - }) - - t.Run("short price == upper bound", func(t *testing.T) { - shortPrice := upperbound - t.Run("short price < long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, new(big.Int).Add(shortPrice, big.NewInt(896)), shortPrice, blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{shortPrice, Taker, Maker}, *output) - }) - - t.Run("short price == long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, shortPrice, shortPrice, blockPlaced0, blockPlaced1) - assert.Nil(t, err) - assert.Equal(t, ValidateOrdersAndDetermineFillPriceOutput{shortPrice, Taker, Maker}, *output) - }) - }) - - t.Run("upper bound < short price", func(t *testing.T) { - shortPrice := new(big.Int).Add(upperbound, big.NewInt(42)) - t.Run("short price < long price", func(t *testing.T) { - output, err := determineFillPrice(oraclePrice, spreadLimit, new(big.Int).Add(shortPrice, big.NewInt(896)), shortPrice, blockPlaced0, blockPlaced1) - assert.Nil(t, output) - assert.Equal(t, ErrTooHigh, err) - }) - }) - } - }) -} - -func TestDetermineLiquidationFillPrice(t *testing.T) { - oraclePrice := multiply1e6(big.NewInt(20)) // $10 - liquidationSpreadLimit := new(big.Int).Mul(big.NewInt(10), big.NewInt(1e4)) // 10% - liqUpperBound, liqLowerBound := multiply1e6(big.NewInt(22)), multiply1e6(big.NewInt(18)) - - spreadLimit := new(big.Int).Mul(big.NewInt(50), big.NewInt(1e4)) // 50% - upperbound := multiply1e6(big.NewInt(30)) // $30 - lowerbound := multiply1e6(big.NewInt(10)) // $10 - - t.Run("test calculateBounds for liquidation spread", func(t *testing.T) { - a, b := calculateBounds(liquidationSpreadLimit, oraclePrice) - assert.Equal(t, liqUpperBound, a) - assert.Equal(t, liqLowerBound, b) - }) - - t.Run("test calculateBounds for oracle spread", func(t *testing.T) { - a, b := calculateBounds(spreadLimit, oraclePrice) - assert.Equal(t, upperbound, a) - assert.Equal(t, lowerbound, b) - }) - - t.Run("long position is being liquidated", func(t *testing.T) { - t.Run("order price < liqLowerBound", func(t *testing.T) { - order := IHubbleBibliophileOrder{BaseAssetQuantity: big.NewInt(5), Price: new(big.Int).Sub(liqLowerBound, big.NewInt(1))} - output, err := determineLiquidationFillPrice(true /* isLongOrder */, order.Price, liqUpperBound, liqLowerBound, upperbound, lowerbound) - assert.Nil(t, output) - assert.Equal(t, ErrTooLow, err) - }) - - t.Run("order price == liqLowerBound", func(t *testing.T) { - order := IHubbleBibliophileOrder{BaseAssetQuantity: big.NewInt(5), Price: liqLowerBound} - output, err := determineLiquidationFillPrice(true /* isLongOrder */, order.Price, liqUpperBound, liqLowerBound, upperbound, lowerbound) - assert.Nil(t, err) - assert.Equal(t, liqLowerBound, output) - }) - - t.Run("liqLowerBound < order price < upper bound", func(t *testing.T) { - price := new(big.Int).Add(liqLowerBound, big.NewInt(99)) - order := IHubbleBibliophileOrder{BaseAssetQuantity: big.NewInt(5), Price: price} - output, err := determineLiquidationFillPrice(true /* isLongOrder */, order.Price, liqUpperBound, liqLowerBound, upperbound, lowerbound) - assert.Nil(t, err) - assert.Equal(t, price, output) - }) - - t.Run("order price == upper bound", func(t *testing.T) { - price := upperbound - order := IHubbleBibliophileOrder{BaseAssetQuantity: big.NewInt(5), Price: price} - output, err := determineLiquidationFillPrice(true /* isLongOrder */, order.Price, liqUpperBound, liqLowerBound, upperbound, lowerbound) - assert.Nil(t, err) - assert.Equal(t, upperbound, output) - }) - - t.Run("order price > upper bound", func(t *testing.T) { - price := new(big.Int).Add(upperbound, big.NewInt(99)) - order := IHubbleBibliophileOrder{BaseAssetQuantity: big.NewInt(5), Price: price} - output, err := determineLiquidationFillPrice(true /* isLongOrder */, order.Price, liqUpperBound, liqLowerBound, upperbound, lowerbound) - assert.Nil(t, err) - assert.Equal(t, upperbound, output) - }) - }) - - t.Run("short position is being liquidated", func(t *testing.T) { - t.Run("order price > liqUpperBound", func(t *testing.T) { - order := IHubbleBibliophileOrder{BaseAssetQuantity: big.NewInt(-5), Price: new(big.Int).Add(liqUpperBound, big.NewInt(1))} - output, err := determineLiquidationFillPrice(false /* isLongOrder */, order.Price, liqUpperBound, liqLowerBound, upperbound, lowerbound) - assert.Nil(t, output) - assert.Equal(t, ErrTooHigh, err) - }) - - t.Run("order price == liqUpperBound", func(t *testing.T) { - order := IHubbleBibliophileOrder{BaseAssetQuantity: big.NewInt(-5), Price: liqUpperBound} - output, err := determineLiquidationFillPrice(false /* isLongOrder */, order.Price, liqUpperBound, liqLowerBound, upperbound, lowerbound) - assert.Nil(t, err) - assert.Equal(t, liqUpperBound, output) - }) - - t.Run("liqUpperBound > order price > lower bound", func(t *testing.T) { - price := new(big.Int).Sub(liqUpperBound, big.NewInt(99)) - order := IHubbleBibliophileOrder{BaseAssetQuantity: big.NewInt(-5), Price: price} - output, err := determineLiquidationFillPrice(false /* isLongOrder */, order.Price, liqUpperBound, liqLowerBound, upperbound, lowerbound) - assert.Nil(t, err) - assert.Equal(t, price, output) - }) - - t.Run("order price == lower bound", func(t *testing.T) { - price := lowerbound - order := IHubbleBibliophileOrder{BaseAssetQuantity: big.NewInt(-5), Price: price} - output, err := determineLiquidationFillPrice(false /* isLongOrder */, order.Price, liqUpperBound, liqLowerBound, upperbound, lowerbound) - assert.Nil(t, err) - assert.Equal(t, lowerbound, output) - }) - - t.Run("order price < lower bound", func(t *testing.T) { - price := new(big.Int).Sub(lowerbound, big.NewInt(99)) - order := IHubbleBibliophileOrder{BaseAssetQuantity: big.NewInt(-5), Price: price} - output, err := determineLiquidationFillPrice(false /* isLongOrder */, order.Price, liqUpperBound, liqLowerBound, upperbound, lowerbound) - assert.Nil(t, err) - assert.Equal(t, lowerbound, output) - }) - }) -} diff --git a/precompile/contracts/bibliophile/orderbook.go b/precompile/contracts/bibliophile/orderbook.go index aff60b75ec..d57dc0aa10 100644 --- a/precompile/contracts/bibliophile/orderbook.go +++ b/precompile/contracts/bibliophile/orderbook.go @@ -5,7 +5,6 @@ import ( "math/big" "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -82,113 +81,6 @@ func IsValidator(stateDB contract.StateDB, senderOrSigner common.Address) bool { return stateDB.GetState(common.HexToAddress(ORDERBOOK_GENESIS_ADDRESS), common.BytesToHash(isValidatorMappingSlot)).Big().Cmp(big.NewInt(1)) == 0 } -// Business Logic - -func ValidateOrdersAndDetermineFillPrice(stateDB contract.StateDB, inputStruct *ValidateOrdersAndDetermineFillPriceInput) (*ValidateOrdersAndDetermineFillPriceOutput, error) { - longOrder := inputStruct.Orders[0] - shortOrder := inputStruct.Orders[1] - - if longOrder.BaseAssetQuantity.Cmp(big.NewInt(0)) <= 0 { - return nil, ErrNotLongOrder - } - - if shortOrder.BaseAssetQuantity.Cmp(big.NewInt(0)) >= 0 { - return nil, ErrNotShortOrder - } - - if longOrder.AmmIndex.Cmp(shortOrder.AmmIndex) != 0 { - return nil, ErrNotSameAMM - } - - if longOrder.Price.Cmp(shortOrder.Price) == -1 { - return nil, ErrNoMatch - } - - if getOrderStatus(stateDB, inputStruct.OrderHashes[0]) != 1 || getOrderStatus(stateDB, inputStruct.OrderHashes[1]) != 1 { - return nil, ErrInvalidOrder - } - - blockPlaced0 := getBlockPlaced(stateDB, inputStruct.OrderHashes[0]) - blockPlaced1 := getBlockPlaced(stateDB, inputStruct.OrderHashes[1]) - minSize := GetMinSizeRequirement(stateDB, longOrder.AmmIndex.Int64()) - if new(big.Int).Mod(inputStruct.FillAmount, minSize).Cmp(big.NewInt(0)) != 0 { - return nil, ErrNotMultiple - } - return DetermineFillPrice(stateDB, longOrder.AmmIndex.Int64(), longOrder.Price, shortOrder.Price, blockPlaced0, blockPlaced1) -} - -func DetermineFillPrice(stateDB contract.StateDB, marketId int64, longOrderPrice, shortOrderPrice, blockPlaced0, blockPlaced1 *big.Int) (*ValidateOrdersAndDetermineFillPriceOutput, error) { - market := getMarketAddressFromMarketID(marketId, stateDB) - oraclePrice := getUnderlyingPrice(stateDB, market) - spreadLimit := GetMaxOraclePriceSpread(stateDB, marketId) - return determineFillPrice(oraclePrice, spreadLimit, longOrderPrice, shortOrderPrice, blockPlaced0, blockPlaced1) -} - -func determineFillPrice(oraclePrice, spreadLimit, longOrderPrice, shortOrderPrice, blockPlaced0, blockPlaced1 *big.Int) (*ValidateOrdersAndDetermineFillPriceOutput, error) { - upperbound, lowerbound := calculateBounds(spreadLimit, oraclePrice) - if longOrderPrice.Cmp(lowerbound) == -1 { - return nil, ErrTooLow - } - if shortOrderPrice.Cmp(upperbound) == 1 { - return nil, ErrTooHigh - } - - output := ValidateOrdersAndDetermineFillPriceOutput{} - if blockPlaced0.Cmp(blockPlaced1) == -1 { - // long order is the maker order - output.FillPrice = utils.BigIntMin(longOrderPrice, upperbound) - output.Mode0 = 1 // Mode0 corresponds to the long order and `1` is maker - output.Mode1 = 0 // Mode1 corresponds to the short order and `0` is taker - } else { // if long order is placed after short order or in the same block as short - // short order is the maker order - output.FillPrice = utils.BigIntMax(shortOrderPrice, lowerbound) - output.Mode0 = 0 // Mode0 corresponds to the long order and `0` is taker - output.Mode1 = 1 // Mode1 corresponds to the short order and `1` is maker - } - return &output, nil -} - -func ValidateLiquidationOrderAndDetermineFillPrice(stateDB contract.StateDB, inputStruct *ValidateLiquidationOrderAndDetermineFillPriceInput) (*big.Int, error) { - order := inputStruct.Order - minSize := GetMinSizeRequirement(stateDB, order.AmmIndex.Int64()) - if new(big.Int).Mod(inputStruct.FillAmount, minSize).Cmp(big.NewInt(0)) != 0 { - return nil, ErrNotMultiple - } - return DetermineLiquidationFillPrice(stateDB, order.AmmIndex.Int64(), order.BaseAssetQuantity, order.Price) -} - -func DetermineLiquidationFillPrice(stateDB contract.StateDB, marketId int64, baseAssetQuantity, price *big.Int) (*big.Int, error) { - isLongOrder := true - if baseAssetQuantity.Sign() < 0 { - isLongOrder = false - } - market := getMarketAddressFromMarketID(marketId, stateDB) - oraclePrice := getUnderlyingPrice(stateDB, market) - liquidationSpreadLimit := GetMaxLiquidationPriceSpread(stateDB, marketId) - liqUpperBound, liqLowerBound := calculateBounds(liquidationSpreadLimit, oraclePrice) - - oracleSpreadLimit := GetMaxOraclePriceSpread(stateDB, marketId) - upperbound, lowerbound := calculateBounds(oracleSpreadLimit, oraclePrice) - return determineLiquidationFillPrice(isLongOrder, price, liqUpperBound, liqLowerBound, upperbound, lowerbound) -} - -func determineLiquidationFillPrice(isLongOrder bool, price, liqUpperBound, liqLowerBound, upperbound, lowerbound *big.Int) (*big.Int, error) { - if isLongOrder { - // we are liquidating a long position - // do not allow liquidation if order.Price < liqLowerBound, because that gives scope for malicious activity to a validator - if price.Cmp(liqLowerBound) == -1 { - return nil, ErrTooLow - } - return utils.BigIntMin(price, upperbound /* oracle spread upper bound */), nil - } - - // short order - if price.Cmp(liqUpperBound) == 1 { - return nil, ErrTooHigh - } - return utils.BigIntMax(price, lowerbound /* oracle spread lower bound */), nil -} - // Helper functions func GetAcceptableBounds(stateDB contract.StateDB, marketID int64) (upperBound, lowerBound *big.Int) { diff --git a/precompile/contracts/juror/contract_test.go b/precompile/contracts/juror/contract_test.go index 751cd4aad7..11e48fd787 100644 --- a/precompile/contracts/juror/contract_test.go +++ b/precompile/contracts/juror/contract_test.go @@ -2363,13 +2363,6 @@ func getOrder(ammIndex *big.Int, trader common.Address, baseAssetQuantity *big.I } } -func getMockBibliophile(t *testing.T) b.BibliophileClient { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - mockBibliophile := b.NewMockBibliophileClient(ctrl) - return mockBibliophile -} - func multiplyTwoBigInts(a, b *big.Int) *big.Int { return big.NewInt(0).Mul(a, b) } @@ -2401,3 +2394,377 @@ func getValidateCancelLimitOrderInput(order ILimitOrderBookOrderV2, trader commo AssertLowMargin: assertLowMargin, } } + +var _1e6 = big.NewInt(1e6) + +func TestDetermineFillPrice(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockBibliophile := b.NewMockBibliophileClient(ctrl) + + oraclePrice := multiply1e6(big.NewInt(20)) // $10 + spreadLimit := new(big.Int).Mul(big.NewInt(50), big.NewInt(1e4)) // 50% + upperbound := divide1e6(new(big.Int).Mul(oraclePrice, new(big.Int).Add(_1e6, spreadLimit))) // $10 + lowerbound := divide1e6(new(big.Int).Mul(oraclePrice, new(big.Int).Sub(_1e6, spreadLimit))) // $30 + market := int64(5) + + t.Run("long order came first", func(t *testing.T) { + blockPlaced0 := big.NewInt(69) + blockPlaced1 := big.NewInt(70) + t.Run("long price < lower bound", func(t *testing.T) { + t.Run("short price < long price", func(t *testing.T) { + m0 := &Metadata{ + Price: multiply1e6(big.NewInt(9)), + AmmIndex: big.NewInt(market), + BlockPlaced: blockPlaced0, + } + m1 := &Metadata{ + Price: multiply1e6(big.NewInt(8)), + BlockPlaced: blockPlaced1, + } + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineFillPrice(mockBibliophile, m0, m1) + assert.Nil(t, output) + assert.Equal(t, ErrTooLow, err) + }) + + t.Run("short price == long price", func(t *testing.T) { + m0 := &Metadata{ + Price: multiply1e6(big.NewInt(7)), + AmmIndex: big.NewInt(market), + BlockPlaced: blockPlaced0, + } + m1 := &Metadata{ + Price: multiply1e6(big.NewInt(7)), + BlockPlaced: blockPlaced1, + } + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineFillPrice(mockBibliophile, m0, m1) + assert.Nil(t, output) + assert.Equal(t, ErrTooLow, err) + }) + }) + + t.Run("long price == lower bound", func(t *testing.T) { + longPrice := lowerbound + t.Run("short price < long price", func(t *testing.T) { + m0 := &Metadata{ + Price: longPrice, + AmmIndex: big.NewInt(market), + BlockPlaced: blockPlaced0, + } + m1 := &Metadata{ + Price: new(big.Int).Sub(longPrice, big.NewInt(1)), + BlockPlaced: blockPlaced1, + } + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineFillPrice(mockBibliophile, m0, m1) + assert.Nil(t, err) + assert.Equal(t, FillPriceAndModes{longPrice, Maker, Taker}, *output) + }) + + t.Run("short price == long price", func(t *testing.T) { + m0 := &Metadata{ + Price: longPrice, + AmmIndex: big.NewInt(market), + BlockPlaced: blockPlaced0, + } + m1 := &Metadata{ + Price: longPrice, + BlockPlaced: blockPlaced1, + } + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineFillPrice(mockBibliophile, m0, m1) + assert.Nil(t, err) + assert.Equal(t, FillPriceAndModes{longPrice, Maker, Taker}, *output) + }) + }) + + t.Run("lowerbound < long price < oracle", func(t *testing.T) { + longPrice := multiply1e6(big.NewInt(15)) + t.Run("short price < long price", func(t *testing.T) { + m0 := &Metadata{ + Price: longPrice, + AmmIndex: big.NewInt(market), + BlockPlaced: blockPlaced0, + } + m1 := &Metadata{ + Price: new(big.Int).Sub(longPrice, big.NewInt(1)), + BlockPlaced: blockPlaced1, + } + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineFillPrice(mockBibliophile, m0, m1) + assert.Nil(t, err) + assert.Equal(t, FillPriceAndModes{longPrice, Maker, Taker}, *output) + }) + + t.Run("short price == long price", func(t *testing.T) { + m0 := &Metadata{ + Price: longPrice, + AmmIndex: big.NewInt(market), + BlockPlaced: blockPlaced0, + } + m1 := &Metadata{ + Price: longPrice, + BlockPlaced: blockPlaced1, + } + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineFillPrice(mockBibliophile, m0, m1) + assert.Nil(t, err) + assert.Equal(t, FillPriceAndModes{longPrice, Maker, Taker}, *output) + }) + }) + + t.Run("long price == oracle", func(t *testing.T) { + longPrice := oraclePrice + t.Run("short price < long price", func(t *testing.T) { + m0 := &Metadata{ + Price: longPrice, + AmmIndex: big.NewInt(market), + BlockPlaced: blockPlaced0, + } + m1 := &Metadata{ + Price: new(big.Int).Sub(longPrice, big.NewInt(1)), + BlockPlaced: blockPlaced1, + } + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineFillPrice(mockBibliophile, m0, m1) + assert.Nil(t, err) + assert.Equal(t, FillPriceAndModes{longPrice, Maker, Taker}, *output) + }) + + t.Run("short price == long price", func(t *testing.T) { + m0 := &Metadata{ + Price: longPrice, + AmmIndex: big.NewInt(market), + BlockPlaced: blockPlaced0, + } + m1 := &Metadata{ + Price: longPrice, + BlockPlaced: blockPlaced1, + } + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineFillPrice(mockBibliophile, m0, m1) + assert.Nil(t, err) + assert.Equal(t, FillPriceAndModes{longPrice, Maker, Taker}, *output) + }) + }) + + t.Run("oracle < long price < upper bound", func(t *testing.T) { + longPrice := multiply1e6(big.NewInt(25)) + t.Run("short price < long price", func(t *testing.T) { + m0 := &Metadata{ + Price: longPrice, + AmmIndex: big.NewInt(market), + BlockPlaced: blockPlaced0, + } + m1 := &Metadata{ + Price: new(big.Int).Sub(longPrice, big.NewInt(1)), + BlockPlaced: blockPlaced1, + } + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineFillPrice(mockBibliophile, m0, m1) + assert.Nil(t, err) + assert.Equal(t, FillPriceAndModes{longPrice, Maker, Taker}, *output) + }) + + t.Run("short price == long price", func(t *testing.T) { + m0 := &Metadata{ + Price: longPrice, + AmmIndex: big.NewInt(market), + BlockPlaced: blockPlaced0, + } + m1 := &Metadata{ + Price: longPrice, + BlockPlaced: blockPlaced1, + } + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineFillPrice(mockBibliophile, m0, m1) + assert.Nil(t, err) + assert.Equal(t, FillPriceAndModes{longPrice, Maker, Taker}, *output) + }) + }) + + t.Run("long price == upper bound", func(t *testing.T) { + longPrice := upperbound + t.Run("short price < long price", func(t *testing.T) { + m0 := &Metadata{ + Price: longPrice, + AmmIndex: big.NewInt(market), + BlockPlaced: blockPlaced0, + } + m1 := &Metadata{ + Price: new(big.Int).Sub(longPrice, big.NewInt(1)), + BlockPlaced: blockPlaced1, + } + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineFillPrice(mockBibliophile, m0, m1) + assert.Nil(t, err) + assert.Equal(t, FillPriceAndModes{longPrice, Maker, Taker}, *output) + }) + + t.Run("short price == long price", func(t *testing.T) { + m0 := &Metadata{ + Price: longPrice, + AmmIndex: big.NewInt(market), + BlockPlaced: blockPlaced0, + } + m1 := &Metadata{ + Price: longPrice, + BlockPlaced: blockPlaced1, + } + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineFillPrice(mockBibliophile, m0, m1) + assert.Nil(t, err) + assert.Equal(t, FillPriceAndModes{longPrice, Maker, Taker}, *output) + }) + }) + + t.Run("upper bound < long price", func(t *testing.T) { + longPrice := new(big.Int).Add(upperbound, big.NewInt(42)) + t.Run("upper < short price < long price", func(t *testing.T) { + m0 := &Metadata{ + Price: longPrice, + AmmIndex: big.NewInt(market), + BlockPlaced: blockPlaced0, + } + m1 := &Metadata{ + Price: new(big.Int).Add(upperbound, big.NewInt(1)), + BlockPlaced: blockPlaced1, + } + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineFillPrice(mockBibliophile, m0, m1) + assert.Nil(t, output) + assert.Equal(t, ErrTooHigh, err) + }) + + t.Run("upper == short price < long price", func(t *testing.T) { + m0 := &Metadata{ + Price: longPrice, + AmmIndex: big.NewInt(market), + BlockPlaced: blockPlaced0, + } + m1 := &Metadata{ + Price: upperbound, + BlockPlaced: blockPlaced1, + } + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineFillPrice(mockBibliophile, m0, m1) + assert.Nil(t, err) + assert.Equal(t, FillPriceAndModes{upperbound, Maker, Taker}, *output) + }) + + t.Run("short price < upper", func(t *testing.T) { + m0 := &Metadata{ + Price: longPrice, + AmmIndex: big.NewInt(market), + BlockPlaced: blockPlaced0, + } + m1 := &Metadata{ + Price: new(big.Int).Sub(upperbound, big.NewInt(1)), + BlockPlaced: blockPlaced1, + } + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineFillPrice(mockBibliophile, m0, m1) + assert.Nil(t, err) + assert.Equal(t, FillPriceAndModes{upperbound, Maker, Taker}, *output) + }) + + t.Run("short price < lower", func(t *testing.T) { + m0 := &Metadata{ + Price: longPrice, + AmmIndex: big.NewInt(market), + BlockPlaced: blockPlaced0, + } + m1 := &Metadata{ + Price: new(big.Int).Sub(lowerbound, big.NewInt(1)), + BlockPlaced: blockPlaced1, + } + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineFillPrice(mockBibliophile, m0, m1) + assert.Nil(t, err) + assert.Equal(t, FillPriceAndModes{upperbound, Maker, Taker}, *output) + }) + }) + }) +} + +func TestDetermineLiquidationFillPrice(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockBibliophile := b.NewMockBibliophileClient(ctrl) + + liqUpperBound, liqLowerBound := multiply1e6(big.NewInt(22)), multiply1e6(big.NewInt(18)) + + upperbound := multiply1e6(big.NewInt(30)) // $30 + lowerbound := multiply1e6(big.NewInt(10)) // $10 + market := int64(7) + + t.Run("long position is being liquidated", func(t *testing.T) { + t.Run("order price < liqLowerBound", func(t *testing.T) { + m0 := &Metadata{ + Price: new(big.Int).Sub(liqLowerBound, big.NewInt(1)), + BaseAssetQuantity: big.NewInt(5), + AmmIndex: big.NewInt(market), + } + mockBibliophile.EXPECT().GetAcceptableBoundsForLiquidation(market).Return(liqUpperBound, liqLowerBound).Times(1) + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineLiquidationFillPrice(mockBibliophile, m0) + assert.Nil(t, output) + assert.Equal(t, ErrTooLow, err) + }) + t.Run("order price == liqLowerBound", func(t *testing.T) { + m0 := &Metadata{ + Price: liqLowerBound, + BaseAssetQuantity: big.NewInt(5), + AmmIndex: big.NewInt(market), + } + mockBibliophile.EXPECT().GetAcceptableBoundsForLiquidation(market).Return(liqUpperBound, liqLowerBound).Times(1) + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineLiquidationFillPrice(mockBibliophile, m0) + assert.Nil(t, err) + assert.Equal(t, liqLowerBound, output) + }) + + t.Run("liqLowerBound < order price < upper bound", func(t *testing.T) { + m0 := &Metadata{ + Price: new(big.Int).Add(liqLowerBound, big.NewInt(99)), + BaseAssetQuantity: big.NewInt(5), + AmmIndex: big.NewInt(market), + } + mockBibliophile.EXPECT().GetAcceptableBoundsForLiquidation(market).Return(liqUpperBound, liqLowerBound).Times(1) + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineLiquidationFillPrice(mockBibliophile, m0) + assert.Nil(t, err) + assert.Equal(t, m0.Price, output) + }) + + t.Run("order price == upper bound", func(t *testing.T) { + m0 := &Metadata{ + Price: upperbound, + BaseAssetQuantity: big.NewInt(5), + AmmIndex: big.NewInt(market), + } + mockBibliophile.EXPECT().GetAcceptableBoundsForLiquidation(market).Return(liqUpperBound, liqLowerBound).Times(1) + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineLiquidationFillPrice(mockBibliophile, m0) + assert.Nil(t, err) + assert.Equal(t, upperbound, output) + }) + + t.Run("order price > upper bound", func(t *testing.T) { + m0 := &Metadata{ + Price: new(big.Int).Add(upperbound, big.NewInt(99)), + BaseAssetQuantity: big.NewInt(5), + AmmIndex: big.NewInt(market), + } + mockBibliophile.EXPECT().GetAcceptableBoundsForLiquidation(market).Return(liqUpperBound, liqLowerBound).Times(1) + mockBibliophile.EXPECT().GetUpperAndLowerBoundForMarket(market).Return(upperbound, lowerbound).Times(1) + output, err := determineLiquidationFillPrice(mockBibliophile, m0) + assert.Nil(t, err) + assert.Equal(t, upperbound, output) + }) + }) +}