Skip to content
This repository was archived by the owner on Jun 30, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,23 @@ description = "pyth price oracle data structures and example usage"
keywords = [ "pyth", "solana", "oracle" ]
readme = "README.md"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
test-bpf = []
no-entrypoint = []

[dependencies]
solana-program = "1.8.1"
borsh = "0.9"
borsh-derive = "0.9.0"

[dev-dependencies]
solana-client = "1.6.7"
solana-sdk = "1.6.7"
solana-program = "1.6.7"
solana-program-test = "1.8.1"
solana-client = "1.8.1"
solana-sdk = "1.8.1"

[lib]
crate-type = ["cdylib", "lib"]

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# pyth-client-rs

A rust API for desribing on-chain pyth account structures. A primer on pyth accounts can be found at https://github.com/pyth-network/pyth-client/blob/main/doc/aggregate_price.md
A rust API for describing on-chain pyth account structures. A primer on pyth accounts can be found at https://github.com/pyth-network/pyth-client/blob/main/doc/aggregate_price.md


Contains a library for use in on-chain program development and an off-chain example program for loading and printing product reference data and aggregate prices from all devnet pyth accounts.
Expand Down Expand Up @@ -37,4 +37,4 @@ product_account .. 6MEwdxe4g1NeAF9u6KDG14anJpFsVEa2cvr5H6iriFZ8
publish_slot . 91340925
twap ......... 7426390900
twac ......... 2259870
```
```
2 changes: 2 additions & 0 deletions Xargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []
14 changes: 7 additions & 7 deletions examples/get_accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,9 @@ fn main() {

let maybe_price = pa.get_current_price();
match maybe_price {
Some((price, confidence, expo)) => {
println!(" price ........ {} x 10^{}", price, expo);
println!(" conf ......... {} x 10^{}", confidence, expo);
Some(p) => {
println!(" price ........ {} x 10^{}", p.price, p.expo);
println!(" conf ......... {} x 10^{}", p.conf, p.expo);
}
None => {
println!(" price ........ unavailable");
Expand All @@ -138,16 +138,16 @@ fn main() {

let maybe_twap = pa.get_twap();
match maybe_twap {
Some((twap, expo)) => {
println!( " twap ......... {} x 10^{}", twap, expo );
Some(twap) => {
println!( " twap ......... {} x 10^{}", twap.price, twap.expo );
println!( " twac ......... {} x 10^{}", twap.conf, twap.expo );
}
None => {
println!( " twap ......... unavailable");
println!( " twac ......... unavailable");
}
}

println!( " twac ......... {}", pa.twac.val );

// go to next price account in list
if pa.next.is_valid() {
px_pkey = Pubkey::new( &pa.next.val );
Expand Down
16 changes: 16 additions & 0 deletions src/entrypoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//! Program entrypoint

#![cfg(not(feature = "no-entrypoint"))]

use solana_program::{
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,
};

entrypoint!(process_instruction);
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
crate::processor::process_instruction(program_id, accounts, instruction_data)
}
55 changes: 55 additions & 0 deletions src/instruction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//! Program instructions, used for end-to-end testing and instruction counts

use {
crate::id,
borsh::{BorshDeserialize, BorshSerialize},
solana_program::instruction::Instruction,
crate::PriceConf,
};

/// Instructions supported by the pyth-client program, used for testing and
/// instruction counts
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)]
pub enum PythClientInstruction {
Divide {
numerator: PriceConf,
denominator: PriceConf,
},
Multiply {
x: PriceConf,
y: PriceConf,
},
/// Don't do anything for comparison
///
/// No accounts required for this instruction
Noop,
}

pub fn divide(numerator: PriceConf, denominator: PriceConf) -> Instruction {
Instruction {
program_id: id(),
accounts: vec![],
data: PythClientInstruction::Divide { numerator, denominator }
.try_to_vec()
.unwrap(),
}
}

pub fn multiply(x: PriceConf, y: PriceConf) -> Instruction {
Instruction {
program_id: id(),
accounts: vec![],
data: PythClientInstruction::Multiply { x, y }
.try_to_vec()
.unwrap(),
}
}

/// Noop instruction for comparison purposes
pub fn noop() -> Instruction {
Instruction {
program_id: id(),
accounts: vec![],
data: PythClientInstruction::Noop.try_to_vec().unwrap(),
}
}
77 changes: 60 additions & 17 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
pub use self::price_conf::PriceConf;

mod entrypoint;
pub mod processor;
pub mod instruction;

mod price_conf;
solana_program::declare_id!("PythC11111111111111111111111111111111111111");

pub const MAGIC : u32 = 0xa1b2c3d4;
pub const VERSION_2 : u32 = 2;
pub const VERSION : u32 = VERSION_2;
Expand Down Expand Up @@ -134,34 +143,68 @@ pub struct Price
impl Price {
/**
* Get the current price and confidence interval as fixed-point numbers of the form a * 10^e.
* Returns a triple of the current price, confidence interval, and the exponent for both
* numbers. For example:
*
* get_current_price() -> Some((12345, 267, -2)) // represents 123.45 +- 2.67
* get_current_price() -> Some((123, 1, 2)) // represents 12300 +- 100
*
* Returns None if price information is currently unavailable.
* Returns a struct containing the current price, confidence interval, and the exponent for both
* numbers. Returns None if price information is currently unavailable.
*/
pub fn get_current_price(&self) -> Option<(i64, u64, i32)> {
pub fn get_current_price(&self) -> Option<PriceConf> {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should also offer to return an enum

Live(Price)
Stale(OldPrice, Age)
"Dead"

if !matches!(self.agg.status, PriceStatus::Trading) {
None
} else {
Some((self.agg.price, self.agg.conf, self.expo))
Some(PriceConf {
price: self.agg.price,
conf: self.agg.conf,
expo: self.expo
})
}
}

/**
* Get the time-weighted average price (TWAP) as a fixed point number of the form a * 10^e.
* Returns a tuple of the current twap and its exponent. For example:
*
* get_twap() -> Some((123, -2)) // represents 1.23
* get_twap() -> Some((45, 3)) // represents 45000
*
* Get the time-weighted average price (TWAP) and a confidence interval on the result.
* Returns None if the twap is currently unavailable.
*
* At the moment, the confidence interval returned by this method is computed in
* a somewhat questionable way, so we do not recommend using it for high-value applications.
*/
pub fn get_twap(&self) -> Option<(i64, i32)> {
pub fn get_twap(&self) -> Option<PriceConf> {
// This method currently cannot return None, but may do so in the future.
Some((self.twap.val, self.expo))
// Note that the twac is a positive number in i64, so safe to cast to u64.
Some(PriceConf { price: self.twap.val, conf: self.twac.val as u64, expo: self.expo })
}

/**
* Get the current price of this account in a different quote currency. If this account
* represents the price of the product X/Z, and `quote` represents the price of the product Y/Z,
* this method returns the price of X/Y. Use this method to get the price of e.g., mSOL/SOL from
* the mSOL/USD and SOL/USD accounts.
*
* `result_expo` determines the exponent of the result, i.e., the number of digits below the decimal
* point. This method returns `None` if either the price or confidence are too large to be
* represented with the requested exponent.
*/
pub fn get_price_in_quote(&self, quote: &Price, result_expo: i32) -> Option<PriceConf> {
return match (self.get_current_price(), quote.get_current_price()) {
(Some(base_price_conf), Some(quote_price_conf)) =>
base_price_conf.div(&quote_price_conf)?.scale_to_exponent(result_expo),
(_, _) => None,
}
}

/**
* Get the price of a basket of currencies. Each entry in `amounts` is of the form
* `(price, qty, qty_expo)`, and the result is the sum of `price * qty * 10^qty_expo`.
* The result is returned with exponent `result_expo`.
*
* An example use case for this function is to get the value of an LP token.
*/
pub fn price_basket(amounts: &[(Price, i64, i32)], result_expo: i32) -> Option<PriceConf> {
assert!(amounts.len() > 0);
let mut res = PriceConf { price: 0, conf: 0, expo: result_expo };
for i in 0..amounts.len() {
res = res.add(
&amounts[i].0.get_current_price()?.cmul(amounts[i].1, amounts[i].2)?.scale_to_exponent(result_expo)?
)?
}
Some(res)
}
}

Expand Down
Loading