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  }