pyth.move 74 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453
  1. module pyth::pyth {
  2. use pyth::batch_price_attestation::{Self};
  3. use pyth::price_identifier::{Self, PriceIdentifier};
  4. use pyth::price_info::{Self, PriceInfo};
  5. use pyth::price_feed::{Self};
  6. use aptos_framework::coin::{Self, Coin};
  7. use aptos_framework::aptos_coin::{AptosCoin};
  8. use pyth::price::Price;
  9. use pyth::price;
  10. use pyth::data_source::{Self, DataSource};
  11. use aptos_framework::timestamp;
  12. use pyth::deserialize::{Self};
  13. use wormhole::cursor::{Self, Cursor};
  14. use std::vector;
  15. use pyth::state;
  16. use wormhole::vaa;
  17. use wormhole::u16;
  18. use wormhole::external_address;
  19. use std::account;
  20. use std::signer;
  21. use deployer::deployer;
  22. use pyth::error;
  23. use pyth::event;
  24. use pyth::merkle;
  25. use pyth::keccak160;
  26. #[test_only]
  27. friend pyth::pyth_test;
  28. const PYTHNET_ACCUMULATOR_UPDATE_MAGIC: u64 = 1347305813;
  29. const ACCUMULATOR_UPDATE_WORMHOLE_VERIFICATION_MAGIC: u64 = 1096111958;
  30. // -----------------------------------------------------------------------------
  31. // Initialisation functions
  32. public entry fun init(
  33. deployer: &signer,
  34. stale_price_threshold: u64,
  35. governance_emitter_chain_id: u64,
  36. governance_emitter_address: vector<u8>,
  37. data_sources_emitter_chain_ids: vector<u64>,
  38. data_sources_emitter_addresses: vector<vector<u8>>,
  39. update_fee: u64,
  40. ) {
  41. // Claim the signer capability from the deployer. Note that this is a one-time operation,
  42. // so that this function can only be called once.
  43. let signer_capability = deployer::claim_signer_capability(deployer, @pyth);
  44. init_internal(
  45. signer_capability,
  46. stale_price_threshold,
  47. governance_emitter_chain_id,
  48. governance_emitter_address,
  49. parse_data_sources(
  50. data_sources_emitter_chain_ids,
  51. data_sources_emitter_addresses,
  52. ),
  53. update_fee
  54. )
  55. }
  56. fun init_internal(
  57. signer_capability: account::SignerCapability,
  58. stale_price_threshold: u64,
  59. governance_emitter_chain_id: u64,
  60. governance_emitter_address: vector<u8>,
  61. data_sources: vector<DataSource>,
  62. update_fee: u64) {
  63. let pyth = account::create_signer_with_capability(&signer_capability);
  64. state::init(
  65. &pyth,
  66. stale_price_threshold,
  67. update_fee,
  68. data_source::new(
  69. governance_emitter_chain_id,
  70. external_address::from_bytes(governance_emitter_address)),
  71. data_sources,
  72. signer_capability
  73. );
  74. event::init(&pyth);
  75. if (!coin::is_account_registered<AptosCoin>(signer::address_of(&pyth))) {
  76. coin::register<AptosCoin>(&pyth);
  77. }
  78. }
  79. fun parse_data_sources(
  80. emitter_chain_ids: vector<u64>,
  81. emitter_addresses: vector<vector<u8>>): vector<DataSource> {
  82. assert!(vector::length(&emitter_chain_ids) == vector::length(&emitter_addresses),
  83. error::data_source_emitter_address_and_chain_ids_different_lengths());
  84. let sources = vector::empty();
  85. let i = 0;
  86. while (i < vector::length(&emitter_chain_ids)) {
  87. vector::push_back(&mut sources, data_source::new(
  88. *vector::borrow(&emitter_chain_ids, i),
  89. external_address::from_bytes(*vector::borrow(&emitter_addresses, i))
  90. ));
  91. i = i + 1;
  92. };
  93. sources
  94. }
  95. #[test_only]
  96. /// Expose a public initialization function for use in tests
  97. public fun init_test(
  98. signer_capability: account::SignerCapability,
  99. stale_price_threshold: u64,
  100. governance_emitter_chain_id: u64,
  101. governance_emitter_address: vector<u8>,
  102. data_sources: vector<DataSource>,
  103. update_fee: u64,
  104. ) {
  105. init_internal(
  106. signer_capability,
  107. stale_price_threshold,
  108. governance_emitter_chain_id,
  109. governance_emitter_address,
  110. data_sources,
  111. update_fee
  112. )
  113. }
  114. // -----------------------------------------------------------------------------
  115. // Update the cached prices
  116. //
  117. // Pyth uses an uses an on-demand update model, where consumers need to update the
  118. /// cached prices before using them. Please read more about this at https://docs.pyth.network/documentation/pythnet-price-feeds/on-demand.
  119. /// Update the cached price feeds with the data in the given VAAs. This is a
  120. /// convenience wrapper around update_price_feeds(), which allows you to update the price feeds
  121. /// using an entry function.
  122. ///
  123. /// If possible, it is recommended to use update_price_feeds() instead, which avoids the need
  124. /// to pass a signer account. update_price_feeds_with_funder() should only be used when
  125. /// you need to call an entry function.
  126. ///
  127. /// This function will charge an update fee, transferring some AptosCoin's
  128. /// from the given funder account to the Pyth contract. The amount of coins that will be transferred
  129. /// to perform this update can be queried with get_update_fee(&vaas). The signer must have sufficient
  130. /// account balance to pay this fee, otherwise the transaction will abort.
  131. ///
  132. /// Please read more information about the update fee here: https://docs.pyth.network/documentation/pythnet-price-feeds/on-demand#fees
  133. public entry fun update_price_feeds_with_funder(account: &signer, vaas: vector<vector<u8>>) {
  134. let total_updates = 0;
  135. // Update the price feed from each VAA
  136. while (!vector::is_empty(&vaas)) {
  137. total_updates = total_updates + update_price_feed_from_single_vaa(vector::pop_back(&mut vaas));
  138. };
  139. // Charge the message update fee
  140. let update_fee = state::get_base_update_fee() * total_updates;
  141. let fee = coin::withdraw<AptosCoin>(account, update_fee);
  142. coin::deposit(@pyth, fee);
  143. }
  144. /// Update the cached price feeds with the data in the given VAAs.
  145. /// The vaas argument is a vector of VAAs encoded as bytes.
  146. ///
  147. /// The javascript https://github.com/pyth-network/pyth-js/tree/main/pyth-aptos-js package
  148. /// should be used to fetch these VAAs from the Price Service. More information about this
  149. /// process can be found at https://docs.pyth.network/documentation/pythnet-price-feeds.
  150. ///
  151. /// The given fee must contain a sufficient number of coins to pay the update fee for the given vaas.
  152. /// The update fee amount can be queried by calling get_update_fee(&vaas).
  153. ///
  154. /// Please read more information about the update fee here: https://docs.pyth.network/documentation/pythnet-price-feeds/on-demand#fees
  155. public fun update_price_feeds(vaas: vector<vector<u8>>, fee: Coin<AptosCoin>) {
  156. let total_updates = 0;
  157. // Update the price feed from each VAA
  158. while (!vector::is_empty(&vaas)) {
  159. total_updates = total_updates + update_price_feed_from_single_vaa(vector::pop_back(&mut vaas));
  160. };
  161. // Charge the message update fee
  162. let update_fee = state::get_base_update_fee() * total_updates;
  163. assert!(update_fee <= coin::value(&fee), error::insufficient_fee());
  164. coin::deposit(@pyth, fee);
  165. }
  166. fun verify_data_source(vaa: &vaa::VAA) {
  167. assert!(
  168. state::is_valid_data_source(
  169. data_source::new(
  170. u16::to_u64(vaa::get_emitter_chain(vaa)),
  171. vaa::get_emitter_address(vaa))),
  172. error::invalid_data_source());
  173. }
  174. /// Given the payload of a VAA related to accumulator messages, asserts the verification variant is merkle and
  175. /// extracts the merkle root digest
  176. fun parse_accumulator_merkle_root_from_vaa_payload(message: vector<u8>): keccak160::Hash {
  177. let msg_payload_cursor = cursor::init(message);
  178. let payload_type = deserialize::deserialize_u32(&mut msg_payload_cursor);
  179. assert!(payload_type == ACCUMULATOR_UPDATE_WORMHOLE_VERIFICATION_MAGIC, error::invalid_wormhole_message());
  180. let wh_message_payload_type = deserialize::deserialize_u8(&mut msg_payload_cursor);
  181. assert!(wh_message_payload_type == 0, error::invalid_wormhole_message()); // Merkle variant
  182. let _merkle_root_slot = deserialize::deserialize_u64(&mut msg_payload_cursor);
  183. let _merkle_root_ring_size = deserialize::deserialize_u32(&mut msg_payload_cursor);
  184. let merkle_root_hash = deserialize::deserialize_vector(&mut msg_payload_cursor, 20);
  185. cursor::rest(msg_payload_cursor);
  186. keccak160::new(merkle_root_hash)
  187. }
  188. /// Given a single accumulator price update message, asserts that it is a PriceFeedMessage,
  189. /// parses the info and returns a PriceInfo representing the encoded information
  190. fun parse_accumulator_update_message(message: vector<u8>): PriceInfo {
  191. let message_cur = cursor::init(message);
  192. let message_type = deserialize::deserialize_u8(&mut message_cur);
  193. assert!(message_type == 0, error::invalid_accumulator_message()); // PriceFeedMessage variant
  194. let price_identifier = price_identifier::from_byte_vec(deserialize::deserialize_vector(&mut message_cur, 32));
  195. let price = deserialize::deserialize_i64(&mut message_cur);
  196. let conf = deserialize::deserialize_u64(&mut message_cur);
  197. let expo = deserialize::deserialize_i32(&mut message_cur);
  198. let publish_time = deserialize::deserialize_u64(&mut message_cur);
  199. let _prev_publish_time = deserialize::deserialize_i64(&mut message_cur);
  200. let ema_price = deserialize::deserialize_i64(&mut message_cur);
  201. let ema_conf = deserialize::deserialize_u64(&mut message_cur);
  202. let price_info = price_info::new(
  203. timestamp::now_seconds(), // not used anywhere kept for backward compatibility
  204. timestamp::now_seconds(),
  205. price_feed::new(
  206. price_identifier,
  207. pyth::price::new(price, conf, expo, publish_time),
  208. pyth::price::new(ema_price, ema_conf, expo, publish_time),
  209. )
  210. );
  211. cursor::rest(message_cur);
  212. price_info
  213. }
  214. /// Given a cursor at the beginning of accumulator price updates array data and a merkle_root hash,
  215. /// parses the price updates and proofs, verifies the proofs against the merkle_root and
  216. /// returns an array of PriceInfo representing the updates
  217. fun parse_and_verify_accumulator_updates(
  218. cursor: &mut Cursor<u8>,
  219. merkle_root: &keccak160::Hash
  220. ): vector<PriceInfo> {
  221. let update_size = deserialize::deserialize_u8(cursor);
  222. let updates: vector<PriceInfo> = vector[];
  223. while (update_size > 0) {
  224. let message_size = deserialize::deserialize_u16(cursor);
  225. let message = deserialize::deserialize_vector(cursor, message_size);
  226. let update = parse_accumulator_update_message(message);
  227. vector::push_back(&mut updates, update);
  228. let path_size = deserialize::deserialize_u8(cursor);
  229. let merkle_path: vector<keccak160::Hash> = vector[];
  230. while (path_size > 0) {
  231. let hash = deserialize::deserialize_vector(cursor, keccak160::get_hash_length());
  232. vector::push_back(&mut merkle_path, keccak160::new(hash));
  233. path_size = path_size - 1;
  234. };
  235. assert!(merkle::check(&merkle_path, merkle_root, message), error::invalid_proof());
  236. update_size = update_size - 1;
  237. };
  238. updates
  239. }
  240. /// Given a cursor at the beginning of an accumulator message, verifies the validity of the message and the
  241. /// embedded VAA, parses and verifies the price updates and returns an array of PriceInfo representing the updates
  242. fun parse_and_verify_accumulator_message(cursor: &mut Cursor<u8>): vector<PriceInfo> {
  243. let major = deserialize::deserialize_u8(cursor);
  244. assert!(major == 1, error::invalid_accumulator_payload());
  245. let _minor = deserialize::deserialize_u8(cursor);
  246. let trailing_size = deserialize::deserialize_u8(cursor);
  247. deserialize::deserialize_vector(cursor, (trailing_size as u64));
  248. let proof_type = deserialize::deserialize_u8(cursor);
  249. assert!(proof_type == 0, error::invalid_accumulator_payload());
  250. let vaa_size = deserialize::deserialize_u16(cursor);
  251. let vaa = deserialize::deserialize_vector(cursor, vaa_size);
  252. let msg_vaa = vaa::parse_and_verify(vaa);
  253. verify_data_source(&msg_vaa);
  254. let merkle_root_hash = parse_accumulator_merkle_root_from_vaa_payload(vaa::get_payload(&msg_vaa));
  255. vaa::destroy(msg_vaa);
  256. parse_and_verify_accumulator_updates(cursor, &merkle_root_hash)
  257. }
  258. fun update_price_feed_from_single_vaa(vaa: vector<u8>): u64 {
  259. let cur = cursor::init(vaa);
  260. let header: u64 = deserialize::deserialize_u32(&mut cur);
  261. let total_updates;
  262. let updates = if (header == PYTHNET_ACCUMULATOR_UPDATE_MAGIC) {
  263. let result = parse_and_verify_accumulator_message(&mut cur);
  264. total_updates = vector::length(&result);
  265. result
  266. }
  267. else {
  268. // Deserialize the VAA
  269. let vaa = vaa::parse_and_verify(vaa);
  270. // Check that the VAA is from a valid data source (emitter)
  271. verify_data_source(&vaa);
  272. // Deserialize the batch price attestation
  273. total_updates = 1;
  274. batch_price_attestation::destroy(batch_price_attestation::deserialize(vaa::destroy(vaa)))
  275. };
  276. update_cache(updates);
  277. cursor::rest(cur);
  278. total_updates
  279. }
  280. /// Update the cache with given price updates, if they are newer than the ones currently cached.
  281. public(friend) fun update_cache(updates: vector<PriceInfo>) {
  282. while (!vector::is_empty(&updates)) {
  283. let update = vector::pop_back(&mut updates);
  284. if (is_fresh_update(&update)) {
  285. let price_feed = *price_info::get_price_feed(&update);
  286. let price_identifier = price_feed::get_price_identifier(&price_feed);
  287. state::set_latest_price_info(
  288. *price_identifier,
  289. update,
  290. );
  291. event::emit_price_feed_update(price_feed, timestamp::now_microseconds());
  292. }
  293. };
  294. vector::destroy_empty(updates);
  295. }
  296. /// A convenience wrapper around update_price_feeds_if_fresh(), allowing you to conditionally
  297. /// update the price feeds using an entry function.
  298. ///
  299. /// If possible, it is recommended to use update_price_feeds_if_fresh() instead, which avoids the need
  300. /// to pass a signer account. update_price_feeds_if_fresh_with_funder() should only be used when
  301. /// you need to call an entry function.
  302. public entry fun update_price_feeds_if_fresh_with_funder(
  303. account: &signer,
  304. vaas: vector<vector<u8>>,
  305. price_identifiers: vector<vector<u8>>,
  306. publish_times: vector<u64>) {
  307. let coins = coin::withdraw<AptosCoin>(account, get_update_fee(&vaas));
  308. update_price_feeds_if_fresh(vaas, price_identifiers, publish_times, coins);
  309. }
  310. #[legacy_entry_fun]
  311. /// Update the cached price feeds with the data in the given VAAs, using
  312. /// update_price_feeds(). However, this function will only have an effect if any of the
  313. /// prices in the update are fresh. The price_identifiers and publish_times parameters
  314. /// are used to determine if the update is fresh without doing any serialisation or verification
  315. /// of the VAAs, potentially saving time and gas. If the update contains no fresh data, this function
  316. /// will revert with error::no_fresh_data().
  317. ///
  318. /// For a given price update i in the batch, that price is considered fresh if the current cached
  319. /// price for price_identifiers[i] is older than publish_times[i].
  320. public entry fun update_price_feeds_if_fresh(
  321. vaas: vector<vector<u8>>,
  322. price_identifiers: vector<vector<u8>>,
  323. publish_times: vector<u64>,
  324. fee: Coin<AptosCoin>) {
  325. assert!(vector::length(&price_identifiers) == vector::length(&publish_times),
  326. error::invalid_publish_times_length());
  327. let fresh_data = false;
  328. let i = 0;
  329. while (i < vector::length(&publish_times)) {
  330. let price_identifier = price_identifier::from_byte_vec(
  331. *vector::borrow(&price_identifiers, i));
  332. if (!state::price_info_cached(price_identifier)) {
  333. fresh_data = true;
  334. break
  335. };
  336. let cached_timestamp = price::get_timestamp(&get_price_unsafe(price_identifier));
  337. if (cached_timestamp < *vector::borrow(&publish_times, i)) {
  338. fresh_data = true;
  339. break
  340. };
  341. i = i + 1;
  342. };
  343. assert!(fresh_data, error::no_fresh_data());
  344. update_price_feeds(vaas, fee);
  345. }
  346. /// Determine if the given price update is "fresh": we have nothing newer already cached for that
  347. /// price feed.
  348. fun is_fresh_update(update: &PriceInfo): bool {
  349. // Get the timestamp of the update's current price
  350. let price_feed = price_info::get_price_feed(update);
  351. let update_timestamp = price::get_timestamp(&price_feed::get_price(price_feed));
  352. // Get the timestamp of the cached data for the price identifier
  353. let price_identifier = price_feed::get_price_identifier(price_feed);
  354. if (!price_feed_exists(*price_identifier)) {
  355. return true
  356. };
  357. let cached_timestamp = price::get_timestamp(&get_price_unsafe(*price_identifier));
  358. update_timestamp > cached_timestamp
  359. }
  360. // -----------------------------------------------------------------------------
  361. // Query the cached prices
  362. //
  363. // It is strongly recommended to update the cached prices using the functions above,
  364. // before using the functions below to query the cached data.
  365. /// Determine if a price feed for the given price_identifier exists
  366. public fun price_feed_exists(price_identifier: PriceIdentifier): bool {
  367. state::price_info_cached(price_identifier)
  368. }
  369. /// Get the latest available price cached for the given price identifier, if that price is
  370. /// no older than the stale price threshold.
  371. ///
  372. /// Please refer to the documentation at https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices for
  373. /// how to how this price safely.
  374. ///
  375. /// Important: Pyth uses an on-demand update model, where consumers need to update the
  376. /// cached prices before using them. Please read more about this at https://docs.pyth.network/documentation/pythnet-price-feeds/on-demand.
  377. /// get_price() is likely to abort unless you call update_price_feeds() to update the cached price
  378. /// beforehand, as the cached prices may be older than the stale price threshold.
  379. ///
  380. /// Note that the price_identifier does not correspond to a seperate Aptos account:
  381. /// all price feeds are stored in the single pyth account. The price identifier is an
  382. /// opaque identifier for a price feed.
  383. public fun get_price(price_identifier: PriceIdentifier): Price {
  384. get_price_no_older_than(price_identifier, state::get_stale_price_threshold_secs())
  385. }
  386. #[view]
  387. /// A view function version of get_price(...) that's available in offchain programming environments
  388. /// including aptos fullnode api, aptos cli, and aptos ts sdk.
  389. public fun get_price_by_feed_id(feed_id: vector<u8>): Price {
  390. let price_identifier = price_identifier::from_byte_vec(feed_id);
  391. get_price(price_identifier)
  392. }
  393. /// Get the latest available price cached for the given price identifier, if that price is
  394. /// no older than the given age.
  395. public fun get_price_no_older_than(price_identifier: PriceIdentifier, max_age_secs: u64): Price {
  396. let price = get_price_unsafe(price_identifier);
  397. check_price_is_fresh(&price, max_age_secs);
  398. price
  399. }
  400. #[view]
  401. public fun get_price_no_older_than_by_feed_id(feed_id: vector<u8>, max_age_secs: u64): Price {
  402. let price_identifier = price_identifier::from_byte_vec(feed_id);
  403. get_price_no_older_than(price_identifier, max_age_secs)
  404. }
  405. /// Get the latest available price cached for the given price identifier.
  406. ///
  407. /// WARNING: the returned price can be from arbitrarily far in the past.
  408. /// This function makes no guarantees that the returned price is recent or
  409. /// useful for any particular application. Users of this function should check
  410. /// the returned timestamp to ensure that the returned price is sufficiently
  411. /// recent for their application. The checked get_price_no_older_than()
  412. /// function should be used in preference to this.
  413. public fun get_price_unsafe(price_identifier: PriceIdentifier): Price {
  414. price_feed::get_price(
  415. price_info::get_price_feed(&state::get_latest_price_info(price_identifier)))
  416. }
  417. #[view]
  418. public fun get_price_unsafe_by_feed_id(feed_id: vector<u8>): Price {
  419. let price_identifier = price_identifier::from_byte_vec(feed_id);
  420. get_price_unsafe(price_identifier)
  421. }
  422. fun abs_diff(x: u64, y: u64): u64 {
  423. if (x > y) {
  424. return x - y
  425. } else {
  426. return y - x
  427. }
  428. }
  429. /// Get the stale price threshold: the amount of time after which a cached price
  430. /// is considered stale and no longer returned by get_price()/get_ema_price().
  431. public fun get_stale_price_threshold_secs(): u64 {
  432. state::get_stale_price_threshold_secs()
  433. }
  434. fun check_price_is_fresh(price: &Price, max_age_secs: u64) {
  435. let age = abs_diff(timestamp::now_seconds(), price::get_timestamp(price));
  436. assert!(age < max_age_secs, error::stale_price_update());
  437. }
  438. /// Get the latest available exponentially moving average price cached for the given
  439. /// price identifier, if that price is no older than the stale price threshold.
  440. ///
  441. /// Important: Pyth uses an on-demand update model, where consumers need to update the
  442. /// cached prices before using them. Please read more about this at https://docs.pyth.network/documentation/pythnet-price-feeds/on-demand.
  443. /// get_ema_price() is likely to abort unless you call update_price_feeds() to update the cached price
  444. /// beforehand, as the cached prices may be older than the stale price threshold.
  445. public fun get_ema_price(price_identifier: PriceIdentifier): Price {
  446. get_ema_price_no_older_than(price_identifier, state::get_stale_price_threshold_secs())
  447. }
  448. /// Get the latest available exponentially moving average price cached for the given price identifier,
  449. /// if that price is no older than the given age.
  450. public fun get_ema_price_no_older_than(price_identifier: PriceIdentifier, max_age_secs: u64): Price {
  451. let price = get_ema_price_unsafe(price_identifier);
  452. check_price_is_fresh(&price, max_age_secs);
  453. price
  454. }
  455. /// Get the latest available exponentially moving average price cached for the given price identifier.
  456. ///
  457. /// WARNING: the returned price can be from arbitrarily far in the past.
  458. /// This function makes no guarantees that the returned price is recent or
  459. /// useful for any particular application. Users of this function should check
  460. /// the returned timestamp to ensure that the returned price is sufficiently
  461. /// recent for their application. The checked get_ema_price_no_older_than()
  462. /// function should be used in preference to this.
  463. public fun get_ema_price_unsafe(price_identifier: PriceIdentifier): Price {
  464. price_feed::get_ema_price(
  465. price_info::get_price_feed(&state::get_latest_price_info(price_identifier)))
  466. }
  467. /// Get the number of AptosCoin's required to perform the given price updates.
  468. ///
  469. /// Please read more information about the update fee here: https://docs.pyth.network/documentation/pythnet-price-feeds/on-demand#fees
  470. public fun get_update_fee(update_data: &vector<vector<u8>>): u64 {
  471. let i = 0;
  472. let total_updates = 0;
  473. while (i < vector::length(update_data)) {
  474. let cur = cursor::init(*vector::borrow(update_data, i));
  475. let header: u64 = deserialize::deserialize_u32(&mut cur);
  476. if (header == PYTHNET_ACCUMULATOR_UPDATE_MAGIC) {
  477. //TODO: this may be expensive and can be optimized by not verifying the messages
  478. let updates = parse_and_verify_accumulator_message(&mut cur);
  479. total_updates = total_updates + vector::length(&updates);
  480. }
  481. else {
  482. total_updates = total_updates + 1;
  483. };
  484. cursor::rest(cur);
  485. i = i + 1;
  486. };
  487. state::get_base_update_fee() * total_updates
  488. }
  489. }
  490. // -----------------------------------------------------------------------------
  491. // Tests
  492. #[test_only]
  493. module pyth::pyth_test {
  494. use pyth::pyth;
  495. use pyth::price_identifier::{Self};
  496. use pyth::price_info::{Self, PriceInfo};
  497. use pyth::price_feed::{Self};
  498. use aptos_framework::coin::{Self, Coin, BurnCapability, MintCapability};
  499. use aptos_framework::aptos_coin::{Self, AptosCoin};
  500. use pyth::i64;
  501. use pyth::price;
  502. use pyth::data_source::{Self, DataSource};
  503. use aptos_framework::timestamp;
  504. use std::vector;
  505. use wormhole::external_address;
  506. use std::account;
  507. use std::signer;
  508. use wormhole::wormhole;
  509. #[test_only]
  510. fun setup_test(
  511. aptos_framework: &signer,
  512. stale_price_threshold: u64,
  513. governance_emitter_chain_id: u64,
  514. governance_emitter_address: vector<u8>,
  515. data_sources: vector<DataSource>,
  516. update_fee: u64,
  517. to_mint: u64): (BurnCapability<AptosCoin>, MintCapability<AptosCoin>, Coin<AptosCoin>) {
  518. // Initialize wormhole with a large message collection fee
  519. wormhole::wormhole_test::setup(100000);
  520. // Set the current time
  521. timestamp::update_global_time_for_test_secs(1663680745);
  522. // Deploy and initialize a test instance of the Pyth contract
  523. let deployer = account::create_signer_with_capability(&
  524. account::create_test_signer_cap(@0x277fa055b6a73c42c0662d5236c65c864ccbf2d4abd21f174a30c8b786eab84b));
  525. let (_pyth, signer_capability) = account::create_resource_account(&deployer, b"pyth");
  526. pyth::init_test(signer_capability, stale_price_threshold, governance_emitter_chain_id, governance_emitter_address, data_sources, update_fee);
  527. let (burn_capability, mint_capability) = aptos_coin::initialize_for_test(aptos_framework);
  528. let coins = coin::mint(to_mint, &mint_capability);
  529. (burn_capability, mint_capability, coins)
  530. }
  531. #[test_only]
  532. fun cleanup_test(burn_capability: BurnCapability<AptosCoin>, mint_capability: MintCapability<AptosCoin>) {
  533. coin::destroy_mint_cap(mint_capability);
  534. coin::destroy_burn_cap(burn_capability);
  535. }
  536. #[test_only]
  537. fun get_mock_price_infos(): vector<PriceInfo> {
  538. vector<PriceInfo>[
  539. price_info::new(
  540. 1663680747,
  541. 1663074349,
  542. price_feed::new(
  543. price_identifier::from_byte_vec(x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1"),
  544. price::new(i64::new(1557, false), 7, i64::new(5, true), 1663680740),
  545. price::new(i64::new(1500, false), 3, i64::new(5, true), 1663680740),
  546. ),
  547. ),
  548. price_info::new(
  549. 1663680747,
  550. 1663074349,
  551. price_feed::new(
  552. price_identifier::from_byte_vec(x"3b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe"),
  553. price::new(i64::new(1050, false), 3, i64::new(5, true), 1663680745),
  554. price::new(i64::new(1483, false), 3, i64::new(5, true), 1663680745),
  555. ),
  556. ),
  557. price_info::new(
  558. 1663680747,
  559. 1663074349,
  560. price_feed::new(
  561. price_identifier::from_byte_vec(x"33832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d"),
  562. price::new(i64::new(1010, false), 2, i64::new(5, true), 1663680745),
  563. price::new(i64::new(1511, false), 3, i64::new(5, true), 1663680745),
  564. ),
  565. ),
  566. price_info::new(
  567. 1663680747,
  568. 1663074349,
  569. price_feed::new(
  570. price_identifier::from_byte_vec(x"21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db8"),
  571. price::new(i64::new(1739, false), 1, i64::new(5, true), 1663680745),
  572. price::new(i64::new(1508, false), 3, i64::new(5, true), 1663680745),
  573. ),
  574. ),
  575. ]
  576. }
  577. #[test_only]
  578. /// A vector containing a single VAA with:
  579. /// - emitter chain ID 17
  580. /// - emitter address 0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b
  581. /// - payload corresponding to the batch price attestation of the prices returned by get_mock_price_infos()
  582. const TEST_VAAS: vector<vector<u8>> = vector[x"0100000000010036eb563b80a24f4253bee6150eb8924e4bdf6e4fa1dfc759a6664d2e865b4b134651a7b021b7f1ce3bd078070b688b6f2e37ce2de0d9b48e6a78684561e49d5201527e4f9b00000001001171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b0000000000000001005032574800030000000102000400951436e0be37536be96f0896366089506a59763d036728332d3e3038047851aea7c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1000000000000049a0000000000000008fffffffb00000000000005dc0000000000000003000000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000006150000000000000007215258d81468614f6b7e194c5d145609394f67b041e93e6695dcc616faadd0603b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe000000000000041a0000000000000003fffffffb00000000000005cb0000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e4000000000000048600000000000000078ac9cf3ab299af710d735163726fdae0db8465280502eb9f801f74b3c1bd190333832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d00000000000003f20000000000000002fffffffb00000000000005e70000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e40000000000000685000000000000000861db714e9ff987b6fedf00d01f9fea6db7c30632d6fc83b7bc9459d7192bc44a21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db800000000000006cb0000000000000001fffffffb00000000000005e40000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000007970000000000000001"];
  583. #[test_only]
  584. const TEST_ACCUMULATOR: vector<u8> = x"504e41550100000000a0010000000001005d461ac1dfffa8451edda17e4b28a46c8ae912422b2dc0cb7732828c497778ea27147fb95b4d250651931845e7f3e22c46326716bcf82be2874a9c9ab94b6e42000000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b0000000000000000004155575600000000000000000000000000da936d73429246d131873a0bab90ad7b416510be01005500b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65f958f4883f9d2a8b5b1008d1fa01db95cf4a8c7000000006491cc757be59f3f377c0d3f423a695e81ad1eb504f8554c3620c3fd02f2ee15ea639b73fa3db9b34a245bdfa015c260c5a8a1180177cf30b2c0bebbb1adfe8f7985d051d2";
  585. #[test_only]
  586. const TEST_ACCUMULATOR_3_MSGS: vector<u8> = x"504e41550100000000a001000000000100d39b55fa311213959f91866d52624f3a9c07350d8956f6d42cfbb037883f31575c494a2f09fea84e4884dc9c244123fd124bc7825cd64d7c11e33ba5cfbdea7e010000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b000000000000000000415557560000000000000000000000000029da4c066b6e03b16a71e77811570dd9e19f258103005500b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60000000000000064000000000000003200000009000000006491cc747be59f3f377c0d3f000000000000006300000000000000340436992facb15658a7e9f08c4df4848ca80750f61fadcd96993de66b1fe7aef94e29e3bbef8b12db2305a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d950055006e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af000000000000006500000000000000330000000a000000006491cc7504f8554c3620c3fd0000000000000064000000000000003504171ed10ac4f1eacf3a4951e1da6b119f07c45da5adcd96993de66b1fe7aef94e29e3bbef8b12db2305a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d9500550031ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c68000000000000006600000000000000340000000b000000006491cc76e87d69c7b51242890000000000000065000000000000003604f2ee15ea639b73fa3db9b34a245bdfa015c260c5fe83e4772e0e346613de00e5348158a01bcb27b305a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95";
  587. #[test_only]
  588. const TEST_ACCUMULATOR_3_MSGS_LATER: vector<u8> = x"504e41550100000000a00100000000010056c1b66d4c93315ace5b0d8f5cee8ff068226e8623d7774389083a0ddf9952124cdc90c36f064c4e2b613a0dfe440c48892cf430b232abdc4993ce32463e4a1a000000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b0000000000000000004155575600000000000000000000000000cff05501cdaa105762dfedd238329fa3a96706dd03005500b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6000000000000006e000000000000003c00000013000000006491cc7e7be59f3f377c0d3f000000000000006d000000000000003e0445fb6c6bcdc996d401388588879afee37bc9c721394c4cc20e3b10b0c72d37ab53b0d56880d2d37905a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d950055006e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af000000000000006f000000000000003d00000014000000006491cc7f04f8554c3620c3fd000000000000006e000000000000003f04f41f65577b25a78041be51a163e2e6e991c4c92b394c4cc20e3b10b0c72d37ab53b0d56880d2d37905a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d9500550031ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c680000000000000070000000000000003e00000015000000006491cc80e87d69c7b5124289000000000000006f000000000000004004f2ee15ea639b73fa3db9b34a245bdfa015c260c512ff930a26f8bce92f17545a6d60502c7767e71e05a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95";
  589. #[test_only]
  590. const TEST_ACCUMULATOR_INVALID_PROOF_1: vector<u8> = x"504e41550100000000a001000000000100110db9cd8325ccfab0dae92eeb9ea70a1faba5c5e96dc21ff46a8ddc560afc9a60df096b8ff21172804692bbdc958153e838437d8b474cbf45f0dc2a8acae831000000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b0000000000000000004155575600000000000000000000000000a8bea2b5f12f3177ff9b3929d77c3476ab2d32c602005500b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6fa75cd3aa3bb5ace5e2516446f71f85be36bd19bb0703f3154bb3db07be59f3f377c0d3f44661d9a8736c68884c8169e8b636ee3043202397384073120dce9e5d0efe24b44b4a0d62da8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d950055006e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af5f958f4883f9d2a8b5b1008d1fa01db95cf4a8c7423a695e81ad1eb504f8554c3620c3fd40b40f7d581ac802e2de5cb82a9ae672043202397384073120dce9e5d0efe24b44b4a0d62da8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95";
  591. #[test_only]
  592. const TEST_ACCUMULATOR_INVALID_ACC_MSG: vector<u8> = x"504e41550100000000a0010000000001005d461ac1dfffa8451edda17e4b28a46c8ae912422b2dc0cb7732828c497778ea27147fb95b4d250651931845e7f3e22c46326716bcf82be2874a9c9ab94b6e42000000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b0000000000000000004155575600000000000000000000000000da936d73429246d131873a0bab90ad7b416510be01005540b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65f958f4883f9d2a8b5b1008d1fa01db95cf4a8c7000000006491cc757be59f3f377c0d3f423a695e81ad1eb504f8554c3620c3fd02f2ee15ea639b73fa3db9b34a245bdfa015c260c5a8a1180177cf30b2c0bebbb1adfe8f7985d051d2";
  593. #[test_only]
  594. const TEST_ACCUMULATOR_INVALID_WH_MSG: vector<u8> = x"504e41550100000000a001000000000100e87f98238c5357730936cfdfde3a37249e5219409a4f41b301924b8eb10815a43ea2f96e4fe1bc8cd398250f39448d3b8ca57c96f9cf7a2be292517280683caa010000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b00000000000000000041555755000000000000000000000000000fb6f9f2b3b6cc1c9ef6708985fef226d92a3c0801005500b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6fa75cd3aa3bb5ace5e2516446f71f85be36bd19b000000006491cc747be59f3f377c0d3f44661d9a8736c68884c8169e8b636ee301f2ee15ea639b73fa3db9b34a245bdfa015c260c5";
  595. #[test_only]
  596. const TEST_ACCUMULATOR_INVALID_MAJOR_VERSION: vector<u8> = x"504e41553c00000000a001000000000100496b7fbd18dca2f0e690712fd8ca522ff79ca7d9d6d22e9f5d753fba4bd16fff440a811bad710071c79859290bcb1700de49dd8400db90b048437b521200123e010000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b000000000000000000415557560000000000000000000000000005f5db4488a7cae9f9a6c1938340c0fbf4beb9090200550031ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6879bc5a3617ec3444d93c06501cf6a0909c38d4ec81d96026b71ec475e87d69c7b5124289adbf24212bed8c15db354391d2378d2e0454d2655c6c34e7e50580fd8c94511322968bbc6da8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95005500944998273e477b495144fb8794c914197f3ccb46be2900f4698fd0ef743c9695a573a6ff665ff63edb5f9a85ad579dc14500a2112c09680fc146134f9a539ca82cb6e3501c801278fd08d80732a24118292866bb049e6e88181a1e1e8b6d3c6bbb95135a73041f3b56a8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95";
  597. #[test_only]
  598. const TEST_ACCUMULATOR_INCREASED_MINOR_VERSION: vector<u8> = x"504e4155010a000000a001000000000100496b7fbd18dca2f0e690712fd8ca522ff79ca7d9d6d22e9f5d753fba4bd16fff440a811bad710071c79859290bcb1700de49dd8400db90b048437b521200123e010000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b000000000000000000415557560000000000000000000000000005f5db4488a7cae9f9a6c1938340c0fbf4beb9090200550031ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6879bc5a3617ec3444d93c06501cf6a0909c38d4ec81d96026b71ec475e87d69c7b5124289adbf24212bed8c15db354391d2378d2e0454d2655c6c34e7e50580fd8c94511322968bbc6da8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95005500944998273e477b495144fb8794c914197f3ccb46be2900f4698fd0ef743c9695a573a6ff665ff63edb5f9a85ad579dc14500a2112c09680fc146134f9a539ca82cb6e3501c801278fd08d80732a24118292866bb049e6e88181a1e1e8b6d3c6bbb95135a73041f3b56a8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95";
  599. #[test_only]
  600. const TEST_ACCUMULATOR_EXTRA_PAYLOAD: vector<u8> = x"504e41550100000000a001000000000100b2d11f181d81b4ff10beca30091754b464dc48bc1f7432d114f64a7a8f660e7964f2a0c6121bae6c1977514d46ee7a29d9395b20a45f2086071715c1dc19ab74000000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b000000000000000000415557560000000000000000000000000013f83cfdf63a5a1b3189182fa0a52e6de53ba7d002005d0031ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6879bc5a3617ec3444d93c06501cf6a0909c38d4ec81d96026b71ec475e87d69c7b5124289adbf24212bed8c15db354391d2378d2e000000000000000004a576f4a87f443f7d961a682f508c4f7b06ee1595a8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95005d00944998273e477b495144fb8794c914197f3ccb46be2900f4698fd0ef743c9695a573a6ff665ff63edb5f9a85ad579dc14500a2112c09680fc146134f9a539ca82cb6e3501c801278fd08d80732a24118292866bb0000000000000000045be67ba87a8dfbea404827ccbf07790299b6c023a8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95";
  601. #[test_only]
  602. /// Allow anyone to update the cache with given updates. For testing purpose only.
  603. public fun update_cache_for_test(updates: vector<PriceInfo>) {
  604. pyth::update_cache(updates);
  605. }
  606. #[test(aptos_framework = @aptos_framework)]
  607. fun test_get_update_fee(aptos_framework: &signer) {
  608. let single_update_fee = 50;
  609. let (burn_capability, mint_capability, coins) = setup_test(aptos_framework, 500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", vector[], 50, 0);
  610. // Pass in a single VAA
  611. assert!(pyth::get_update_fee(&vector[
  612. x"fb1543888001083cf2e6ef3afdcf827e89b11efd87c563638df6e1995ada9f93",
  613. ]) == single_update_fee, 1);
  614. // Pass in multiple VAAs
  615. assert!(pyth::get_update_fee(&vector[
  616. x"4ee17a1a4524118de513fddcf82b77454e51be5d6fc9e29fc72dd6c204c0e4fa",
  617. x"c72fdf81cfc939d4286c93fbaaae2eec7bae28a5926fa68646b43a279846ccc1",
  618. x"d9a8123a793529c31200339820a3210059ecace6c044f81ecad62936e47ca049",
  619. x"84e4f21b3e65cef47fda25d15b4eddda1edf720a1d062ccbf441d6396465fbe6",
  620. x"9e73f9041476a93701a0b9c7501422cc2aa55d16100bec628cf53e0281b6f72f"
  621. ]) == 250, 1);
  622. coin::destroy_zero(coins);
  623. cleanup_test(burn_capability, mint_capability);
  624. }
  625. #[test(aptos_framework = @aptos_framework)]
  626. #[expected_failure(abort_code = 6, location = wormhole::vaa)]
  627. fun test_update_price_feeds_corrupt_vaa(aptos_framework: &signer) {
  628. let (burn_capability, mint_capability, coins) = setup_test(aptos_framework, 500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", vector[], 50, 100);
  629. // Pass in a corrupt VAA, which should fail deseriaizing
  630. let corrupt_vaa = x"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1";
  631. pyth::update_price_feeds(vector[corrupt_vaa], coins);
  632. cleanup_test(burn_capability, mint_capability);
  633. }
  634. #[test(aptos_framework = @aptos_framework)]
  635. #[expected_failure(abort_code = 65539, location = pyth::pyth)]
  636. fun test_update_price_feeds_invalid_data_source(aptos_framework: &signer) {
  637. // Initialize the contract with some valid data sources, excluding our test VAA's source
  638. let data_sources = vector<DataSource>[
  639. data_source::new(
  640. 4, external_address::from_bytes(x"0000000000000000000000000000000000000000000000000000000000007742")),
  641. data_source::new(
  642. 5, external_address::from_bytes(x"0000000000000000000000000000000000000000000000000000000000007637"))
  643. ];
  644. let (burn_capability, mint_capability, coins) = setup_test(aptos_framework, 500, 1, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources, 50, 100);
  645. pyth::update_price_feeds(TEST_VAAS, coins);
  646. cleanup_test(burn_capability, mint_capability);
  647. }
  648. #[test_only]
  649. fun data_sources_for_test_vaa(): vector<DataSource> {
  650. // Set some valid data sources, including our test VAA's source
  651. vector<DataSource>[
  652. data_source::new(
  653. 1, external_address::from_bytes(x"0000000000000000000000000000000000000000000000000000000000000004")),
  654. data_source::new(
  655. 5, external_address::from_bytes(x"0000000000000000000000000000000000000000000000000000000000007637")),
  656. data_source::new(
  657. 17, external_address::from_bytes(x"71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b")),
  658. data_source::new(
  659. 1, external_address::from_bytes(x"71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b")),
  660. ]
  661. }
  662. #[test(aptos_framework = @aptos_framework)]
  663. #[expected_failure(abort_code = 65542, location = pyth::pyth)]
  664. fun test_update_price_feeds_insufficient_fee(aptos_framework: &signer) {
  665. let (burn_capability, mint_capability, coins) = setup_test(aptos_framework, 500, 1,
  666. x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92",
  667. data_sources_for_test_vaa(),
  668. // Update fee
  669. 50,
  670. // Coins provided to update < update fee
  671. 20);
  672. pyth::update_price_feeds(TEST_VAAS, coins);
  673. cleanup_test(burn_capability, mint_capability);
  674. }
  675. #[test(aptos_framework = @aptos_framework)]
  676. fun test_update_price_feeds_success(aptos_framework: &signer) {
  677. let (burn_capability, mint_capability, coins) = setup_test(aptos_framework, 500, 1, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), 50, 100);
  678. // Update the price feeds from the VAA
  679. pyth::update_price_feeds(TEST_VAAS, coins);
  680. // Check that the cache has been updated
  681. let expected = get_mock_price_infos();
  682. check_price_feeds_cached(&expected);
  683. cleanup_test(burn_capability, mint_capability);
  684. }
  685. #[test_only]
  686. fun setup_accumulator_test(
  687. aptos_framework: &signer,
  688. data_sources: vector<DataSource>,
  689. to_mint: u64
  690. ): (BurnCapability<AptosCoin>, MintCapability<AptosCoin>, Coin<AptosCoin>) {
  691. let aptos_framework_account = std::account::create_account_for_test(@aptos_framework);
  692. std::timestamp::set_time_has_started_for_testing(&aptos_framework_account);
  693. wormhole::init_test(
  694. 22,
  695. 1,
  696. x"0000000000000000000000000000000000000000000000000000000000000004",
  697. x"7E5F4552091A69125d5DfCb7b8C2659029395Bdf",
  698. 100000
  699. );
  700. // Set the current time
  701. timestamp::update_global_time_for_test_secs(1687276659);
  702. // Deploy and initialize a test instance of the Pyth contract
  703. let deployer = account::create_signer_with_capability(
  704. &account::create_test_signer_cap(@0x277fa055b6a73c42c0662d5236c65c864ccbf2d4abd21f174a30c8b786eab84b)
  705. );
  706. let (_pyth, signer_capability) = account::create_resource_account(&deployer, b"pyth");
  707. pyth::init_test(signer_capability,
  708. 500,
  709. 1,
  710. x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92",
  711. data_sources,
  712. 50);
  713. let (burn_capability, mint_capability) = aptos_coin::initialize_for_test(aptos_framework);
  714. let coins = coin::mint(to_mint, &mint_capability);
  715. (burn_capability, mint_capability, coins)
  716. }
  717. #[test(aptos_framework = @aptos_framework)]
  718. fun test_accumulator_update_price(aptos_framework: &signer) {
  719. let (burn_capability, mint_capability, coins) = setup_accumulator_test(
  720. aptos_framework,
  721. data_sources_for_test_vaa(),
  722. 50
  723. );
  724. pyth::update_price_feeds(vector[TEST_ACCUMULATOR], coins);
  725. let expected = vector<PriceInfo>[
  726. price_info::new(
  727. 1663680747,
  728. 1663074349,
  729. price_feed::new(
  730. price_identifier::from_byte_vec(
  731. x"b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6"
  732. ),
  733. price::new(
  734. i64::new(6887568746747646632, false),
  735. 13092246197863718329,
  736. i64::new(1559537863, false),
  737. 1687276661
  738. ),
  739. price::new(
  740. i64::new(4772242609775910581, false),
  741. 358129956189946877,
  742. i64::new(1559537863, false),
  743. 1687276661
  744. ),
  745. ),
  746. )];
  747. check_price_feeds_cached(&expected);
  748. cleanup_test(burn_capability, mint_capability);
  749. }
  750. #[test_only]
  751. fun check_accumulator_test_price_feeds(offset: u64) {
  752. let i = 0;
  753. let feed_ids = vector[x"b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6",
  754. x"6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af",
  755. x"31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c68"];
  756. let expected: vector<PriceInfo> = vector[];
  757. while (i < 3) {
  758. vector::push_back(&mut expected, price_info::new(
  759. 1663680747,
  760. 1663074349,
  761. price_feed::new(
  762. price_identifier::from_byte_vec(
  763. *vector::borrow(&feed_ids, i)
  764. ),
  765. price::new(
  766. i64::new(100 + i + offset, false),
  767. 50 + i + offset,
  768. i64::new(9 + i + offset, false),
  769. 1687276660 + i + offset
  770. ),
  771. price::new(
  772. i64::new(99 + i + offset, false),
  773. 52 + i + offset,
  774. i64::new(9 + i + offset, false),
  775. 1687276660 + i + offset
  776. ),
  777. ),
  778. ));
  779. i = i + 1;
  780. };
  781. check_price_feeds_cached(&expected);
  782. }
  783. #[test(aptos_framework = @aptos_framework)]
  784. #[expected_failure(abort_code = 65542, location = pyth::pyth)]
  785. fun test_accumulator_update_price_feeds_insufficient_fee(aptos_framework: &signer) {
  786. let (burn_capability, mint_capability, coins) = setup_accumulator_test(
  787. aptos_framework,
  788. data_sources_for_test_vaa(),
  789. 149
  790. );
  791. pyth::update_price_feeds(vector[TEST_ACCUMULATOR_3_MSGS], coins);
  792. cleanup_test(burn_capability, mint_capability);
  793. }
  794. #[test(aptos_framework = @aptos_framework)]
  795. fun test_accumulator_update_price_multi_feed(aptos_framework: &signer) {
  796. let (burn_capability, mint_capability, coins) = setup_accumulator_test(
  797. aptos_framework,
  798. data_sources_for_test_vaa(),
  799. 150
  800. );
  801. pyth::update_price_feeds(vector[TEST_ACCUMULATOR_3_MSGS], coins);
  802. check_accumulator_test_price_feeds(0);
  803. cleanup_test(burn_capability, mint_capability);
  804. }
  805. #[test(aptos_framework = @aptos_framework)]
  806. fun test_accumulator_update_price_out_of_order(aptos_framework: &signer) {
  807. let (burn_capability, mint_capability, coins) = setup_accumulator_test(
  808. aptos_framework,
  809. data_sources_for_test_vaa(),
  810. 300
  811. );
  812. pyth::update_price_feeds(vector[TEST_ACCUMULATOR_3_MSGS_LATER, TEST_ACCUMULATOR_3_MSGS], coins);
  813. check_accumulator_test_price_feeds(10);
  814. // we pass the old message again in a separate call to make sure it will not overwrite the recent values in neither case
  815. let coins_second_call = coin::mint(150, &mint_capability);
  816. pyth::update_price_feeds(vector[TEST_ACCUMULATOR_3_MSGS], coins_second_call);
  817. check_accumulator_test_price_feeds(10);
  818. cleanup_test(burn_capability, mint_capability);
  819. }
  820. #[test(aptos_framework = @aptos_framework)]
  821. fun test_accumulator_update_price_multi_msg(aptos_framework: &signer) {
  822. let (burn_capability, mint_capability, coins) = setup_accumulator_test(
  823. aptos_framework,
  824. data_sources_for_test_vaa(),
  825. 150
  826. );
  827. pyth::update_price_feeds(vector[TEST_ACCUMULATOR_3_MSGS], coins);
  828. check_accumulator_test_price_feeds(0);
  829. let coins_second_call = coin::mint(150, &mint_capability);
  830. pyth::update_price_feeds(vector[TEST_ACCUMULATOR_3_MSGS_LATER], coins_second_call);
  831. check_accumulator_test_price_feeds(10);
  832. cleanup_test(burn_capability, mint_capability);
  833. }
  834. #[test(aptos_framework = @aptos_framework)]
  835. #[expected_failure(abort_code = 65562, location = pyth::pyth)]
  836. fun test_accumulator_invalid_payload(aptos_framework: &signer) {
  837. let (burn_capability, mint_capability, coins) = setup_accumulator_test(
  838. aptos_framework,
  839. data_sources_for_test_vaa(),
  840. 100
  841. );
  842. pyth::update_price_feeds(vector[x"504e415500000000"], coins);
  843. cleanup_test(burn_capability, mint_capability);
  844. }
  845. #[test(aptos_framework = @aptos_framework)]
  846. #[expected_failure(abort_code = 65563, location = pyth::pyth)]
  847. fun test_accumulator_invalid_accumulator_message(aptos_framework: &signer) {
  848. let (burn_capability, mint_capability, coins) = setup_accumulator_test(
  849. aptos_framework,
  850. data_sources_for_test_vaa(),
  851. 100
  852. );
  853. pyth::update_price_feeds(vector[TEST_ACCUMULATOR_INVALID_ACC_MSG], coins);
  854. cleanup_test(burn_capability, mint_capability);
  855. }
  856. #[test(aptos_framework = @aptos_framework)]
  857. #[expected_failure(abort_code = 65564, location = pyth::pyth)]
  858. fun test_accumulator_invalid_wormhole_message(aptos_framework: &signer) {
  859. let (burn_capability, mint_capability, coins) = setup_accumulator_test(
  860. aptos_framework,
  861. data_sources_for_test_vaa(),
  862. 100
  863. );
  864. pyth::update_price_feeds(vector[TEST_ACCUMULATOR_INVALID_WH_MSG], coins);
  865. cleanup_test(burn_capability, mint_capability);
  866. }
  867. #[test(aptos_framework = @aptos_framework)]
  868. #[expected_failure(abort_code = 65562, location = pyth::pyth)]
  869. fun test_accumulator_invalid_major_version(aptos_framework: &signer) {
  870. let (burn_capability, mint_capability, coins) = setup_accumulator_test(
  871. aptos_framework,
  872. data_sources_for_test_vaa(),
  873. 100
  874. );
  875. pyth::update_price_feeds(vector[TEST_ACCUMULATOR_INVALID_MAJOR_VERSION], coins);
  876. cleanup_test(burn_capability, mint_capability);
  877. }
  878. #[test(aptos_framework = @aptos_framework)]
  879. fun test_accumulator_forward_compatibility(aptos_framework: &signer) {
  880. let (burn_capability, mint_capability, coins) = setup_accumulator_test(
  881. aptos_framework,
  882. data_sources_for_test_vaa(),
  883. 100
  884. );
  885. pyth::update_price_feeds(vector[TEST_ACCUMULATOR_INCREASED_MINOR_VERSION], coins);
  886. let coins_second_call = coin::mint(100, &mint_capability);
  887. pyth::update_price_feeds(vector[TEST_ACCUMULATOR_EXTRA_PAYLOAD], coins_second_call);
  888. cleanup_test(burn_capability, mint_capability);
  889. }
  890. #[test(aptos_framework = @aptos_framework)]
  891. #[expected_failure(abort_code = 65539, location = pyth::pyth)]
  892. fun test_accumulator_invalid_data_source(aptos_framework: &signer) {
  893. let (burn_capability, mint_capability, coins) = setup_accumulator_test(aptos_framework, vector[data_source::new(
  894. 2, // correct emitter chain is 1
  895. external_address::from_bytes(x"71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b")
  896. )], 100);
  897. pyth::update_price_feeds(vector[TEST_ACCUMULATOR], coins);
  898. cleanup_test(burn_capability, mint_capability);
  899. }
  900. #[test(aptos_framework = @aptos_framework)]
  901. fun test_accumulator_update_fee(aptos_framework: &signer) {
  902. let (burn_capability, mint_capability, coins) = setup_accumulator_test(
  903. aptos_framework,
  904. data_sources_for_test_vaa(),
  905. 0
  906. );
  907. let single_update_fee = 50;
  908. assert!(pyth::get_update_fee(&vector[
  909. TEST_ACCUMULATOR,
  910. ]) == single_update_fee, 1);
  911. assert!(pyth::get_update_fee(&vector[
  912. TEST_ACCUMULATOR,
  913. TEST_ACCUMULATOR,
  914. ]) == single_update_fee * 2, 1);
  915. assert!(pyth::get_update_fee(&vector[
  916. TEST_ACCUMULATOR,
  917. TEST_ACCUMULATOR_3_MSGS,
  918. ]) == single_update_fee * 4, 1);
  919. assert!(pyth::get_update_fee(&vector[
  920. TEST_ACCUMULATOR,
  921. TEST_ACCUMULATOR_3_MSGS,
  922. x"deaddeaddead", // random non-accumulator data
  923. ]) == single_update_fee * 5, 1);
  924. coin::destroy_zero(coins);
  925. cleanup_test(burn_capability, mint_capability);
  926. }
  927. #[test(aptos_framework = @aptos_framework)]
  928. #[expected_failure(abort_code = 65565, location = pyth::pyth)]
  929. fun test_accumulator_invalid_proof(aptos_framework: &signer) {
  930. let (burn_capability, mint_capability, coins) = setup_accumulator_test(
  931. aptos_framework,
  932. data_sources_for_test_vaa(),
  933. 100
  934. );
  935. pyth::update_price_feeds(vector[TEST_ACCUMULATOR_INVALID_PROOF_1], coins);
  936. cleanup_test(burn_capability, mint_capability);
  937. }
  938. #[test(aptos_framework = @aptos_framework)]
  939. fun test_update_price_feeds_with_funder(aptos_framework: &signer) {
  940. let update_fee = 50;
  941. let initial_balance = 75;
  942. let (burn_capability, mint_capability, coins) = setup_test(
  943. aptos_framework,
  944. 500,
  945. 23,
  946. x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92",
  947. data_sources_for_test_vaa(),
  948. update_fee,
  949. initial_balance
  950. );
  951. // Create a test funder account and register it to store funds
  952. let funder_addr = @0xbfbffd8e2af9a3e3ce20d2d2b22bd640;
  953. let funder = account::create_account_for_test(funder_addr);
  954. coin::register<AptosCoin>(&funder);
  955. coin::deposit(funder_addr, coins);
  956. assert!(pyth::get_update_fee(&TEST_VAAS) == update_fee, 1);
  957. assert!(coin::balance<AptosCoin>(signer::address_of(&funder)) == initial_balance, 1);
  958. assert!(coin::balance<AptosCoin>(@pyth) == 0, 1);
  959. // Update the price feeds using the funder
  960. pyth::update_price_feeds_with_funder(&funder, TEST_VAAS);
  961. // Check that the price feeds are now cached
  962. check_price_feeds_cached(&get_mock_price_infos());
  963. // Check that the funder's balance has decreased by the update_fee amount
  964. assert!(coin::balance<AptosCoin>(signer::address_of(&funder)) == initial_balance - pyth::get_update_fee(&TEST_VAAS), 1);
  965. // Check that the amount has been transferred to the Pyth contract
  966. assert!(coin::balance<AptosCoin>(@pyth) == pyth::get_update_fee(&TEST_VAAS), 1);
  967. cleanup_test(burn_capability, mint_capability);
  968. }
  969. #[test(aptos_framework = @aptos_framework)]
  970. #[expected_failure(abort_code = 65542, location = aptos_framework::coin)]
  971. fun test_update_price_feeds_with_funder_insufficient_balance(aptos_framework: &signer) {
  972. let update_fee = 50;
  973. let initial_balance = 25;
  974. let (burn_capability, mint_capability, coins) = setup_test(aptos_framework, 500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), update_fee, initial_balance);
  975. // Create a test funder account and register it to store funds
  976. let funder_addr = @0xbfbffd8e2af9a3e3ce20d2d2b22bd640;
  977. let funder = account::create_account_for_test(funder_addr);
  978. coin::register<AptosCoin>(&funder);
  979. coin::deposit(funder_addr, coins);
  980. assert!(pyth::get_update_fee(&TEST_VAAS) == update_fee, 1);
  981. assert!(coin::balance<AptosCoin>(signer::address_of(&funder)) == initial_balance, 1);
  982. assert!(coin::balance<AptosCoin>(@pyth) == 0, 1);
  983. // Update the price feeds using the funder
  984. pyth::update_price_feeds_with_funder(&funder, TEST_VAAS);
  985. cleanup_test(burn_capability, mint_capability);
  986. }
  987. #[test_only]
  988. fun check_price_feeds_cached(expected: &vector<PriceInfo>) {
  989. // Check that we can retrieve the correct current price and ema price for each price feed
  990. let i = 0;
  991. while (i < vector::length(expected)) {
  992. let price_feed = price_info::get_price_feed(vector::borrow(expected, i));
  993. let price = price_feed::get_price(price_feed);
  994. let price_identifier = *price_feed::get_price_identifier(price_feed);
  995. assert!(pyth::price_feed_exists(price_identifier), 1);
  996. let cached_price = pyth::get_price(price_identifier);
  997. assert!(cached_price == price, 1);
  998. let ema_price = price_feed::get_ema_price(price_feed);
  999. let cached_ema_price = pyth::get_ema_price(price_identifier);
  1000. assert!(cached_ema_price == ema_price, 1);
  1001. i = i + 1;
  1002. };
  1003. }
  1004. #[test(aptos_framework = @aptos_framework)]
  1005. fun test_update_cache(aptos_framework: &signer) {
  1006. let (burn_capability, mint_capability, coins) = setup_test(aptos_framework, 500, 1, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), 50, 0);
  1007. let updates = get_mock_price_infos();
  1008. // Check that initially the price feeds are not cached
  1009. let i = 0;
  1010. while (i < vector::length(&updates)) {
  1011. let price_feed = price_info::get_price_feed(vector::borrow(&updates, i));
  1012. assert!(!pyth::price_feed_exists(*price_feed::get_price_identifier(price_feed)), 1);
  1013. i = i + 1;
  1014. };
  1015. // Submit the updates
  1016. pyth::update_cache(updates);
  1017. // Check that the price feeds are now cached
  1018. check_price_feeds_cached(&updates);
  1019. cleanup_test(burn_capability, mint_capability);
  1020. coin::destroy_zero(coins);
  1021. }
  1022. #[test(aptos_framework = @aptos_framework)]
  1023. fun test_update_cache_old_update(aptos_framework: &signer) {
  1024. let (burn_capability, mint_capability, coins) = setup_test(aptos_framework, 1000, 1, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), 50, 0);
  1025. // Submit a price update
  1026. let timestamp = 1663680700;
  1027. let price_identifier = price_identifier::from_byte_vec(x"baa284eaf23edf975b371ba2818772f93dbae72836bbdea28b07d40f3cf8b485");
  1028. let price = price::new(i64::new(7648, false), 674, i64::new(8, true), timestamp);
  1029. let ema_price = price::new(i64::new(1536, true), 869, i64::new(100, false), timestamp);
  1030. let update = price_info::new(
  1031. 1257278600,
  1032. 1690226180,
  1033. price_feed::new(
  1034. price_identifier,
  1035. price,
  1036. ema_price,
  1037. )
  1038. );
  1039. pyth::update_cache(vector<PriceInfo>[update]);
  1040. // Check that we can retrieve the current price
  1041. assert!(pyth::get_price(price_identifier) == price, 1);
  1042. // Attempt to update the price with an update older than the current cached one
  1043. let old_price = price::new(i64::new(1243, true), 9802, i64::new(6, false), timestamp - 200);
  1044. let old_ema_price = price::new(i64::new(8976, true), 234, i64::new(897, false), timestamp - 200);
  1045. let old_update = price_info::new(
  1046. 1257278600,
  1047. 1690226180,
  1048. price_feed::new(
  1049. price_identifier,
  1050. old_price,
  1051. old_ema_price,
  1052. )
  1053. );
  1054. pyth::update_cache(vector<PriceInfo>[old_update]);
  1055. // Confirm that the current price and ema price didn't change
  1056. assert!(pyth::get_price(price_identifier) == price, 1);
  1057. assert!(pyth::get_ema_price(price_identifier) == ema_price, 1);
  1058. // Update the cache with a fresh update
  1059. let fresh_price = price::new(i64::new(4857, true), 9979, i64::new(243, false), timestamp + 200);
  1060. let fresh_ema_price = price::new(i64::new(74637, false), 9979, i64::new(1433, false), timestamp + 1);
  1061. let fresh_update = price_info::new(
  1062. 1257278600,
  1063. 1690226180,
  1064. price_feed::new(
  1065. price_identifier,
  1066. fresh_price,
  1067. fresh_ema_price,
  1068. )
  1069. );
  1070. pyth::update_cache(vector<PriceInfo>[fresh_update]);
  1071. // Confirm that the current price was updated
  1072. assert!(pyth::get_price(price_identifier) == fresh_price, 1);
  1073. assert!(pyth::get_ema_price(price_identifier) == fresh_ema_price, 1);
  1074. cleanup_test(burn_capability, mint_capability);
  1075. coin::destroy_zero(coins);
  1076. }
  1077. #[test(aptos_framework = @aptos_framework)]
  1078. #[expected_failure(abort_code = 524292, location = pyth::pyth)]
  1079. fun test_stale_price_threshold_exceeded(aptos_framework: &signer) {
  1080. let stale_price_threshold = 500;
  1081. let (burn_capability, mint_capability, coins) = setup_test(aptos_framework, stale_price_threshold, 1, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), 50, 0);
  1082. // Submit a price update
  1083. let current_timestamp = timestamp::now_seconds();
  1084. let price_identifier = price_identifier::from_byte_vec(x"baa284eaf23edf975b371ba2818772f93dbae72836bbdea28b07d40f3cf8b485");
  1085. let price = price::new(i64::new(7648, false), 674, i64::new(8, true), current_timestamp);
  1086. let update = price_info::new(
  1087. 1257278600,
  1088. 1690226180,
  1089. price_feed::new(
  1090. price_identifier,
  1091. price,
  1092. price::new(i64::new(1536, true), 869, i64::new(100, false), 1257212500),
  1093. )
  1094. );
  1095. pyth::update_cache(vector<PriceInfo>[update]);
  1096. assert!(pyth::get_price(price_identifier) == price, 1);
  1097. // Now advance the clock on the target chain, until the age of the cached update exceeds the
  1098. // stale_price_threshold.
  1099. timestamp::update_global_time_for_test_secs(current_timestamp + stale_price_threshold);
  1100. // Check that we can access the price if we increase the threshold by 1
  1101. assert!(pyth::get_price_no_older_than(
  1102. price_identifier, pyth::get_stale_price_threshold_secs() + 1) == price, 1);
  1103. // However, retrieving the latest price fails
  1104. assert!(pyth::get_price(price_identifier) == price, 1);
  1105. cleanup_test(burn_capability, mint_capability);
  1106. coin::destroy_zero(coins);
  1107. }
  1108. #[test(aptos_framework = @aptos_framework)]
  1109. #[expected_failure(abort_code = 524292, location = pyth::pyth)]
  1110. fun test_stale_price_threshold_exceeded_ema(aptos_framework: &signer) {
  1111. let stale_price_threshold = 500;
  1112. let (burn_capability, mint_capability, coins) = setup_test(aptos_framework, stale_price_threshold, 1, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), 50, 0);
  1113. // Submit a price update
  1114. let current_timestamp = timestamp::now_seconds();
  1115. let price_identifier = price_identifier::from_byte_vec(x"baa284eaf23edf975b371ba2818772f93dbae72836bbdea28b07d40f3cf8b485");
  1116. let ema_price = price::new(i64::new(1536, true), 869, i64::new(100, false), current_timestamp);
  1117. let update = price_info::new(
  1118. 1257278600,
  1119. 1690226180,
  1120. price_feed::new(
  1121. price_identifier,
  1122. price::new(i64::new(7648, false), 674, i64::new(8, true), 1257212500),
  1123. ema_price,
  1124. )
  1125. );
  1126. pyth::update_cache(vector<PriceInfo>[update]);
  1127. // Check that the EMA price has been updated
  1128. assert!(pyth::get_ema_price(price_identifier) == ema_price, 1);
  1129. // Now advance the clock on the target chain, until the age of the cached update exceeds the
  1130. // stale_price_threshold.
  1131. timestamp::update_global_time_for_test_secs(current_timestamp + stale_price_threshold);
  1132. // Check that we can access the EMA price if we increase the threshold by 1
  1133. assert!(pyth::get_ema_price_no_older_than(
  1134. price_identifier, pyth::get_stale_price_threshold_secs() + 1) == ema_price, 1);
  1135. // However, retrieving the latest EMA price fails
  1136. assert!(pyth::get_ema_price(price_identifier) == ema_price, 1);
  1137. cleanup_test(burn_capability, mint_capability);
  1138. coin::destroy_zero(coins);
  1139. }
  1140. #[test(aptos_framework = @aptos_framework)]
  1141. #[expected_failure(abort_code = 65541, location = pyth::pyth)]
  1142. fun test_update_price_feeds_if_fresh_invalid_length(aptos_framework: &signer) {
  1143. let (burn_capability, mint_capability, coins) = setup_test(aptos_framework, 500, 1, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), 50, 0);
  1144. // Update the price feeds
  1145. let bytes = vector[vector[0u8, 1u8, 2u8]];
  1146. let price_identifiers = vector[
  1147. x"baa284eaf23edf975b371ba2818772f93dbae72836bbdea28b07d40f3cf8b485",
  1148. x"c9d5fe0d836688f4c88c221415d23e4bcabee21a6a21124bfcc9a5410a297818",
  1149. x"eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a",
  1150. ];
  1151. let publish_times = vector[
  1152. 734639463
  1153. ];
  1154. pyth::update_price_feeds_if_fresh(bytes, price_identifiers, publish_times, coins);
  1155. cleanup_test(burn_capability, mint_capability);
  1156. }
  1157. #[test(aptos_framework = @aptos_framework)]
  1158. fun test_update_price_feeds_if_fresh_fresh_data(aptos_framework: &signer) {
  1159. let (burn_capability, mint_capability, coins) = setup_test(aptos_framework, 500, 1, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), 50, 50);
  1160. // Update the price feeds
  1161. let bytes = TEST_VAAS;
  1162. let price_identifiers = vector[
  1163. x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1",
  1164. x"3b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe",
  1165. x"33832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d",
  1166. x"21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db8",
  1167. ];
  1168. let publish_times = vector[
  1169. 1663680745, 1663680730, 1663680760, 1663680720
  1170. ];
  1171. pyth::update_price_feeds_if_fresh(bytes, price_identifiers, publish_times, coins);
  1172. // Check that the cache has been updated
  1173. let expected = get_mock_price_infos();
  1174. check_price_feeds_cached(&expected);
  1175. cleanup_test(burn_capability, mint_capability);
  1176. }
  1177. #[test(aptos_framework = @aptos_framework)]
  1178. fun test_update_price_feeds_if_fresh_with_funder_fresh_data(aptos_framework: &signer) {
  1179. let update_fee = 50;
  1180. let initial_balance = 75;
  1181. let (burn_capability, mint_capability, coins) = setup_test(aptos_framework, 500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), update_fee, initial_balance);
  1182. // Create a test funder account and register it to store funds
  1183. let funder_addr = @0xbfbffd8e2af9a3e3ce20d2d2b22bd640;
  1184. let funder = account::create_account_for_test(funder_addr);
  1185. coin::register<AptosCoin>(&funder);
  1186. coin::deposit(funder_addr, coins);
  1187. assert!(pyth::get_update_fee(&TEST_VAAS) == update_fee, 1);
  1188. assert!(coin::balance<AptosCoin>(signer::address_of(&funder)) == initial_balance, 1);
  1189. assert!(coin::balance<AptosCoin>(@pyth) == 0, 1);
  1190. // Update the price feeds using the funder
  1191. let bytes = TEST_VAAS;
  1192. let price_identifiers = vector[
  1193. x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1",
  1194. x"3b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe",
  1195. x"33832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d",
  1196. x"21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db8",
  1197. ];
  1198. let publish_times = vector[
  1199. 1663680790, 1663680730, 1663680760, 1663680720
  1200. ];
  1201. pyth::update_price_feeds_if_fresh_with_funder(&funder, bytes, price_identifiers, publish_times);
  1202. // Check that the price feeds are now cached
  1203. check_price_feeds_cached(&get_mock_price_infos());
  1204. // Check that the funder's balance has decreased by the update_fee amount
  1205. assert!(coin::balance<AptosCoin>(signer::address_of(&funder)) == initial_balance - pyth::get_update_fee(&TEST_VAAS), 1);
  1206. // Check that the amount has been transferred to the Pyth contract
  1207. assert!(coin::balance<AptosCoin>(@pyth) == pyth::get_update_fee(&TEST_VAAS), 1);
  1208. cleanup_test(burn_capability, mint_capability);
  1209. }
  1210. #[test(aptos_framework = @aptos_framework)]
  1211. #[expected_failure(abort_code = 524295, location = pyth::pyth)]
  1212. fun test_update_price_feeds_if_fresh_stale_data(aptos_framework: &signer) {
  1213. let (burn_capability, mint_capability, coins) = setup_test(aptos_framework, 500, 1, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), 50, 50);
  1214. // First populate the cache
  1215. pyth::update_cache(get_mock_price_infos());
  1216. // Now attempt to update the price feeds with publish_times that are older than those we have cached
  1217. // This should abort with error::no_fresh_data()
  1218. let bytes = TEST_VAAS;
  1219. let price_identifiers = vector[
  1220. x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1",
  1221. x"3b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe",
  1222. x"33832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d",
  1223. x"21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db8",
  1224. ];
  1225. let publish_times = vector[
  1226. 67, 35, 26, 64
  1227. ];
  1228. pyth::update_price_feeds_if_fresh(bytes, price_identifiers, publish_times, coins);
  1229. cleanup_test(burn_capability, mint_capability);
  1230. }
  1231. #[test(aptos_framework = @aptos_framework)]
  1232. #[expected_failure(abort_code = 524295, location = pyth::pyth)]
  1233. fun test_update_price_feeds_if_fresh_with_funder_stale_data(aptos_framework: &signer) {
  1234. let update_fee = 50;
  1235. let initial_balance = 75;
  1236. let (burn_capability, mint_capability, coins) = setup_test(aptos_framework, 500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), update_fee, initial_balance);
  1237. // Create a test funder account and register it to store funds
  1238. let funder_addr = @0xbfbffd8e2af9a3e3ce20d2d2b22bd640;
  1239. let funder = account::create_account_for_test(funder_addr);
  1240. coin::register<AptosCoin>(&funder);
  1241. coin::deposit(funder_addr, coins);
  1242. assert!(pyth::get_update_fee(&TEST_VAAS) == update_fee, 1);
  1243. assert!(coin::balance<AptosCoin>(signer::address_of(&funder)) == initial_balance, 1);
  1244. assert!(coin::balance<AptosCoin>(@pyth) == 0, 1);
  1245. // First populate the cache
  1246. pyth::update_cache(get_mock_price_infos());
  1247. // Now attempt to update the price feeds with publish_times that are older than those we have cached
  1248. // This should abort with error::no_fresh_data()
  1249. let bytes = TEST_VAAS;
  1250. let price_identifiers = vector[
  1251. x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1",
  1252. x"3b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe",
  1253. x"33832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d",
  1254. x"21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db8",
  1255. ];
  1256. let publish_times = vector[
  1257. 100, 76, 29, 64
  1258. ];
  1259. pyth::update_price_feeds_if_fresh_with_funder(&funder, bytes, price_identifiers, publish_times);
  1260. cleanup_test(burn_capability, mint_capability);
  1261. }
  1262. }