code.vegaprotocol.io/vega@v0.79.0/core/liquidity/v2/engine_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 liquidity_test
    17  
    18  import (
    19  	"context"
    20  	"testing"
    21  	"time"
    22  
    23  	"code.vegaprotocol.io/vega/core/events"
    24  	"code.vegaprotocol.io/vega/core/idgeneration"
    25  	"code.vegaprotocol.io/vega/core/types"
    26  	"code.vegaprotocol.io/vega/libs/crypto"
    27  	"code.vegaprotocol.io/vega/libs/num"
    28  	commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1"
    29  
    30  	"github.com/golang/mock/gomock"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  )
    34  
    35  func TestSubmissions(t *testing.T) {
    36  	t.Run("Create and cancel", testSubmissionCreateAndCancel)
    37  	t.Run("Cancel non existing", testCancelNonExistingSubmission)
    38  	t.Run("Can to submit when current or pending LP exists", testFailsWhenLPExists)
    39  }
    40  
    41  func testFailsWhenLPExists(t *testing.T) {
    42  	var (
    43  		party = "party-1"
    44  		ctx   = context.Background()
    45  		te    = newTestEngine(t)
    46  	)
    47  	defer te.ctrl.Finish()
    48  
    49  	require.Nil(t, te.engine.LiquidityProvisionByPartyID("some-party"))
    50  
    51  	lps1 := &commandspb.LiquidityProvisionSubmission{
    52  		MarketId: te.marketID, CommitmentAmount: "100", Fee: "0.5",
    53  	}
    54  	lps, err := types.LiquidityProvisionSubmissionFromProto(lps1)
    55  	require.NoError(t, err)
    56  
    57  	deterministicID := crypto.RandomHash()
    58  	idGen := idgeneration.New(deterministicID)
    59  
    60  	lpID := idGen.NextID()
    61  	now := te.tsvc.GetTimeNow()
    62  	nowNano := now.UnixNano()
    63  
    64  	expected := &types.LiquidityProvision{
    65  		ID:               lpID,
    66  		MarketID:         te.marketID,
    67  		Party:            party,
    68  		Fee:              num.DecimalFromFloat(0.5),
    69  		CommitmentAmount: lps.CommitmentAmount.Clone(),
    70  		CreatedAt:        nowNano,
    71  		UpdatedAt:        nowNano,
    72  		Status:           types.LiquidityProvisionStatusActive,
    73  		Version:          1,
    74  	}
    75  
    76  	// Creating a submission should fire an event
    77  	te.broker.EXPECT().Send(gomock.Any()).AnyTimes()
    78  	te.auctionState.EXPECT().IsOpeningAuction().Return(false).AnyTimes()
    79  
    80  	idgen := idgeneration.New(deterministicID)
    81  	_, err = te.engine.SubmitLiquidityProvision(ctx, lps, party, idgen)
    82  	require.NoError(t, err)
    83  
    84  	// first validate that the amendment is pending
    85  	pendingLp := te.engine.PendingProvisionByPartyID(party)
    86  	assert.Equal(t, expected.CommitmentAmount.String(), pendingLp.CommitmentAmount.String())
    87  	assert.Equal(t, expected.Fee.String(), pendingLp.Fee.String())
    88  
    89  	idgen = idgeneration.New(deterministicID)
    90  
    91  	lps.CommitmentAmount = num.NewUint(1000)
    92  	_, err = te.engine.SubmitLiquidityProvision(ctx, lps, party, idgen)
    93  	require.Error(t, err, "liquidity provision already exists")
    94  }
    95  
    96  func testSubmissionCreateAndCancel(t *testing.T) {
    97  	var (
    98  		party = "party-1"
    99  		ctx   = context.Background()
   100  		te    = newTestEngine(t)
   101  	)
   102  	defer te.ctrl.Finish()
   103  
   104  	require.Nil(t, te.engine.LiquidityProvisionByPartyID("some-party"))
   105  
   106  	lps1 := &commandspb.LiquidityProvisionSubmission{
   107  		MarketId: te.marketID, CommitmentAmount: "100", Fee: "0.5",
   108  	}
   109  	lps, err := types.LiquidityProvisionSubmissionFromProto(lps1)
   110  	require.NoError(t, err)
   111  
   112  	deterministicID := crypto.RandomHash()
   113  	idGen := idgeneration.New(deterministicID)
   114  
   115  	lpID := idGen.NextID()
   116  	now := te.tsvc.GetTimeNow()
   117  	nowNano := now.UnixNano()
   118  
   119  	expected := &types.LiquidityProvision{
   120  		ID:               lpID,
   121  		MarketID:         te.marketID,
   122  		Party:            party,
   123  		Fee:              num.DecimalFromFloat(0.5),
   124  		CommitmentAmount: lps.CommitmentAmount.Clone(),
   125  		CreatedAt:        nowNano,
   126  		UpdatedAt:        nowNano,
   127  		Status:           types.LiquidityProvisionStatusActive,
   128  		Version:          1,
   129  	}
   130  
   131  	// Creating a submission should fire an event
   132  	te.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   133  	te.auctionState.EXPECT().IsOpeningAuction().Return(false).AnyTimes()
   134  
   135  	idgen := idgeneration.New(deterministicID)
   136  	_, err = te.engine.SubmitLiquidityProvision(ctx, lps, party, idgen)
   137  	require.NoError(t, err)
   138  
   139  	// first validate that the amendment is pending
   140  	pendingLp := te.engine.PendingProvisionByPartyID(party)
   141  	assert.Equal(t, expected.CommitmentAmount.String(), pendingLp.CommitmentAmount.String())
   142  	assert.Equal(t, expected.Fee.String(), pendingLp.Fee.String())
   143  
   144  	got := te.engine.LiquidityProvisionByPartyID(party)
   145  	require.Nil(t, got)
   146  
   147  	zero := num.UintZero()
   148  
   149  	te.engine.ResetSLAEpoch(now, zero, zero, num.DecimalZero())
   150  	te.engine.ApplyPendingProvisions(ctx, now)
   151  
   152  	got = te.engine.LiquidityProvisionByPartyID(party)
   153  	require.Equal(t, expected.CommitmentAmount.String(), got.CommitmentAmount.String())
   154  	require.Equal(t, expected.Fee, got.Fee)
   155  	require.Equal(t, expected.Version, got.Version)
   156  
   157  	expected.Status = types.LiquidityProvisionStatusCancelled
   158  	te.broker.EXPECT().Send(
   159  		events.NewLiquidityProvisionEvent(ctx, expected),
   160  	).AnyTimes()
   161  
   162  	err = te.engine.CancelLiquidityProvision(ctx, party)
   163  	require.NoError(t, err)
   164  	require.Nil(t, te.engine.LiquidityProvisionByPartyID(party),
   165  		"Party '%s' should not be a LiquidityProvider after Committing 0 amount", party)
   166  }
   167  
   168  func testCancelNonExistingSubmission(t *testing.T) {
   169  	var (
   170  		party = "party-1"
   171  		ctx   = context.Background()
   172  		tng   = newTestEngine(t)
   173  	)
   174  	defer tng.ctrl.Finish()
   175  
   176  	err := tng.engine.CancelLiquidityProvision(ctx, party)
   177  	require.Error(t, err)
   178  }
   179  
   180  func TestCalculateSuppliedStake(t *testing.T) {
   181  	var (
   182  		party1 = "party-1"
   183  		party2 = "party-2"
   184  		party3 = "party-3"
   185  		ctx    = context.Background()
   186  		tng    = newTestEngine(t)
   187  	)
   188  	defer tng.ctrl.Finish()
   189  
   190  	// We don't care about the following calls
   191  	tng.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   192  	tng.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   193  	tng.auctionState.EXPECT().IsOpeningAuction().Return(false).AnyTimes()
   194  
   195  	zero := num.UintZero()
   196  	tng.orderbook.EXPECT().GetBestStaticBidPrice().Return(zero, nil).AnyTimes()
   197  	tng.orderbook.EXPECT().GetBestStaticAskPrice().Return(zero, nil).AnyTimes()
   198  
   199  	tng.auctionState.EXPECT().InAuction().Return(false).AnyTimes()
   200  
   201  	// Send a submission
   202  	lp1pb := &commandspb.LiquidityProvisionSubmission{
   203  		MarketId: tng.marketID, CommitmentAmount: "100", Fee: "0.5",
   204  	}
   205  	lp1, err := types.LiquidityProvisionSubmissionFromProto(lp1pb)
   206  	require.NoError(t, err)
   207  
   208  	idgen := idgeneration.New(crypto.RandomHash())
   209  	_, err = tng.engine.SubmitLiquidityProvision(ctx, lp1, party1, idgen)
   210  	require.NoError(t, err)
   211  
   212  	now := tng.tsvc.GetTimeNow()
   213  
   214  	tng.engine.ApplyPendingProvisions(ctx, now)
   215  	tng.engine.ResetSLAEpoch(time.Now(), zero, zero, num.DecimalOne())
   216  
   217  	suppliedStake := tng.engine.CalculateSuppliedStake()
   218  	require.Equal(t, lp1.CommitmentAmount, suppliedStake)
   219  
   220  	lp2pb := &commandspb.LiquidityProvisionSubmission{
   221  		MarketId: tng.marketID, CommitmentAmount: "500", Fee: "0.5",
   222  	}
   223  	lp2, err := types.LiquidityProvisionSubmissionFromProto(lp2pb)
   224  	require.NoError(t, err)
   225  
   226  	idgen = idgeneration.New(crypto.RandomHash())
   227  	_, err = tng.engine.SubmitLiquidityProvision(ctx, lp2, party2, idgen)
   228  	require.NoError(t, err)
   229  
   230  	tng.engine.ResetSLAEpoch(now, zero, zero, num.DecimalZero())
   231  	tng.engine.ApplyPendingProvisions(ctx, now)
   232  
   233  	suppliedStake = tng.engine.CalculateSuppliedStake()
   234  	require.Equal(t, num.Sum(lp1.CommitmentAmount, lp2.CommitmentAmount), suppliedStake)
   235  
   236  	lp3pb := &commandspb.LiquidityProvisionSubmission{
   237  		MarketId: tng.marketID, CommitmentAmount: "962", Fee: "0.5",
   238  	}
   239  	lp3, err := types.LiquidityProvisionSubmissionFromProto(lp3pb)
   240  	require.NoError(t, err)
   241  
   242  	idgen = idgeneration.New(crypto.RandomHash())
   243  	_, err = tng.engine.SubmitLiquidityProvision(ctx, lp3, party3, idgen)
   244  	require.NoError(t, err)
   245  
   246  	suppliedStake = tng.engine.CalculateSuppliedStake()
   247  	require.Equal(t, num.Sum(lp1.CommitmentAmount, lp2.CommitmentAmount, lp3.CommitmentAmount), suppliedStake)
   248  
   249  	err = tng.engine.CancelLiquidityProvision(ctx, party1)
   250  	require.NoError(t, err)
   251  	suppliedStake = tng.engine.CalculateSuppliedStake()
   252  	require.Equal(t, num.Sum(lp2.CommitmentAmount, lp3.CommitmentAmount), suppliedStake)
   253  }