1#![deprecated = "This crate has been deprecated. Please use pyth-sdk-solana instead."]
5
6pub use self::price_conf::PriceConf;
7pub use self::error::PythError;
8
9mod entrypoint;
10mod error;
11mod price_conf;
12
13pub mod processor;
14pub mod instruction;
15
16use std::mem::size_of;
17use borsh::{BorshSerialize, BorshDeserialize};
18use bytemuck::{
19 cast_slice, from_bytes, try_cast_slice,
20 Pod, PodCastError, Zeroable,
21};
22
23#[cfg(target_arch = "bpf")]
24use solana_program::{clock::Clock, sysvar::Sysvar};
25
26solana_program::declare_id!("PythC11111111111111111111111111111111111111");
27
28pub const MAGIC : u32 = 0xa1b2c3d4;
29pub const VERSION_2 : u32 = 2;
30pub const VERSION : u32 = VERSION_2;
31pub const MAP_TABLE_SIZE : usize = 640;
32pub const PROD_ACCT_SIZE : usize = 512;
33pub const PROD_HDR_SIZE : usize = 48;
34pub const PROD_ATTR_SIZE : usize = PROD_ACCT_SIZE - PROD_HDR_SIZE;
35pub const MAX_SLOT_DIFFERENCE : u64 = 25;
36
37#[derive(Copy, Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, serde::Serialize, serde::Deserialize)]
39#[repr(C)]
40pub enum AccountType
41{
42 Unknown,
43 Mapping,
44 Product,
45 Price
46}
47
48impl Default for AccountType {
49 fn default() -> Self {
50 AccountType::Unknown
51 }
52}
53
54#[derive(Copy, Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, serde::Serialize, serde::Deserialize)]
56#[repr(C)]
57pub enum PriceStatus
58{
59 Unknown,
61 Trading,
63 Halted,
65 Auction
67}
68
69impl Default for PriceStatus {
70 fn default() -> Self {
71 PriceStatus::Unknown
72 }
73}
74
75#[derive(Copy, Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, serde::Serialize, serde::Deserialize)]
78#[repr(C)]
79pub enum CorpAction
80{
81 NoCorpAct
82}
83
84impl Default for CorpAction {
85 fn default() -> Self {
86 CorpAction::NoCorpAct
87 }
88}
89
90#[derive(Copy, Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, serde::Serialize, serde::Deserialize)]
92#[repr(C)]
93pub enum PriceType
94{
95 Unknown,
96 Price
97}
98
99impl Default for PriceType {
100 fn default() -> Self {
101 PriceType::Unknown
102 }
103}
104
105#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize, serde::Serialize, serde::Deserialize)]
107#[repr(C)]
108pub struct AccKey
109{
110 pub val: [u8;32]
111}
112
113#[derive(Copy, Clone, Debug, PartialEq, Eq)]
115#[repr(C)]
116pub struct Mapping
117{
118 pub magic : u32,
120 pub ver : u32,
122 pub atype : u32,
124 pub size : u32,
126 pub num : u32,
128 pub unused : u32,
129 pub next : AccKey,
131 pub products : [AccKey;MAP_TABLE_SIZE]
132}
133
134#[cfg(target_endian = "little")]
135unsafe impl Zeroable for Mapping {}
136
137#[cfg(target_endian = "little")]
138unsafe impl Pod for Mapping {}
139
140
141#[derive(Copy, Clone, Debug, PartialEq, Eq)]
144#[repr(C)]
145pub struct Product
146{
147 pub magic : u32,
149 pub ver : u32,
151 pub atype : u32,
153 pub size : u32,
155 pub px_acc : AccKey,
157 pub attr : [u8;PROD_ATTR_SIZE]
159}
160
161impl Product {
162 pub fn iter(&self) -> AttributeIter {
163 AttributeIter { attrs: &self.attr }
164 }
165}
166
167#[cfg(target_endian = "little")]
168unsafe impl Zeroable for Product {}
169
170#[cfg(target_endian = "little")]
171unsafe impl Pod for Product {}
172
173#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize, serde::Serialize, serde::Deserialize)]
176#[repr(C)]
177pub struct PriceInfo
178{
179 pub price : i64,
182 pub conf : u64,
185 pub status : PriceStatus,
189 pub corp_act : CorpAction,
191 pub pub_slot : u64
192}
193
194#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize, serde::Serialize, serde::Deserialize)]
196#[repr(C)]
197pub struct PriceComp
198{
199 pub publisher : AccKey,
201 pub agg : PriceInfo,
203 pub latest : PriceInfo
206
207}
208
209#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize, serde::Serialize, serde::Deserialize)]
211#[repr(C)]
212pub struct Ema
213{
214 pub val : i64,
216 pub numer : i64,
218 pub denom : i64
220}
221
222#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
224#[repr(C)]
225pub struct Price
226{
227 pub magic : u32,
229 pub ver : u32,
231 pub atype : u32,
233 pub size : u32,
235 pub ptype : PriceType,
237 pub expo : i32,
239 pub num : u32,
241 pub num_qt : u32,
243 pub last_slot : u64,
245 pub valid_slot : u64,
247 pub ema_price : Ema,
249 pub ema_confidence : Ema,
251 pub drv1 : i64,
253 pub drv2 : i64,
255 pub prod : AccKey,
257 pub next : AccKey,
259 pub prev_slot : u64,
261 pub prev_price : i64,
263 pub prev_conf : u64,
265 pub drv3 : i64,
267 pub agg : PriceInfo,
269 pub comp : [PriceComp;32]
271}
272
273#[cfg(target_endian = "little")]
274unsafe impl Zeroable for Price {}
275
276#[cfg(target_endian = "little")]
277unsafe impl Pod for Price {}
278
279impl Price {
280 pub fn get_current_price_status(&self) -> PriceStatus {
285 #[cfg(target_arch = "bpf")]
286 if matches!(self.agg.status, PriceStatus::Trading) &&
287 Clock::get().unwrap().slot - self.agg.pub_slot > MAX_SLOT_DIFFERENCE {
288 return PriceStatus::Unknown;
289 }
290 self.agg.status
291 }
292
293 pub fn get_current_price(&self) -> Option<PriceConf> {
299 if !matches!(self.get_current_price_status(), PriceStatus::Trading) {
300 None
301 } else {
302 Some(PriceConf {
303 price: self.agg.price,
304 conf: self.agg.conf,
305 expo: self.expo
306 })
307 }
308 }
309
310 pub fn get_ema_price(&self) -> Option<PriceConf> {
318 Some(PriceConf { price: self.ema_price.val, conf: self.ema_confidence.val as u64, expo: self.expo })
321 }
322
323 pub fn get_price_in_quote(&self, quote: &Price, result_expo: i32) -> Option<PriceConf> {
334 return match (self.get_current_price(), quote.get_current_price()) {
335 (Some(base_price_conf), Some(quote_price_conf)) =>
336 base_price_conf.div("e_price_conf)?.scale_to_exponent(result_expo),
337 (_, _) => None,
338 }
339 }
340
341 pub fn price_basket(amounts: &[(Price, i64, i32)], result_expo: i32) -> Option<PriceConf> {
349 assert!(amounts.len() > 0);
350 let mut res = PriceConf { price: 0, conf: 0, expo: result_expo };
351 for i in 0..amounts.len() {
352 res = res.add(
353 &amounts[i].0.get_current_price()?.cmul(amounts[i].1, amounts[i].2)?.scale_to_exponent(result_expo)?
354 )?
355 }
356 Some(res)
357 }
358}
359
360#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
361struct AccKeyU64
362{
363 pub val: [u64;4]
364}
365
366#[cfg(target_endian = "little")]
367unsafe impl Zeroable for AccKeyU64 {}
368
369#[cfg(target_endian = "little")]
370unsafe impl Pod for AccKeyU64 {}
371
372impl AccKey
373{
374 pub fn is_valid( &self ) -> bool {
375 match load::<AccKeyU64>( &self.val ) {
376 Ok(k8) => k8.val[0]!=0 || k8.val[1]!=0 || k8.val[2]!=0 || k8.val[3]!=0,
377 Err(_) => false,
378 }
379 }
380}
381
382fn load<T: Pod>(data: &[u8]) -> Result<&T, PodCastError> {
383 let size = size_of::<T>();
384 if data.len() >= size {
385 Ok(from_bytes(cast_slice::<u8, u8>(try_cast_slice(
386 &data[0..size],
387 )?)))
388 } else {
389 Err(PodCastError::SizeMismatch)
390 }
391}
392
393pub fn load_mapping(data: &[u8]) -> Result<&Mapping, PythError> {
395 let pyth_mapping = load::<Mapping>(&data).map_err(|_| PythError::InvalidAccountData)?;
396
397 if pyth_mapping.magic != MAGIC {
398 return Err(PythError::InvalidAccountData);
399 }
400 if pyth_mapping.ver != VERSION_2 {
401 return Err(PythError::BadVersionNumber);
402 }
403 if pyth_mapping.atype != AccountType::Mapping as u32 {
404 return Err(PythError::WrongAccountType);
405 }
406
407 return Ok(pyth_mapping);
408}
409
410pub fn load_product(data: &[u8]) -> Result<&Product, PythError> {
412 let pyth_product = load::<Product>(&data).map_err(|_| PythError::InvalidAccountData)?;
413
414 if pyth_product.magic != MAGIC {
415 return Err(PythError::InvalidAccountData);
416 }
417 if pyth_product.ver != VERSION_2 {
418 return Err(PythError::BadVersionNumber);
419 }
420 if pyth_product.atype != AccountType::Product as u32 {
421 return Err(PythError::WrongAccountType);
422 }
423
424 return Ok(pyth_product);
425}
426
427pub fn load_price(data: &[u8]) -> Result<&Price, PythError> {
429 let pyth_price = load::<Price>(&data).map_err(|_| PythError::InvalidAccountData)?;
430
431 if pyth_price.magic != MAGIC {
432 return Err(PythError::InvalidAccountData);
433 }
434 if pyth_price.ver != VERSION_2 {
435 return Err(PythError::BadVersionNumber);
436 }
437 if pyth_price.atype != AccountType::Price as u32 {
438 return Err(PythError::WrongAccountType);
439 }
440
441 return Ok(pyth_price);
442}
443
444
445pub struct AttributeIter<'a> {
446 attrs: &'a [u8],
447}
448
449impl<'a> Iterator for AttributeIter<'a> {
450 type Item = (&'a str, &'a str);
451
452 fn next(&mut self) -> Option<Self::Item> {
453 if self.attrs.is_empty() {
454 return None;
455 }
456 let (key, data) = get_attr_str(self.attrs);
457 let (val, data) = get_attr_str(data);
458 self.attrs = data;
459 return Some((key, val));
460 }
461}
462
463fn get_attr_str(buf: &[u8]) -> (&str, &[u8]) {
464 if buf.is_empty() {
465 return ("", &[]);
466 }
467 let len = buf[0] as usize;
468 let str = std::str::from_utf8(&buf[1..len + 1]).expect("attr should be ascii or utf-8");
469 let remaining_buf = &buf[len + 1..];
470 (str, remaining_buf)
471}