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 }