code.vegaprotocol.io/vega@v0.79.0/core/execution/spot/spot_execution_test.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package spot_test
    17  
    18  import (
    19  	"context"
    20  	"testing"
    21  	"time"
    22  
    23  	"code.vegaprotocol.io/vega/core/execution/common"
    24  	"code.vegaprotocol.io/vega/core/idgeneration"
    25  	"code.vegaprotocol.io/vega/core/types"
    26  	vegacontext "code.vegaprotocol.io/vega/libs/context"
    27  	"code.vegaprotocol.io/vega/libs/crypto"
    28  	"code.vegaprotocol.io/vega/libs/num"
    29  	"code.vegaprotocol.io/vega/libs/ptr"
    30  
    31  	"github.com/stretchr/testify/require"
    32  )
    33  
    34  func TestOpeningAuction(t *testing.T) {
    35  	now := time.Now()
    36  	ctx := context.Background()
    37  	ctx = vegacontext.WithTraceID(ctx, crypto.RandomHash())
    38  	tm := newTestMarket(t, defaultPriceMonitorSettings, &types.AuctionDuration{Duration: 1}, now)
    39  
    40  	addAccountWithAmount(tm, "party1", 10, "BTC")
    41  	addAccountWithAmount(tm, "party1", 100000, "ETH")
    42  	addAccountWithAmount(tm, "party2", 5, "BTC")
    43  	addAccountWithAmount(tm, "party3", 500, "ETH")
    44  	addAccountWithAmount(tm, "party3", 2, "BTC")
    45  	addAccountWithAmount(tm, "party4", 10000, "ETH")
    46  
    47  	tm.market.StartOpeningAuction(ctx)
    48  
    49  	order1 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideBuy, "party1", 2, 30000)
    50  	tm.market.SubmitOrder(ctx, order1.IntoSubmission(), order1.Party, crypto.RandomHash())
    51  
    52  	gaBalance1, err := tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
    53  	require.NoError(t, err)
    54  	require.Equal(t, "40000", gaBalance1.Balance.String())
    55  	haBalance1, err := tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
    56  	require.NoError(t, err)
    57  	require.Equal(t, "60000", haBalance1.Balance.String())
    58  
    59  	order2 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideSell, "party2", 1, 32000)
    60  	tm.market.SubmitOrder(ctx, order2.IntoSubmission(), order2.Party, crypto.RandomHash())
    61  
    62  	gaBalance2, err := tm.collateralEngine.GetPartyGeneralAccount("party2", tm.baseAsset)
    63  	require.NoError(t, err)
    64  	require.Equal(t, "4", gaBalance2.Balance.String())
    65  
    66  	haBalance2, err := tm.collateralEngine.GetPartyHoldingAccount("party2", tm.baseAsset)
    67  	require.NoError(t, err)
    68  	require.Equal(t, "1", haBalance2.Balance.String())
    69  
    70  	md := tm.market.GetMarketData()
    71  	require.Equal(t, types.MarketTradingModeOpeningAuction, md.MarketTradingMode)
    72  
    73  	order3 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideSell, "party3", 1, 30000)
    74  	tm.market.SubmitOrder(ctx, order3.IntoSubmission(), order3.Party, crypto.RandomHash())
    75  
    76  	haBalance3, err := tm.collateralEngine.GetPartyHoldingAccount("party3", tm.baseAsset)
    77  	require.NoError(t, err)
    78  	require.Equal(t, "1", haBalance3.Balance.String())
    79  
    80  	gaBalance3, err := tm.collateralEngine.GetPartyGeneralAccount("party3", tm.baseAsset)
    81  	require.NoError(t, err)
    82  	require.Equal(t, "1", gaBalance3.Balance.String())
    83  
    84  	md = tm.market.GetMarketData()
    85  	require.Equal(t, types.MarketTradingModeOpeningAuction, md.MarketTradingMode)
    86  	tm.market.OnTick(ctx, now.Add(2*time.Second))
    87  	md = tm.market.GetMarketData()
    88  	require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode)
    89  
    90  	// trade has been done between party1 and party3, lets check balances
    91  	// party 1 had 60k ETH in holding and 40k ETH in their general account. The bought 1 BTC for 30k ETH at the end of the opening auction so now they have:
    92  	// 11 BTC in their BTC general account (10 they had + 1 they bought)
    93  	// 40k ETH in their ETH general account
    94  	// 30k ETH in their holding account
    95  	gaBalance1, err = tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
    96  	require.NoError(t, err)
    97  	require.Equal(t, "40000", gaBalance1.Balance.String())
    98  
    99  	haBalance1, err = tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   100  	require.NoError(t, err)
   101  	require.Equal(t, "30000", haBalance1.Balance.String())
   102  
   103  	gaBaseBalance1, err := tm.collateralEngine.GetPartyGeneralAccount("party1", tm.baseAsset)
   104  	require.NoError(t, err)
   105  	require.Equal(t, "11", gaBaseBalance1.Balance.String())
   106  
   107  	// party 3 has 2 BTC and 500 ETH
   108  	// they sold 1 BTC and received 30k ETH so now they have:
   109  	// 1 BTC
   110  	// 30500 ETH
   111  	// and nothing in the holding account
   112  	gaBalance3, err = tm.collateralEngine.GetPartyGeneralAccount("party3", tm.quoteAsset)
   113  	require.NoError(t, err)
   114  	require.Equal(t, "30500", gaBalance3.Balance.String())
   115  
   116  	gaBaseBalance3, err := tm.collateralEngine.GetPartyGeneralAccount("party3", tm.baseAsset)
   117  	require.NoError(t, err)
   118  	require.Equal(t, "1", gaBaseBalance3.Balance.String())
   119  
   120  	haBalance3, err = tm.collateralEngine.GetPartyHoldingAccount("party3", tm.baseAsset)
   121  	require.NoError(t, err)
   122  	require.Equal(t, "0", haBalance3.Balance.String())
   123  }
   124  
   125  func TestAmend(t *testing.T) {
   126  	now := time.Now()
   127  	ctx := context.Background()
   128  	ctx = vegacontext.WithTraceID(ctx, crypto.RandomHash())
   129  	tm := newTestMarket(t, defaultPriceMonitorSettings, &types.AuctionDuration{Duration: 1}, now)
   130  
   131  	addAccountWithAmount(tm, "party1", 100000, "ETH")
   132  	tm.market.StartOpeningAuction(ctx)
   133  
   134  	order1 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideBuy, "party1", 2, 30000)
   135  	conf, err := tm.market.SubmitOrder(ctx, order1.IntoSubmission(), order1.Party, crypto.RandomHash())
   136  	require.NoError(t, err)
   137  
   138  	gaBalance1, err := tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   139  	require.NoError(t, err)
   140  	require.Equal(t, "40000", gaBalance1.Balance.String())
   141  	haBalance1, err := tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   142  	require.NoError(t, err)
   143  	require.Equal(t, "60000", haBalance1.Balance.String())
   144  
   145  	// increase price reduce size, now we expect the holding to have only 40k and general 60k
   146  	conf, err = tm.market.AmendOrder(ctx, &types.OrderAmendment{OrderID: conf.Order.ID, Price: num.NewUint(40000), SizeDelta: -1}, "party1", crypto.RandomHash())
   147  	require.NoError(t, err)
   148  
   149  	gaBalance1, err = tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   150  	require.NoError(t, err)
   151  	require.Equal(t, "60000", gaBalance1.Balance.String())
   152  	haBalance1, err = tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   153  	require.NoError(t, err)
   154  	require.Equal(t, "40000", haBalance1.Balance.String())
   155  
   156  	// increase size, reduce price
   157  	_, err = tm.market.AmendOrder(ctx, &types.OrderAmendment{OrderID: conf.Order.ID, Price: num.NewUint(20000), SizeDelta: 2}, "party1", crypto.RandomHash())
   158  	require.NoError(t, err)
   159  
   160  	// should be back to 60k in holding and 40k in general
   161  	gaBalance1, err = tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   162  	require.NoError(t, err)
   163  	require.Equal(t, "40000", gaBalance1.Balance.String())
   164  	haBalance1, err = tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   165  	require.NoError(t, err)
   166  	require.Equal(t, "60000", haBalance1.Balance.String())
   167  }
   168  
   169  func TestMarketWithAllowedSellers(t *testing.T) {
   170  	now := time.Now()
   171  	ctx := context.Background()
   172  	ctx = vegacontext.WithTraceID(ctx, crypto.RandomHash())
   173  	tm := newTestMarketWithAllowedSellers(t, defaultPriceMonitorSettings, &types.AuctionDuration{Duration: 1}, now, []string{"party1", "party2"})
   174  
   175  	addAccountWithAmount(tm, "party1", 100000, "ETH")
   176  	addAccountWithAmount(tm, "party2", 100000, "ETH")
   177  	addAccountWithAmount(tm, "party3", 100000, "ETH")
   178  	addAccountWithAmount(tm, "party1", 100000, "BTC")
   179  	addAccountWithAmount(tm, "party2", 100000, "BTC")
   180  	addAccountWithAmount(tm, "party3", 100000, "BTC")
   181  	tm.market.StartOpeningAuction(ctx)
   182  
   183  	t.Run("allowed seller can post sell orders", func(t *testing.T) {
   184  		order1 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideSell, "party1", 1, 300)
   185  		_, err := tm.market.SubmitOrder(ctx, order1.IntoSubmission(), order1.Party, crypto.RandomHash())
   186  		require.NoError(t, err)
   187  		order2 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideSell, "party2", 1, 300)
   188  		_, err = tm.market.SubmitOrder(ctx, order2.IntoSubmission(), order2.Party, crypto.RandomHash())
   189  		require.NoError(t, err)
   190  	})
   191  
   192  	t.Run("allowed seller can post buy orders", func(t *testing.T) {
   193  		order1 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideBuy, "party1", 2, 200)
   194  		_, err := tm.market.SubmitOrder(ctx, order1.IntoSubmission(), order1.Party, crypto.RandomHash())
   195  		require.NoError(t, err)
   196  		order2 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideBuy, "party2", 2, 200)
   197  		_, err = tm.market.SubmitOrder(ctx, order2.IntoSubmission(), order2.Party, crypto.RandomHash())
   198  		require.NoError(t, err)
   199  	})
   200  
   201  	t.Run("non allowed seller cannot post sell orders", func(t *testing.T) {
   202  		order1 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideSell, "party3", 2, 300)
   203  		_, err := tm.market.SubmitOrder(ctx, order1.IntoSubmission(), order1.Party, crypto.RandomHash())
   204  		require.EqualError(t, err, "sell order not allowed")
   205  	})
   206  
   207  	t.Run("non allowed seller can post buy orders", func(t *testing.T) {
   208  		order1 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideBuy, "party3", 2, 200)
   209  		_, err := tm.market.SubmitOrder(ctx, order1.IntoSubmission(), order1.Party, crypto.RandomHash())
   210  		require.NoError(t, err)
   211  	})
   212  
   213  	t.Run("exit auction", func(t *testing.T) {
   214  		order1 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideBuy, "party1", 2, 30000)
   215  		_, err := tm.market.SubmitOrder(ctx, order1.IntoSubmission(), order1.Party, crypto.RandomHash())
   216  		require.NoError(t, err)
   217  
   218  		order2 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideSell, "party2", 1, 30000)
   219  		_, err = tm.market.SubmitOrder(ctx, order2.IntoSubmission(), order2.Party, crypto.RandomHash())
   220  		require.NoError(t, err)
   221  
   222  		tm.market.OnTick(ctx, now.Add(2*time.Second))
   223  		md := tm.market.GetMarketData()
   224  		require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode)
   225  	})
   226  
   227  	t.Run("increase max stop orders per parties", func(t *testing.T) {
   228  		tm.market.OnMarketPartiesMaximumStopOrdersUpdate(
   229  			context.Background(), num.NewUint(1000))
   230  	})
   231  
   232  	t.Run("allowed seller can post sell stop orders", func(t *testing.T) {
   233  		idgen := idgeneration.New(crypto.RandomHash())
   234  		order1 := getStopOrderSubmission(tm, now, crypto.RandomHash(), types.SideSell, types.SideBuy, "party1", 1, 300)
   235  		_, err := tm.market.SubmitStopOrdersWithIDGeneratorAndOrderIDs(ctx, order1, "party1", idgen, ptr.From(idgen.NextID()), ptr.From(idgen.NextID()))
   236  		require.NoError(t, err)
   237  
   238  		order2 := getStopOrderSubmission(tm, now, crypto.RandomHash(), types.SideSell, types.SideBuy, "party2", 1, 300)
   239  		_, err = tm.market.SubmitStopOrdersWithIDGeneratorAndOrderIDs(ctx, order2, "party2", idgen, ptr.From(idgen.NextID()), ptr.From(idgen.NextID()))
   240  		require.NoError(t, err)
   241  
   242  		order3 := getStopOrderSubmission(tm, now, crypto.RandomHash(), types.SideBuy, types.SideSell, "party1", 1, 300)
   243  		_, err = tm.market.SubmitStopOrdersWithIDGeneratorAndOrderIDs(ctx, order3, "party1", idgen, ptr.From(idgen.NextID()), ptr.From(idgen.NextID()))
   244  		require.NoError(t, err)
   245  
   246  		order4 := getStopOrderSubmission(tm, now, crypto.RandomHash(), types.SideBuy, types.SideSell, "party2", 1, 300)
   247  		_, err = tm.market.SubmitStopOrdersWithIDGeneratorAndOrderIDs(ctx, order4, "party2", idgen, ptr.From(idgen.NextID()), ptr.From(idgen.NextID()))
   248  		require.NoError(t, err)
   249  	})
   250  
   251  	t.Run("non allowed seller cannot post sell stop orders", func(t *testing.T) {
   252  		idgen := idgeneration.New(crypto.RandomHash())
   253  		order1 := getStopOrderSubmission(tm, now, crypto.RandomHash(), types.SideSell, types.SideBuy, "party3", 1, 300)
   254  		_, err := tm.market.SubmitStopOrdersWithIDGeneratorAndOrderIDs(ctx, order1, "party3", idgen, ptr.From(idgen.NextID()), ptr.From(idgen.NextID()))
   255  		require.EqualError(t, err, "sell order not allowed")
   256  
   257  		order2 := getStopOrderSubmission(tm, now, crypto.RandomHash(), types.SideBuy, types.SideSell, "party3", 1, 300)
   258  		_, err = tm.market.SubmitStopOrdersWithIDGeneratorAndOrderIDs(ctx, order2, "party3", idgen, ptr.From(idgen.NextID()), ptr.From(idgen.NextID()))
   259  		require.EqualError(t, err, "sell order not allowed")
   260  	})
   261  }
   262  
   263  func TestCancelAll(t *testing.T) {
   264  	now := time.Now()
   265  	ctx := context.Background()
   266  	ctx = vegacontext.WithTraceID(ctx, crypto.RandomHash())
   267  	tm := newTestMarket(t, defaultPriceMonitorSettings, &types.AuctionDuration{Duration: 1}, now)
   268  
   269  	addAccountWithAmount(tm, "party1", 100000, "ETH")
   270  	tm.market.StartOpeningAuction(ctx)
   271  
   272  	order1 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideBuy, "party1", 2, 30000)
   273  	_, err := tm.market.SubmitOrder(ctx, order1.IntoSubmission(), order1.Party, crypto.RandomHash())
   274  	require.NoError(t, err)
   275  
   276  	gaBalance1, err := tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   277  	require.NoError(t, err)
   278  	require.Equal(t, "40000", gaBalance1.Balance.String())
   279  	haBalance1, err := tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   280  	require.NoError(t, err)
   281  	require.Equal(t, "60000", haBalance1.Balance.String())
   282  
   283  	order2 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideBuy, "party1", 2, 15000)
   284  	_, err = tm.market.SubmitOrder(ctx, order2.IntoSubmission(), order1.Party, crypto.RandomHash())
   285  	require.NoError(t, err)
   286  
   287  	gaBalance1, err = tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   288  	require.NoError(t, err)
   289  	require.Equal(t, "10000", gaBalance1.Balance.String())
   290  	haBalance1, err = tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   291  	require.NoError(t, err)
   292  	require.Equal(t, "90000", haBalance1.Balance.String())
   293  
   294  	tm.market.CancelAllOrders(ctx, "party1")
   295  	gaBalance1, err = tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   296  	require.NoError(t, err)
   297  	require.Equal(t, "100000", gaBalance1.Balance.String())
   298  	haBalance1, err = tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   299  	require.NoError(t, err)
   300  	require.Equal(t, "0", haBalance1.Balance.String())
   301  }
   302  
   303  func TestCancelSingleOrder(t *testing.T) {
   304  	now := time.Now()
   305  	ctx := context.Background()
   306  	ctx = vegacontext.WithTraceID(ctx, crypto.RandomHash())
   307  	tm := newTestMarket(t, defaultPriceMonitorSettings, &types.AuctionDuration{Duration: 1}, now)
   308  
   309  	addAccountWithAmount(tm, "party1", 100000, "ETH")
   310  	addAccountWithAmount(tm, "party2", 5, "BTC")
   311  	tm.market.StartOpeningAuction(ctx)
   312  
   313  	order1 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideBuy, "party1", 2, 30000)
   314  	conf1, err := tm.market.SubmitOrder(ctx, order1.IntoSubmission(), order1.Party, crypto.RandomHash())
   315  	require.NoError(t, err)
   316  
   317  	gaBalance1, err := tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   318  	require.NoError(t, err)
   319  	require.Equal(t, "40000", gaBalance1.Balance.String())
   320  	haBalance1, err := tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   321  	require.NoError(t, err)
   322  	require.Equal(t, "60000", haBalance1.Balance.String())
   323  
   324  	order2 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideBuy, "party1", 2, 15000)
   325  	conf2, err := tm.market.SubmitOrder(ctx, order2.IntoSubmission(), order1.Party, crypto.RandomHash())
   326  	require.NoError(t, err)
   327  
   328  	gaBalance1, err = tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   329  	require.NoError(t, err)
   330  	require.Equal(t, "10000", gaBalance1.Balance.String())
   331  	haBalance1, err = tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   332  	require.NoError(t, err)
   333  	require.Equal(t, "90000", haBalance1.Balance.String())
   334  
   335  	// cancel the second order, now we should have
   336  	tm.market.CancelOrder(ctx, "party1", conf2.Order.ID, crypto.RandomHash())
   337  	gaBalance1, err = tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   338  	require.NoError(t, err)
   339  	require.Equal(t, "40000", gaBalance1.Balance.String())
   340  	haBalance1, err = tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   341  	require.NoError(t, err)
   342  	require.Equal(t, "60000", haBalance1.Balance.String())
   343  
   344  	// cancel the first order to have the full amount back in the general account
   345  	tm.market.CancelOrder(ctx, "party1", conf1.Order.ID, crypto.RandomHash())
   346  	gaBalance1, err = tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   347  	require.NoError(t, err)
   348  	require.Equal(t, "100000", gaBalance1.Balance.String())
   349  	haBalance1, err = tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   350  	require.NoError(t, err)
   351  	require.Equal(t, "0", haBalance1.Balance.String())
   352  }
   353  
   354  func TestInsufficientCoverInSubmit(t *testing.T) {
   355  	now := time.Now()
   356  	ctx := context.Background()
   357  	ctx = vegacontext.WithTraceID(ctx, crypto.RandomHash())
   358  	tm := newTestMarket(t, defaultPriceMonitorSettings, &types.AuctionDuration{Duration: 1}, now)
   359  
   360  	addAccountWithAmount(tm, "party1", 100000, "ETH")
   361  	tm.market.StartOpeningAuction(ctx)
   362  
   363  	// submit an order with insufficient cover should fail
   364  	order1 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideBuy, "party1", 4, 30000)
   365  	_, err := tm.market.SubmitOrder(ctx, order1.IntoSubmission(), order1.Party, crypto.RandomHash())
   366  	require.Equal(t, "party does not have sufficient balance to cover the trade and fees", err.Error())
   367  
   368  	gaBalance1, err := tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   369  	require.NoError(t, err)
   370  	require.Equal(t, "100000", gaBalance1.Balance.String())
   371  
   372  	// now submit a valid one
   373  	order1 = getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideBuy, "party1", 2, 30000)
   374  	_, err = tm.market.SubmitOrder(ctx, order1.IntoSubmission(), order1.Party, crypto.RandomHash())
   375  	require.NoError(t, err)
   376  
   377  	gaBalance1, err = tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   378  	require.NoError(t, err)
   379  	require.Equal(t, "40000", gaBalance1.Balance.String())
   380  	haBalance1, err := tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   381  	require.NoError(t, err)
   382  	require.Equal(t, "60000", haBalance1.Balance.String())
   383  
   384  	// and again an invalid one
   385  	order2 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideBuy, "party1", 2, 30000)
   386  	_, err = tm.market.SubmitOrder(ctx, order2.IntoSubmission(), order2.Party, crypto.RandomHash())
   387  	require.Equal(t, "party does not have sufficient balance to cover the trade and fees", err.Error())
   388  
   389  	// and again a valid one
   390  	order2 = getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideBuy, "party1", 2, 20000)
   391  	_, err = tm.market.SubmitOrder(ctx, order2.IntoSubmission(), order2.Party, crypto.RandomHash())
   392  	require.NoError(t, err)
   393  
   394  	gaBalance1, err = tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   395  	require.NoError(t, err)
   396  	require.Equal(t, "0", gaBalance1.Balance.String())
   397  	haBalance1, err = tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   398  	require.NoError(t, err)
   399  	require.Equal(t, "100000", haBalance1.Balance.String())
   400  }
   401  
   402  func TestNoValidAccount(t *testing.T) {
   403  	now := time.Now()
   404  	ctx := context.Background()
   405  	ctx = vegacontext.WithTraceID(ctx, crypto.RandomHash())
   406  	tm := newTestMarket(t, defaultPriceMonitorSettings, &types.AuctionDuration{Duration: 1}, now)
   407  
   408  	tm.market.StartOpeningAuction(ctx)
   409  
   410  	// submit an order with insufficient cover should fail
   411  	order1 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideBuy, "party1", 4, 30000)
   412  	_, err := tm.market.SubmitOrder(ctx, order1.IntoSubmission(), order1.Party, crypto.RandomHash())
   413  	require.Error(t, common.ErrPartyInsufficientAssetBalance, err)
   414  }
   415  
   416  func TestPartialFill(t *testing.T) {
   417  	now := time.Now()
   418  	ctx := context.Background()
   419  	ctx = vegacontext.WithTraceID(ctx, crypto.RandomHash())
   420  	tm := newTestMarket(t, defaultPriceMonitorSettings, &types.AuctionDuration{Duration: 1}, now)
   421  
   422  	addAccountWithAmount(tm, "party1", 100000, "ETH")
   423  	addAccountWithAmount(tm, "party2", 5, "BTC")
   424  
   425  	tm.market.StartOpeningAuction(ctx)
   426  
   427  	order1 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideBuy, "party1", 2, 30000)
   428  	conf1, err := tm.market.SubmitOrder(ctx, order1.IntoSubmission(), order1.Party, crypto.RandomHash())
   429  	require.NoError(t, err)
   430  
   431  	order2 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideSell, "party2", 1, 30000)
   432  	_, err = tm.market.SubmitOrder(ctx, order2.IntoSubmission(), order2.Party, crypto.RandomHash())
   433  	require.NoError(t, err)
   434  
   435  	tm.market.OnTick(ctx, now.Add(2*time.Second))
   436  	md := tm.market.GetMarketData()
   437  	require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode)
   438  
   439  	// expect one trade of size 1
   440  	gaBalance1, err := tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   441  	require.NoError(t, err)
   442  	require.Equal(t, "40000", gaBalance1.Balance.String())
   443  	haBalance1, err := tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   444  	require.NoError(t, err)
   445  	require.Equal(t, "30000", haBalance1.Balance.String())
   446  	gaBaseBalance1, err := tm.collateralEngine.GetPartyGeneralAccount("party1", tm.baseAsset)
   447  	require.NoError(t, err)
   448  	require.Equal(t, "1", gaBaseBalance1.Balance.String())
   449  
   450  	// party 2 should get the 30k quote and should have 1 less base
   451  	gaBaseBalance2, err := tm.collateralEngine.GetPartyGeneralAccount("party2", tm.baseAsset)
   452  	require.NoError(t, err)
   453  	require.Equal(t, "4", gaBaseBalance2.Balance.String())
   454  	haBaseBalance2, err := tm.collateralEngine.GetPartyHoldingAccount("party2", tm.baseAsset)
   455  	require.NoError(t, err)
   456  	require.Equal(t, "0", haBaseBalance2.Balance.String())
   457  	gaBalance2, err := tm.collateralEngine.GetPartyGeneralAccount("party2", tm.quoteAsset)
   458  	require.NoError(t, err)
   459  	require.Equal(t, "30000", gaBalance2.Balance.String())
   460  
   461  	tm.market.OnTick(ctx, now.Add(2*time.Second))
   462  	md = tm.market.GetMarketData()
   463  	require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode)
   464  
   465  	// increase the remaining size to 2, decrease the size to 29900
   466  	conf, err := tm.market.AmendOrder(ctx, &types.OrderAmendment{OrderID: conf1.Order.ID, SizeDelta: 1, Price: num.NewUint(29900)}, "party1", crypto.RandomHash())
   467  	require.NoError(t, err)
   468  	require.Equal(t, 0, len(conf.Trades))
   469  	gaBalance1, err = tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   470  	require.NoError(t, err)
   471  	require.Equal(t, "10200", gaBalance1.Balance.String())
   472  	haBalance1, err = tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   473  	require.NoError(t, err)
   474  	require.Equal(t, "59800", haBalance1.Balance.String())
   475  
   476  	tm.market.OnTick(ctx, now.Add(2*time.Second))
   477  	md = tm.market.GetMarketData()
   478  	require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode)
   479  
   480  	// now fill some and then cancel
   481  	order3 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideSell, "party2", 1, 29900)
   482  	conf3, err := tm.market.SubmitOrder(ctx, order3.IntoSubmission(), order3.Party, crypto.RandomHash())
   483  	require.NoError(t, err)
   484  	require.Equal(t, 1, len(conf3.Trades))
   485  
   486  	gaBalance1, err = tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   487  	require.NoError(t, err)
   488  	require.Equal(t, "10320", gaBalance1.Balance.String()) // maker fees paid to party1
   489  	haBalance1, err = tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   490  	require.NoError(t, err)
   491  	require.Equal(t, "29900", haBalance1.Balance.String())
   492  	gaBaseBalance1, err = tm.collateralEngine.GetPartyGeneralAccount("party1", tm.baseAsset)
   493  	require.NoError(t, err)
   494  	require.Equal(t, "2", gaBaseBalance1.Balance.String())
   495  
   496  	gaBaseBalance2, err = tm.collateralEngine.GetPartyGeneralAccount("party2", tm.baseAsset)
   497  	require.NoError(t, err)
   498  	require.Equal(t, "3", gaBaseBalance2.Balance.String())
   499  	haBaseBalance2, err = tm.collateralEngine.GetPartyHoldingAccount("party2", tm.baseAsset)
   500  	require.NoError(t, err)
   501  	require.Equal(t, "0", haBaseBalance2.Balance.String())
   502  	gaBalance2, err = tm.collateralEngine.GetPartyGeneralAccount("party2", tm.quoteAsset)
   503  	require.NoError(t, err)
   504  	// party2 is the aggressor so they pay the fees which are deducted from their 29990 payment leaving 29750 (240 paid in fees)
   505  	require.Equal(t, "59750", gaBalance2.Balance.String())
   506  
   507  	// cancel all orders for party1
   508  	tm.market.CancelAllOrders(ctx, "party1")
   509  	gaBalance1, err = tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   510  	require.NoError(t, err)
   511  	require.Equal(t, "40220", gaBalance1.Balance.String()) // maker fees paid to party1
   512  	haBalance1, err = tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   513  	require.NoError(t, err)
   514  	require.Equal(t, "0", haBalance1.Balance.String())
   515  	gaBaseBalance1, err = tm.collateralEngine.GetPartyGeneralAccount("party1", tm.baseAsset)
   516  	require.NoError(t, err)
   517  	require.Equal(t, "2", gaBaseBalance1.Balance.String())
   518  }
   519  
   520  func TestIncreaseHolding(t *testing.T) {
   521  	now := time.Now()
   522  	ctx := context.Background()
   523  	ctx = vegacontext.WithTraceID(ctx, crypto.RandomHash())
   524  	tm := newTestMarket(t, defaultPriceMonitorSettings, &types.AuctionDuration{Duration: 1}, now)
   525  
   526  	addAccountWithAmount(tm, "party1", 100000, "ETH")
   527  	addAccountWithAmount(tm, "party2", 5, "BTC")
   528  
   529  	tm.market.StartOpeningAuction(ctx)
   530  
   531  	order1 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideBuy, "party1", 2, 30000)
   532  	conf1, err := tm.market.SubmitOrder(ctx, order1.IntoSubmission(), order1.Party, crypto.RandomHash())
   533  	require.NoError(t, err)
   534  
   535  	gaBalance1, err := tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   536  	require.NoError(t, err)
   537  	require.Equal(t, "40000", gaBalance1.Balance.String())
   538  	haBalance1, err := tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   539  	require.NoError(t, err)
   540  	require.Equal(t, "60000", haBalance1.Balance.String())
   541  
   542  	_, err = tm.market.AmendOrder(ctx, &types.OrderAmendment{OrderID: conf1.Order.ID, Price: num.NewUint(25000), SizeDelta: 1}, "party1", crypto.RandomHash())
   543  	require.NoError(t, err)
   544  
   545  	gaBalance1, err = tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   546  	require.NoError(t, err)
   547  	require.Equal(t, "25000", gaBalance1.Balance.String())
   548  	haBalance1, err = tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   549  	require.NoError(t, err)
   550  	require.Equal(t, "75000", haBalance1.Balance.String())
   551  }
   552  
   553  func TestDecreaseHolding(t *testing.T) {
   554  	now := time.Now()
   555  	ctx := context.Background()
   556  	ctx = vegacontext.WithTraceID(ctx, crypto.RandomHash())
   557  	tm := newTestMarket(t, defaultPriceMonitorSettings, &types.AuctionDuration{Duration: 1}, now)
   558  
   559  	addAccountWithAmount(tm, "party1", 100000, "ETH")
   560  	addAccountWithAmount(tm, "party2", 5, "BTC")
   561  
   562  	tm.market.StartOpeningAuction(ctx)
   563  
   564  	order1 := getGTCLimitOrder(tm, now, crypto.RandomHash(), types.SideBuy, "party1", 2, 30000)
   565  	conf1, err := tm.market.SubmitOrder(ctx, order1.IntoSubmission(), order1.Party, crypto.RandomHash())
   566  	require.NoError(t, err)
   567  
   568  	gaBalance1, err := tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   569  	require.NoError(t, err)
   570  	require.Equal(t, "40000", gaBalance1.Balance.String())
   571  	haBalance1, err := tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   572  	require.NoError(t, err)
   573  	require.Equal(t, "60000", haBalance1.Balance.String())
   574  
   575  	_, err = tm.market.AmendOrder(ctx, &types.OrderAmendment{OrderID: conf1.Order.ID, Price: num.NewUint(45000), SizeDelta: -1}, "party1", crypto.RandomHash())
   576  	require.NoError(t, err)
   577  
   578  	gaBalance1, err = tm.collateralEngine.GetPartyGeneralAccount("party1", tm.quoteAsset)
   579  	require.NoError(t, err)
   580  	require.Equal(t, "55000", gaBalance1.Balance.String())
   581  	haBalance1, err = tm.collateralEngine.GetPartyHoldingAccount("party1", tm.quoteAsset)
   582  	require.NoError(t, err)
   583  	require.Equal(t, "45000", haBalance1.Balance.String())
   584  }