Skip to content

Commit 276dccb

Browse files
committed
Latest version of parse_price_feed_update with tests
1 parent 368e108 commit 276dccb

File tree

1 file changed

+298
-0
lines changed
  • target_chains/aptos/contracts/sources

1 file changed

+298
-0
lines changed

target_chains/aptos/contracts/sources/pyth.move

+298
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ module pyth::pyth {
3030
const PYTHNET_ACCUMULATOR_UPDATE_MAGIC: u64 = 1347305813;
3131
const ACCUMULATOR_UPDATE_WORMHOLE_VERIFICATION_MAGIC: u64 = 1096111958;
3232

33+
struct ParseConfig has copy, drop {
34+
min_publish_time: u64,
35+
max_publish_time: u64,
36+
check_uniqueness: bool,
37+
}
38+
3339

3440
// -----------------------------------------------------------------------------
3541
// Initialisation functions
@@ -395,6 +401,119 @@ module pyth::pyth {
395401
update_timestamp > cached_timestamp
396402
}
397403

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+
398517
// -----------------------------------------------------------------------------
399518
// Query the cached prices
400519
//
@@ -1430,4 +1549,183 @@ module pyth::pyth_test {
14301549

14311550
cleanup_test(burn_capability, mint_capability);
14321551
}
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+
14331731
}

0 commit comments

Comments
 (0)