pyth.move 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. module pyth::pyth {
  2. use std::vector;
  3. use sui::tx_context::{TxContext};
  4. use sui::coin::{Coin};
  5. use sui::sui::{SUI};
  6. use sui::transfer::{Self};
  7. use sui::tx_context::{Self};
  8. use pyth::event::{Self as pyth_event};
  9. use pyth::data_source::{Self, DataSource};
  10. use pyth::state::{Self as state, State as PythState, DeployerCap};
  11. use pyth::price_info::{Self, PriceInfo, PriceInfoObject};
  12. use pyth::batch_price_attestation::{Self};
  13. use pyth::price_feed::{Self};
  14. use pyth::price::{Self};
  15. use wormhole::external_address::{Self};
  16. use wormhole::vaa::{Self};
  17. use wormhole::state::{State as WormState};
  18. /// Call init_and_share_state with deployer cap to initialize
  19. /// state and emit event corresponding to Pyth initialization.
  20. public entry fun init_pyth(
  21. deployer: DeployerCap,
  22. stale_price_threshold: u64,
  23. governance_emitter_chain_id: u64,
  24. governance_emitter_address: vector<u8>,
  25. data_sources_emitter_chain_ids: vector<u64>,
  26. data_sources_emitter_addresses: vector<vector<u8>>,
  27. update_fee: u64,
  28. ctx: &mut TxContext
  29. ) {
  30. state::init_and_share_state(
  31. deployer,
  32. stale_price_threshold,
  33. update_fee,
  34. data_source::new(
  35. governance_emitter_chain_id,
  36. external_address::from_bytes(governance_emitter_address)),
  37. parse_data_sources(
  38. data_sources_emitter_chain_ids,
  39. data_sources_emitter_addresses,
  40. ),
  41. ctx
  42. );
  43. // Emit Pyth initialization event.
  44. pyth_event::emit_pyth_initialization_event();
  45. }
  46. fun parse_data_sources(
  47. emitter_chain_ids: vector<u64>,
  48. emitter_addresses: vector<vector<u8>>
  49. ): vector<DataSource> {
  50. // TODO - add custom error type error::data_source_emitter_address_and_chain_ids_different_lengths()
  51. assert!(vector::length(&emitter_chain_ids) == vector::length(&emitter_addresses), 0);
  52. let sources = vector::empty();
  53. let i = 0;
  54. while (i < vector::length(&emitter_chain_ids)) {
  55. vector::push_back(&mut sources, data_source::new(
  56. *vector::borrow(&emitter_chain_ids, i),
  57. external_address::from_bytes(*vector::borrow(&emitter_addresses, i))
  58. ));
  59. i = i + 1;
  60. };
  61. sources
  62. }
  63. /// Create and share new price feed objects if they don't already exist.
  64. public fun create_price_feeds(
  65. worm_state: &WormState,
  66. pyth_state: &mut PythState,
  67. vaas: vector<vector<u8>>,
  68. ctx: &mut TxContext
  69. ){
  70. while (!vector::is_empty(&vaas)) {
  71. let vaa = vector::pop_back(&mut vaas);
  72. // Deserialize the VAA
  73. let vaa = vaa::parse_and_verify(worm_state, vaa, ctx);
  74. // Check that the VAA is from a valid data source (emitter)
  75. assert!(
  76. state::is_valid_data_source(
  77. pyth_state,
  78. data_source::new(
  79. (vaa::emitter_chain(&vaa) as u64),
  80. vaa::emitter_address(&vaa))
  81. ),
  82. 0); // TODO - use custom error message - error::invalid_data_source()
  83. // Deserialize the batch price attestation
  84. let price_infos = batch_price_attestation::destroy(batch_price_attestation::deserialize(vaa::take_payload(vaa), ctx));
  85. while (!vector::is_empty(&price_infos)){
  86. let cur_price_info = vector::pop_back(&mut price_infos);
  87. // Only create new Sui PriceInfoObject if not already
  88. // registered with the Pyth State object.
  89. if (!state::price_feed_object_exists(
  90. pyth_state,
  91. price_feed::get_price_identifier(
  92. price_info::get_price_feed(&cur_price_info)
  93. )
  94. )
  95. ){
  96. // Create and share newly created Sui PriceInfoObject containing a price feed,
  97. // and then register a copy of its ID with State.
  98. let new_price_info_object = price_info::new_price_info_object(cur_price_info, ctx);
  99. let price_identifier = price_info::get_price_identifier(&cur_price_info);
  100. let id = price_info::uid_to_inner(&new_price_info_object);
  101. state::register_price_info_object(pyth_state, price_identifier, id);
  102. transfer::public_share_object(new_price_info_object);
  103. }
  104. }
  105. };
  106. }
  107. /// Update PriceInfo objects and corresponding price feeds with the
  108. /// data in the given VAAs.
  109. ///
  110. /// The vaas argument is a vector of VAAs encoded as bytes.
  111. ///
  112. /// The javascript https://github.com/pyth-network/pyth-js/tree/main/pyth-aptos-js package
  113. /// should be used to fetch these VAAs from the Price Service. More information about this
  114. /// process can be found at https://docs.pyth.network/consume-data.
  115. ///
  116. /// The given fee must contain a sufficient number of coins to pay the update fee for the given vaas.
  117. /// The update fee amount can be queried by calling get_update_fee(&vaas).
  118. ///
  119. /// Please read more information about the update fee here: https://docs.pyth.network/consume-data/on-demand#fees
  120. public fun update_price_feeds(
  121. worm_state: &WormState,
  122. pyth_state: &PythState,
  123. vaas: vector<vector<u8>>,
  124. price_info_objects: &mut vector<PriceInfoObject>,
  125. fee: Coin<SUI>,
  126. ctx: &mut TxContext
  127. ) {
  128. // Charge the message update fee
  129. // TODO - error::insufficient_fee()
  130. //assert!(get_update_fee(&vaas) <= coin::value(&fee), 0);
  131. transfer::public_transfer(fee, @pyth);
  132. // Update the price feed from each VAA
  133. while (!vector::is_empty(&vaas)) {
  134. update_price_feed_from_single_vaa(
  135. worm_state,
  136. pyth_state,
  137. vector::pop_back(&mut vaas),
  138. price_info_objects,
  139. ctx
  140. );
  141. };
  142. }
  143. /// Precondition: A Sui object of type PriceInfoObject must exist for each update
  144. /// encoded in the worm_vaa (batch_attestation_vaa). These should be passed in
  145. /// via the price_info_objects argument.
  146. fun update_price_feed_from_single_vaa(
  147. worm_state: &WormState,
  148. pyth_state: &PythState,
  149. worm_vaa: vector<u8>,
  150. price_info_objects: &mut vector<PriceInfoObject>,
  151. ctx: &mut TxContext
  152. ) {
  153. // Deserialize the VAA
  154. let vaa = vaa::parse_and_verify(worm_state, worm_vaa, ctx);
  155. // Check that the VAA is from a valid data source (emitter)
  156. assert!(
  157. state::is_valid_data_source(
  158. pyth_state,
  159. data_source::new(
  160. (vaa::emitter_chain(&vaa) as u64),
  161. vaa::emitter_address(&vaa))
  162. ),
  163. 0); // TODO - use custom error message - error::invalid_data_source()
  164. // Deserialize the batch price attestation
  165. let price_infos = batch_price_attestation::destroy(batch_price_attestation::deserialize(vaa::take_payload(vaa), ctx));
  166. // Update price info objects.
  167. update_cache(price_infos, price_info_objects, ctx);
  168. }
  169. /// Update PriceInfoObjects using up-to-date PriceInfos.
  170. fun update_cache(
  171. updates: vector<PriceInfo>,
  172. price_info_objects: &mut vector<PriceInfoObject>,
  173. ctx: &mut TxContext
  174. ){
  175. while (!vector::is_empty(&updates)) {
  176. let update = vector::pop_back(&mut updates);
  177. let i = 0;
  178. let found = false;
  179. // Find PriceInfoObjects corresponding to the current update (PriceInfo).
  180. // TODO - This for loop might be expensive if there are a large
  181. // number of updates and/or price_info_objects we are updating.
  182. while (i < vector::length<PriceInfoObject>(price_info_objects)){
  183. // Check if the current price info object corresponds to the price feed that
  184. // the update is meant for.
  185. let price_info = price_info::get_price_info_from_price_info_object(vector::borrow(price_info_objects, i));
  186. if (price_info::get_price_identifier(&price_info) ==
  187. price_info::get_price_identifier(&update)){
  188. found = true;
  189. // TODO: use clock timestamp instead of epoch in the future
  190. pyth_event::emit_price_feed_update(price_feed::from(price_info::get_price_feed(&update)), tx_context::epoch(ctx));
  191. // Update the price info object with the new updated price info.
  192. if (is_fresh_update(&update, vector::borrow(price_info_objects, i))){
  193. price_info::update_price_info_object(
  194. vector::borrow_mut(price_info_objects, i),
  195. update
  196. );
  197. }
  198. }
  199. };
  200. if (!found){
  201. // TODO - throw error, since the price_feeds in price_info_objects do
  202. // not constitute a superset of the price_feeds to be updated
  203. }
  204. };
  205. vector::destroy_empty(updates);
  206. }
  207. /// Determine if the given price update is "fresh": we have nothing newer already cached for that
  208. /// price feed within a PriceInfoObject.
  209. fun is_fresh_update(update: &PriceInfo, price_info_object: &PriceInfoObject): bool {
  210. // Get the timestamp of the update's current price
  211. let price_feed = price_info::get_price_feed(update);
  212. let update_timestamp = price::get_timestamp(&price_feed::get_price(price_feed));
  213. // Get the timestamp of the cached data for the price identifier
  214. let cached_price_info = price_info::get_price_info_from_price_info_object(price_info_object);
  215. let cached_price_feed = price_info::get_price_feed(&cached_price_info);
  216. let cached_timestamp = price::get_timestamp(&price_feed::get_price(cached_price_feed));
  217. update_timestamp > cached_timestamp
  218. }
  219. }