code.vegaprotocol.io/vega@v0.79.0/core/datasource/spec/spec_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 spec_test
    17  
    18  import (
    19  	"errors"
    20  	"testing"
    21  
    22  	"code.vegaprotocol.io/vega/core/datasource"
    23  	"code.vegaprotocol.io/vega/core/datasource/common"
    24  	dserrors "code.vegaprotocol.io/vega/core/datasource/errors"
    25  	"code.vegaprotocol.io/vega/core/datasource/external/signedoracle"
    26  	dsspec "code.vegaprotocol.io/vega/core/datasource/spec"
    27  	datapb "code.vegaprotocol.io/vega/protos/vega/data/v1"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  	"github.com/stretchr/testify/require"
    31  )
    32  
    33  func TestOracleSpec(t *testing.T) {
    34  	t.Run("Creating builtin oracle without public keys succeeds", testBuiltInOracleSpecCreatingWithoutPubKeysSucceeds)
    35  	t.Run("Creating with filters but without key fails", testOracleSpecCreatingWithFiltersWithoutKeyFails)
    36  	t.Run("Creating with split filters with same type works", testOracleSpecCreatingWithSplitFiltersWithSameTypeFails)
    37  	t.Run("Creating with filters with inconvertible type fails", testOracleSpecCreatingWithFiltersWithInconvertibleTypeFails)
    38  	t.Run("Matching with unauthorized public keys fails", testOracleSpecMatchingUnauthorizedPubKeysFails)
    39  	t.Run("Matching with authorized public keys succeeds", testOracleSpecMatchingAuthorizedPubKeysSucceeds)
    40  	t.Run("Matching with equal properties works", testOracleSpecMatchingEqualPropertiesWorks)
    41  	t.Run("Matching with greater than properties works", testOracleSpecMatchingGreaterThanPropertiesWorks)
    42  	t.Run("Matching with greater than or equal properties works", testOracleSpecMatchingGreaterThanOrEqualPropertiesWorks)
    43  	t.Run("Matching with less than properties succeeds only for non-time based spec", testOracleSpecMatchingLessThanPropertiesSucceedsOnlyForNonTimestamp)
    44  	t.Run("Matching with less than or equal properties succeeds only for non-time based spec", testOracleSpecMatchingLessThanOrEqualPropertiesSucceedsOnlyForNonTimestamp)
    45  	t.Run("Matching presence of present properties succeeds", testOracleSpecMatchingPropertiesPresenceSucceeds)
    46  	t.Run("Matching presence of missing properties fails", testOracleSpecMatchingPropertiesPresenceFails)
    47  	t.Run("Matching with inconvertible type fails", testOracleSpecMatchingWithInconvertibleTypeFails)
    48  	t.Run("Verifying binding of property works", testOracleSpecVerifyingBindingWorks)
    49  	t.Run("Verifying eth oracle key mismatch fails", testEthOracleSpecMismatchedEthKeysFails)
    50  	t.Run("Verifying eth oracle key match works", testEthOracleSpecMatchedEthKeysSucceeds)
    51  }
    52  
    53  func testBuiltInOracleSpecCreatingWithoutPubKeysSucceeds(t *testing.T) {
    54  	// given
    55  	spec := datasource.Spec{
    56  		Data: datasource.NewDefinition(
    57  			datasource.ContentTypeOracle,
    58  		).SetOracleConfig(
    59  			&signedoracle.SpecConfiguration{
    60  				Signers: []*common.Signer{},
    61  				Filters: []*common.SpecFilter{
    62  					{
    63  						Key: &common.SpecPropertyKey{
    64  							Name: "vegaprotocol.builtin.timestamp",
    65  							Type: datapb.PropertyKey_TYPE_TIMESTAMP,
    66  						},
    67  						Conditions: []*common.SpecCondition{},
    68  					},
    69  				},
    70  			},
    71  		),
    72  	}
    73  
    74  	// when
    75  	oracleSpec, err := dsspec.New(spec)
    76  
    77  	// then
    78  	require.NoError(t, err)
    79  	assert.NotNil(t, oracleSpec)
    80  }
    81  
    82  func testOracleSpecCreatingWithFiltersWithoutKeyFails(t *testing.T) {
    83  	// given
    84  	spec := datasource.Spec{
    85  		Data: datasource.NewDefinition(
    86  			datasource.ContentTypeOracle,
    87  		).SetOracleConfig(
    88  			&signedoracle.SpecConfiguration{
    89  				Signers: []*common.Signer{
    90  					common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
    91  				},
    92  				Filters: []*common.SpecFilter{
    93  					{
    94  						Key:        nil,
    95  						Conditions: nil,
    96  					},
    97  				},
    98  			},
    99  		),
   100  	}
   101  
   102  	// when
   103  	oracleSpec, err := dsspec.New(spec)
   104  
   105  	// then
   106  	require.Error(t, err)
   107  	assert.Equal(t, "a property key is required", err.Error())
   108  	assert.Nil(t, oracleSpec)
   109  }
   110  
   111  func testOracleSpecCreatingWithSplitFiltersWithSameTypeFails(t *testing.T) {
   112  	// given
   113  	spec, err := dsspec.New(datasource.Spec{
   114  		Data: datasource.NewDefinition(
   115  			datasource.ContentTypeOracle,
   116  		).SetOracleConfig(
   117  			&signedoracle.SpecConfiguration{
   118  				Signers: []*common.Signer{
   119  					common.CreateSignerFromString("0xDEADBEEF", common.SignerTypePubKey),
   120  					common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
   121  				},
   122  				Filters: []*common.SpecFilter{
   123  					{
   124  						Key: &common.SpecPropertyKey{
   125  							Name: "prices.BTC.value",
   126  							Type: datapb.PropertyKey_TYPE_INTEGER,
   127  						},
   128  						Conditions: []*common.SpecCondition{
   129  							{
   130  								Value:    "42",
   131  								Operator: datapb.Condition_OPERATOR_GREATER_THAN,
   132  							},
   133  						},
   134  					}, {
   135  						Key: &common.SpecPropertyKey{
   136  							Name: "prices.BTC.value",
   137  							Type: datapb.PropertyKey_TYPE_INTEGER,
   138  						},
   139  						Conditions: []*common.SpecCondition{
   140  							{
   141  								Value:    "84",
   142  								Operator: datapb.Condition_OPERATOR_LESS_THAN,
   143  							},
   144  						},
   145  					},
   146  				},
   147  			},
   148  		),
   149  	})
   150  
   151  	assert.ErrorIs(t, dserrors.ErrDataSourceSpecHasMultipleSameKeyNamesInFilterList, err)
   152  	assert.Nil(t, spec)
   153  }
   154  
   155  func testOracleSpecCreatingWithFiltersWithInconvertibleTypeFails(t *testing.T) {
   156  	cases := []struct {
   157  		msg   string
   158  		typ   datapb.PropertyKey_Type
   159  		value string
   160  	}{
   161  		{
   162  			msg:   "not an integer",
   163  			typ:   datapb.PropertyKey_TYPE_INTEGER,
   164  			value: "not an integer",
   165  		}, {
   166  			msg:   "not a boolean",
   167  			typ:   datapb.PropertyKey_TYPE_BOOLEAN,
   168  			value: "42",
   169  		}, {
   170  			msg:   "not a decimal",
   171  			typ:   datapb.PropertyKey_TYPE_DECIMAL,
   172  			value: "not a decimal",
   173  		}, {
   174  			msg:   "not a timestamp",
   175  			typ:   datapb.PropertyKey_TYPE_TIMESTAMP,
   176  			value: "not a timestamp",
   177  		},
   178  	}
   179  
   180  	for _, c := range cases {
   181  		t.Run(c.msg, func(t *testing.T) {
   182  			// given
   183  			originalSpec := datasource.Spec{
   184  				Data: datasource.NewDefinition(
   185  					datasource.ContentTypeOracle,
   186  				).SetOracleConfig(
   187  					&signedoracle.SpecConfiguration{
   188  						Signers: []*common.Signer{
   189  							common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
   190  						},
   191  						Filters: []*common.SpecFilter{
   192  							{
   193  								Key: &common.SpecPropertyKey{
   194  									Name: "prices.BTC.value",
   195  									Type: c.typ,
   196  								},
   197  								Conditions: []*common.SpecCondition{
   198  									{
   199  										Value:    c.value,
   200  										Operator: datapb.Condition_OPERATOR_EQUALS,
   201  									},
   202  								},
   203  							},
   204  						},
   205  					},
   206  				),
   207  			}
   208  
   209  			// when
   210  			spec, err := dsspec.New(originalSpec)
   211  
   212  			// then
   213  			require.Error(t, err)
   214  			assert.Nil(t, spec)
   215  		})
   216  	}
   217  }
   218  
   219  func testEthOracleSpecMismatchedEthKeysFails(t *testing.T) {
   220  	// given
   221  	spec, _ := dsspec.New(datasource.Spec{
   222  		ID: "somekey",
   223  		Data: datasource.NewDefinition(
   224  			datasource.ContentTypeOracle,
   225  		).SetOracleConfig(
   226  			&signedoracle.SpecConfiguration{
   227  				Signers: nil,
   228  				Filters: []*common.SpecFilter{
   229  					{
   230  						Key: &common.SpecPropertyKey{
   231  							Name: "prices.BTC.value",
   232  							Type: datapb.PropertyKey_TYPE_INTEGER,
   233  						},
   234  						Conditions: []*common.SpecCondition{
   235  							{
   236  								Value:    "42",
   237  								Operator: datapb.Condition_OPERATOR_EQUALS,
   238  							},
   239  						},
   240  					},
   241  				},
   242  			},
   243  		),
   244  	})
   245  
   246  	data := common.Data{
   247  		EthKey: "someotherkey",
   248  		Data: map[string]string{
   249  			"prices.BTC.value": "42",
   250  		},
   251  	}
   252  
   253  	// when
   254  	matched, err := spec.MatchData(data)
   255  
   256  	// then
   257  	require.NoError(t, err)
   258  	assert.False(t, matched)
   259  }
   260  
   261  func testEthOracleSpecMatchedEthKeysSucceeds(t *testing.T) {
   262  	// given
   263  	spec, _ := dsspec.New(datasource.Spec{
   264  		ID: "somekey",
   265  		Data: datasource.NewDefinition(
   266  			datasource.ContentTypeOracle,
   267  		).SetOracleConfig(
   268  			&signedoracle.SpecConfiguration{
   269  				Signers: nil,
   270  				Filters: []*common.SpecFilter{
   271  					{
   272  						Key: &common.SpecPropertyKey{
   273  							Name: "prices.BTC.value",
   274  							Type: datapb.PropertyKey_TYPE_INTEGER,
   275  						},
   276  						Conditions: []*common.SpecCondition{
   277  							{
   278  								Value:    "42",
   279  								Operator: datapb.Condition_OPERATOR_EQUALS,
   280  							},
   281  						},
   282  					},
   283  				},
   284  			},
   285  		),
   286  	})
   287  
   288  	data := common.Data{
   289  		EthKey: "somekey",
   290  		Data: map[string]string{
   291  			"prices.BTC.value": "42",
   292  		},
   293  	}
   294  
   295  	// when
   296  	matched, err := spec.MatchData(data)
   297  
   298  	// then
   299  	require.NoError(t, err)
   300  	assert.True(t, matched)
   301  }
   302  
   303  func testOracleSpecMatchingUnauthorizedPubKeysFails(t *testing.T) {
   304  	// given
   305  	spec, _ := dsspec.New(datasource.Spec{
   306  		Data: datasource.NewDefinition(
   307  			datasource.ContentTypeOracle,
   308  		).SetOracleConfig(
   309  			&signedoracle.SpecConfiguration{
   310  				Signers: []*common.Signer{
   311  					common.CreateSignerFromString("0xDEADBEEF", common.SignerTypePubKey),
   312  					common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
   313  				},
   314  				Filters: []*common.SpecFilter{
   315  					{
   316  						Key: &common.SpecPropertyKey{
   317  							Name: "prices.BTC.value",
   318  							Type: datapb.PropertyKey_TYPE_INTEGER,
   319  						},
   320  						Conditions: []*common.SpecCondition{
   321  							{
   322  								Value:    "42",
   323  								Operator: datapb.Condition_OPERATOR_EQUALS,
   324  							},
   325  						},
   326  					},
   327  				},
   328  			},
   329  		),
   330  	})
   331  
   332  	data := common.Data{
   333  		Signers: []*common.Signer{
   334  			common.CreateSignerFromString("0xDEADBEEF", common.SignerTypePubKey),
   335  			common.CreateSignerFromString("0xBADDCAFE", common.SignerTypePubKey),
   336  		},
   337  		Data: map[string]string{
   338  			"prices.BTC.value": "42",
   339  		},
   340  	}
   341  
   342  	// when
   343  	matched, err := spec.MatchData(data)
   344  
   345  	// then
   346  	require.NoError(t, err)
   347  	assert.False(t, matched)
   348  }
   349  
   350  func testOracleSpecMatchingAuthorizedPubKeysSucceeds(t *testing.T) {
   351  	// given
   352  	spec, _ := dsspec.New(datasource.Spec{
   353  		Data: datasource.NewDefinition(
   354  			datasource.ContentTypeOracle,
   355  		).SetOracleConfig(
   356  			&signedoracle.SpecConfiguration{
   357  				Signers: []*common.Signer{
   358  					common.CreateSignerFromString("0xDEADBEEF", common.SignerTypePubKey),
   359  					common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
   360  					common.CreateSignerFromString("0xBADDCAFE", common.SignerTypePubKey),
   361  				},
   362  				Filters: []*common.SpecFilter{
   363  					{
   364  						Key: &common.SpecPropertyKey{
   365  							Name: "prices.BTC.value",
   366  							Type: datapb.PropertyKey_TYPE_INTEGER,
   367  						},
   368  						Conditions: []*common.SpecCondition{
   369  							{
   370  								Value:    "42",
   371  								Operator: datapb.Condition_OPERATOR_EQUALS,
   372  							},
   373  						},
   374  					},
   375  				},
   376  			},
   377  		),
   378  	})
   379  
   380  	data := common.Data{
   381  		Signers: []*common.Signer{
   382  			common.CreateSignerFromString("0xDEADBEEF", common.SignerTypePubKey),
   383  			common.CreateSignerFromString("0xBADDCAFE", common.SignerTypePubKey),
   384  		},
   385  		Data: map[string]string{
   386  			"prices.BTC.value": "42",
   387  		},
   388  	}
   389  
   390  	// when
   391  	matched, err := spec.MatchData(data)
   392  
   393  	// then
   394  	require.NoError(t, err)
   395  	assert.True(t, matched)
   396  }
   397  
   398  func testOracleSpecMatchingEqualPropertiesWorks(t *testing.T) {
   399  	cases := []struct {
   400  		msg       string
   401  		keyType   datapb.PropertyKey_Type
   402  		specValue string
   403  		dataValue string
   404  		matched   bool
   405  	}{
   406  		{
   407  			msg:       "integer values should be equal",
   408  			keyType:   datapb.PropertyKey_TYPE_INTEGER,
   409  			specValue: "42",
   410  			dataValue: "42",
   411  			matched:   true,
   412  		}, {
   413  			msg:       "integer values should not be equal",
   414  			keyType:   datapb.PropertyKey_TYPE_INTEGER,
   415  			specValue: "84",
   416  			dataValue: "42",
   417  			matched:   false,
   418  		}, {
   419  			msg:       "boolean values should be equal",
   420  			keyType:   datapb.PropertyKey_TYPE_BOOLEAN,
   421  			specValue: "true",
   422  			dataValue: "true",
   423  			matched:   true,
   424  		}, {
   425  			msg:       "boolean values should not be equal",
   426  			keyType:   datapb.PropertyKey_TYPE_BOOLEAN,
   427  			specValue: "true",
   428  			dataValue: "false",
   429  			matched:   false,
   430  		}, {
   431  			msg:       "decimal values should be equal",
   432  			keyType:   datapb.PropertyKey_TYPE_DECIMAL,
   433  			specValue: "1.2",
   434  			dataValue: "1.2",
   435  			matched:   true,
   436  		}, {
   437  			msg:       "decimal values should not be equal",
   438  			keyType:   datapb.PropertyKey_TYPE_DECIMAL,
   439  			specValue: "3.4",
   440  			dataValue: "1.2",
   441  			matched:   false,
   442  		}, {
   443  			msg:       "string values should be equal",
   444  			keyType:   datapb.PropertyKey_TYPE_STRING,
   445  			specValue: "hello, world!",
   446  			dataValue: "hello, world!",
   447  			matched:   true,
   448  		}, {
   449  			msg:       "string values should not be equal",
   450  			keyType:   datapb.PropertyKey_TYPE_STRING,
   451  			specValue: "hello, world!",
   452  			dataValue: "hello, galaxy!",
   453  			matched:   false,
   454  		}, {
   455  			msg:       "timestamp values should be equal",
   456  			keyType:   datapb.PropertyKey_TYPE_TIMESTAMP,
   457  			specValue: "1612279145",
   458  			dataValue: "1612279145",
   459  			matched:   true,
   460  		}, {
   461  			msg:       "timestamp values should not be equal",
   462  			keyType:   datapb.PropertyKey_TYPE_TIMESTAMP,
   463  			specValue: "1111111111",
   464  			dataValue: "2222222222",
   465  			matched:   false,
   466  		},
   467  	}
   468  
   469  	for _, c := range cases {
   470  		t.Run(c.msg, func(t *testing.T) {
   471  			// given
   472  			spec, _ := dsspec.New(datasource.Spec{
   473  				Data: datasource.NewDefinition(
   474  					datasource.ContentTypeOracle,
   475  				).SetOracleConfig(
   476  					&signedoracle.SpecConfiguration{
   477  						Signers: []*common.Signer{
   478  							common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
   479  						},
   480  						Filters: []*common.SpecFilter{
   481  							{
   482  								Key: &common.SpecPropertyKey{
   483  									Name: "prices.BTC.value",
   484  									Type: c.keyType,
   485  								},
   486  								Conditions: []*common.SpecCondition{
   487  									{
   488  										Value:    c.specValue,
   489  										Operator: datapb.Condition_OPERATOR_EQUALS,
   490  									},
   491  								},
   492  							}, {
   493  								Key: &common.SpecPropertyKey{
   494  									Name: "prices.ETH.value",
   495  									Type: datapb.PropertyKey_TYPE_INTEGER,
   496  								},
   497  								Conditions: []*common.SpecCondition{
   498  									{
   499  										Value:    "42",
   500  										Operator: datapb.Condition_OPERATOR_EQUALS,
   501  									},
   502  								},
   503  							},
   504  						},
   505  					},
   506  				),
   507  			})
   508  
   509  			data := common.Data{
   510  				Signers: []*common.Signer{
   511  					common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
   512  				},
   513  				Data: map[string]string{
   514  					"prices.BTC.value": c.dataValue,
   515  					"prices.ETH.value": "42",
   516  				},
   517  			}
   518  
   519  			// when
   520  			matched, err := spec.MatchData(data)
   521  
   522  			// then
   523  			require.NoError(t, err)
   524  			assert.Equal(t, c.matched, matched)
   525  		})
   526  	}
   527  }
   528  
   529  func testOracleSpecMatchingGreaterThanPropertiesWorks(t *testing.T) {
   530  	cases := []struct {
   531  		msg       string
   532  		keyType   datapb.PropertyKey_Type
   533  		specValue string
   534  		dataValue string
   535  		matched   bool
   536  	}{
   537  		{
   538  			msg:       "integer: data value should be greater than spec value",
   539  			keyType:   datapb.PropertyKey_TYPE_INTEGER,
   540  			specValue: "42",
   541  			dataValue: "84",
   542  			matched:   true,
   543  		}, {
   544  			msg:       "integer: data value should not be greater than spec value",
   545  			keyType:   datapb.PropertyKey_TYPE_INTEGER,
   546  			specValue: "84",
   547  			dataValue: "42",
   548  			matched:   false,
   549  		}, {
   550  			msg:       "decimal: data value should be greater than spec value",
   551  			keyType:   datapb.PropertyKey_TYPE_DECIMAL,
   552  			specValue: "1.2",
   553  			dataValue: "3.4",
   554  			matched:   true,
   555  		}, {
   556  			msg:       "decimal: data value should not be greater than spec value",
   557  			keyType:   datapb.PropertyKey_TYPE_DECIMAL,
   558  			specValue: "3.4",
   559  			dataValue: "1.2",
   560  			matched:   false,
   561  		}, {
   562  			msg:       "timestamp: data value should be greater than spec value",
   563  			keyType:   datapb.PropertyKey_TYPE_TIMESTAMP,
   564  			specValue: "1111111111",
   565  			dataValue: "2222222222",
   566  			matched:   true,
   567  		}, {
   568  			msg:       "timestamp: data value should not be greater than spec value",
   569  			keyType:   datapb.PropertyKey_TYPE_TIMESTAMP,
   570  			specValue: "2222222222",
   571  			dataValue: "1111111111",
   572  			matched:   false,
   573  		},
   574  	}
   575  
   576  	for _, c := range cases {
   577  		t.Run(c.msg, func(t *testing.T) {
   578  			// given
   579  			spec, _ := dsspec.New(datasource.Spec{
   580  				Data: datasource.NewDefinition(
   581  					datasource.ContentTypeOracle,
   582  				).SetOracleConfig(
   583  					&signedoracle.SpecConfiguration{
   584  						Signers: []*common.Signer{
   585  							common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
   586  						},
   587  						Filters: []*common.SpecFilter{
   588  							{
   589  								Key: &common.SpecPropertyKey{
   590  									Name: "prices.BTC.value",
   591  									Type: c.keyType,
   592  								},
   593  								Conditions: []*common.SpecCondition{
   594  									{
   595  										Value:    c.specValue,
   596  										Operator: datapb.Condition_OPERATOR_GREATER_THAN,
   597  									},
   598  								},
   599  							}, {
   600  								Key: &common.SpecPropertyKey{
   601  									Name: "prices.ETH.value",
   602  									Type: datapb.PropertyKey_TYPE_INTEGER,
   603  								},
   604  								Conditions: []*common.SpecCondition{
   605  									{
   606  										Value:    "42",
   607  										Operator: datapb.Condition_OPERATOR_GREATER_THAN,
   608  									},
   609  								},
   610  							},
   611  						},
   612  					},
   613  				),
   614  			})
   615  
   616  			data := common.Data{
   617  				Signers: []*common.Signer{
   618  					common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
   619  				},
   620  				Data: map[string]string{
   621  					"prices.BTC.value": c.dataValue,
   622  					"prices.ETH.value": "84",
   623  				},
   624  			}
   625  
   626  			// when
   627  			matched, err := spec.MatchData(data)
   628  
   629  			// then
   630  			require.NoError(t, err)
   631  			assert.Equal(t, c.matched, matched)
   632  		})
   633  	}
   634  }
   635  
   636  func testOracleSpecMatchingGreaterThanOrEqualPropertiesWorks(t *testing.T) {
   637  	cases := []struct {
   638  		msg       string
   639  		keyType   datapb.PropertyKey_Type
   640  		specValue string
   641  		dataValue string
   642  		matched   bool
   643  	}{
   644  		{
   645  			msg:       "integer: data value should be equal to spec value",
   646  			keyType:   datapb.PropertyKey_TYPE_INTEGER,
   647  			specValue: "42",
   648  			dataValue: "42",
   649  			matched:   true,
   650  		}, {
   651  			msg:       "integer: data value should be greater than spec value",
   652  			keyType:   datapb.PropertyKey_TYPE_INTEGER,
   653  			specValue: "42",
   654  			dataValue: "84",
   655  			matched:   true,
   656  		}, {
   657  			msg:       "integer: data value should not be greater than spec value",
   658  			keyType:   datapb.PropertyKey_TYPE_INTEGER,
   659  			specValue: "84",
   660  			dataValue: "42",
   661  			matched:   false,
   662  		}, {
   663  			msg:       "decimal: data value should be equal to spec value",
   664  			keyType:   datapb.PropertyKey_TYPE_DECIMAL,
   665  			specValue: "1.2",
   666  			dataValue: "1.2",
   667  			matched:   true,
   668  		}, {
   669  			msg:       "decimal: data value should be greater than spec value",
   670  			keyType:   datapb.PropertyKey_TYPE_DECIMAL,
   671  			specValue: "1.2",
   672  			dataValue: "3.4",
   673  			matched:   true,
   674  		}, {
   675  			msg:       "decimal: data value should not be greater than spec value",
   676  			keyType:   datapb.PropertyKey_TYPE_DECIMAL,
   677  			specValue: "3.4",
   678  			dataValue: "1.2",
   679  			matched:   false,
   680  		}, {
   681  			msg:       "timestamp: data value should be equal to spec value",
   682  			keyType:   datapb.PropertyKey_TYPE_TIMESTAMP,
   683  			specValue: "1111111111",
   684  			dataValue: "1111111111",
   685  			matched:   true,
   686  		}, {
   687  			msg:       "timestamp: data value should be greater than spec value",
   688  			keyType:   datapb.PropertyKey_TYPE_TIMESTAMP,
   689  			specValue: "1111111111",
   690  			dataValue: "2222222222",
   691  			matched:   true,
   692  		}, {
   693  			msg:       "timestamp: data value should not be greater than spec value",
   694  			keyType:   datapb.PropertyKey_TYPE_TIMESTAMP,
   695  			specValue: "2222222222",
   696  			dataValue: "1111111111",
   697  			matched:   false,
   698  		},
   699  	}
   700  
   701  	for _, c := range cases {
   702  		t.Run(c.msg, func(t *testing.T) {
   703  			// given
   704  			spec, _ := dsspec.New(datasource.Spec{
   705  				Data: datasource.NewDefinition(
   706  					datasource.ContentTypeOracle,
   707  				).SetOracleConfig(
   708  					&signedoracle.SpecConfiguration{
   709  						Signers: []*common.Signer{
   710  							common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
   711  						},
   712  						Filters: []*common.SpecFilter{
   713  							{
   714  								Key: &common.SpecPropertyKey{
   715  									Name: "prices.BTC.value",
   716  									Type: c.keyType,
   717  								},
   718  								Conditions: []*common.SpecCondition{
   719  									{
   720  										Value:    c.specValue,
   721  										Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL,
   722  									},
   723  								},
   724  							}, {
   725  								Key: &common.SpecPropertyKey{
   726  									Name: "prices.ETH.value",
   727  									Type: datapb.PropertyKey_TYPE_INTEGER,
   728  								},
   729  								Conditions: []*common.SpecCondition{
   730  									{
   731  										Value:    "42",
   732  										Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL,
   733  									},
   734  								},
   735  							},
   736  						},
   737  					},
   738  				),
   739  			})
   740  
   741  			data := common.Data{
   742  				Signers: []*common.Signer{
   743  					common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
   744  				},
   745  				Data: map[string]string{
   746  					"prices.BTC.value": c.dataValue,
   747  					"prices.ETH.value": "42",
   748  				},
   749  			}
   750  
   751  			// when
   752  			matched, err := spec.MatchData(data)
   753  
   754  			// then
   755  			require.NoError(t, err)
   756  			assert.Equal(t, c.matched, matched)
   757  		})
   758  	}
   759  }
   760  
   761  func testOracleSpecMatchingLessThanPropertiesSucceedsOnlyForNonTimestamp(t *testing.T) {
   762  	cases := []struct {
   763  		msg       string
   764  		keyType   datapb.PropertyKey_Type
   765  		specValue string
   766  		dataValue string
   767  		matched   bool
   768  	}{
   769  		{
   770  			msg:       "integer: data value should be less than spec value",
   771  			keyType:   datapb.PropertyKey_TYPE_INTEGER,
   772  			specValue: "84",
   773  			dataValue: "42",
   774  			matched:   true,
   775  		}, {
   776  			msg:       "integer: data value should not be less than spec value",
   777  			keyType:   datapb.PropertyKey_TYPE_INTEGER,
   778  			specValue: "42",
   779  			dataValue: "84",
   780  			matched:   false,
   781  		}, {
   782  			msg:       "decimal: data value should be less than spec value",
   783  			keyType:   datapb.PropertyKey_TYPE_DECIMAL,
   784  			specValue: "3.4",
   785  			dataValue: "1.2",
   786  			matched:   true,
   787  		}, {
   788  			msg:       "decimal: data value should not be less than spec value",
   789  			keyType:   datapb.PropertyKey_TYPE_DECIMAL,
   790  			specValue: "1.2",
   791  			dataValue: "3.4",
   792  			matched:   false,
   793  		}, {
   794  			msg:       "timestamp: data value should be less than spec value",
   795  			keyType:   datapb.PropertyKey_TYPE_TIMESTAMP,
   796  			specValue: "2222222222",
   797  			dataValue: "1111111111",
   798  			matched:   true,
   799  		}, {
   800  			msg:       "timestamp: data value should not be less than spec value",
   801  			keyType:   datapb.PropertyKey_TYPE_TIMESTAMP,
   802  			specValue: "1111111111",
   803  			dataValue: "2222222222",
   804  			matched:   false,
   805  		},
   806  	}
   807  
   808  	for _, c := range cases {
   809  		t.Run(c.msg, func(t *testing.T) {
   810  			// given
   811  			spec, err := dsspec.New(datasource.Spec{
   812  				Data: datasource.NewDefinition(
   813  					datasource.ContentTypeOracle,
   814  				).SetOracleConfig(
   815  					&signedoracle.SpecConfiguration{
   816  						Signers: []*common.Signer{
   817  							common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
   818  						},
   819  						Filters: []*common.SpecFilter{
   820  							{
   821  								Key: &common.SpecPropertyKey{
   822  									Name: "prices.BTC.value",
   823  									Type: c.keyType,
   824  								},
   825  								Conditions: []*common.SpecCondition{
   826  									{
   827  										Value:    c.specValue,
   828  										Operator: datapb.Condition_OPERATOR_LESS_THAN,
   829  									},
   830  								},
   831  							}, {
   832  								Key: &common.SpecPropertyKey{
   833  									Name: "prices.ETH.value",
   834  									Type: datapb.PropertyKey_TYPE_INTEGER,
   835  								},
   836  								Conditions: []*common.SpecCondition{
   837  									{
   838  										Value:    "42",
   839  										Operator: datapb.Condition_OPERATOR_LESS_THAN,
   840  									},
   841  								},
   842  							},
   843  						},
   844  					},
   845  				),
   846  			})
   847  
   848  			if c.keyType == datapb.PropertyKey_TYPE_TIMESTAMP {
   849  				assert.Error(t, err)
   850  				assert.EqualError(t, err, dserrors.ErrDataSourceSpecHasInvalidTimeCondition.Error())
   851  				assert.Nil(t, spec)
   852  			} else {
   853  				data := common.Data{
   854  					Signers: []*common.Signer{
   855  						common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
   856  					},
   857  					Data: map[string]string{
   858  						"prices.BTC.value": c.dataValue,
   859  						"prices.ETH.value": "21",
   860  					},
   861  				}
   862  
   863  				// when
   864  				matched, err := spec.MatchData(data)
   865  
   866  				// then
   867  				require.NoError(t, err)
   868  				assert.Equal(t, c.matched, matched)
   869  			}
   870  		})
   871  	}
   872  }
   873  
   874  func testOracleSpecMatchingLessThanOrEqualPropertiesSucceedsOnlyForNonTimestamp(t *testing.T) {
   875  	cases := []struct {
   876  		msg       string
   877  		keyType   datapb.PropertyKey_Type
   878  		specValue string
   879  		dataValue string
   880  		matched   bool
   881  	}{
   882  		{
   883  			msg:       "integer: data value should be equal to spec value",
   884  			keyType:   datapb.PropertyKey_TYPE_INTEGER,
   885  			specValue: "42",
   886  			dataValue: "42",
   887  			matched:   true,
   888  		}, {
   889  			msg:       "integer: data value should be less than spec value",
   890  			keyType:   datapb.PropertyKey_TYPE_INTEGER,
   891  			specValue: "84",
   892  			dataValue: "42",
   893  			matched:   true,
   894  		}, {
   895  			msg:       "integer: data value should not be less than spec value",
   896  			keyType:   datapb.PropertyKey_TYPE_INTEGER,
   897  			specValue: "42",
   898  			dataValue: "84",
   899  			matched:   false,
   900  		}, {
   901  			msg:       "decimal: data value should be equal to spec value",
   902  			keyType:   datapb.PropertyKey_TYPE_DECIMAL,
   903  			specValue: "1.2",
   904  			dataValue: "1.2",
   905  			matched:   true,
   906  		}, {
   907  			msg:       "decimal: data value should be less than spec value",
   908  			keyType:   datapb.PropertyKey_TYPE_DECIMAL,
   909  			specValue: "3.4",
   910  			dataValue: "1.2",
   911  			matched:   true,
   912  		}, {
   913  			msg:       "decimal: data value should not be less than spec value",
   914  			keyType:   datapb.PropertyKey_TYPE_DECIMAL,
   915  			specValue: "1.2",
   916  			dataValue: "3.4",
   917  			matched:   false,
   918  		}, {
   919  			msg:       "timestamp: data value should be equal to spec value",
   920  			keyType:   datapb.PropertyKey_TYPE_TIMESTAMP,
   921  			specValue: "1111111111",
   922  			dataValue: "1111111111",
   923  			matched:   true,
   924  		}, {
   925  			msg:       "timestamp: data value should be less than spec value",
   926  			keyType:   datapb.PropertyKey_TYPE_TIMESTAMP,
   927  			specValue: "2222222222",
   928  			dataValue: "1111111111",
   929  			matched:   true,
   930  		}, {
   931  			msg:       "timestamp: data value should not be less than spec value",
   932  			keyType:   datapb.PropertyKey_TYPE_TIMESTAMP,
   933  			specValue: "1111111111",
   934  			dataValue: "2222222222",
   935  			matched:   false,
   936  		},
   937  	}
   938  
   939  	for _, c := range cases {
   940  		t.Run(c.msg, func(t *testing.T) {
   941  			// given
   942  			spec, err := dsspec.New(datasource.Spec{
   943  				Data: datasource.NewDefinition(
   944  					datasource.ContentTypeOracle,
   945  				).SetOracleConfig(
   946  					&signedoracle.SpecConfiguration{
   947  						Signers: []*common.Signer{
   948  							common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
   949  						},
   950  						Filters: []*common.SpecFilter{
   951  							{
   952  								Key: &common.SpecPropertyKey{
   953  									Name: "prices.BTC.value",
   954  									Type: c.keyType,
   955  								},
   956  								Conditions: []*common.SpecCondition{
   957  									{
   958  										Value:    c.specValue,
   959  										Operator: datapb.Condition_OPERATOR_LESS_THAN_OR_EQUAL,
   960  									},
   961  								},
   962  							}, {
   963  								Key: &common.SpecPropertyKey{
   964  									Name: "prices.ETH.value",
   965  									Type: datapb.PropertyKey_TYPE_INTEGER,
   966  								},
   967  								Conditions: []*common.SpecCondition{
   968  									{
   969  										Value:    "42",
   970  										Operator: datapb.Condition_OPERATOR_LESS_THAN_OR_EQUAL,
   971  									},
   972  								},
   973  							},
   974  						},
   975  					},
   976  				),
   977  			})
   978  
   979  			if c.keyType == datapb.PropertyKey_TYPE_TIMESTAMP {
   980  				assert.Error(t, err)
   981  				assert.EqualError(t, err, dserrors.ErrDataSourceSpecHasInvalidTimeCondition.Error())
   982  				assert.Nil(t, spec)
   983  			} else {
   984  				data := common.Data{
   985  					Signers: []*common.Signer{
   986  						common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
   987  					},
   988  					Data: map[string]string{
   989  						"prices.BTC.value": c.dataValue,
   990  						"prices.ETH.value": "42",
   991  					},
   992  				}
   993  
   994  				// when
   995  				matched, err := spec.MatchData(data)
   996  
   997  				// then
   998  				require.NoError(t, err)
   999  				assert.Equal(t, c.matched, matched)
  1000  			}
  1001  		})
  1002  	}
  1003  }
  1004  
  1005  func testOracleSpecMatchingPropertiesPresenceSucceeds(t *testing.T) {
  1006  	cases := []struct {
  1007  		msg     string
  1008  		keyType datapb.PropertyKey_Type
  1009  	}{
  1010  		{
  1011  			msg:     "integer values is present",
  1012  			keyType: datapb.PropertyKey_TYPE_INTEGER,
  1013  		}, {
  1014  			msg:     "boolean values is present",
  1015  			keyType: datapb.PropertyKey_TYPE_BOOLEAN,
  1016  		}, {
  1017  			msg:     "decimal values is present",
  1018  			keyType: datapb.PropertyKey_TYPE_DECIMAL,
  1019  		}, {
  1020  			msg:     "string values is present",
  1021  			keyType: datapb.PropertyKey_TYPE_STRING,
  1022  		}, {
  1023  			msg:     "timestamp values is present",
  1024  			keyType: datapb.PropertyKey_TYPE_TIMESTAMP,
  1025  		},
  1026  	}
  1027  
  1028  	for _, c := range cases {
  1029  		t.Run(c.msg, func(t *testing.T) {
  1030  			// given
  1031  			spec, _ := dsspec.New(datasource.Spec{
  1032  				Data: datasource.NewDefinition(
  1033  					datasource.ContentTypeOracle,
  1034  				).SetOracleConfig(
  1035  					&signedoracle.SpecConfiguration{
  1036  						Signers: []*common.Signer{
  1037  							common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
  1038  						},
  1039  						Filters: []*common.SpecFilter{
  1040  							{
  1041  								Key: &common.SpecPropertyKey{
  1042  									Name: "prices.BTC.value",
  1043  									Type: c.keyType,
  1044  								},
  1045  								Conditions: []*common.SpecCondition{},
  1046  							},
  1047  							{
  1048  								Key: &common.SpecPropertyKey{
  1049  									Name: "prices.ETH.value",
  1050  									Type: datapb.PropertyKey_TYPE_INTEGER,
  1051  								},
  1052  								Conditions: []*common.SpecCondition{},
  1053  							},
  1054  						},
  1055  					},
  1056  				),
  1057  			})
  1058  
  1059  			data := common.Data{
  1060  				Signers: []*common.Signer{
  1061  					common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
  1062  				},
  1063  				Data: map[string]string{
  1064  					"prices.BTC.value": "42",
  1065  					"prices.ETH.value": "42",
  1066  				},
  1067  			}
  1068  
  1069  			// when
  1070  			matched, err := spec.MatchData(data)
  1071  
  1072  			// then
  1073  			require.NoError(t, err)
  1074  			assert.True(t, matched)
  1075  		})
  1076  	}
  1077  }
  1078  
  1079  func testOracleSpecMatchingPropertiesPresenceFails(t *testing.T) {
  1080  	cases := []struct {
  1081  		msg     string
  1082  		keyType datapb.PropertyKey_Type
  1083  	}{
  1084  		{
  1085  			msg:     "integer values is absent",
  1086  			keyType: datapb.PropertyKey_TYPE_INTEGER,
  1087  		}, {
  1088  			msg:     "boolean values is absent",
  1089  			keyType: datapb.PropertyKey_TYPE_BOOLEAN,
  1090  		}, {
  1091  			msg:     "decimal values is absent",
  1092  			keyType: datapb.PropertyKey_TYPE_DECIMAL,
  1093  		}, {
  1094  			msg:     "string values is absent",
  1095  			keyType: datapb.PropertyKey_TYPE_STRING,
  1096  		}, {
  1097  			msg:     "timestamp values is absent",
  1098  			keyType: datapb.PropertyKey_TYPE_TIMESTAMP,
  1099  		},
  1100  	}
  1101  
  1102  	for _, c := range cases {
  1103  		t.Run(c.msg, func(t *testing.T) {
  1104  			// given
  1105  			spec, _ := dsspec.New(datasource.Spec{
  1106  				Data: datasource.NewDefinition(
  1107  					datasource.ContentTypeOracle,
  1108  				).SetOracleConfig(
  1109  					&signedoracle.SpecConfiguration{
  1110  						Signers: []*common.Signer{
  1111  							common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
  1112  						},
  1113  						Filters: []*common.SpecFilter{
  1114  							{
  1115  								Key: &common.SpecPropertyKey{
  1116  									Name: "prices.BTC.value",
  1117  									Type: c.keyType,
  1118  								},
  1119  								Conditions: []*common.SpecCondition{},
  1120  							},
  1121  							{
  1122  								Key: &common.SpecPropertyKey{
  1123  									Name: "prices.ETH.value",
  1124  									Type: datapb.PropertyKey_TYPE_INTEGER,
  1125  								},
  1126  								Conditions: []*common.SpecCondition{},
  1127  							},
  1128  						},
  1129  					},
  1130  				),
  1131  			})
  1132  
  1133  			data := common.Data{
  1134  				Signers: []*common.Signer{
  1135  					common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
  1136  				},
  1137  				Data: map[string]string{
  1138  					"prices.ETH.value": "42",
  1139  				},
  1140  			}
  1141  
  1142  			// when
  1143  			matched, err := spec.MatchData(data)
  1144  
  1145  			// then
  1146  			require.NoError(t, err)
  1147  			assert.False(t, matched)
  1148  		})
  1149  	}
  1150  }
  1151  
  1152  func testOracleSpecMatchingWithInconvertibleTypeFails(t *testing.T) {
  1153  	cases := []struct {
  1154  		msg       string
  1155  		keyType   datapb.PropertyKey_Type
  1156  		specValue string
  1157  		dataValue string
  1158  	}{
  1159  		{
  1160  			msg:       "not an integer",
  1161  			keyType:   datapb.PropertyKey_TYPE_INTEGER,
  1162  			specValue: "42",
  1163  			dataValue: "not an integer",
  1164  		}, {
  1165  			msg:       "not a boolean",
  1166  			keyType:   datapb.PropertyKey_TYPE_BOOLEAN,
  1167  			specValue: "true",
  1168  			dataValue: "not a boolean",
  1169  		}, {
  1170  			msg:       "not a decimal",
  1171  			keyType:   datapb.PropertyKey_TYPE_DECIMAL,
  1172  			specValue: "1.2",
  1173  			dataValue: "not a decimal",
  1174  		}, {
  1175  			msg:       "not a timestamp",
  1176  			keyType:   datapb.PropertyKey_TYPE_TIMESTAMP,
  1177  			specValue: "1111111111",
  1178  			dataValue: "not a timestamp",
  1179  		},
  1180  	}
  1181  
  1182  	for _, c := range cases {
  1183  		t.Run(c.msg, func(t *testing.T) {
  1184  			// given
  1185  			spec, _ := dsspec.New(datasource.Spec{
  1186  				Data: datasource.NewDefinition(
  1187  					datasource.ContentTypeOracle,
  1188  				).SetOracleConfig(
  1189  					&signedoracle.SpecConfiguration{
  1190  						Signers: []*common.Signer{
  1191  							common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
  1192  						},
  1193  						Filters: []*common.SpecFilter{
  1194  							{
  1195  								Key: &common.SpecPropertyKey{
  1196  									Name: "prices.BTC.value",
  1197  									Type: c.keyType,
  1198  								},
  1199  								Conditions: []*common.SpecCondition{
  1200  									{
  1201  										Value:    c.specValue,
  1202  										Operator: datapb.Condition_OPERATOR_EQUALS,
  1203  									},
  1204  								},
  1205  							},
  1206  						},
  1207  					},
  1208  				),
  1209  			})
  1210  
  1211  			data := common.Data{
  1212  				Signers: []*common.Signer{
  1213  					common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
  1214  				},
  1215  				Data: map[string]string{
  1216  					"prices.BTC.value": c.dataValue,
  1217  				},
  1218  			}
  1219  
  1220  			// when
  1221  			matched, err := spec.MatchData(data)
  1222  
  1223  			// then
  1224  			require.Error(t, err)
  1225  			assert.False(t, matched)
  1226  		})
  1227  	}
  1228  }
  1229  
  1230  func testOracleSpecVerifyingBindingWorks(t *testing.T) {
  1231  	cases := []struct {
  1232  		msg              string
  1233  		declaredType     datapb.PropertyKey_Type
  1234  		declaredProperty string
  1235  		decimalPlaces    uint64
  1236  		boundType        datapb.PropertyKey_Type
  1237  		boundProperty    string
  1238  		expectedError    error
  1239  	}{
  1240  		{
  1241  			msg:              "same integer properties can be bound",
  1242  			declaredType:     datapb.PropertyKey_TYPE_INTEGER,
  1243  			declaredProperty: "price.ETH.value",
  1244  			decimalPlaces:    7,
  1245  			boundType:        datapb.PropertyKey_TYPE_INTEGER,
  1246  			boundProperty:    "price.ETH.value",
  1247  			expectedError:    nil,
  1248  		}, {
  1249  			msg:              "different integer properties cannot be bound",
  1250  			declaredType:     datapb.PropertyKey_TYPE_INTEGER,
  1251  			declaredProperty: "price.USD.value",
  1252  			decimalPlaces:    19,
  1253  			boundType:        datapb.PropertyKey_TYPE_INTEGER,
  1254  			boundProperty:    "price.BTC.value",
  1255  			expectedError:    errors.New("bound property \"price.BTC.value\" not filtered by oracle spec"),
  1256  		}, {
  1257  			msg:              "same integer properties can be bound",
  1258  			declaredType:     datapb.PropertyKey_TYPE_BOOLEAN,
  1259  			declaredProperty: "price.ETH.value",
  1260  			decimalPlaces:    2,
  1261  			boundType:        datapb.PropertyKey_TYPE_BOOLEAN,
  1262  			boundProperty:    "price.ETH.value",
  1263  			expectedError:    nil,
  1264  		}, {
  1265  			msg:              "different integer properties can be bound",
  1266  			declaredType:     datapb.PropertyKey_TYPE_BOOLEAN,
  1267  			decimalPlaces:    4,
  1268  			declaredProperty: "price.USD.value",
  1269  			boundType:        datapb.PropertyKey_TYPE_BOOLEAN,
  1270  			boundProperty:    "price.BTC.value",
  1271  			expectedError:    errors.New("bound property \"price.BTC.value\" not filtered by oracle spec"),
  1272  		}, {
  1273  			msg:              "same integer properties can be bound",
  1274  			declaredType:     datapb.PropertyKey_TYPE_DECIMAL,
  1275  			decimalPlaces:    0,
  1276  			declaredProperty: "price.ETH.value",
  1277  			boundType:        datapb.PropertyKey_TYPE_DECIMAL,
  1278  			boundProperty:    "price.ETH.value",
  1279  			expectedError:    nil,
  1280  		}, {
  1281  			msg:              "different integer properties can be bound",
  1282  			declaredType:     datapb.PropertyKey_TYPE_DECIMAL,
  1283  			declaredProperty: "price.USD.value",
  1284  			boundType:        datapb.PropertyKey_TYPE_DECIMAL,
  1285  			boundProperty:    "price.BTC.value",
  1286  			expectedError:    errors.New("bound property \"price.BTC.value\" not filtered by oracle spec"),
  1287  		}, {
  1288  			msg:              "same integer properties can be bound",
  1289  			declaredType:     datapb.PropertyKey_TYPE_STRING,
  1290  			declaredProperty: "price.ETH.value",
  1291  			boundType:        datapb.PropertyKey_TYPE_STRING,
  1292  			boundProperty:    "price.ETH.value",
  1293  			expectedError:    nil,
  1294  		}, {
  1295  			msg:              "different integer properties can be bound",
  1296  			declaredType:     datapb.PropertyKey_TYPE_STRING,
  1297  			declaredProperty: "price.USD.value",
  1298  			boundType:        datapb.PropertyKey_TYPE_STRING,
  1299  			boundProperty:    "price.BTC.value",
  1300  			expectedError:    errors.New("bound property \"price.BTC.value\" not filtered by oracle spec"),
  1301  		}, {
  1302  			msg:              "same integer properties can be bound",
  1303  			declaredType:     datapb.PropertyKey_TYPE_TIMESTAMP,
  1304  			declaredProperty: "price.ETH.value",
  1305  			boundType:        datapb.PropertyKey_TYPE_TIMESTAMP,
  1306  			boundProperty:    "price.ETH.value",
  1307  			expectedError:    nil,
  1308  		}, {
  1309  			msg:              "different integer properties can be bound",
  1310  			declaredType:     datapb.PropertyKey_TYPE_TIMESTAMP,
  1311  			declaredProperty: "price.USD.value",
  1312  			boundType:        datapb.PropertyKey_TYPE_TIMESTAMP,
  1313  			boundProperty:    "price.BTC.value",
  1314  			expectedError:    errors.New("bound property \"price.BTC.value\" not filtered by oracle spec"),
  1315  		}, {
  1316  			msg:              "same properties but different type can't be bound",
  1317  			declaredType:     datapb.PropertyKey_TYPE_TIMESTAMP,
  1318  			declaredProperty: "price.USD.value",
  1319  			boundType:        datapb.PropertyKey_TYPE_STRING,
  1320  			boundProperty:    "price.USD.value",
  1321  			expectedError:    errors.New("bound type \"TYPE_STRING\" doesn't match filtered property type \"TYPE_TIMESTAMP\""),
  1322  		},
  1323  	}
  1324  
  1325  	for _, c := range cases {
  1326  		t.Run(c.msg, func(t *testing.T) {
  1327  			// given
  1328  			spec, _ := dsspec.New(datasource.Spec{
  1329  				Data: datasource.NewDefinition(
  1330  					datasource.ContentTypeOracle,
  1331  				).SetOracleConfig(
  1332  					&signedoracle.SpecConfiguration{
  1333  						Signers: []*common.Signer{
  1334  							common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
  1335  						},
  1336  						Filters: []*common.SpecFilter{
  1337  							{
  1338  								Key: &common.SpecPropertyKey{
  1339  									Name:                c.declaredProperty,
  1340  									Type:                c.declaredType,
  1341  									NumberDecimalPlaces: &c.decimalPlaces,
  1342  								},
  1343  								Conditions: []*common.SpecCondition{},
  1344  							},
  1345  						},
  1346  					},
  1347  				),
  1348  			})
  1349  
  1350  			// when
  1351  			err := spec.EnsureBoundableProperty(c.boundProperty, c.boundType)
  1352  
  1353  			// then
  1354  			assert.Equal(t, c.expectedError, err)
  1355  		})
  1356  	}
  1357  }