| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- module pyth::pyth {
- use std::vector;
- use sui::tx_context::{TxContext};
- use sui::coin::{Coin};
- use sui::sui::{SUI};
- use sui::transfer::{Self};
- use sui::tx_context::{Self};
- use pyth::event::{Self as pyth_event};
- use pyth::data_source::{Self, DataSource};
- use pyth::state::{Self as state, State as PythState, DeployerCap};
- use pyth::price_info::{Self, PriceInfo, PriceInfoObject};
- use pyth::batch_price_attestation::{Self};
- use pyth::price_feed::{Self};
- use pyth::price::{Self};
- use wormhole::external_address::{Self};
- use wormhole::vaa::{Self};
- use wormhole::state::{State as WormState};
- /// Call init_and_share_state with deployer cap to initialize
- /// state and emit event corresponding to Pyth initialization.
- public entry fun init_pyth(
- deployer: DeployerCap,
- stale_price_threshold: u64,
- governance_emitter_chain_id: u64,
- governance_emitter_address: vector<u8>,
- data_sources_emitter_chain_ids: vector<u64>,
- data_sources_emitter_addresses: vector<vector<u8>>,
- update_fee: u64,
- ctx: &mut TxContext
- ) {
- state::init_and_share_state(
- deployer,
- stale_price_threshold,
- update_fee,
- data_source::new(
- governance_emitter_chain_id,
- external_address::from_bytes(governance_emitter_address)),
- parse_data_sources(
- data_sources_emitter_chain_ids,
- data_sources_emitter_addresses,
- ),
- ctx
- );
- // Emit Pyth initialization event.
- pyth_event::emit_pyth_initialization_event();
- }
- fun parse_data_sources(
- emitter_chain_ids: vector<u64>,
- emitter_addresses: vector<vector<u8>>
- ): vector<DataSource> {
- // TODO - add custom error type error::data_source_emitter_address_and_chain_ids_different_lengths()
- assert!(vector::length(&emitter_chain_ids) == vector::length(&emitter_addresses), 0);
- let sources = vector::empty();
- let i = 0;
- while (i < vector::length(&emitter_chain_ids)) {
- vector::push_back(&mut sources, data_source::new(
- *vector::borrow(&emitter_chain_ids, i),
- external_address::from_bytes(*vector::borrow(&emitter_addresses, i))
- ));
- i = i + 1;
- };
- sources
- }
- /// Create and share new price feed objects if they don't already exist.
- public fun create_price_feeds(
- worm_state: &WormState,
- pyth_state: &mut PythState,
- vaas: vector<vector<u8>>,
- ctx: &mut TxContext
- ){
- while (!vector::is_empty(&vaas)) {
- let vaa = vector::pop_back(&mut vaas);
- // Deserialize the VAA
- let vaa = vaa::parse_and_verify(worm_state, vaa, ctx);
- // Check that the VAA is from a valid data source (emitter)
- assert!(
- state::is_valid_data_source(
- pyth_state,
- data_source::new(
- (vaa::emitter_chain(&vaa) as u64),
- vaa::emitter_address(&vaa))
- ),
- 0); // TODO - use custom error message - error::invalid_data_source()
- // Deserialize the batch price attestation
- let price_infos = batch_price_attestation::destroy(batch_price_attestation::deserialize(vaa::take_payload(vaa), ctx));
- while (!vector::is_empty(&price_infos)){
- let cur_price_info = vector::pop_back(&mut price_infos);
- // Only create new Sui PriceInfoObject if not already
- // registered with the Pyth State object.
- if (!state::price_feed_object_exists(
- pyth_state,
- price_feed::get_price_identifier(
- price_info::get_price_feed(&cur_price_info)
- )
- )
- ){
- // Create and share newly created Sui PriceInfoObject containing a price feed,
- // and then register a copy of its ID with State.
- let new_price_info_object = price_info::new_price_info_object(cur_price_info, ctx);
- let price_identifier = price_info::get_price_identifier(&cur_price_info);
- let id = price_info::uid_to_inner(&new_price_info_object);
- state::register_price_info_object(pyth_state, price_identifier, id);
- transfer::public_share_object(new_price_info_object);
- }
- }
- };
- }
- /// Update PriceInfo objects and corresponding price feeds with the
- /// data in the given VAAs.
- ///
- /// The vaas argument is a vector of VAAs encoded as bytes.
- ///
- /// The javascript https://github.com/pyth-network/pyth-js/tree/main/pyth-aptos-js package
- /// should be used to fetch these VAAs from the Price Service. More information about this
- /// process can be found at https://docs.pyth.network/consume-data.
- ///
- /// The given fee must contain a sufficient number of coins to pay the update fee for the given vaas.
- /// The update fee amount can be queried by calling get_update_fee(&vaas).
- ///
- /// Please read more information about the update fee here: https://docs.pyth.network/consume-data/on-demand#fees
- public fun update_price_feeds(
- worm_state: &WormState,
- pyth_state: &PythState,
- vaas: vector<vector<u8>>,
- price_info_objects: &mut vector<PriceInfoObject>,
- fee: Coin<SUI>,
- ctx: &mut TxContext
- ) {
- // Charge the message update fee
- // TODO - error::insufficient_fee()
- //assert!(get_update_fee(&vaas) <= coin::value(&fee), 0);
- transfer::public_transfer(fee, @pyth);
- // Update the price feed from each VAA
- while (!vector::is_empty(&vaas)) {
- update_price_feed_from_single_vaa(
- worm_state,
- pyth_state,
- vector::pop_back(&mut vaas),
- price_info_objects,
- ctx
- );
- };
- }
- /// Precondition: A Sui object of type PriceInfoObject must exist for each update
- /// encoded in the worm_vaa (batch_attestation_vaa). These should be passed in
- /// via the price_info_objects argument.
- fun update_price_feed_from_single_vaa(
- worm_state: &WormState,
- pyth_state: &PythState,
- worm_vaa: vector<u8>,
- price_info_objects: &mut vector<PriceInfoObject>,
- ctx: &mut TxContext
- ) {
- // Deserialize the VAA
- let vaa = vaa::parse_and_verify(worm_state, worm_vaa, ctx);
- // Check that the VAA is from a valid data source (emitter)
- assert!(
- state::is_valid_data_source(
- pyth_state,
- data_source::new(
- (vaa::emitter_chain(&vaa) as u64),
- vaa::emitter_address(&vaa))
- ),
- 0); // TODO - use custom error message - error::invalid_data_source()
- // Deserialize the batch price attestation
- let price_infos = batch_price_attestation::destroy(batch_price_attestation::deserialize(vaa::take_payload(vaa), ctx));
- // Update price info objects.
- update_cache(price_infos, price_info_objects, ctx);
- }
- /// Update PriceInfoObjects using up-to-date PriceInfos.
- fun update_cache(
- updates: vector<PriceInfo>,
- price_info_objects: &mut vector<PriceInfoObject>,
- ctx: &mut TxContext
- ){
- while (!vector::is_empty(&updates)) {
- let update = vector::pop_back(&mut updates);
- let i = 0;
- let found = false;
- // Find PriceInfoObjects corresponding to the current update (PriceInfo).
- // TODO - This for loop might be expensive if there are a large
- // number of updates and/or price_info_objects we are updating.
- while (i < vector::length<PriceInfoObject>(price_info_objects)){
- // Check if the current price info object corresponds to the price feed that
- // the update is meant for.
- let price_info = price_info::get_price_info_from_price_info_object(vector::borrow(price_info_objects, i));
- if (price_info::get_price_identifier(&price_info) ==
- price_info::get_price_identifier(&update)){
- found = true;
- // TODO: use clock timestamp instead of epoch in the future
- pyth_event::emit_price_feed_update(price_feed::from(price_info::get_price_feed(&update)), tx_context::epoch(ctx));
- // Update the price info object with the new updated price info.
- if (is_fresh_update(&update, vector::borrow(price_info_objects, i))){
- price_info::update_price_info_object(
- vector::borrow_mut(price_info_objects, i),
- update
- );
- }
- }
- };
- if (!found){
- // TODO - throw error, since the price_feeds in price_info_objects do
- // not constitute a superset of the price_feeds to be updated
- }
- };
- vector::destroy_empty(updates);
- }
- /// Determine if the given price update is "fresh": we have nothing newer already cached for that
- /// price feed within a PriceInfoObject.
- fun is_fresh_update(update: &PriceInfo, price_info_object: &PriceInfoObject): bool {
- // Get the timestamp of the update's current price
- let price_feed = price_info::get_price_feed(update);
- let update_timestamp = price::get_timestamp(&price_feed::get_price(price_feed));
- // Get the timestamp of the cached data for the price identifier
- let cached_price_info = price_info::get_price_info_from_price_info_object(price_info_object);
- let cached_price_feed = price_info::get_price_feed(&cached_price_info);
- let cached_timestamp = price::get_timestamp(&price_feed::get_price(cached_price_feed));
- update_timestamp > cached_timestamp
- }
- }
|