code.vegaprotocol.io/vega@v0.79.0/datanode/gateway/graphql/resolvers_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 gql_test 17 18 import ( 19 "context" 20 "errors" 21 "fmt" 22 "testing" 23 "time" 24 25 dstypes "code.vegaprotocol.io/vega/core/datasource/common" 26 "code.vegaprotocol.io/vega/datanode/gateway" 27 gql "code.vegaprotocol.io/vega/datanode/gateway/graphql" 28 "code.vegaprotocol.io/vega/datanode/gateway/graphql/mocks" 29 "code.vegaprotocol.io/vega/logging" 30 v2 "code.vegaprotocol.io/vega/protos/data-node/api/v2" 31 "code.vegaprotocol.io/vega/protos/vega" 32 protoTypes "code.vegaprotocol.io/vega/protos/vega" 33 datav1 "code.vegaprotocol.io/vega/protos/vega/data/v1" 34 35 "github.com/golang/mock/gomock" 36 "github.com/stretchr/testify/assert" 37 "github.com/stretchr/testify/require" 38 "google.golang.org/grpc" 39 ) 40 41 func TestNewResolverRoot_ConstructAndResolve(t *testing.T) { 42 root := buildTestResolverRoot(t) 43 assert.NotNil(t, root) 44 45 partyResolver := root.Party() 46 assert.NotNil(t, partyResolver) 47 48 marketResolver := root.Market() 49 assert.NotNil(t, marketResolver) 50 51 depthResolver := root.MarketDepth() 52 assert.NotNil(t, depthResolver) 53 54 candleResolver := root.Candle() 55 assert.NotNil(t, candleResolver) 56 57 orderResolver := root.Order() 58 assert.NotNil(t, orderResolver) 59 60 tradeResolver := root.Trade() 61 assert.NotNil(t, tradeResolver) 62 63 priceLevelResolver := root.PriceLevel() 64 assert.NotNil(t, priceLevelResolver) 65 66 positionResolver := root.Position() 67 assert.NotNil(t, positionResolver) 68 69 queryResolver := root.Query() 70 assert.NotNil(t, queryResolver) 71 72 subsResolver := root.Subscription() 73 assert.NotNil(t, subsResolver) 74 75 epochResolver := root.Epoch() 76 assert.NotNil(t, epochResolver) 77 78 perpetualResolver := root.Perpetual() 79 assert.NotNil(t, perpetualResolver) 80 81 perpetualProductResolver := root.PerpetualProduct() 82 assert.NotNil(t, perpetualProductResolver) 83 84 volumeDiscountStatsResolver := root.VolumeDiscountStats() 85 assert.NotNil(t, volumeDiscountStatsResolver) 86 } 87 88 func TestNewResolverRoot_QueryResolver(t *testing.T) { 89 root := buildTestResolverRoot(t) 90 91 assert.NotNil(t, root) 92 93 queryResolver := root.Query() 94 assert.NotNil(t, queryResolver) 95 } 96 97 func getTestFutureMarket(termType protoTypes.DataSourceContentType) *protoTypes.Market { 98 pk := dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey) 99 term := &protoTypes.DataSourceSpec{} 100 switch termType { 101 case protoTypes.DataSourceContentTypeOracle: 102 term = &protoTypes.DataSourceSpec{ 103 Data: protoTypes.NewDataSourceDefinition( 104 protoTypes.DataSourceContentTypeOracle, 105 ).SetOracleConfig( 106 &vega.DataSourceDefinitionExternal_Oracle{ 107 Oracle: &protoTypes.DataSourceSpecConfiguration{ 108 Signers: []*datav1.Signer{pk.IntoProto()}, 109 Filters: []*datav1.Filter{ 110 { 111 Key: &datav1.PropertyKey{ 112 Name: "trading.terminated", 113 Type: datav1.PropertyKey_TYPE_BOOLEAN, 114 }, 115 Conditions: []*datav1.Condition{}, 116 }, 117 }, 118 }, 119 }, 120 ), 121 } 122 123 case protoTypes.DataSourceContentTypeEthOracle: 124 term = &protoTypes.DataSourceSpec{ 125 Data: protoTypes.NewDataSourceDefinition( 126 protoTypes.DataSourceContentTypeOracle, 127 ).SetOracleConfig( 128 &vega.DataSourceDefinitionExternal_EthOracle{ 129 EthOracle: &protoTypes.EthCallSpec{ 130 Address: "test-address", 131 Abi: "null", 132 Method: "stake", 133 RequiredConfirmations: uint64(9), 134 Trigger: &protoTypes.EthCallTrigger{ 135 Trigger: &protoTypes.EthCallTrigger_TimeTrigger{ 136 TimeTrigger: &protoTypes.EthTimeTrigger{}, 137 }, 138 }, 139 }, 140 }, 141 ), 142 } 143 144 case protoTypes.DataSourceContentTypeInternalTimeTermination: 145 term = &protoTypes.DataSourceSpec{ 146 Data: protoTypes.NewDataSourceDefinition( 147 protoTypes.DataSourceContentTypeInternalTimeTermination, 148 ).SetTimeTriggerConditionConfig( 149 []*datav1.Condition{ 150 { 151 Operator: datav1.Condition_OPERATOR_GREATER_THAN, 152 Value: "test-value", 153 }, 154 }, 155 ), 156 } 157 } 158 market := getTestMarket() 159 market.TradableInstrument.Instrument.Product = &protoTypes.Instrument_Future{ 160 Future: &protoTypes.Future{ 161 SettlementAsset: "Ethereum/Ether", 162 DataSourceSpecForSettlementData: &protoTypes.DataSourceSpec{ 163 Data: protoTypes.NewDataSourceDefinition( 164 protoTypes.DataSourceContentTypeOracle, 165 ).SetOracleConfig( 166 &vega.DataSourceDefinitionExternal_Oracle{ 167 Oracle: &protoTypes.DataSourceSpecConfiguration{ 168 Signers: []*datav1.Signer{pk.IntoProto()}, 169 Filters: []*datav1.Filter{ 170 { 171 Key: &datav1.PropertyKey{ 172 Name: "prices.ETH.value", 173 Type: datav1.PropertyKey_TYPE_INTEGER, 174 }, 175 Conditions: []*datav1.Condition{}, 176 }, 177 }, 178 }, 179 }, 180 ), 181 }, 182 DataSourceSpecForTradingTermination: term, 183 DataSourceSpecBinding: &protoTypes.DataSourceSpecToFutureBinding{ 184 SettlementDataProperty: "prices.ETH.value", 185 TradingTerminationProperty: "trading.terminated", 186 }, 187 }, 188 } 189 return market 190 } 191 192 func getTestSpotMarket() *protoTypes.Market { 193 mkt := getTestMarket() 194 195 mkt.TradableInstrument.Instrument.Product = &protoTypes.Instrument_Spot{ 196 Spot: &protoTypes.Spot{ 197 BaseAsset: "Ethereum", 198 QuoteAsset: "USD", 199 }, 200 } 201 202 return mkt 203 } 204 205 func getTestPerpetualMarket() *protoTypes.Market { 206 pk := dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey) 207 mkt := getTestMarket() 208 mkt.TradableInstrument.Instrument.Product = &protoTypes.Instrument_Perpetual{ 209 Perpetual: &protoTypes.Perpetual{ 210 SettlementAsset: "Ethereum/Ether", 211 QuoteName: "ETH-230929", 212 MarginFundingFactor: "0.5", 213 InterestRate: "0.012", 214 ClampLowerBound: "0.2", 215 ClampUpperBound: "0.8", 216 DataSourceSpecForSettlementSchedule: &protoTypes.DataSourceSpec{ 217 Id: "test-settlement-schedule", 218 CreatedAt: time.Now().UnixNano(), 219 UpdatedAt: time.Now().UnixNano(), 220 Data: protoTypes.NewDataSourceDefinition( 221 protoTypes.DataSourceContentTypeOracle, 222 ).SetOracleConfig( 223 &vega.DataSourceDefinitionExternal_Oracle{ 224 Oracle: &protoTypes.DataSourceSpecConfiguration{ 225 Signers: []*datav1.Signer{pk.IntoProto()}, 226 Filters: []*datav1.Filter{ 227 { 228 Key: &datav1.PropertyKey{ 229 Name: "prices.ETH.value", 230 Type: datav1.PropertyKey_TYPE_INTEGER, 231 }, 232 Conditions: []*datav1.Condition{}, 233 }, 234 }, 235 }, 236 }, 237 ), 238 Status: protoTypes.DataSourceSpec_STATUS_ACTIVE, 239 }, 240 DataSourceSpecForSettlementData: &protoTypes.DataSourceSpec{ 241 Id: "test-settlement-data", 242 CreatedAt: time.Now().UnixNano(), 243 UpdatedAt: time.Now().UnixNano(), 244 Data: protoTypes.NewDataSourceDefinition( 245 protoTypes.DataSourceContentTypeOracle, 246 ).SetOracleConfig( 247 &vega.DataSourceDefinitionExternal_Oracle{ 248 Oracle: &protoTypes.DataSourceSpecConfiguration{ 249 Signers: []*datav1.Signer{pk.IntoProto()}, 250 Filters: []*datav1.Filter{ 251 { 252 Key: &datav1.PropertyKey{ 253 Name: "prices.ETH.value", 254 Type: datav1.PropertyKey_TYPE_INTEGER, 255 }, 256 Conditions: []*datav1.Condition{}, 257 }, 258 }, 259 }, 260 }, 261 ), 262 Status: protoTypes.DataSourceSpec_STATUS_ACTIVE, 263 }, 264 DataSourceSpecBinding: &protoTypes.DataSourceSpecToPerpetualBinding{ 265 SettlementDataProperty: "prices.ETH.value", 266 SettlementScheduleProperty: "2023-09-29T00:00:00.000000000Z", 267 }, 268 }, 269 } 270 return mkt 271 } 272 273 func getTestMarket() *protoTypes.Market { 274 return &protoTypes.Market{ 275 Id: "BTC/DEC19", 276 TradableInstrument: &protoTypes.TradableInstrument{ 277 Instrument: &protoTypes.Instrument{ 278 Id: "Crypto/BTCUSD/Futures/Dec19", 279 Code: "FX:BTCUSD/DEC19", 280 Name: "December 2019 BTC vs USD future", 281 Metadata: &protoTypes.InstrumentMetadata{ 282 Tags: []string{ 283 "asset_class:fx/crypto", 284 "product:futures", 285 }, 286 }, 287 }, 288 MarginCalculator: &protoTypes.MarginCalculator{ 289 ScalingFactors: &protoTypes.ScalingFactors{ 290 SearchLevel: 1.1, 291 InitialMargin: 1.2, 292 CollateralRelease: 1.4, 293 }, 294 }, 295 RiskModel: &protoTypes.TradableInstrument_LogNormalRiskModel{ 296 LogNormalRiskModel: &protoTypes.LogNormalRiskModel{ 297 RiskAversionParameter: 0.01, 298 Tau: 1.0 / 365.25 / 24, 299 Params: &protoTypes.LogNormalModelParams{ 300 Mu: 0, 301 R: 0.016, 302 Sigma: 0.09, 303 }, 304 }, 305 }, 306 }, 307 LiquidityMonitoringParameters: &protoTypes.LiquidityMonitoringParameters{}, 308 } 309 } 310 311 func getNewProposal() *protoTypes.Proposal { 312 return &protoTypes.Proposal{ 313 Id: "ETH/DEC23", 314 Reference: "TestNewMarket", 315 PartyId: "DEADBEEF01", 316 State: protoTypes.Proposal_STATE_OPEN, 317 Timestamp: time.Now().UnixNano(), 318 Terms: &protoTypes.ProposalTerms{ 319 Change: &protoTypes.ProposalTerms_NewMarket{ 320 NewMarket: &protoTypes.NewMarket{ 321 Changes: &protoTypes.NewMarketConfiguration{ 322 Instrument: &protoTypes.InstrumentConfiguration{}, 323 }, 324 }, 325 }, 326 }, 327 } 328 } 329 330 func getNewFutureMarketProposal() *protoTypes.Proposal { 331 pk := dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey) 332 proposal := getNewProposal() 333 334 proposal.Terms.Change = &protoTypes.ProposalTerms_NewMarket{ 335 NewMarket: &protoTypes.NewMarket{ 336 Changes: &protoTypes.NewMarketConfiguration{ 337 Instrument: &protoTypes.InstrumentConfiguration{ 338 Product: &protoTypes.InstrumentConfiguration_Future{ 339 Future: &protoTypes.FutureProduct{ 340 SettlementAsset: "Ethereum/Ether", 341 QuoteName: "ETH/DEC23", 342 DataSourceSpecForSettlementData: &protoTypes.DataSourceDefinition{ 343 SourceType: &protoTypes.DataSourceDefinition_External{ 344 External: &protoTypes.DataSourceDefinitionExternal{ 345 SourceType: &protoTypes.DataSourceDefinitionExternal_Oracle{ 346 Oracle: &protoTypes.DataSourceSpecConfiguration{ 347 Signers: []*datav1.Signer{pk.IntoProto()}, 348 Filters: []*datav1.Filter{ 349 { 350 Key: &datav1.PropertyKey{ 351 Name: "prices.ETH.value", 352 Type: datav1.PropertyKey_TYPE_INTEGER, 353 }, 354 Conditions: []*datav1.Condition{}, 355 }, 356 }, 357 }, 358 }, 359 }, 360 }, 361 }, 362 DataSourceSpecForTradingTermination: &protoTypes.DataSourceDefinition{ 363 SourceType: &protoTypes.DataSourceDefinition_Internal{ 364 Internal: &protoTypes.DataSourceDefinitionInternal{ 365 SourceType: &protoTypes.DataSourceDefinitionInternal_Time{ 366 Time: &protoTypes.DataSourceSpecConfigurationTime{ 367 Conditions: []*datav1.Condition{ 368 { 369 Operator: datav1.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 370 Value: "2023-09-29T00:00:00.000000000Z", 371 }, 372 }, 373 }, 374 }, 375 }, 376 }, 377 }, 378 DataSourceSpecBinding: &protoTypes.DataSourceSpecToFutureBinding{ 379 SettlementDataProperty: "prices.ETH.value", 380 TradingTerminationProperty: "trading.terminated", 381 }, 382 }, 383 }, 384 }, 385 }, 386 }, 387 } 388 return proposal 389 } 390 391 func getFutureMarketUpdateProposal() *protoTypes.Proposal { 392 pk := dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey) 393 proposal := getNewProposal() 394 proposal.Terms.Change = &protoTypes.ProposalTerms_UpdateMarket{ 395 UpdateMarket: &protoTypes.UpdateMarket{ 396 MarketId: "ETH/DEC23", 397 Changes: &protoTypes.UpdateMarketConfiguration{ 398 Instrument: &protoTypes.UpdateInstrumentConfiguration{ 399 Code: "", 400 Product: &protoTypes.UpdateInstrumentConfiguration_Future{ 401 Future: &protoTypes.UpdateFutureProduct{ 402 QuoteName: "ETH/DEC23", 403 DataSourceSpecForSettlementData: &protoTypes.DataSourceDefinition{ 404 SourceType: &protoTypes.DataSourceDefinition_External{ 405 External: &protoTypes.DataSourceDefinitionExternal{ 406 SourceType: &protoTypes.DataSourceDefinitionExternal_Oracle{ 407 Oracle: &protoTypes.DataSourceSpecConfiguration{ 408 Signers: []*datav1.Signer{pk.IntoProto()}, 409 Filters: []*datav1.Filter{ 410 { 411 Key: &datav1.PropertyKey{ 412 Name: "prices.ETH.value", 413 Type: datav1.PropertyKey_TYPE_INTEGER, 414 }, 415 Conditions: []*datav1.Condition{}, 416 }, 417 }, 418 }, 419 }, 420 }, 421 }, 422 }, 423 DataSourceSpecForTradingTermination: &protoTypes.DataSourceDefinition{ 424 SourceType: &protoTypes.DataSourceDefinition_Internal{ 425 Internal: &protoTypes.DataSourceDefinitionInternal{ 426 SourceType: &protoTypes.DataSourceDefinitionInternal_Time{ 427 Time: &protoTypes.DataSourceSpecConfigurationTime{ 428 Conditions: []*datav1.Condition{ 429 { 430 Operator: datav1.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 431 Value: "2023-09-28T00:00:00.000000000Z", 432 }, 433 }, 434 }, 435 }, 436 }, 437 }, 438 }, 439 DataSourceSpecBinding: &protoTypes.DataSourceSpecToFutureBinding{ 440 SettlementDataProperty: "prices.ETH.value", 441 TradingTerminationProperty: "trading.terminated", 442 }, 443 }, 444 }, 445 }, 446 }, 447 }, 448 } 449 450 return proposal 451 } 452 453 func getNewSpotMarketProposal() *protoTypes.Proposal { 454 proposal := getNewProposal() 455 456 proposal.Terms.Change = &protoTypes.ProposalTerms_NewSpotMarket{ 457 NewSpotMarket: &protoTypes.NewSpotMarket{ 458 Changes: &protoTypes.NewSpotMarketConfiguration{ 459 Instrument: &protoTypes.InstrumentConfiguration{ 460 Product: &protoTypes.InstrumentConfiguration_Spot{ 461 Spot: &protoTypes.SpotProduct{ 462 BaseAsset: "USD", 463 QuoteAsset: "ETH", 464 }, 465 }, 466 }, 467 }, 468 }, 469 } 470 return proposal 471 } 472 473 func getSpotMarketUpdateProposal() *protoTypes.Proposal { 474 proposal := getNewProposal() 475 proposal.Terms.Change = &protoTypes.ProposalTerms_UpdateSpotMarket{ 476 UpdateSpotMarket: &protoTypes.UpdateSpotMarket{ 477 MarketId: "USD/ETH", 478 Changes: &protoTypes.UpdateSpotMarketConfiguration{ 479 Metadata: []string{"ETH", "USD"}, 480 PriceMonitoringParameters: &protoTypes.PriceMonitoringParameters{ 481 Triggers: []*protoTypes.PriceMonitoringTrigger{ 482 { 483 Horizon: 1, 484 Probability: "0.5", 485 AuctionExtension: 0, 486 }, 487 }, 488 }, 489 TargetStakeParameters: &protoTypes.TargetStakeParameters{ 490 TimeWindow: 1, 491 ScalingFactor: 1, 492 }, 493 RiskParameters: &protoTypes.UpdateSpotMarketConfiguration_Simple{ 494 Simple: &protoTypes.SimpleModelParams{ 495 FactorLong: 1, 496 FactorShort: 1, 497 MaxMoveUp: 1, 498 MinMoveDown: 1, 499 ProbabilityOfTrading: 1, 500 }, 501 }, 502 SlaParams: &protoTypes.LiquiditySLAParameters{ 503 PriceRange: "", 504 CommitmentMinTimeFraction: "0.5", 505 PerformanceHysteresisEpochs: 2, 506 SlaCompetitionFactor: "0.75", 507 }, 508 }, 509 }, 510 } 511 return proposal 512 } 513 514 func getNewPerpetualMarketProposal() *protoTypes.Proposal { 515 pk := dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey) 516 proposal := getNewProposal() 517 518 proposal.Terms.Change = &protoTypes.ProposalTerms_NewMarket{ 519 NewMarket: &protoTypes.NewMarket{ 520 Changes: &protoTypes.NewMarketConfiguration{ 521 Instrument: &protoTypes.InstrumentConfiguration{ 522 Product: &protoTypes.InstrumentConfiguration_Perpetual{ 523 Perpetual: &protoTypes.PerpetualProduct{ 524 SettlementAsset: "Ethereum/Ether", 525 QuoteName: "ETH-230929", 526 MarginFundingFactor: "0.5", 527 InterestRate: "0.0125", 528 ClampLowerBound: "0.2", 529 ClampUpperBound: "0.8", 530 DataSourceSpecForSettlementSchedule: &protoTypes.DataSourceDefinition{ 531 SourceType: &protoTypes.DataSourceDefinition_External{ 532 External: &protoTypes.DataSourceDefinitionExternal{ 533 SourceType: &protoTypes.DataSourceDefinitionExternal_Oracle{ 534 Oracle: &protoTypes.DataSourceSpecConfiguration{ 535 Signers: []*datav1.Signer{pk.IntoProto()}, 536 Filters: []*datav1.Filter{ 537 { 538 Key: &datav1.PropertyKey{ 539 Name: "prices.ETH.value", 540 Type: datav1.PropertyKey_TYPE_INTEGER, 541 }, 542 Conditions: []*datav1.Condition{}, 543 }, 544 }, 545 }, 546 }, 547 }, 548 }, 549 }, 550 DataSourceSpecForSettlementData: &protoTypes.DataSourceDefinition{ 551 SourceType: &protoTypes.DataSourceDefinition_Internal{ 552 Internal: &protoTypes.DataSourceDefinitionInternal{ 553 SourceType: &protoTypes.DataSourceDefinitionInternal_Time{ 554 Time: &protoTypes.DataSourceSpecConfigurationTime{ 555 Conditions: []*datav1.Condition{ 556 { 557 Operator: datav1.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 558 Value: "2023-09-29T00:00:00.000000000Z", 559 }, 560 }, 561 }, 562 }, 563 }, 564 }, 565 }, 566 DataSourceSpecBinding: &protoTypes.DataSourceSpecToPerpetualBinding{ 567 SettlementDataProperty: "prices.ETH.value", 568 SettlementScheduleProperty: "2023-09-29T00:00:00.000000000Z", 569 }, 570 }, 571 }, 572 }, 573 }, 574 }, 575 } 576 return proposal 577 } 578 579 func getPerpetualMarketUpdateProposal() *protoTypes.Proposal { 580 pk := dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey) 581 proposal := getNewProposal() 582 583 proposal.Terms.Change = &protoTypes.ProposalTerms_UpdateMarket{ 584 UpdateMarket: &protoTypes.UpdateMarket{ 585 Changes: &protoTypes.UpdateMarketConfiguration{ 586 Instrument: &protoTypes.UpdateInstrumentConfiguration{ 587 Product: &protoTypes.UpdateInstrumentConfiguration_Perpetual{ 588 Perpetual: &protoTypes.UpdatePerpetualProduct{ 589 QuoteName: "ETH-230929", 590 MarginFundingFactor: "0.6", 591 InterestRate: "0.015", 592 ClampLowerBound: "0.1", 593 ClampUpperBound: "0.9", 594 DataSourceSpecForSettlementSchedule: &protoTypes.DataSourceDefinition{ 595 SourceType: &protoTypes.DataSourceDefinition_External{ 596 External: &protoTypes.DataSourceDefinitionExternal{ 597 SourceType: &protoTypes.DataSourceDefinitionExternal_Oracle{ 598 Oracle: &protoTypes.DataSourceSpecConfiguration{ 599 Signers: []*datav1.Signer{pk.IntoProto()}, 600 Filters: []*datav1.Filter{ 601 { 602 Key: &datav1.PropertyKey{ 603 Name: "prices.ETH.value", 604 Type: datav1.PropertyKey_TYPE_INTEGER, 605 }, 606 Conditions: []*datav1.Condition{}, 607 }, 608 }, 609 }, 610 }, 611 }, 612 }, 613 }, 614 DataSourceSpecForSettlementData: &protoTypes.DataSourceDefinition{ 615 SourceType: &protoTypes.DataSourceDefinition_Internal{ 616 Internal: &protoTypes.DataSourceDefinitionInternal{ 617 SourceType: &protoTypes.DataSourceDefinitionInternal_Time{ 618 Time: &protoTypes.DataSourceSpecConfigurationTime{ 619 Conditions: []*datav1.Condition{ 620 { 621 Operator: datav1.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 622 Value: "2023-09-29T00:00:00.000000000Z", 623 }, 624 }, 625 }, 626 }, 627 }, 628 }, 629 }, 630 DataSourceSpecBinding: &protoTypes.DataSourceSpecToPerpetualBinding{ 631 SettlementDataProperty: "prices.ETH.value", 632 SettlementScheduleProperty: "2023-09-29T00:00:00.000000000Z", 633 }, 634 }, 635 }, 636 }, 637 }, 638 }, 639 } 640 641 return proposal 642 } 643 644 func TestNewResolverRoot_Proposals(t *testing.T) { 645 root := buildTestResolverRoot(t) 646 647 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 648 defer cancel() 649 650 proposals := map[string]*protoTypes.Proposal{ 651 "NewFutureMarket": getNewFutureMarketProposal(), 652 "NewSpotMarket": getNewSpotMarketProposal(), 653 "NewPerpetualMarket": getNewPerpetualMarketProposal(), 654 "UpdateFutureMarket": getFutureMarketUpdateProposal(), 655 "UpdateSpotMarket": getSpotMarketUpdateProposal(), 656 "UpdatePerpetualMarket": getPerpetualMarketUpdateProposal(), 657 } 658 659 t.Run("GraphQL should support new futures market proposals", func(t *testing.T) { 660 id := "NewFutureMarket" 661 root.tradingDataClient.EXPECT().GetGovernanceData(gomock.Any(), gomock.Any()).Return( 662 &v2.GetGovernanceDataResponse{ 663 Data: &protoTypes.GovernanceData{ 664 Proposal: proposals[id], 665 }, 666 }, nil, 667 ) 668 669 var ( 670 terms *protoTypes.ProposalTerms 671 newMarket *protoTypes.ProposalTerms_NewMarket 672 asset *protoTypes.Asset 673 product *protoTypes.InstrumentConfiguration_Future 674 err error 675 ) 676 677 pn, err := root.Query().Proposal(ctx, &id, nil) 678 p := pn.(*protoTypes.GovernanceData) 679 680 t.Run("Proposal terms should be for a new market", func(t *testing.T) { 681 terms, err = root.Proposal().Terms(ctx, p) 682 require.NoError(t, err) 683 want := proposals[id].Terms 684 assert.Equal(t, want, terms) 685 assert.IsType(t, &protoTypes.ProposalTerms_NewMarket{}, terms.Change) 686 }) 687 688 t.Run("New market should be for a futures market", func(t *testing.T) { 689 newMarket = terms.Change.(*protoTypes.ProposalTerms_NewMarket) 690 assert.IsType(t, &protoTypes.InstrumentConfiguration_Future{}, newMarket.NewMarket.Changes.Instrument.Product) 691 }) 692 693 t.Run("The product and asset should be a future", func(t *testing.T) { 694 product = newMarket.NewMarket.Changes.Instrument.Product.(*protoTypes.InstrumentConfiguration_Future) 695 assert.IsType(t, &protoTypes.FutureProduct{}, product.Future) 696 }) 697 698 t.Run("The future resolver should retrieve the settlement asset using the data node API", func(t *testing.T) { 699 wantAsset := &protoTypes.Asset{ 700 Id: "TestFuture", 701 Details: &protoTypes.AssetDetails{ 702 Name: "TestFuture", 703 Symbol: "Test", 704 }, 705 Status: protoTypes.Asset_STATUS_PROPOSED, 706 } 707 708 root.tradingDataClient.EXPECT().GetAsset(gomock.Any(), gomock.Any()).Return( 709 &v2.GetAssetResponse{ 710 Asset: wantAsset, 711 }, nil, 712 ).Times(1) 713 714 asset, err = root.FutureProduct().SettlementAsset(ctx, product.Future) 715 assert.Equal(t, wantAsset, asset) 716 }) 717 }) 718 719 t.Run("GraphQL should support new spot market proposals", func(t *testing.T) { 720 id := "NewSpotMarket" 721 root.tradingDataClient.EXPECT().GetGovernanceData(gomock.Any(), gomock.Any()).Return( 722 &v2.GetGovernanceDataResponse{ 723 Data: &protoTypes.GovernanceData{ 724 Proposal: proposals[id], 725 }, 726 }, nil, 727 ) 728 729 var ( 730 terms *protoTypes.ProposalTerms 731 newMarket *protoTypes.ProposalTerms_NewSpotMarket 732 asset *protoTypes.Asset 733 product *protoTypes.InstrumentConfiguration_Spot 734 err error 735 ) 736 737 pn, err := root.Query().Proposal(ctx, &id, nil) 738 p := pn.(*protoTypes.GovernanceData) 739 740 t.Run("Proposal should be for a new spot market", func(t *testing.T) { 741 terms, err = root.Proposal().Terms(ctx, p) 742 require.NoError(t, err) 743 want := proposals[id].Terms 744 assert.Equal(t, want, terms) 745 assert.IsType(t, &protoTypes.ProposalTerms_NewSpotMarket{}, terms.Change) 746 }) 747 748 t.Run("Product should be a spot product", func(t *testing.T) { 749 newMarket = terms.Change.(*protoTypes.ProposalTerms_NewSpotMarket) 750 assert.IsType(t, &protoTypes.InstrumentConfiguration_Spot{}, newMarket.NewSpotMarket.Changes.Instrument.Product) 751 }) 752 753 t.Run("Spot product resolver should retrieve the asset data using the data node API", func(t *testing.T) { 754 wantQuote := &protoTypes.Asset{ 755 Id: "ETH", 756 Details: &protoTypes.AssetDetails{ 757 Name: "Ethereum/Ether", 758 Symbol: "ETH", 759 }, 760 Status: protoTypes.Asset_STATUS_ENABLED, 761 } 762 763 wantBase := &protoTypes.Asset{ 764 Id: "USD", 765 Details: &protoTypes.AssetDetails{ 766 Name: "US Dollar", 767 Symbol: "USD", 768 }, 769 Status: protoTypes.Asset_STATUS_ENABLED, 770 } 771 772 callCount := 0 773 root.tradingDataClient.EXPECT().GetAsset(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, req *v2.GetAssetRequest, _ ...grpc.CallOption) (*v2.GetAssetResponse, error) { 774 defer func() { 775 callCount++ 776 }() 777 778 if callCount%2 == 1 { 779 return &v2.GetAssetResponse{ 780 Asset: wantBase, 781 }, nil 782 } 783 784 return &v2.GetAssetResponse{ 785 Asset: wantQuote, 786 }, nil 787 }).Times(2) 788 789 product = newMarket.NewSpotMarket.Changes.Instrument.Product.(*protoTypes.InstrumentConfiguration_Spot) 790 assert.IsType(t, &protoTypes.SpotProduct{}, product.Spot) 791 asset, err = root.SpotProduct().QuoteAsset(ctx, product.Spot) 792 assert.Equal(t, wantQuote, asset) 793 asset, err = root.SpotProduct().BaseAsset(ctx, product.Spot) 794 assert.Equal(t, wantBase, asset) 795 }) 796 }) 797 798 t.Run("GraphQL should support new perpetual market proposals", func(t *testing.T) { 799 id := "NewPerpetualMarket" 800 root.tradingDataClient.EXPECT().GetGovernanceData(gomock.Any(), gomock.Any()).Return( 801 &v2.GetGovernanceDataResponse{ 802 Data: &protoTypes.GovernanceData{ 803 Proposal: proposals[id], 804 }, 805 }, nil, 806 ) 807 808 var ( 809 terms *protoTypes.ProposalTerms 810 newMarket *protoTypes.ProposalTerms_NewMarket 811 asset *protoTypes.Asset 812 product *protoTypes.InstrumentConfiguration_Perpetual 813 err error 814 ) 815 816 pn, err := root.Query().Proposal(ctx, &id, nil) 817 p := pn.(*protoTypes.GovernanceData) 818 819 t.Run("Proposal terms should be for a new market", func(t *testing.T) { 820 terms, err = root.Proposal().Terms(ctx, p) 821 require.NoError(t, err) 822 want := proposals[id].Terms 823 assert.Equal(t, want, terms) 824 assert.IsType(t, &protoTypes.ProposalTerms_NewMarket{}, terms.Change) 825 }) 826 827 t.Run("New market should be for a perpetual market", func(t *testing.T) { 828 newMarket = terms.Change.(*protoTypes.ProposalTerms_NewMarket) 829 assert.IsType(t, &protoTypes.InstrumentConfiguration_Perpetual{}, newMarket.NewMarket.Changes.Instrument.Product) 830 }) 831 832 t.Run("The product and asset should be a perpetual", func(t *testing.T) { 833 product = newMarket.NewMarket.Changes.Instrument.Product.(*protoTypes.InstrumentConfiguration_Perpetual) 834 assert.IsType(t, &protoTypes.PerpetualProduct{}, product.Perpetual) 835 }) 836 837 t.Run("The perpetual product resolver should retrieve the settlement asset using the data node API", func(t *testing.T) { 838 wantAsset := &protoTypes.Asset{ 839 Id: "TestPerpetual", 840 Details: &protoTypes.AssetDetails{ 841 Name: "TestPerpetual", 842 Symbol: "Test", 843 }, 844 Status: protoTypes.Asset_STATUS_PROPOSED, 845 } 846 847 root.tradingDataClient.EXPECT().GetAsset(gomock.Any(), gomock.Any()).Return( 848 &v2.GetAssetResponse{ 849 Asset: wantAsset, 850 }, nil, 851 ).Times(1) 852 853 asset, err = root.PerpetualProduct().SettlementAsset(ctx, product.Perpetual) 854 assert.Equal(t, wantAsset, asset) 855 }) 856 }) 857 858 t.Run("GraohQL should support update futures market proposals", func(t *testing.T) { 859 id := "UpdateFutureMarket" 860 root.tradingDataClient.EXPECT().GetGovernanceData(gomock.Any(), gomock.Any()).Return( 861 &v2.GetGovernanceDataResponse{ 862 Data: &protoTypes.GovernanceData{ 863 Proposal: proposals[id], 864 }, 865 }, nil, 866 ) 867 868 var ( 869 terms *protoTypes.ProposalTerms 870 newMarket *protoTypes.ProposalTerms_UpdateMarket 871 product *protoTypes.UpdateInstrumentConfiguration_Future 872 err error 873 ) 874 875 pn, err := root.Query().Proposal(ctx, &id, nil) 876 p := pn.(*protoTypes.GovernanceData) 877 878 t.Run("Proposal terms should be to update market", func(t *testing.T) { 879 terms, err = root.Proposal().Terms(ctx, p) 880 require.NoError(t, err) 881 want := proposals[id].Terms 882 assert.Equal(t, want, terms) 883 assert.IsType(t, &protoTypes.ProposalTerms_UpdateMarket{}, terms.Change) 884 }) 885 886 t.Run("Update market should be for a futures market", func(t *testing.T) { 887 newMarket = terms.Change.(*protoTypes.ProposalTerms_UpdateMarket) 888 assert.IsType(t, &protoTypes.UpdateInstrumentConfiguration_Future{}, newMarket.UpdateMarket.Changes.Instrument.Product) 889 }) 890 891 t.Run("The product and asset should be a future", func(t *testing.T) { 892 pk := dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey) 893 product = newMarket.UpdateMarket.Changes.Instrument.Product.(*protoTypes.UpdateInstrumentConfiguration_Future) 894 assert.IsType(t, &protoTypes.UpdateFutureProduct{}, product.Future) 895 want := &protoTypes.UpdateFutureProduct{ 896 QuoteName: "ETH/DEC23", 897 DataSourceSpecForSettlementData: &protoTypes.DataSourceDefinition{ 898 SourceType: &protoTypes.DataSourceDefinition_External{ 899 External: &protoTypes.DataSourceDefinitionExternal{ 900 SourceType: &protoTypes.DataSourceDefinitionExternal_Oracle{ 901 Oracle: &protoTypes.DataSourceSpecConfiguration{ 902 Signers: []*datav1.Signer{pk.IntoProto()}, 903 Filters: []*datav1.Filter{ 904 { 905 Key: &datav1.PropertyKey{ 906 Name: "prices.ETH.value", 907 Type: datav1.PropertyKey_TYPE_INTEGER, 908 }, 909 Conditions: []*datav1.Condition{}, 910 }, 911 }, 912 }, 913 }, 914 }, 915 }, 916 }, 917 DataSourceSpecForTradingTermination: &protoTypes.DataSourceDefinition{ 918 SourceType: &protoTypes.DataSourceDefinition_Internal{ 919 Internal: &protoTypes.DataSourceDefinitionInternal{ 920 SourceType: &protoTypes.DataSourceDefinitionInternal_Time{ 921 Time: &protoTypes.DataSourceSpecConfigurationTime{ 922 Conditions: []*datav1.Condition{ 923 { 924 Operator: datav1.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 925 Value: "2023-09-28T00:00:00.000000000Z", 926 }, 927 }, 928 }, 929 }, 930 }, 931 }, 932 }, 933 DataSourceSpecBinding: &protoTypes.DataSourceSpecToFutureBinding{ 934 SettlementDataProperty: "prices.ETH.value", 935 TradingTerminationProperty: "trading.terminated", 936 }, 937 } 938 assert.Equal(t, want, product.Future) 939 }) 940 }) 941 942 t.Run("GraphQL should support update spot market proposals", func(t *testing.T) { 943 id := "UpdateSpotMarket" 944 root.tradingDataClient.EXPECT().GetGovernanceData(gomock.Any(), gomock.Any()).Return( 945 &v2.GetGovernanceDataResponse{ 946 Data: &protoTypes.GovernanceData{ 947 Proposal: proposals[id], 948 }, 949 }, nil, 950 ) 951 952 var ( 953 terms *protoTypes.ProposalTerms 954 newMarket *protoTypes.ProposalTerms_UpdateSpotMarket 955 err error 956 ) 957 958 pn, err := root.Query().Proposal(ctx, &id, nil) 959 p := pn.(*protoTypes.GovernanceData) 960 961 t.Run("Proposal should be to update a spot market", func(t *testing.T) { 962 terms, err = root.Proposal().Terms(ctx, p) 963 require.NoError(t, err) 964 want := proposals[id].Terms 965 assert.Equal(t, want, terms) 966 assert.IsType(t, &protoTypes.ProposalTerms_UpdateSpotMarket{}, terms.Change) 967 }) 968 969 t.Run("Product should be a spot product", func(t *testing.T) { 970 newMarket = terms.Change.(*protoTypes.ProposalTerms_UpdateSpotMarket) 971 assert.IsType(t, &protoTypes.UpdateSpotMarketConfiguration{}, newMarket.UpdateSpotMarket.Changes) 972 want := &protoTypes.UpdateSpotMarketConfiguration{ 973 Metadata: []string{"ETH", "USD"}, 974 PriceMonitoringParameters: &protoTypes.PriceMonitoringParameters{ 975 Triggers: []*protoTypes.PriceMonitoringTrigger{ 976 { 977 Horizon: 1, 978 Probability: "0.5", 979 AuctionExtension: 0, 980 }, 981 }, 982 }, 983 TargetStakeParameters: &protoTypes.TargetStakeParameters{ 984 TimeWindow: 1, 985 ScalingFactor: 1, 986 }, 987 RiskParameters: &protoTypes.UpdateSpotMarketConfiguration_Simple{ 988 Simple: &protoTypes.SimpleModelParams{ 989 FactorLong: 1, 990 FactorShort: 1, 991 MaxMoveUp: 1, 992 MinMoveDown: 1, 993 ProbabilityOfTrading: 1, 994 }, 995 }, 996 SlaParams: &protoTypes.LiquiditySLAParameters{ 997 PriceRange: "", 998 CommitmentMinTimeFraction: "0.5", 999 PerformanceHysteresisEpochs: 2, 1000 SlaCompetitionFactor: "0.75", 1001 }, 1002 } 1003 assert.Equal(t, want, newMarket.UpdateSpotMarket.Changes) 1004 }) 1005 }) 1006 1007 t.Run("GraphQL should support update perpetual market proposals", func(t *testing.T) { 1008 id := "UpdatePerpetualMarket" 1009 root.tradingDataClient.EXPECT().GetGovernanceData(gomock.Any(), gomock.Any()).Return( 1010 &v2.GetGovernanceDataResponse{ 1011 Data: &protoTypes.GovernanceData{ 1012 Proposal: proposals[id], 1013 }, 1014 }, nil, 1015 ) 1016 1017 var ( 1018 terms *protoTypes.ProposalTerms 1019 newMarket *protoTypes.ProposalTerms_UpdateMarket 1020 product *protoTypes.UpdateInstrumentConfiguration_Perpetual 1021 err error 1022 ) 1023 1024 pn, err := root.Query().Proposal(ctx, &id, nil) 1025 p := pn.(*protoTypes.GovernanceData) 1026 1027 t.Run("Proposal terms should be to update market", func(t *testing.T) { 1028 // Test the proposal resolver to make sure the terms and underlying changes are correct 1029 terms, err = root.Proposal().Terms(ctx, p) 1030 require.NoError(t, err) 1031 want := proposals[id].Terms 1032 assert.Equal(t, want, terms) 1033 assert.IsType(t, &protoTypes.ProposalTerms_UpdateMarket{}, terms.Change) 1034 }) 1035 1036 t.Run("Update market should be for a perpetual market", func(t *testing.T) { 1037 newMarket = terms.Change.(*protoTypes.ProposalTerms_UpdateMarket) 1038 assert.IsType(t, &protoTypes.UpdateInstrumentConfiguration_Perpetual{}, newMarket.UpdateMarket.Changes.Instrument.Product) 1039 }) 1040 1041 t.Run("The product and asset should be a future", func(t *testing.T) { 1042 pk := dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey) 1043 product = newMarket.UpdateMarket.Changes.Instrument.Product.(*protoTypes.UpdateInstrumentConfiguration_Perpetual) 1044 assert.IsType(t, &protoTypes.UpdatePerpetualProduct{}, product.Perpetual) 1045 want := &protoTypes.UpdatePerpetualProduct{ 1046 QuoteName: "ETH-230929", 1047 MarginFundingFactor: "0.6", 1048 InterestRate: "0.015", 1049 ClampLowerBound: "0.1", 1050 ClampUpperBound: "0.9", 1051 DataSourceSpecForSettlementSchedule: &protoTypes.DataSourceDefinition{ 1052 SourceType: &protoTypes.DataSourceDefinition_External{ 1053 External: &protoTypes.DataSourceDefinitionExternal{ 1054 SourceType: &protoTypes.DataSourceDefinitionExternal_Oracle{ 1055 Oracle: &protoTypes.DataSourceSpecConfiguration{ 1056 Signers: []*datav1.Signer{pk.IntoProto()}, 1057 Filters: []*datav1.Filter{ 1058 { 1059 Key: &datav1.PropertyKey{ 1060 Name: "prices.ETH.value", 1061 Type: datav1.PropertyKey_TYPE_INTEGER, 1062 }, 1063 Conditions: []*datav1.Condition{}, 1064 }, 1065 }, 1066 }, 1067 }, 1068 }, 1069 }, 1070 }, 1071 DataSourceSpecForSettlementData: &protoTypes.DataSourceDefinition{ 1072 SourceType: &protoTypes.DataSourceDefinition_Internal{ 1073 Internal: &protoTypes.DataSourceDefinitionInternal{ 1074 SourceType: &protoTypes.DataSourceDefinitionInternal_Time{ 1075 Time: &protoTypes.DataSourceSpecConfigurationTime{ 1076 Conditions: []*datav1.Condition{ 1077 { 1078 Operator: datav1.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 1079 Value: "2023-09-29T00:00:00.000000000Z", 1080 }, 1081 }, 1082 }, 1083 }, 1084 }, 1085 }, 1086 }, 1087 DataSourceSpecBinding: &protoTypes.DataSourceSpecToPerpetualBinding{ 1088 SettlementDataProperty: "prices.ETH.value", 1089 SettlementScheduleProperty: "2023-09-29T00:00:00.000000000Z", 1090 }, 1091 } 1092 assert.Equal(t, want, product.Perpetual) 1093 }) 1094 }) 1095 } 1096 1097 func TestNewResolverRoot_SpotResolver(t *testing.T) { 1098 ctx := context.Background() 1099 root := buildTestResolverRoot(t) 1100 1101 spotMarket := getTestSpotMarket() 1102 root.tradingDataClient.EXPECT().GetMarket(gomock.Any(), gomock.Any()).Return(&v2.GetMarketResponse{Market: spotMarket}, nil) 1103 wantAsset1 := &protoTypes.Asset{ 1104 Id: "Asset1", 1105 Details: nil, 1106 Status: 0, 1107 } 1108 1109 wantAsset2 := &protoTypes.Asset{ 1110 Id: "Asset2", 1111 Details: nil, 1112 Status: 0, 1113 } 1114 call := 0 1115 root.tradingDataClient.EXPECT().GetAsset(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( 1116 func(ctx context.Context, req *v2.GetAssetRequest, opts ...grpc.CallOption) (*v2.GetAssetResponse, error) { 1117 defer func() { call++ }() 1118 if call%2 == 0 { 1119 return &v2.GetAssetResponse{Asset: wantAsset1}, nil 1120 } 1121 1122 return &v2.GetAssetResponse{Asset: wantAsset2}, nil 1123 }, 1124 ).Times(2) 1125 1126 mkt, err := root.tradingDataClient.GetMarket(ctx, &v2.GetMarketRequest{ 1127 MarketId: spotMarket.Id, 1128 }) 1129 require.NoError(t, err) 1130 1131 asset, err := root.Spot().BaseAsset(ctx, mkt.GetMarket().TradableInstrument.Instrument.GetSpot()) 1132 require.NoError(t, err) 1133 assert.Equal(t, wantAsset1, asset) 1134 1135 asset, err = root.Spot().QuoteAsset(ctx, mkt.GetMarket().TradableInstrument.Instrument.GetSpot()) 1136 require.NoError(t, err) 1137 assert.Equal(t, wantAsset2, asset) 1138 } 1139 1140 func TestNewResolverRoot_PerpetualResolver(t *testing.T) { 1141 ctx := context.Background() 1142 root := buildTestResolverRoot(t) 1143 1144 perpsMarket := getTestPerpetualMarket() 1145 want := perpsMarket.TradableInstrument.Instrument.GetPerpetual() 1146 1147 root.tradingDataClient.EXPECT().GetMarket(gomock.Any(), gomock.Any()).Return(&v2.GetMarketResponse{Market: perpsMarket}, nil) 1148 wantAsset := &protoTypes.Asset{ 1149 Id: "Asset1", 1150 } 1151 root.tradingDataClient.EXPECT().GetAsset(gomock.Any(), gomock.Any(), gomock.Any()).Return(&v2.GetAssetResponse{Asset: wantAsset}, nil) 1152 1153 mkt, err := root.tradingDataClient.GetMarket(ctx, &v2.GetMarketRequest{ 1154 MarketId: perpsMarket.Id, 1155 }) 1156 require.NoError(t, err) 1157 perps := mkt.GetMarket().TradableInstrument.Instrument.GetPerpetual() 1158 asset, err := root.Perpetual().SettlementAsset(ctx, perps) 1159 require.NoError(t, err) 1160 assert.Equal(t, wantAsset, asset) 1161 1162 gotSchedule, err := root.Perpetual().DataSourceSpecForSettlementSchedule(ctx, perps) 1163 require.NoError(t, err) 1164 assert.Equal(t, want.DataSourceSpecForSettlementSchedule.Id, gotSchedule.ID) 1165 assert.Equal(t, want.DataSourceSpecForSettlementSchedule.CreatedAt, gotSchedule.CreatedAt) 1166 assert.NotNil(t, gotSchedule.UpdatedAt) 1167 assert.Equal(t, want.DataSourceSpecForSettlementSchedule.UpdatedAt, *gotSchedule.UpdatedAt) 1168 assert.Equal(t, want.DataSourceSpecForSettlementSchedule.Data, gotSchedule.Data) 1169 assert.Equal(t, want.DataSourceSpecForSettlementSchedule.Status.String(), gotSchedule.Status.String()) 1170 1171 gotData, err := root.Perpetual().DataSourceSpecForSettlementData(ctx, perps) 1172 require.NoError(t, err) 1173 assert.Equal(t, want.DataSourceSpecForSettlementData.Id, gotData.ID) 1174 assert.Equal(t, want.DataSourceSpecForSettlementData.CreatedAt, gotData.CreatedAt) 1175 assert.NotNil(t, gotData.UpdatedAt) 1176 assert.Equal(t, want.DataSourceSpecForSettlementData.UpdatedAt, *gotData.UpdatedAt) 1177 assert.Equal(t, want.DataSourceSpecForSettlementData.Data, gotData.Data) 1178 assert.Equal(t, want.DataSourceSpecForSettlementData.Status.String(), gotData.Status.String()) 1179 } 1180 1181 func TestNewResolverRoot_Resolver(t *testing.T) { 1182 root := buildTestResolverRoot(t) 1183 1184 ctx := context.Background() 1185 1186 marketNotExistsErr := errors.New("market does not exist") 1187 markets := map[string]*protoTypes.Market{ 1188 "BTC/DEC19": getTestFutureMarket(protoTypes.DataSourceContentTypeInternalTimeTermination), 1189 "ETH/USD18": nil, 1190 "ETH/USD": getTestSpotMarket(), 1191 "ETH-230929": getTestPerpetualMarket(), 1192 } 1193 1194 root.tradingDataClient.EXPECT().GetAsset(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&v2.GetAssetResponse{Asset: &protoTypes.Asset{}}, nil) 1195 1196 root.tradingDataClient.EXPECT().GetMarket(gomock.Any(), gomock.Any()).Times(len(markets)).DoAndReturn(func(_ context.Context, req *v2.GetMarketRequest, _ ...grpc.CallOption) (*v2.GetMarketResponse, error) { 1197 m, ok := markets[req.MarketId] 1198 assert.True(t, ok) 1199 if m == nil { 1200 return nil, marketNotExistsErr 1201 } 1202 return &v2.GetMarketResponse{Market: m}, nil 1203 }) 1204 1205 name := "BTC/DEC19" 1206 vMarkets, err := root.Query().MarketsConnection(ctx, &name, nil, nil) 1207 assert.Nil(t, err) 1208 assert.NotNil(t, vMarkets) 1209 assert.Len(t, vMarkets.Edges, 1) 1210 1211 name = "ETH/USD18" 1212 vMarkets, err = root.Query().MarketsConnection(ctx, &name, nil, nil) 1213 assert.Error(t, err) 1214 assert.Nil(t, vMarkets) 1215 1216 name = "ETH/USD" 1217 vMarkets, err = root.Query().MarketsConnection(ctx, &name, nil, nil) 1218 assert.Nil(t, err) 1219 assert.NotNil(t, vMarkets) 1220 assert.Len(t, vMarkets.Edges, 1) 1221 1222 name = "ETH-230929" 1223 vMarkets, err = root.Query().MarketsConnection(ctx, &name, nil, nil) 1224 assert.Nil(t, err) 1225 assert.NotNil(t, vMarkets) 1226 assert.Len(t, vMarkets.Edges, 1) 1227 1228 name = "barney" 1229 root.tradingDataClient.EXPECT().ListParties(gomock.Any(), gomock.Any()).Times(1).Return(&v2.ListPartiesResponse{ 1230 Parties: &v2.PartyConnection{ 1231 Edges: []*v2.PartyEdge{ 1232 { 1233 Node: &protoTypes.Party{Id: name}, 1234 Cursor: name, 1235 }, 1236 }, 1237 }, 1238 }, nil) 1239 vParties, err := root.Query().PartiesConnection(ctx, &name, nil) 1240 assert.Nil(t, err) 1241 assert.NotNil(t, vParties) 1242 assert.Len(t, vParties.Edges, 1) 1243 1244 root.tradingDataClient.EXPECT().ListParties(gomock.Any(), gomock.Any()).Times(1).Return(&v2.ListPartiesResponse{Parties: &v2.PartyConnection{ 1245 Edges: nil, 1246 PageInfo: &v2.PageInfo{}, 1247 }}, nil) 1248 vParties, err = root.Query().PartiesConnection(ctx, nil, nil) 1249 assert.NoError(t, err) 1250 assert.NotNil(t, vParties) 1251 assert.Equal(t, len(vParties.Edges), 0) 1252 } 1253 1254 func TestNewResolverRoot_MarketResolver(t *testing.T) { 1255 root := buildTestResolverRoot(t) 1256 1257 ctx := context.Background() 1258 1259 marketID := "BTC/DEC19" 1260 market := &protoTypes.Market{ 1261 Id: marketID, 1262 } 1263 1264 root.tradingDataClient.EXPECT().ListOrders(gomock.Any(), gomock.Any()).Times(1).Return(&v2.ListOrdersResponse{Orders: &v2.OrderConnection{ 1265 Edges: []*v2.OrderEdge{ 1266 { 1267 Node: &protoTypes.Order{ 1268 Id: "order-id-1", 1269 Price: "1000", 1270 CreatedAt: 1, 1271 }, 1272 Cursor: "1", 1273 }, 1274 { 1275 Node: &protoTypes.Order{ 1276 Id: "order-id-2", 1277 Price: "2000", 1278 CreatedAt: 2, 1279 }, 1280 Cursor: "2", 1281 }, 1282 }, 1283 }}, nil) 1284 1285 marketResolver := root.Market() 1286 assert.NotNil(t, marketResolver) 1287 1288 orders, err := marketResolver.OrdersConnection(ctx, market, nil, nil) 1289 assert.NotNil(t, orders) 1290 assert.Nil(t, err) 1291 assert.Len(t, orders.Edges, 2) 1292 } 1293 1294 func TestRewardsResolver(t *testing.T) { 1295 root := buildTestResolverRoot(t) 1296 1297 ctx := context.Background() 1298 partyResolver := root.Party() 1299 root.tradingDataClient.EXPECT().ListRewardSummaries(gomock.Any(), gomock.Any()).Times(1).Return(nil, errors.New("some error")) 1300 assetID := "asset" 1301 r, e := partyResolver.RewardSummaries(ctx, &protoTypes.Party{Id: "some"}, &assetID, nil) 1302 require.Nil(t, r) 1303 require.NotNil(t, e) 1304 } 1305 1306 func TestNewResolverRoot_EpochResolver(t *testing.T) { 1307 root := buildTestResolverRoot(t) 1308 ctx := context.Background() 1309 1310 now := time.Now() 1311 epochResp := &v2.GetEpochResponse{Epoch: &protoTypes.Epoch{ 1312 Seq: 10, 1313 Timestamps: &protoTypes.EpochTimestamps{ 1314 StartTime: now.Unix(), 1315 ExpiryTime: now.Add(time.Hour).Unix(), 1316 EndTime: now.Add(time.Hour * 2).Unix(), 1317 FirstBlock: 100, 1318 LastBlock: 110, 1319 }, 1320 }} 1321 root.tradingDataClient.EXPECT().GetEpoch(gomock.Any(), gomock.Any()).Times(1).Return(epochResp, nil) 1322 1323 epochResolver := root.Epoch() 1324 assert.NotNil(t, epochResolver) 1325 1326 block := uint64(100) 1327 got, err := root.tradingDataClient.GetEpoch(ctx, &v2.GetEpochRequest{Block: &block}) 1328 assert.Nil(t, err) 1329 assert.NotNil(t, got) 1330 assert.Equal(t, got.Epoch, epochResp.Epoch) 1331 1332 id, err := epochResolver.ID(ctx, got.Epoch) 1333 assert.Nil(t, err) 1334 assert.Equal(t, id, fmt.Sprint(got.Epoch.Seq)) 1335 } 1336 1337 //nolint:interfacebloat 1338 type resolverRoot interface { 1339 Query() gql.QueryResolver 1340 Candle() gql.CandleResolver 1341 MarketDepth() gql.MarketDepthResolver 1342 MarketDepthUpdate() gql.MarketDepthUpdateResolver 1343 PriceLevel() gql.PriceLevelResolver 1344 Market() gql.MarketResolver 1345 Order() gql.OrderResolver 1346 Trade() gql.TradeResolver 1347 Position() gql.PositionResolver 1348 Party() gql.PartyResolver 1349 Subscription() gql.SubscriptionResolver 1350 Epoch() gql.EpochResolver 1351 Future() gql.FutureResolver 1352 FutureProduct() gql.FutureProductResolver 1353 Perpetual() gql.PerpetualResolver 1354 PerpetualProduct() gql.PerpetualProductResolver 1355 Proposal() gql.ProposalResolver 1356 Spot() gql.SpotResolver 1357 SpotProduct() gql.SpotProductResolver 1358 VolumeDiscountStats() gql.VolumeDiscountStatsResolver 1359 } 1360 1361 type testResolver struct { 1362 resolverRoot 1363 log *logging.Logger 1364 ctrl *gomock.Controller 1365 coreProxyClient *mocks.MockCoreProxyServiceClient 1366 tradingDataClient *mocks.MockTradingDataServiceClientV2 1367 } 1368 1369 func buildTestResolverRoot(t *testing.T) *testResolver { 1370 t.Helper() 1371 ctrl := gomock.NewController(t) 1372 log := logging.NewTestLogger() 1373 conf := gateway.NewDefaultConfig() 1374 coreProxyClient := mocks.NewMockCoreProxyServiceClient(ctrl) 1375 tradingDataClientV2 := mocks.NewMockTradingDataServiceClientV2(ctrl) 1376 resolver := gql.NewResolverRoot( 1377 log, 1378 conf, 1379 coreProxyClient, 1380 tradingDataClientV2, 1381 ) 1382 t.Cleanup(func() { 1383 _ = log.Sync() 1384 }) 1385 return &testResolver{ 1386 resolverRoot: resolver, 1387 log: log, 1388 ctrl: ctrl, 1389 coreProxyClient: coreProxyClient, 1390 tradingDataClient: tradingDataClientV2, 1391 } 1392 }