code.vegaprotocol.io/vega@v0.79.0/core/execution/engine_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 execution_test
    17  
    18  import (
    19  	"bytes"
    20  	"context"
    21  	"encoding/hex"
    22  	"errors"
    23  	"testing"
    24  	"time"
    25  
    26  	"code.vegaprotocol.io/vega/core/assets"
    27  	bmocks "code.vegaprotocol.io/vega/core/broker/mocks"
    28  	"code.vegaprotocol.io/vega/core/datasource"
    29  	dstypes "code.vegaprotocol.io/vega/core/datasource/common"
    30  	"code.vegaprotocol.io/vega/core/datasource/external/signedoracle"
    31  	"code.vegaprotocol.io/vega/core/datasource/spec"
    32  	"code.vegaprotocol.io/vega/core/execution"
    33  	"code.vegaprotocol.io/vega/core/execution/common"
    34  	"code.vegaprotocol.io/vega/core/execution/common/mocks"
    35  	fmock "code.vegaprotocol.io/vega/core/fee/mocks"
    36  	"code.vegaprotocol.io/vega/core/types"
    37  	vgcontext "code.vegaprotocol.io/vega/libs/context"
    38  	"code.vegaprotocol.io/vega/libs/crypto"
    39  	"code.vegaprotocol.io/vega/libs/num"
    40  	"code.vegaprotocol.io/vega/libs/proto"
    41  	"code.vegaprotocol.io/vega/logging"
    42  	"code.vegaprotocol.io/vega/protos/vega"
    43  	commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1"
    44  	datapb "code.vegaprotocol.io/vega/protos/vega/data/v1"
    45  	snapshot "code.vegaprotocol.io/vega/protos/vega/snapshot/v1"
    46  
    47  	"github.com/golang/mock/gomock"
    48  	"github.com/stretchr/testify/assert"
    49  	"github.com/stretchr/testify/require"
    50  )
    51  
    52  type engineFake struct {
    53  	*execution.Engine
    54  	ctrl       *gomock.Controller
    55  	broker     *bmocks.MockBroker
    56  	timeSvc    *mocks.MockTimeService
    57  	collateral *mocks.MockCollateral
    58  	oracle     *mocks.MockOracleEngine
    59  	statevar   *mocks.MockStateVarEngine
    60  	epoch      *mocks.MockEpochEngine
    61  	asset      *mocks.MockAssets
    62  }
    63  
    64  func getMockedEngine(t *testing.T) *engineFake {
    65  	t.Helper()
    66  	ctrl := gomock.NewController(t)
    67  	log := logging.NewTestLogger()
    68  	execConfig := execution.NewDefaultConfig()
    69  	broker := bmocks.NewMockBroker(ctrl)
    70  	// broker.EXPECT().Send(gomock.Any()).AnyTimes()
    71  	// broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
    72  	timeService := mocks.NewMockTimeService(ctrl)
    73  	// timeService.EXPECT().GetTimeNow().AnyTimes()
    74  
    75  	collateralService := mocks.NewMockCollateral(ctrl)
    76  	// collateralService.EXPECT().AssetExists(gomock.Any()).AnyTimes().Return(true)
    77  	// collateralService.EXPECT().CreateMarketAccounts(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
    78  	oracleService := mocks.NewMockOracleEngine(ctrl)
    79  	// oracleService.EXPECT().Subscribe(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
    80  
    81  	statevar := mocks.NewMockStateVarEngine(ctrl)
    82  	// statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
    83  	// statevar.EXPECT().NewEvent(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
    84  
    85  	epochEngine := mocks.NewMockEpochEngine(ctrl)
    86  	epochEngine.EXPECT().NotifyOnEpoch(gomock.Any(), gomock.Any()).Times(1)
    87  	asset := mocks.NewMockAssets(ctrl)
    88  
    89  	teams := mocks.NewMockTeams(ctrl)
    90  	balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl)
    91  	referralDiscountReward := fmock.NewMockReferralDiscountRewardService(ctrl)
    92  	volumeDiscount := fmock.NewMockVolumeDiscountService(ctrl)
    93  	volumeRebate := fmock.NewMockVolumeRebateService(ctrl)
    94  
    95  	referralDiscountReward.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes()
    96  	referralDiscountReward.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes()
    97  	volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes()
    98  	referralDiscountReward.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("not a referrer")).AnyTimes()
    99  	banking := mocks.NewMockBanking(ctrl)
   100  	parties := mocks.NewMockParties(ctrl)
   101  	delayTarget := mocks.NewMockDelayTransactionsTarget(ctrl)
   102  	delayTarget.EXPECT().MarketDelayRequiredUpdated(gomock.Any(), gomock.Any()).AnyTimes()
   103  	mat := common.NewMarketActivityTracker(log, teams, balanceChecker, broker, collateralService)
   104  	exec := execution.NewEngine(log, execConfig, timeService, collateralService, oracleService, broker, statevar, mat, asset, referralDiscountReward, volumeDiscount, volumeRebate, banking, parties, delayTarget)
   105  	epochEngine.NotifyOnEpoch(mat.OnEpochEvent, mat.OnEpochRestore)
   106  	return &engineFake{
   107  		Engine:     exec,
   108  		ctrl:       ctrl,
   109  		broker:     broker,
   110  		timeSvc:    timeService,
   111  		collateral: collateralService,
   112  		oracle:     oracleService,
   113  		statevar:   statevar,
   114  		epoch:      epochEngine,
   115  		asset:      asset,
   116  	}
   117  }
   118  
   119  func createEngine(t *testing.T) (*execution.Engine, *gomock.Controller) {
   120  	t.Helper()
   121  	ctrl := gomock.NewController(t)
   122  	log := logging.NewTestLogger()
   123  	executionConfig := execution.NewDefaultConfig()
   124  	broker := bmocks.NewMockBroker(ctrl)
   125  	broker.EXPECT().Send(gomock.Any()).AnyTimes()
   126  	broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   127  	timeService := mocks.NewMockTimeService(ctrl)
   128  	timeService.EXPECT().GetTimeNow().AnyTimes()
   129  
   130  	collateralService := mocks.NewMockCollateral(ctrl)
   131  	collateralService.EXPECT().HasGeneralAccount(gomock.Any(), gomock.Any()).Return(true).AnyTimes()
   132  	collateralService.EXPECT().GetPartyMargin(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   133  	collateralService.EXPECT().AssetExists(gomock.Any()).AnyTimes().Return(true)
   134  	collateralService.EXPECT().CreateMarketAccounts(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   135  	collateralService.EXPECT().GetMarketLiquidityFeeAccount(gomock.Any(), gomock.Any()).AnyTimes().Return(&types.Account{Balance: num.UintZero()}, nil)
   136  	collateralService.EXPECT().GetInsurancePoolBalance(gomock.Any(), gomock.Any()).AnyTimes().Return(num.UintZero(), true)
   137  	collateralService.EXPECT().CreateSpotMarketAccounts(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   138  	collateralService.EXPECT().GetAssetQuantum("ETH").AnyTimes().Return(num.DecimalFromInt64(1), nil)
   139  	collateralService.EXPECT().GetAssetQuantum("Ethereum/Ether").AnyTimes().Return(num.DecimalFromInt64(1), nil)
   140  	collateralService.EXPECT().GetOrCreatePartyBondAccount(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&types.Account{Balance: num.UintZero()}, nil)
   141  	collateralService.EXPECT().GetOrCreatePartyLiquidityFeeAccount(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   142  	collateralService.EXPECT().BondSpotUpdate(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&types.LedgerMovement{}, nil)
   143  	oracleService := mocks.NewMockOracleEngine(ctrl)
   144  	oracleService.EXPECT().Subscribe(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(spec.SubscriptionID(0), func(_ context.Context, _ spec.SubscriptionID) {}, nil)
   145  	oracleService.EXPECT().Unsubscribe(gomock.Any(), gomock.Any()).AnyTimes()
   146  
   147  	statevar := mocks.NewMockStateVarEngine(ctrl)
   148  	statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   149  	statevar.EXPECT().NewEvent(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   150  	statevar.EXPECT().UnregisterStateVariable(gomock.Any(), gomock.Any()).AnyTimes()
   151  
   152  	epochEngine := mocks.NewMockEpochEngine(ctrl)
   153  	epochEngine.EXPECT().NotifyOnEpoch(gomock.Any(), gomock.Any()).Times(1)
   154  	asset := mocks.NewMockAssets(ctrl)
   155  	asset.EXPECT().Get(gomock.Any()).AnyTimes().DoAndReturn(func(a string) (*assets.Asset, error) {
   156  		as := NewAssetStub(a, 0)
   157  		return as, nil
   158  	})
   159  	teams := mocks.NewMockTeams(ctrl)
   160  	balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl)
   161  	referralDiscountReward := fmock.NewMockReferralDiscountRewardService(ctrl)
   162  	volumeDiscount := fmock.NewMockVolumeDiscountService(ctrl)
   163  	volumeRebate := fmock.NewMockVolumeRebateService(ctrl)
   164  	referralDiscountReward.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes()
   165  	referralDiscountReward.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes()
   166  	volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes()
   167  	referralDiscountReward.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("not a referrer")).AnyTimes()
   168  	mat := common.NewMarketActivityTracker(log, teams, balanceChecker, broker, collateralService)
   169  	banking := mocks.NewMockBanking(ctrl)
   170  	parties := mocks.NewMockParties(ctrl)
   171  	delayTarget := mocks.NewMockDelayTransactionsTarget(ctrl)
   172  	delayTarget.EXPECT().MarketDelayRequiredUpdated(gomock.Any(), gomock.Any()).AnyTimes()
   173  	e := execution.NewEngine(log, executionConfig, timeService, collateralService, oracleService, broker, statevar, mat, asset, referralDiscountReward, volumeDiscount, volumeRebate, banking, parties, delayTarget)
   174  	epochEngine.NotifyOnEpoch(mat.OnEpochEvent, mat.OnEpochRestore)
   175  	return e, ctrl
   176  }
   177  
   178  func TestEmptyMarkets(t *testing.T) {
   179  	engine, ctrl := createEngine(t)
   180  	assert.NotNil(t, engine)
   181  	defer ctrl.Finish()
   182  
   183  	keys := engine.Keys()
   184  	require.Equal(t, 1, len(keys))
   185  	key := keys[0]
   186  
   187  	// Check that the starting state is empty
   188  	bytes, providers, err := engine.GetState(key)
   189  	assert.NoError(t, err)
   190  	assert.NotEmpty(t, bytes)
   191  	assert.Empty(t, providers)
   192  }
   193  
   194  func getSpotMarketConfig() *types.Market {
   195  	return &types.Market{
   196  		ID: "SpotMarketID", // ID will be generated
   197  		PriceMonitoringSettings: &types.PriceMonitoringSettings{
   198  			Parameters: &types.PriceMonitoringParameters{
   199  				Triggers: []*types.PriceMonitoringTrigger{
   200  					{
   201  						Horizon:          1000,
   202  						HorizonDec:       num.DecimalFromFloat(1000.0),
   203  						Probability:      num.DecimalFromFloat(0.3),
   204  						AuctionExtension: 10000,
   205  					},
   206  				},
   207  			},
   208  		},
   209  		LiquidityMonitoringParameters: &types.LiquidityMonitoringParameters{
   210  			TargetStakeParameters: &types.TargetStakeParameters{
   211  				TimeWindow:    101,
   212  				ScalingFactor: num.DecimalFromFloat(1.0),
   213  			},
   214  		},
   215  		Fees: &types.Fees{
   216  			Factors: &types.FeeFactors{
   217  				MakerFee:          num.DecimalFromFloat(0.1),
   218  				InfrastructureFee: num.DecimalFromFloat(0.1),
   219  				LiquidityFee:      num.DecimalFromFloat(0.1),
   220  			},
   221  			LiquidityFeeSettings: &types.LiquidityFeeSettings{
   222  				Method:      types.LiquidityFeeMethodConstant,
   223  				FeeConstant: num.DecimalFromFloat(0.001),
   224  			},
   225  		},
   226  		LiquiditySLAParams: &types.LiquiditySLAParams{
   227  			PriceRange:                  num.DecimalFromFloat(0.05),
   228  			CommitmentMinTimeFraction:   num.DecimalFromFloat(0.5),
   229  			SlaCompetitionFactor:        num.DecimalFromFloat(0.5),
   230  			PerformanceHysteresisEpochs: 1,
   231  		},
   232  		TradableInstrument: &types.TradableInstrument{
   233  			Instrument: &types.Instrument{
   234  				ID:   "Crypto/BTC/ETH",
   235  				Code: "SPOT:BTC/ETH",
   236  				Name: "BTC/ETH SPOT",
   237  				Metadata: &types.InstrumentMetadata{
   238  					Tags: []string{
   239  						"asset_class:spot/crypto",
   240  						"product:spot",
   241  					},
   242  				},
   243  				Product: &types.InstrumentSpot{
   244  					Spot: &types.Spot{
   245  						BaseAsset:  "BTC",
   246  						QuoteAsset: "ETH",
   247  						Name:       "BTC/ETH",
   248  					},
   249  				},
   250  			},
   251  			RiskModel: &types.TradableInstrumentLogNormalRiskModel{
   252  				LogNormalRiskModel: &types.LogNormalRiskModel{
   253  					RiskAversionParameter: num.DecimalFromFloat(0.01),
   254  					Tau:                   num.DecimalFromFloat(1.0 / 365.25 / 24),
   255  					Params: &types.LogNormalModelParams{
   256  						Mu:    num.DecimalZero(),
   257  						R:     num.DecimalFromFloat(0.016),
   258  						Sigma: num.DecimalFromFloat(0.09),
   259  					},
   260  				},
   261  			},
   262  		},
   263  		State: types.MarketStateActive,
   264  		MarkPriceConfiguration: &types.CompositePriceConfiguration{
   265  			DecayWeight:              num.DecimalZero(),
   266  			DecayPower:               num.DecimalZero(),
   267  			CashAmount:               num.UintZero(),
   268  			SourceWeights:            []num.Decimal{num.DecimalFromFloat(0.1), num.DecimalFromFloat(0.2), num.DecimalFromFloat(0.3), num.DecimalFromFloat(0.4)},
   269  			SourceStalenessTolerance: []time.Duration{0, 0, 0, 0},
   270  			CompositePriceType:       types.CompositePriceTypeByLastTrade,
   271  		},
   272  		TickSize: num.UintOne(),
   273  	}
   274  }
   275  
   276  func getMarketConfig() *types.Market {
   277  	pubKeys := []*dstypes.Signer{
   278  		dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey),
   279  	}
   280  
   281  	return &types.Market{
   282  		ID: "MarketID", // ID will be generated
   283  		PriceMonitoringSettings: &types.PriceMonitoringSettings{
   284  			Parameters: &types.PriceMonitoringParameters{
   285  				Triggers: []*types.PriceMonitoringTrigger{
   286  					{
   287  						Horizon:          1000,
   288  						HorizonDec:       num.DecimalFromFloat(1000.0),
   289  						Probability:      num.DecimalFromFloat(0.3),
   290  						AuctionExtension: 10000,
   291  					},
   292  				},
   293  			},
   294  		},
   295  		LiquidityMonitoringParameters: &types.LiquidityMonitoringParameters{
   296  			TargetStakeParameters: &types.TargetStakeParameters{
   297  				TimeWindow:    101,
   298  				ScalingFactor: num.DecimalFromFloat(1.0),
   299  			},
   300  		},
   301  		Fees: &types.Fees{
   302  			Factors: &types.FeeFactors{
   303  				MakerFee:          num.DecimalFromFloat(0.1),
   304  				InfrastructureFee: num.DecimalFromFloat(0.1),
   305  				LiquidityFee:      num.DecimalFromFloat(0.1),
   306  			},
   307  			LiquidityFeeSettings: &types.LiquidityFeeSettings{
   308  				Method: vega.LiquidityFeeSettings_METHOD_MARGINAL_COST,
   309  			},
   310  		},
   311  		TradableInstrument: &types.TradableInstrument{
   312  			MarginCalculator: &types.MarginCalculator{
   313  				ScalingFactors: &types.ScalingFactors{
   314  					SearchLevel:       num.DecimalFromFloat(1.2),
   315  					InitialMargin:     num.DecimalFromFloat(1.3),
   316  					CollateralRelease: num.DecimalFromFloat(1.4),
   317  				},
   318  			},
   319  			Instrument: &types.Instrument{
   320  				ID:   "Crypto/ETHUSD/Futures/Dec19",
   321  				Code: "FX:ETHUSD/DEC19",
   322  				Name: "December 2019 ETH vs USD future",
   323  				Metadata: &types.InstrumentMetadata{
   324  					Tags: []string{
   325  						"asset_class:fx/crypto",
   326  						"product:futures",
   327  					},
   328  				},
   329  				Product: &types.InstrumentFuture{
   330  					Future: &types.Future{
   331  						SettlementAsset: "Ethereum/Ether",
   332  						DataSourceSpecForSettlementData: &datasource.Spec{
   333  							ID: "1",
   334  							Data: datasource.NewDefinition(
   335  								datasource.ContentTypeOracle,
   336  							).SetOracleConfig(
   337  								&signedoracle.SpecConfiguration{
   338  									Signers: pubKeys,
   339  									Filters: []*dstypes.SpecFilter{
   340  										{
   341  											Key: &dstypes.SpecPropertyKey{
   342  												Name: "prices.ETH.value",
   343  												Type: datapb.PropertyKey_TYPE_INTEGER,
   344  											},
   345  											Conditions: []*dstypes.SpecCondition{},
   346  										},
   347  									},
   348  								},
   349  							),
   350  						},
   351  						DataSourceSpecForTradingTermination: &datasource.Spec{
   352  							ID: "2",
   353  							Data: datasource.NewDefinition(
   354  								datasource.ContentTypeOracle,
   355  							).SetOracleConfig(
   356  								&signedoracle.SpecConfiguration{
   357  									Signers: pubKeys,
   358  									Filters: []*dstypes.SpecFilter{
   359  										{
   360  											Key: &dstypes.SpecPropertyKey{
   361  												Name: "trading.terminated",
   362  												Type: datapb.PropertyKey_TYPE_BOOLEAN,
   363  											},
   364  											Conditions: []*dstypes.SpecCondition{},
   365  										},
   366  									},
   367  								},
   368  							),
   369  						},
   370  						DataSourceSpecBinding: &datasource.SpecBindingForFuture{
   371  							SettlementDataProperty:     "prices.ETH.value",
   372  							TradingTerminationProperty: "trading.terminated",
   373  						},
   374  					},
   375  				},
   376  			},
   377  			RiskModel: &types.TradableInstrumentLogNormalRiskModel{
   378  				LogNormalRiskModel: &types.LogNormalRiskModel{
   379  					RiskAversionParameter: num.DecimalFromFloat(0.01),
   380  					Tau:                   num.DecimalFromFloat(1.0 / 365.25 / 24),
   381  					Params: &types.LogNormalModelParams{
   382  						Mu:    num.DecimalZero(),
   383  						R:     num.DecimalFromFloat(0.016),
   384  						Sigma: num.DecimalFromFloat(0.09),
   385  					},
   386  				},
   387  			},
   388  		},
   389  		LiquiditySLAParams: &types.LiquiditySLAParams{
   390  			PriceRange:                  num.DecimalOne(),
   391  			CommitmentMinTimeFraction:   num.DecimalFromFloat(0.5),
   392  			SlaCompetitionFactor:        num.DecimalOne(),
   393  			PerformanceHysteresisEpochs: 1,
   394  		},
   395  		State: types.MarketStateActive,
   396  		MarkPriceConfiguration: &types.CompositePriceConfiguration{
   397  			DecayWeight:              num.DecimalZero(),
   398  			DecayPower:               num.DecimalZero(),
   399  			CashAmount:               num.UintZero(),
   400  			SourceWeights:            []num.Decimal{num.DecimalFromFloat(0.1), num.DecimalFromFloat(0.2), num.DecimalFromFloat(0.3), num.DecimalFromFloat(0.4)},
   401  			SourceStalenessTolerance: []time.Duration{0, 0, 0, 0},
   402  			CompositePriceType:       types.CompositePriceTypeByLastTrade,
   403  		},
   404  		TickSize: num.UintOne(),
   405  	}
   406  }
   407  
   408  func TestEmptyExecEngineSnapshot(t *testing.T) {
   409  	engine, ctrl := createEngine(t)
   410  	assert.NotNil(t, engine)
   411  	defer ctrl.Finish()
   412  
   413  	keys := engine.Keys()
   414  	require.Equal(t, 1, len(keys))
   415  	key := keys[0]
   416  
   417  	bytes, providers, err := engine.GetState(key)
   418  	require.NoError(t, err)
   419  	require.Empty(t, providers)
   420  	require.NotNil(t, bytes)
   421  }
   422  
   423  func TestValidMarketSnapshot(t *testing.T) {
   424  	ctx := context.Background()
   425  	engine, ctrl := createEngine(t)
   426  	defer ctrl.Finish()
   427  	assert.NotNil(t, engine)
   428  
   429  	marketConfig := getMarketConfig()
   430  	err := engine.SubmitMarket(ctx, marketConfig, "", time.Now())
   431  	assert.NoError(t, err)
   432  
   433  	// submit successor
   434  	marketConfig2 := getMarketConfig()
   435  	marketConfig2.ParentMarketID = marketConfig.ID
   436  	marketConfig2.InsurancePoolFraction = num.DecimalOne()
   437  	err = engine.SubmitMarket(ctx, marketConfig2, "", time.Now())
   438  	assert.NoError(t, err)
   439  
   440  	keys := engine.Keys()
   441  	require.Equal(t, 1, len(keys))
   442  	key := keys[0]
   443  
   444  	// Take the snapshot and hash
   445  	b, providers, err := engine.GetState(key)
   446  	assert.NoError(t, err)
   447  	assert.NotEmpty(t, b)
   448  	assert.Len(t, providers, 7)
   449  
   450  	// Turn the bytes back into a payload and restore to a new engine
   451  	engine2, ctrl := createEngine(t)
   452  
   453  	defer ctrl.Finish()
   454  	assert.NotNil(t, engine2)
   455  	snap := &snapshot.Payload{}
   456  	err = proto.Unmarshal(b, snap)
   457  	assert.NoError(t, err)
   458  
   459  	// check expected successors are in there
   460  	tt, ok := snap.Data.(*snapshot.Payload_ExecutionMarkets)
   461  	require.True(t, ok)
   462  	require.Equal(t, 1, len(tt.ExecutionMarkets.Successors))
   463  	require.Equal(t, marketConfig.ID, tt.ExecutionMarkets.Successors[0].ParentMarket)
   464  	require.Equal(t, 1, len(tt.ExecutionMarkets.Successors[0].SuccessorMarkets))
   465  	require.Equal(t, marketConfig2.ID, tt.ExecutionMarkets.Successors[0].SuccessorMarkets[0])
   466  
   467  	loadStateProviders, err := engine2.LoadState(ctx, types.PayloadFromProto(snap))
   468  	assert.Len(t, loadStateProviders, 14)
   469  	assert.NoError(t, err)
   470  
   471  	providerMap := map[string]map[string]types.StateProvider{}
   472  	for _, p := range loadStateProviders {
   473  		providerMap[p.Namespace().String()] = map[string]types.StateProvider{}
   474  		for _, k := range p.Keys() {
   475  			providerMap[p.Namespace().String()][k] = p
   476  		}
   477  	}
   478  
   479  	// Check the hashes are the same
   480  	state2, _, err := engine2.GetState(key)
   481  	assert.NoError(t, err)
   482  	assert.True(t, bytes.Equal(b, state2))
   483  
   484  	snap = &snapshot.Payload{}
   485  	err = proto.Unmarshal(state2, snap)
   486  	assert.NoError(t, err)
   487  	tt, ok = snap.Data.(*snapshot.Payload_ExecutionMarkets)
   488  	require.True(t, ok)
   489  	require.Equal(t, 1, len(tt.ExecutionMarkets.Successors))
   490  	require.Equal(t, marketConfig.ID, tt.ExecutionMarkets.Successors[0].ParentMarket)
   491  	require.Equal(t, 1, len(tt.ExecutionMarkets.Successors[0].SuccessorMarkets))
   492  	require.Equal(t, marketConfig2.ID, tt.ExecutionMarkets.Successors[0].SuccessorMarkets[0])
   493  
   494  	// now load the providers state
   495  	for _, p := range providers {
   496  		for _, k := range p.Keys() {
   497  			b, _, err := p.GetState(k)
   498  			require.NoError(t, err)
   499  
   500  			snap := &snapshot.Payload{}
   501  			err = proto.Unmarshal(b, snap)
   502  			assert.NoError(t, err)
   503  
   504  			toRestore := providerMap[p.Namespace().String()][k]
   505  			_, err = toRestore.LoadState(ctx, types.PayloadFromProto(snap))
   506  			require.NoError(t, err)
   507  			b2, _, err := toRestore.GetState(k)
   508  			require.NoError(t, err)
   509  			assert.True(t, bytes.Equal(b, b2))
   510  		}
   511  	}
   512  
   513  	m2, ok := engine2.GetMarket(marketConfig2.ID, false)
   514  	require.True(t, ok)
   515  	require.NotEmpty(t, marketConfig2.ParentMarketID, m2.ParentMarketID)
   516  }
   517  
   518  func TestValidSpotMarketSnapshot(t *testing.T) {
   519  	ctx := vgcontext.WithTraceID(context.Background(), hex.EncodeToString([]byte("0deadbeef")))
   520  	engine, ctrl := createEngine(t)
   521  	defer ctrl.Finish()
   522  	assert.NotNil(t, engine)
   523  
   524  	marketConfig := getSpotMarketConfig()
   525  	err := engine.SubmitSpotMarket(ctx, marketConfig, "", time.Now())
   526  	assert.NoError(t, err)
   527  
   528  	marketConfig.State = types.MarketStateActive
   529  	engine.OnTick(ctx, time.Now())
   530  
   531  	err = engine.SubmitLiquidityProvision(ctx, &types.LiquidityProvisionSubmission{
   532  		MarketID:         marketConfig.ID,
   533  		CommitmentAmount: num.NewUint(1000),
   534  		Fee:              num.DecimalFromFloat(0.5),
   535  	}, "zohar", crypto.RandomHash())
   536  	require.NoError(t, err)
   537  
   538  	keys := engine.Keys()
   539  	require.Equal(t, 1, len(keys))
   540  	key := keys[0]
   541  
   542  	// Take the snapshot and hash
   543  	b, providers, err := engine.GetState(key)
   544  	assert.NoError(t, err)
   545  	assert.NotEmpty(t, b)
   546  	assert.Len(t, providers, 4)
   547  
   548  	// Turn the bytes back into a payload and restore to a new engine
   549  	engine2, ctrl := createEngine(t)
   550  
   551  	defer ctrl.Finish()
   552  	assert.NotNil(t, engine2)
   553  	snap := &snapshot.Payload{}
   554  	err = proto.Unmarshal(b, snap)
   555  	assert.NoError(t, err)
   556  
   557  	loadStateProviders, err := engine2.LoadState(ctx, types.PayloadFromProto(snap))
   558  	assert.Len(t, loadStateProviders, 4)
   559  	assert.NoError(t, err)
   560  
   561  	providerMap := map[string]map[string]types.StateProvider{}
   562  	for _, p := range loadStateProviders {
   563  		providerMap[p.Namespace().String()] = map[string]types.StateProvider{}
   564  		for _, k := range p.Keys() {
   565  			providerMap[p.Namespace().String()][k] = p
   566  		}
   567  	}
   568  
   569  	// Check the hashes are the same
   570  	state2, _, err := engine2.GetState(key)
   571  	assert.NoError(t, err)
   572  	assert.True(t, bytes.Equal(b, state2))
   573  
   574  	snap = &snapshot.Payload{}
   575  	err = proto.Unmarshal(state2, snap)
   576  	assert.NoError(t, err)
   577  	_, ok := snap.Data.(*snapshot.Payload_ExecutionMarkets)
   578  	require.True(t, ok)
   579  
   580  	// now load the providers state
   581  	for _, p := range providers {
   582  		for _, k := range p.Keys() {
   583  			b, _, err := p.GetState(k)
   584  			require.NoError(t, err)
   585  
   586  			snap := &snapshot.Payload{}
   587  			err = proto.Unmarshal(b, snap)
   588  			assert.NoError(t, err)
   589  
   590  			toRestore := providerMap[p.Namespace().String()][k]
   591  			_, err = toRestore.LoadState(ctx, types.PayloadFromProto(snap))
   592  			require.NoError(t, err)
   593  			b2, _, err := toRestore.GetState(k)
   594  			require.NoError(t, err)
   595  			assert.True(t, bytes.Equal(b, b2))
   596  		}
   597  	}
   598  	submissionProto := &commandspb.StopOrdersSubmission{
   599  		FallsBelow: &commandspb.StopOrderSetup{
   600  			OrderSubmission: &commandspb.OrderSubmission{
   601  				MarketId:    marketConfig.ID,
   602  				Price:       "100",
   603  				Size:        20,
   604  				Side:        vega.Side_SIDE_BUY,
   605  				TimeInForce: vega.Order_TIME_IN_FORCE_FOK,
   606  				ExpiresAt:   12345,
   607  				Type:        vega.Order_TYPE_LIMIT,
   608  				Reference:   "ref_buy",
   609  			},
   610  		},
   611  		RisesAbove: &commandspb.StopOrderSetup{
   612  			OrderSubmission: &commandspb.OrderSubmission{
   613  				MarketId:    marketConfig.ID,
   614  				Price:       "200",
   615  				Size:        10,
   616  				Side:        vega.Side_SIDE_SELL,
   617  				TimeInForce: vega.Order_TIME_IN_FORCE_GFA,
   618  				ExpiresAt:   54321,
   619  				Type:        vega.Order_TYPE_MARKET,
   620  				Reference:   "ref_sell",
   621  			},
   622  		},
   623  	}
   624  
   625  	stopSubmission, _ := types.NewStopOrderSubmissionFromProto(submissionProto)
   626  	require.NotPanics(t, func() { engine2.SubmitStopOrders(ctx, stopSubmission, "zohar", nil, nil, nil) })
   627  }
   628  
   629  func TestValidSettledMarketSnapshot(t *testing.T) {
   630  	ctx := vgcontext.WithTraceID(context.Background(), hex.EncodeToString([]byte("0deadbeef")))
   631  	engine := getMockedEngine(t)
   632  	engine.collateral.EXPECT().GetAssetQuantum("Ethereum/Ether").AnyTimes().Return(num.DecimalFromInt64(1), nil)
   633  	engine.collateral.EXPECT().AssetExists(gomock.Any()).AnyTimes().Return(true)
   634  	engine.collateral.EXPECT().CreateMarketAccounts(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   635  	engine.collateral.EXPECT().GetMarketLiquidityFeeAccount(gomock.Any(), gomock.Any()).AnyTimes().Return(&types.Account{Balance: num.UintZero()}, nil)
   636  	engine.collateral.EXPECT().GetLiquidityFeesBonusDistributionAccount(gomock.Any(), gomock.Any()).AnyTimes().Return(&types.Account{Balance: num.UintZero()}, nil)
   637  	engine.collateral.EXPECT().GetInsurancePoolBalance(gomock.Any(), gomock.Any()).AnyTimes().Return(num.UintZero(), true)
   638  	engine.collateral.EXPECT().FinalSettlement(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(nil, nil)
   639  	engine.collateral.EXPECT().ClearMarket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), true).AnyTimes().Return(nil, nil)
   640  	engine.collateral.EXPECT().TransferFees(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   641  	engine.timeSvc.EXPECT().GetTimeNow().AnyTimes()
   642  	engine.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   643  	engine.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   644  	// engine.oracle.EXPECT().Subscribe(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   645  	engine.statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   646  	engine.statevar.EXPECT().UnregisterStateVariable(gomock.Any(), gomock.Any()).AnyTimes()
   647  	engine.statevar.EXPECT().NewEvent(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   648  	engine.epoch.EXPECT().NotifyOnEpoch(gomock.Any(), gomock.Any()).AnyTimes()
   649  	engine.asset.EXPECT().Get(gomock.Any()).AnyTimes().DoAndReturn(func(a string) (*assets.Asset, error) {
   650  		as := NewAssetStub(a, 0)
   651  		return as, nil
   652  	})
   653  	// create a market
   654  	marketConfig := getMarketConfig()
   655  	// ensure CP state doesn't get invalidated the moment the market is settled
   656  	engine.OnSuccessorMarketTimeWindowUpdate(ctx, time.Hour)
   657  	// now let's set up the settlement and trading terminated callbacks
   658  	var ttCB, sCB spec.OnMatchedData
   659  	ttData := dstypes.Data{
   660  		Signers: marketConfig.TradableInstrument.Instrument.GetFuture().DataSourceSpecForTradingTermination.Data.GetSigners(),
   661  		Data: map[string]string{
   662  			"trading.terminated": "true",
   663  		},
   664  	}
   665  	sData := dstypes.Data{
   666  		Signers: marketConfig.TradableInstrument.Instrument.GetFuture().DataSourceSpecForSettlementData.Data.GetSigners(),
   667  		Data: map[string]string{
   668  			"prices.ETH.value": "100000",
   669  		},
   670  	}
   671  	engine.oracle.EXPECT().Subscribe(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn(func(_ context.Context, s spec.Spec, cb spec.OnMatchedData) (spec.SubscriptionID, spec.Unsubscriber, error) {
   672  		if ok, _ := s.MatchData(ttData); ok {
   673  			ttCB = cb
   674  		} else if ok, _ := s.MatchData(sData); ok {
   675  			sCB = cb
   676  		}
   677  		return spec.SubscriptionID(0), func(_ context.Context, _ spec.SubscriptionID) {}, nil
   678  	})
   679  	defer engine.ctrl.Finish()
   680  	assert.NotNil(t, engine)
   681  
   682  	err := engine.SubmitMarket(ctx, marketConfig, "", time.Now())
   683  	assert.NoError(t, err)
   684  	// now let's settle the market by:
   685  	// 1. Ensuring the market is in active state
   686  	marketConfig.State = types.MarketStateActive
   687  	engine.OnTick(ctx, time.Now())
   688  	// 2. Using the oracle to set the market to trading terminated, then settling the market
   689  	ttCB(ctx, ttData)
   690  	sCB(ctx, sData)
   691  	require.Equal(t, marketConfig.State, types.MarketStateSettled)
   692  	// ensure the market data returns no trading
   693  	md, err := engine.GetMarketData(marketConfig.ID)
   694  	require.NoError(t, err)
   695  	require.Equal(t, types.MarketTradingModeNoTrading, md.MarketTradingMode)
   696  	engine.OnTick(ctx, time.Now())
   697  
   698  	keys := engine.Keys()
   699  	require.Equal(t, 1, len(keys))
   700  	key := keys[0]
   701  
   702  	// Take the snapshot and hash
   703  	b, providers, err := engine.GetState(key)
   704  	assert.NoError(t, err)
   705  	assert.NotEmpty(t, b)
   706  	// this is now empty, the market is settled, no state providers required
   707  	assert.Len(t, providers, 0)
   708  
   709  	// Turn the bytes back into a payload and restore to a new engine
   710  	engine2, ctrl := createEngine(t)
   711  	engine2.OnSuccessorMarketTimeWindowUpdate(ctx, time.Hour)
   712  
   713  	defer ctrl.Finish()
   714  	assert.NotNil(t, engine2)
   715  	snap := &snapshot.Payload{}
   716  	err = proto.Unmarshal(b, snap)
   717  	assert.NoError(t, err)
   718  	loadStateProviders, err := engine2.LoadState(ctx, types.PayloadFromProto(snap))
   719  	assert.Len(t, loadStateProviders, 0)
   720  	assert.NoError(t, err)
   721  
   722  	providerMap := map[string]map[string]types.StateProvider{}
   723  	for _, p := range loadStateProviders {
   724  		providerMap[p.Namespace().String()] = map[string]types.StateProvider{}
   725  		for _, k := range p.Keys() {
   726  			providerMap[p.Namespace().String()][k] = p
   727  		}
   728  	}
   729  
   730  	// Check the hashes are the same
   731  	state2, _, err := engine2.GetState(key)
   732  	assert.NoError(t, err)
   733  	assert.True(t, bytes.Equal(b, state2))
   734  
   735  	// now load the providers state
   736  	for _, p := range providers {
   737  		for _, k := range p.Keys() {
   738  			b, _, err := p.GetState(k)
   739  			require.NoError(t, err)
   740  
   741  			snap := &snapshot.Payload{}
   742  			err = proto.Unmarshal(b, snap)
   743  			assert.NoError(t, err)
   744  
   745  			toRestore := providerMap[p.Namespace().String()][k]
   746  			_, err = toRestore.LoadState(ctx, types.PayloadFromProto(snap))
   747  			require.NoError(t, err)
   748  			b2, _, err := toRestore.GetState(k)
   749  			require.NoError(t, err)
   750  			assert.True(t, bytes.Equal(b, b2))
   751  		}
   752  	}
   753  	// ensure the market is restored as settled
   754  	_, ok := engine2.GetMarket(marketConfig.ID, true)
   755  	require.True(t, ok)
   756  }
   757  
   758  func TestSuccessorMapSnapshot(t *testing.T) {
   759  	ctx := vgcontext.WithTraceID(context.Background(), hex.EncodeToString([]byte("0deadbeef")))
   760  	engine := getMockedEngine(t)
   761  	engine.collateral.EXPECT().GetAssetQuantum("Ethereum/Ether").AnyTimes().Return(num.DecimalFromInt64(1), nil)
   762  	engine.collateral.EXPECT().AssetExists(gomock.Any()).AnyTimes().Return(true)
   763  	engine.collateral.EXPECT().CreateMarketAccounts(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   764  	engine.collateral.EXPECT().GetMarketLiquidityFeeAccount(gomock.Any(), gomock.Any()).AnyTimes().Return(&types.Account{Balance: num.UintZero()}, nil)
   765  	engine.collateral.EXPECT().GetInsurancePoolBalance(gomock.Any(), gomock.Any()).AnyTimes().Return(num.UintZero(), true)
   766  	engine.collateral.EXPECT().FinalSettlement(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(nil, nil)
   767  	engine.collateral.EXPECT().ClearMarket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(nil, nil)
   768  	engine.timeSvc.EXPECT().GetTimeNow().AnyTimes()
   769  	engine.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   770  	engine.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   771  	// engine.oracle.EXPECT().Subscribe(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   772  	engine.statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   773  	engine.statevar.EXPECT().UnregisterStateVariable(gomock.Any(), gomock.Any()).AnyTimes()
   774  	engine.statevar.EXPECT().NewEvent(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   775  	engine.epoch.EXPECT().NotifyOnEpoch(gomock.Any(), gomock.Any()).AnyTimes()
   776  	engine.asset.EXPECT().Get(gomock.Any()).AnyTimes().DoAndReturn(func(a string) (*assets.Asset, error) {
   777  		as := NewAssetStub(a, 0)
   778  		return as, nil
   779  	})
   780  	// create a market
   781  	marketConfig := getMarketConfig()
   782  	// ensure CP state doesn't get invalidated the moment the market is settled
   783  	engine.OnSuccessorMarketTimeWindowUpdate(ctx, time.Hour)
   784  	// now let's set up the settlement and trading terminated callbacks
   785  	engine.oracle.EXPECT().Subscribe(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn(func(_ context.Context, s spec.Spec, cb spec.OnMatchedData) (spec.SubscriptionID, spec.Unsubscriber, error) {
   786  		return spec.SubscriptionID(0), func(_ context.Context, _ spec.SubscriptionID) {}, nil
   787  	})
   788  	defer engine.ctrl.Finish()
   789  	assert.NotNil(t, engine)
   790  
   791  	err := engine.SubmitMarket(ctx, marketConfig, "", time.Now())
   792  	assert.NoError(t, err)
   793  	// now let's settle the market by:
   794  	// 1. Create successor
   795  	successor := marketConfig.DeepClone()
   796  	successor.ID = "successor-id"
   797  	successor.ParentMarketID = marketConfig.ID
   798  	successor.InsurancePoolFraction = num.DecimalFromFloat(1)
   799  	successor.State = types.MarketStateProposed
   800  	successor.TradingMode = types.MarketTradingModeNoTrading
   801  	// submit the successor market
   802  	engine.SubmitMarket(ctx, successor, "", time.Now())
   803  	engine.OnTick(ctx, time.Now())
   804  	// 2. cancel the parent market (before leaving opening auction)
   805  	engine.RejectMarket(ctx, marketConfig.ID)
   806  	engine.OnTick(ctx, time.Now())
   807  
   808  	// 3. Check the successor map in the snapshot
   809  
   810  	keys := engine.Keys()
   811  	require.Equal(t, 1, len(keys))
   812  	key := keys[0]
   813  
   814  	// Take the snapshot and hash
   815  	b, _, err := engine.GetState(key)
   816  	assert.NoError(t, err)
   817  	assert.NotEmpty(t, b)
   818  
   819  	snap := &snapshot.Payload{}
   820  	err = proto.Unmarshal(b, snap)
   821  	assert.NoError(t, err)
   822  
   823  	// Check the hashes are the same
   824  	execMkts := snap.GetExecutionMarkets()
   825  	require.NotNil(t, execMkts)
   826  	require.Empty(t, execMkts.Successors)
   827  }