@@ -30,6 +30,12 @@ module pyth::pyth {
30
30
const PYTHNET_ACCUMULATOR_UPDATE_MAGIC : u64 = 1347305813 ;
31
31
const ACCUMULATOR_UPDATE_WORMHOLE_VERIFICATION_MAGIC : u64 = 1096111958 ;
32
32
33
+ struct ParseConfig has copy , drop {
34
+ min_publish_time: u64 ,
35
+ max_publish_time: u64 ,
36
+ check_uniqueness: bool ,
37
+ }
38
+
33
39
34
40
// -----------------------------------------------------------------------------
35
41
// Initialisation functions
@@ -395,6 +401,119 @@ module pyth::pyth {
395
401
update_timestamp > cached_timestamp
396
402
}
397
403
404
+ // -----------------------------------------------------------------------------
405
+ // Parse Benchmark Prices
406
+ //
407
+ fun parse_and_validate_price_feeds (
408
+ price_infos: &vector <price_info::PriceInfo >,
409
+ price_ids: vector <vector <u8 >>,
410
+ min_publish_time: u64 ,
411
+ max_publish_time: u64
412
+ ): vector <price_feed::PriceFeed > {
413
+ let parsed_price_feeds = vector ::empty <price_feed::PriceFeed >();
414
+ let parsed_price_ids = vector ::empty <vector <u8 >>();
415
+
416
+ // validate publish times and populate parsed price feed vector
417
+ let i = 0 ;
418
+ while (i < vector ::length (price_infos)) {
419
+ let price_info = vector ::borrow (price_infos, i);
420
+ let price_feed = price_info::get_price_feed (price_info);
421
+ let price: price::Price = price_feed::get_price (price_feed);
422
+ let timestamp = price::get_timestamp (&price);
423
+ if (timestamp >= min_publish_time && timestamp <= max_publish_time) {
424
+ let price_id: &price_identifier::PriceIdentifier = price_feed::get_price_identifier (price_feed);
425
+ let price_id_bytes = price_identifier::get_bytes (price_id);
426
+ vector ::push_back (&mut parsed_price_ids, price_id_bytes);
427
+ vector ::push_back (&mut parsed_price_feeds, *price_feed);
428
+ };
429
+ i = i + 1 ;
430
+ };
431
+
432
+ // ensure all requested price IDs have corresponding valid updates
433
+ let k = 0 ;
434
+ while (k < vector ::length (&price_ids)) {
435
+ let requested_price_id = vector ::borrow (&price_ids, k);
436
+ let found = false ;
437
+
438
+ let j = 0 ;
439
+ while (j < vector ::length (&parsed_price_ids)) {
440
+ let parsed_price_id = vector ::borrow (&parsed_price_ids, j);
441
+ if (requested_price_id == parsed_price_id) {
442
+ found = true ;
443
+ };
444
+ j = j + 1 ;
445
+ };
446
+
447
+ if (!found) {
448
+ abort error::unknown_price_feed () // or replace with more suitable error
449
+ };
450
+ k = k + 1 ;
451
+ };
452
+
453
+ return parsed_price_feeds
454
+ }
455
+
456
+ // parse a single VAA and return vector of price feeds
457
+ fun parse_price_feed_updates_single_vaa (
458
+ vaa: vector <u8 >,
459
+ price_ids: vector <vector <u8 >>,
460
+ min_publish_time: u64 ,
461
+ max_publish_time: u64
462
+ ): vector <price_feed::PriceFeed > {
463
+ let cur = cursor::init (vaa);
464
+ let header: u64 = deserialize::deserialize_u32 (&mut cur);
465
+ if (header == PYTHNET_ACCUMULATOR_UPDATE_MAGIC ) {
466
+ let price_infos = parse_and_verify_accumulator_message (&mut cur);
467
+ cursor::rest (cur);
468
+ return parse_and_validate_price_feeds (&price_infos, price_ids, min_publish_time, max_publish_time)
469
+ } else {
470
+ let vaa = vaa::parse_and_verify (vaa);
471
+ verify_data_source (&vaa);
472
+ let price_infos = batch_price_attestation::destroy (batch_price_attestation::deserialize (vaa::destroy (vaa)));
473
+ cursor::rest (cur);
474
+ return parse_and_validate_price_feeds (&price_infos, price_ids, min_publish_time, max_publish_time)
475
+ }
476
+ }
477
+
478
+ public fun parse_price_feed_updates (
479
+ update_data: vector <vector <u8 >>,
480
+ price_ids: vector <vector <u8 >>,
481
+ min_publish_time: u64 ,
482
+ max_publish_time: u64 ,
483
+ fee: Coin <AptosCoin >
484
+ ): vector <price_feed::PriceFeed > {
485
+ // validate and deposit the update fee
486
+ let update_fee = get_update_fee (&update_data);
487
+ assert !(update_fee <= coin::value (&fee), error::insufficient_fee ());
488
+ coin::deposit (@pyth , fee);
489
+ let pyth_balance = coin::balance <AptosCoin >(@pyth );
490
+ assert !(pyth_balance >= update_fee, error::insufficient_fee ());
491
+
492
+ let price_feeds = vector ::empty <price_feed::PriceFeed >();
493
+
494
+ // iterate through the update_data vector
495
+ let i = 0 ;
496
+ while (i < vector ::length (&update_data)) {
497
+ // pass single VAA into the helper function
498
+ let single_vaa = vector ::borrow (&update_data, i);
499
+ let single_price_feeds = parse_price_feed_updates_single_vaa (*single_vaa, price_ids, min_publish_time, max_publish_time);
500
+
501
+ // iterate through the vector of price feeds from the single parsed VAA
502
+ let j = 0 ;
503
+ while (j < vector ::length (&single_price_feeds)) {
504
+ // add each parsed price feed into the price feeds vector
505
+ let price_feed = vector ::borrow (&single_price_feeds, j);
506
+ vector ::push_back (&mut price_feeds, *price_feed);
507
+ j = j + 1 ;
508
+ };
509
+
510
+ i = i + 1 ;
511
+ };
512
+
513
+ price_feeds
514
+ }
515
+
516
+
398
517
// -----------------------------------------------------------------------------
399
518
// Query the cached prices
400
519
//
@@ -1430,4 +1549,183 @@ module pyth::pyth_test {
1430
1549
1431
1550
cleanup_test (burn_capability, mint_capability);
1432
1551
}
1552
+
1553
+
1554
+ #[test(aptos_framework = @aptos_framework )]
1555
+ fun test_parse_price_feed_updates_success (aptos_framework: &signer ) {
1556
+ let update_fee = 50 ;
1557
+ let initial_balance = 100 ;
1558
+ let (burn_capability, mint_capability, coins) = setup_test (aptos_framework, 500 , 1 ,
1559
+ x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92 ",
1560
+ data_sources_for_test_vaa (),
1561
+ update_fee,
1562
+ initial_balance);
1563
+
1564
+ let update_data = TEST_VAAS ;
1565
+ let price_ids = vector [
1566
+ x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1 ",
1567
+ x"3b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe ",
1568
+ x"33832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d ",
1569
+ x"21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db8 ",
1570
+ ];
1571
+ let min_publish_time: u64 = 1663074345 ;
1572
+ let max_publish_time: u64 = 1663680750 ;
1573
+
1574
+ let test_price_feeds: vector <pyth::price_feed ::PriceFeed > = pyth::parse_price_feed_updates (update_data, price_ids, min_publish_time, max_publish_time, coins);
1575
+ let mock_price_infos: vector <pyth::price_info ::PriceInfo > = get_mock_price_infos ();
1576
+
1577
+ assert !(vector ::length (&test_price_feeds) > 0 , 1 );
1578
+ let i: u64 = 0 ;
1579
+ while (i < vector ::length (&mock_price_infos)) {
1580
+ let mock_price_info: &pyth::price_info ::PriceInfo = vector ::borrow (&mock_price_infos, i);
1581
+ let test_price_feed: &pyth::price_feed ::PriceFeed = vector ::borrow (&test_price_feeds, i);
1582
+ let mock_price_feed: &pyth::price_feed ::PriceFeed = price_info::get_price_feed (mock_price_info);
1583
+
1584
+ let test_price_id: &price_identifier::PriceIdentifier = price_feed::get_price_identifier (test_price_feed);
1585
+ let mock_price_id: &price_identifier::PriceIdentifier = price_feed::get_price_identifier (mock_price_feed);
1586
+ let test_price: price::Price = price_feed::get_price (test_price_feed);
1587
+ let mock_price: price::Price = price_feed::get_price (mock_price_feed);
1588
+ let test_ema_price: price::Price = price_feed::get_ema_price (test_price_feed);
1589
+ let mock_ema_price: price::Price = price_feed::get_ema_price (mock_price_feed);
1590
+
1591
+ assert !(test_price_id == mock_price_id, 1 );
1592
+ assert !(test_price == mock_price, 1 );
1593
+ assert !(test_ema_price == mock_ema_price, 1 );
1594
+
1595
+ i = i + 1 ;
1596
+ };
1597
+
1598
+ cleanup_test (burn_capability, mint_capability);
1599
+ }
1600
+
1601
+
1602
+ #[test(aptos_framework = @aptos_framework )]
1603
+ #[expected_failure(abort_code = 65542 , location = pyth::pyth)]
1604
+ fun test_parse_price_feed_updates_insufficient_fee (aptos_framework: &signer ) {
1605
+ let update_fee = 50 ;
1606
+ let initial_balance = 20 ;
1607
+ let (burn_capability, mint_capability, coins) = setup_test (aptos_framework, 500 , 1 ,
1608
+ x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92 ",
1609
+ data_sources_for_test_vaa (),
1610
+ update_fee,
1611
+ initial_balance);
1612
+
1613
+ let update_data = TEST_VAAS ;
1614
+ let price_ids = vector [
1615
+ x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1 ",
1616
+ x"3b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe ",
1617
+ x"33832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d ",
1618
+ x"21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db8 ",
1619
+ ];
1620
+ let min_publish_time: u64 = 1663074345 ;
1621
+ let max_publish_time: u64 = 1663680750 ;
1622
+
1623
+ pyth::parse_price_feed_updates (update_data, price_ids, min_publish_time, max_publish_time, coins);
1624
+
1625
+ cleanup_test (burn_capability, mint_capability);
1626
+ }
1627
+
1628
+ #[test(aptos_framework = @aptos_framework )]
1629
+ #[expected_failure(abort_code = 6 , location = wormhole::vaa)]
1630
+ fun test_parse_price_feed_updates_corrupt_vaa (aptos_framework: &signer ) {
1631
+ let (burn_capability, mint_capability, coins) = setup_test (aptos_framework, 500 , 23 , x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92 ", vector [], 50 , 100 );
1632
+
1633
+ let corrupt_vaa = x"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1 ";
1634
+ let price_ids = vector [
1635
+ x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1 ",
1636
+ x"3b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe ",
1637
+ x"33832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d ",
1638
+ x"21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db8 ",
1639
+ ];
1640
+ let min_publish_time: u64 = 1663074345 ;
1641
+ let max_publish_time: u64 = 1663680750 ;
1642
+ pyth::parse_price_feed_updates (vector [corrupt_vaa], price_ids, min_publish_time, max_publish_time, coins);
1643
+
1644
+ cleanup_test (burn_capability, mint_capability);
1645
+ }
1646
+
1647
+ #[test(aptos_framework = @aptos_framework )]
1648
+ #[expected_failure(abort_code = 65539 , location = pyth::pyth)]
1649
+ fun test_parse_price_feed_updates_invalid_data_source (aptos_framework: &signer ) {
1650
+ let update_data = TEST_VAAS ;
1651
+ let price_ids = vector [
1652
+ x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1 ",
1653
+ x"3b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe ",
1654
+ x"33832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d ",
1655
+ x"21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db8 ",
1656
+ ];
1657
+ let min_publish_time: u64 = 1663074345 ;
1658
+ let max_publish_time: u64 = 1663680750 ;
1659
+
1660
+ let data_sources = vector <DataSource >[
1661
+ data_source::new (
1662
+ 4 , external_address::from_bytes (x"0000000000000000000000000000000000000000000000000000000000007742 ")),
1663
+ data_source::new (
1664
+ 5 , external_address::from_bytes (x"0000000000000000000000000000000000000000000000000000000000007637 "))
1665
+ ];
1666
+ let (burn_capability, mint_capability, coins) = setup_test (aptos_framework, 500 , 1 , x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92 ", data_sources, 50 , 100 );
1667
+
1668
+ pyth::parse_price_feed_updates (update_data, price_ids, min_publish_time, max_publish_time, coins);
1669
+
1670
+ cleanup_test (burn_capability, mint_capability);
1671
+ }
1672
+
1673
+
1674
+ #[test(aptos_framework = @aptos_framework )]
1675
+ #[expected_failure(abort_code = 393224 , location = pyth::pyth)]
1676
+ fun test_parse_price_feed_updates_invalid_price_id (aptos_framework: &signer ) {
1677
+ let update_fee = 50 ;
1678
+ let initial_balance = 100 ;
1679
+ let (burn_capability, mint_capability, coins) = setup_test (aptos_framework, 500 , 1 ,
1680
+ x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92 ",
1681
+ data_sources_for_test_vaa (),
1682
+ update_fee,
1683
+ initial_balance);
1684
+
1685
+ let update_data = TEST_VAAS ;
1686
+ let price_ids = vector [
1687
+ x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d2 ", // invalid price id
1688
+ x"3b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe ",
1689
+ x"33832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d ",
1690
+ x"21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db8 ",
1691
+ ];
1692
+ let min_publish_time: u64 = 1663074345 ;
1693
+ let max_publish_time: u64 = 1663680750 ;
1694
+
1695
+ pyth::parse_price_feed_updates (update_data, price_ids, min_publish_time, max_publish_time, coins);
1696
+
1697
+ cleanup_test (burn_capability, mint_capability);
1698
+ }
1699
+
1700
+ #[test(aptos_framework = @aptos_framework )]
1701
+ #[expected_failure(abort_code = 393224 , location = pyth::pyth)]
1702
+ fun test_parse_price_feed_updates_invalid_publish_times (aptos_framework: &signer ) {
1703
+ let update_fee = 50 ;
1704
+ let initial_balance = 100 ;
1705
+ let (burn_capability, mint_capability, coins) = setup_test (aptos_framework, 500 , 1 ,
1706
+ x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92 ",
1707
+ data_sources_for_test_vaa (),
1708
+ update_fee,
1709
+ initial_balance);
1710
+
1711
+ let update_data = TEST_VAAS ;
1712
+ let price_ids = vector [
1713
+ x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1 ",
1714
+ x"3b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe ",
1715
+ x"33832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d ",
1716
+ x"21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db8 ",
1717
+ ];
1718
+ // invalid publish times: max_publish_time is less than min_publish_time
1719
+ let min_publish_time: u64 = 1663680750 ;
1720
+ let max_publish_time: u64 = 1663074345 ;
1721
+
1722
+ pyth::parse_price_feed_updates (update_data, price_ids, min_publish_time, max_publish_time, coins);
1723
+
1724
+ cleanup_test (burn_capability, mint_capability);
1725
+ }
1726
+
1727
+
1728
+
1729
+
1730
+
1433
1731
}
0 commit comments