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 }