gmsol/pyth/pull_oracle/
utils.rs

1use gmsol_store::states::{common::TokensWithFeed, PriceProviderKind};
2use pyth_sdk::Identifier;
3use pythnet_sdk::{
4    messages::PriceFeedMessage,
5    wire::{
6        from_slice,
7        v1::{AccumulatorUpdateData, MerklePriceUpdate, Proof},
8    },
9};
10
11use crate::store::utils::Feeds;
12
13use super::hermes::{BinaryPriceUpdate, EncodingType};
14
15/// Parse [`AccumulatorUpdateData`] from price update.
16pub fn parse_accumulator_update_datas(
17    update: &BinaryPriceUpdate,
18) -> crate::Result<Vec<AccumulatorUpdateData>> {
19    let datas = match update.encoding {
20        EncodingType::Base64 => {
21            use base64::{engine::general_purpose::STANDARD, Engine};
22
23            update
24                .data
25                .iter()
26                .map(|data| {
27                    STANDARD
28                        .decode(data)
29                        .map_err(crate::Error::from)
30                        .and_then(|data| parse_accumulator_update_data(&data))
31                })
32                .collect::<crate::Result<Vec<_>>>()?
33        }
34        EncodingType::Hex => {
35            unimplemented!()
36        }
37    };
38    Ok(datas)
39}
40
41#[inline]
42fn parse_accumulator_update_data(data: &[u8]) -> crate::Result<AccumulatorUpdateData> {
43    AccumulatorUpdateData::try_from_slice(data).map_err(crate::Error::unknown)
44}
45
46/// Get guardian set index from [`Proof`].
47pub fn get_guardian_set_index(proof: &Proof) -> crate::Result<i32> {
48    let vaa = get_vaa_buffer(proof);
49    if vaa.len() < 5 {
50        return Err(crate::Error::unknown("invalid vaa"));
51    }
52    let index: &[u8; 4] = (&vaa[1..5]).try_into().map_err(crate::Error::unknown)?;
53    Ok(i32::from_be_bytes(*index))
54}
55
56/// Get vaa buffer.
57pub fn get_vaa_buffer(proof: &Proof) -> &[u8] {
58    match proof {
59        Proof::WormholeMerkle { vaa, .. } => vaa.as_ref(),
60    }
61}
62
63/// Get merkle price updates.
64pub fn get_merkle_price_updates(proof: &Proof) -> &[MerklePriceUpdate] {
65    match proof {
66        Proof::WormholeMerkle { updates, .. } => updates,
67    }
68}
69
70/// Price feed message variant.
71pub const PRICE_FEED_MESSAGE_VARIANT: u8 = 0;
72
73/// Parse price feed message.
74pub fn parse_price_feed_message(update: &MerklePriceUpdate) -> crate::Result<PriceFeedMessage> {
75    const PRICE_FEED_MESSAGE_VARIANT: u8 = 0;
76    let data = update.message.as_ref().as_slice();
77    if data.is_empty() {
78        return Err(crate::Error::invalid_argument("empty message"));
79    }
80    if data[0] != PRICE_FEED_MESSAGE_VARIANT {
81        return Err(crate::Error::invalid_argument(
82            "it is not a price feed message",
83        ));
84    }
85    from_slice::<byteorder::BE, _>(&data[1..]).map_err(|err| {
86        crate::Error::invalid_argument(format!("deserialize price feed message error: {err}"))
87    })
88}
89
90/// Parse feed id from [`MerklePriceUpdate`].
91pub fn parse_feed_id(update: &MerklePriceUpdate) -> crate::Result<Identifier> {
92    let feed_id = parse_price_feed_message(update)?.feed_id;
93    Ok(Identifier::new(feed_id))
94}
95
96/// Extract pyth feed ids from [`TokensWithFeed`].
97pub fn extract_pyth_feed_ids(feeds: &TokensWithFeed) -> crate::Result<Vec<Identifier>> {
98    Feeds::new(feeds)
99        .filter_map(|res| {
100            res.map(|config| {
101                if matches!(config.provider, PriceProviderKind::Pyth) {
102                    Some(Identifier::new(config.feed.to_bytes()))
103                } else {
104                    None
105                }
106            })
107            .transpose()
108        })
109        .collect()
110}