pyth.move 11 KB

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