code.vegaprotocol.io/vega@v0.79.0/core/liquidity/v2/snapshot_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/idgeneration"
    24  	"code.vegaprotocol.io/vega/core/types"
    25  	"code.vegaprotocol.io/vega/libs/crypto"
    26  	"code.vegaprotocol.io/vega/libs/num"
    27  	"code.vegaprotocol.io/vega/libs/proto"
    28  	snapshotpb "code.vegaprotocol.io/vega/protos/vega/snapshot/v1"
    29  
    30  	"github.com/golang/mock/gomock"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  )
    34  
    35  func TestEngineSnapshotV2(t *testing.T) {
    36  	originalEngine := newTestEngine(t)
    37  
    38  	ctx := context.Background()
    39  	idgen := idgeneration.New(crypto.RandomHash())
    40  
    41  	party1 := "party-1"
    42  	commitment1 := 1000000
    43  	party1Orders := []*types.Order{
    44  		{Side: types.SideBuy, Price: num.NewUint(98), Size: 5103},
    45  		{Side: types.SideBuy, Price: num.NewUint(93), Size: 5377},
    46  		{Side: types.SideSell, Price: num.NewUint(102), Size: 4902},
    47  		{Side: types.SideSell, Price: num.NewUint(107), Size: 4673},
    48  	}
    49  
    50  	party2 := "party-2"
    51  	commitment2 := 3000000
    52  	party2Orders := []*types.Order{
    53  		{Side: types.SideBuy, Price: num.NewUint(98), Size: 15307},
    54  		{Side: types.SideBuy, Price: num.NewUint(93), Size: 16130},
    55  		{Side: types.SideSell, Price: num.NewUint(102), Size: 14706},
    56  		{Side: types.SideSell, Price: num.NewUint(107), Size: 14019},
    57  	}
    58  
    59  	party3 := "party-3"
    60  	commitment3 := 2000000
    61  	party3Provision := &types.LiquidityProvisionSubmission{
    62  		MarketID:         originalEngine.marketID,
    63  		CommitmentAmount: num.NewUint(uint64(commitment3)),
    64  		Fee:              num.DecimalFromFloat(0.5),
    65  	}
    66  
    67  	// change the SLA parameters values that were not set in initialisation
    68  	slaParams := &types.LiquiditySLAParams{
    69  		PriceRange:                  num.DecimalFromFloat(0.9), // priceRange
    70  		CommitmentMinTimeFraction:   num.DecimalFromFloat(0.9), // commitmentMinTimeFraction
    71  		SlaCompetitionFactor:        num.DecimalFromFloat(0.9), // slaCompetitionFactor,
    72  		PerformanceHysteresisEpochs: 7,                         // performanceHysteresisEpochs
    73  	}
    74  	originalEngine.engine.UpdateSLAParameters(slaParams)
    75  	originalEngine.engine.OnNonPerformanceBondPenaltyMaxUpdate(num.DecimalFromFloat(0.9))
    76  	originalEngine.engine.OnNonPerformanceBondPenaltySlopeUpdate(num.DecimalFromFloat(0.8))
    77  	originalEngine.engine.OnStakeToCcyVolumeUpdate(num.DecimalFromFloat(0.7))
    78  
    79  	// Adding some state.
    80  	originalEngine.broker.EXPECT().Send(gomock.Any()).AnyTimes()
    81  	originalEngine.auctionState.EXPECT().IsOpeningAuction().Return(false).AnyTimes()
    82  	originalEngine.auctionState.EXPECT().InAuction().Return(false).AnyTimes()
    83  
    84  	// Adding provisions.
    85  	// This helper method flush the initially pending provisions as "on-going"
    86  	// provisions.
    87  	originalEngine.submitLiquidityProvisionAndCreateOrders(t, ctx, party1, commitment1, idgen, party1Orders)
    88  	originalEngine.submitLiquidityProvisionAndCreateOrders(t, ctx, party2, commitment2, idgen, party2Orders)
    89  	// Adding pending provisions.
    90  	// When not calling `ApplyPendingProvisions()`, the submitted provision is
    91  	// automatically pending if market is not in auction.
    92  	provisioned, err := originalEngine.engine.SubmitLiquidityProvision(ctx, party3Provision, party3, idgen)
    93  	require.NoError(t, err)
    94  	require.False(t, provisioned, "this will help testing the pending provisions, so it should not directly be added as provision")
    95  
    96  	originalEngine.engine.RegisterAllocatedFeesPerParty(map[string]*num.Uint{
    97  		"party-1": num.UintFromUint64(1),
    98  		"party-2": num.UintFromUint64(2),
    99  		"party-3": num.UintFromUint64(2),
   100  		"party-4": num.UintFromUint64(3),
   101  	})
   102  
   103  	preStats := originalEngine.engine.LiquidityProviderSLAStats(time.Now())
   104  	require.Len(t, preStats, 2)
   105  
   106  	// Verifying we can salvage the state for each key, and they are a valid
   107  	// Payload.
   108  	engine1Keys := originalEngine.engine.V2StateProvider().Keys()
   109  	stateResults1 := map[string]stateResult{}
   110  	for _, key := range engine1Keys {
   111  		// Salvage the state.
   112  		state, additionalProviders, err := originalEngine.engine.V2StateProvider().GetState(key)
   113  		require.NoError(t, err)
   114  		assert.Nil(t, additionalProviders, "No additional provider should be generated by this engine")
   115  		require.NotNil(t, state)
   116  		require.NotEmpty(t, state)
   117  
   118  		// Deserialize the state to Payload.
   119  		var p snapshotpb.Payload
   120  		require.NoError(t, proto.Unmarshal(state, &p))
   121  
   122  		stateResults1[key] = stateResult{
   123  			state:   state,
   124  			payload: types.PayloadFromProto(&p),
   125  		}
   126  	}
   127  
   128  	// Another engine to test determinism and consistency.
   129  	otherEngine := newTestEngine(t)
   130  	otherEngine.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   131  	otherEngine.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   132  	otherEngine.auctionState.EXPECT().IsOpeningAuction().Return(false).AnyTimes()
   133  	otherEngine.auctionState.EXPECT().InAuction().Return(false).AnyTimes()
   134  
   135  	// Just to verify the keys are deterministic.
   136  	require.Equal(t, engine1Keys, otherEngine.engine.V2StateProvider().Keys())
   137  
   138  	// Reloading previous payload in another engine.
   139  	for _, key := range engine1Keys {
   140  		additionalProviders, err := otherEngine.engine.V2StateProvider().LoadState(ctx, stateResults1[key].payload)
   141  		require.NoError(t, err)
   142  		require.Nil(t, additionalProviders, "No additional provider should be generated by this engine")
   143  	}
   144  
   145  	for _, key1 := range engine1Keys {
   146  		// Salvage the state from other engine to test determinism.
   147  		state2, additionalProviders, err := otherEngine.engine.V2StateProvider().GetState(key1)
   148  		require.NoError(t, err)
   149  		require.Nil(t, additionalProviders, "No additional provider should be generated by this engine")
   150  		require.NotNil(t, state2)
   151  		require.NotEmpty(t, state2)
   152  
   153  		var p snapshotpb.Payload
   154  		require.NoError(t, proto.Unmarshal(state2, &p))
   155  		require.Equalf(t, stateResults1[key1].state, state2, "State for key %q between two engines must match", key1)
   156  	}
   157  
   158  	// Check that the restored state is complete, and lead to the same results.
   159  	now := time.Now()
   160  
   161  	// Check for penalties
   162  	penalties1 := originalEngine.engine.CalculateSLAPenalties(now)
   163  	penalties2 := otherEngine.engine.CalculateSLAPenalties(now)
   164  	assert.Equal(t, penalties1.AllPartiesHaveFullFeePenalty, penalties2.AllPartiesHaveFullFeePenalty)
   165  	assert.Equal(t, len(penalties1.PenaltiesPerParty), len(penalties2.PenaltiesPerParty))
   166  
   167  	for k, p1 := range penalties1.PenaltiesPerParty {
   168  		p2, ok := penalties2.PenaltiesPerParty[k]
   169  		assert.True(t, ok)
   170  		assert.Equal(t, p1.Bond.String(), p2.Bond.String())
   171  		assert.Equal(t, p1.Fee.String(), p2.Fee.String())
   172  	}
   173  
   174  	assert.Equal(t,
   175  		originalEngine.engine.CalculateSuppliedStake(),
   176  		otherEngine.engine.CalculateSuppliedStake(),
   177  	)
   178  
   179  	// Check for fees stats
   180  	feesStats1 := originalEngine.engine.PaidLiquidityFeesStats()
   181  	feesStats2 := otherEngine.engine.PaidLiquidityFeesStats()
   182  	assert.Equal(t,
   183  		feesStats1.TotalFeesPaid.String(),
   184  		feesStats2.TotalFeesPaid.String(),
   185  	)
   186  
   187  	for k, s1 := range feesStats1.FeesPaidPerParty {
   188  		s2, ok := feesStats2.FeesPaidPerParty[k]
   189  		assert.True(t, ok)
   190  		assert.Equal(t, s1.String(), s2.String())
   191  	}
   192  
   193  	postStats := otherEngine.engine.LiquidityProviderSLAStats(time.Now())
   194  	require.Len(t, postStats, 2)
   195  	for i := range preStats {
   196  		assert.Equal(t, preStats[i].NotionalVolumeBuys, postStats[i].NotionalVolumeBuys)
   197  		assert.Equal(t, preStats[i].NotionalVolumeSells, postStats[i].NotionalVolumeSells)
   198  		assert.Equal(t, preStats[i].RequiredLiquidity, postStats[i].RequiredLiquidity)
   199  	}
   200  }
   201  
   202  func TestStopSnapshotTaking(t *testing.T) {
   203  	te := newTestEngine(t)
   204  	keys := te.engine.V2StateProvider().Keys()
   205  
   206  	// signal to kill the engine's snapshots
   207  	te.engine.StopSnapshots()
   208  
   209  	s, _, err := te.engine.V2StateProvider().GetState(keys[0])
   210  	assert.NoError(t, err)
   211  	assert.Nil(t, s)
   212  	assert.True(t, te.engine.V2StateProvider().Stopped())
   213  }
   214  
   215  type stateResult struct {
   216  	state   []byte
   217  	payload *types.Payload
   218  }