Skip to content
This repository was archived by the owner on Jun 30, 2022. It is now read-only.

Commit 25cbde5

Browse files
jayantkJayant Krishnamurthy
andauthored
Helper method for pricing a base currency in a quote currency (#8)
* Add method for getting twap * initial implementation, seems to work * minor * minor * refactor * found bad case * use u128 * working on it * clarify * cleanup * more cleanup * pretty sure i need this * better * bad merge * no println * adding solana tx stuff * change approach a bit * this seems to work * comment * cleanup * refactor * refactor * initial implementation of mul * exponent * tests for normalize * tests for normalize * negative numbers in div * handle negative numbers * comments * stuff * cleanup * unused * minor Co-authored-by: Jayant Krishnamurthy <[email protected]>
1 parent 396b7e9 commit 25cbde5

File tree

10 files changed

+859
-30
lines changed

10 files changed

+859
-30
lines changed

Cargo.toml

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,23 @@ description = "pyth price oracle data structures and example usage"
1010
keywords = [ "pyth", "solana", "oracle" ]
1111
readme = "README.md"
1212

13-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13+
[features]
14+
test-bpf = []
15+
no-entrypoint = []
16+
17+
[dependencies]
18+
solana-program = "1.8.1"
19+
borsh = "0.9"
20+
borsh-derive = "0.9.0"
1421

1522
[dev-dependencies]
16-
solana-client = "1.6.7"
17-
solana-sdk = "1.6.7"
18-
solana-program = "1.6.7"
23+
solana-program-test = "1.8.1"
24+
solana-client = "1.8.1"
25+
solana-sdk = "1.8.1"
26+
27+
[lib]
28+
crate-type = ["cdylib", "lib"]
29+
30+
[package.metadata.docs.rs]
31+
targets = ["x86_64-unknown-linux-gnu"]
1932

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# pyth-client-rs
22

3-
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
3+
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
44

55

66
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.
@@ -37,4 +37,4 @@ product_account .. 6MEwdxe4g1NeAF9u6KDG14anJpFsVEa2cvr5H6iriFZ8
3737
publish_slot . 91340925
3838
twap ......... 7426390900
3939
twac ......... 2259870
40-
```
40+
```

Xargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[target.bpfel-unknown-unknown.dependencies.std]
2+
features = []

examples/get_accounts.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,9 @@ fn main() {
117117

118118
let maybe_price = pa.get_current_price();
119119
match maybe_price {
120-
Some((price, confidence, expo)) => {
121-
println!(" price ........ {} x 10^{}", price, expo);
122-
println!(" conf ......... {} x 10^{}", confidence, expo);
120+
Some(p) => {
121+
println!(" price ........ {} x 10^{}", p.price, p.expo);
122+
println!(" conf ......... {} x 10^{}", p.conf, p.expo);
123123
}
124124
None => {
125125
println!(" price ........ unavailable");
@@ -138,16 +138,16 @@ fn main() {
138138

139139
let maybe_twap = pa.get_twap();
140140
match maybe_twap {
141-
Some((twap, expo)) => {
142-
println!( " twap ......... {} x 10^{}", twap, expo );
141+
Some(twap) => {
142+
println!( " twap ......... {} x 10^{}", twap.price, twap.expo );
143+
println!( " twac ......... {} x 10^{}", twap.conf, twap.expo );
143144
}
144145
None => {
145146
println!( " twap ......... unavailable");
147+
println!( " twac ......... unavailable");
146148
}
147149
}
148150

149-
println!( " twac ......... {}", pa.twac.val );
150-
151151
// go to next price account in list
152152
if pa.next.is_valid() {
153153
px_pkey = Pubkey::new( &pa.next.val );

src/entrypoint.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//! Program entrypoint
2+
3+
#![cfg(not(feature = "no-entrypoint"))]
4+
5+
use solana_program::{
6+
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,
7+
};
8+
9+
entrypoint!(process_instruction);
10+
fn process_instruction(
11+
program_id: &Pubkey,
12+
accounts: &[AccountInfo],
13+
instruction_data: &[u8],
14+
) -> ProgramResult {
15+
crate::processor::process_instruction(program_id, accounts, instruction_data)
16+
}

src/instruction.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//! Program instructions, used for end-to-end testing and instruction counts
2+
3+
use {
4+
crate::id,
5+
borsh::{BorshDeserialize, BorshSerialize},
6+
solana_program::instruction::Instruction,
7+
crate::PriceConf,
8+
};
9+
10+
/// Instructions supported by the pyth-client program, used for testing and
11+
/// instruction counts
12+
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)]
13+
pub enum PythClientInstruction {
14+
Divide {
15+
numerator: PriceConf,
16+
denominator: PriceConf,
17+
},
18+
Multiply {
19+
x: PriceConf,
20+
y: PriceConf,
21+
},
22+
/// Don't do anything for comparison
23+
///
24+
/// No accounts required for this instruction
25+
Noop,
26+
}
27+
28+
pub fn divide(numerator: PriceConf, denominator: PriceConf) -> Instruction {
29+
Instruction {
30+
program_id: id(),
31+
accounts: vec![],
32+
data: PythClientInstruction::Divide { numerator, denominator }
33+
.try_to_vec()
34+
.unwrap(),
35+
}
36+
}
37+
38+
pub fn multiply(x: PriceConf, y: PriceConf) -> Instruction {
39+
Instruction {
40+
program_id: id(),
41+
accounts: vec![],
42+
data: PythClientInstruction::Multiply { x, y }
43+
.try_to_vec()
44+
.unwrap(),
45+
}
46+
}
47+
48+
/// Noop instruction for comparison purposes
49+
pub fn noop() -> Instruction {
50+
Instruction {
51+
program_id: id(),
52+
accounts: vec![],
53+
data: PythClientInstruction::Noop.try_to_vec().unwrap(),
54+
}
55+
}

src/lib.rs

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
pub use self::price_conf::PriceConf;
2+
3+
mod entrypoint;
4+
pub mod processor;
5+
pub mod instruction;
6+
7+
mod price_conf;
8+
solana_program::declare_id!("PythC11111111111111111111111111111111111111");
9+
110
pub const MAGIC : u32 = 0xa1b2c3d4;
211
pub const VERSION_2 : u32 = 2;
312
pub const VERSION : u32 = VERSION_2;
@@ -134,34 +143,68 @@ pub struct Price
134143
impl Price {
135144
/**
136145
* Get the current price and confidence interval as fixed-point numbers of the form a * 10^e.
137-
* Returns a triple of the current price, confidence interval, and the exponent for both
138-
* numbers. For example:
139-
*
140-
* get_current_price() -> Some((12345, 267, -2)) // represents 123.45 +- 2.67
141-
* get_current_price() -> Some((123, 1, 2)) // represents 12300 +- 100
142-
*
143-
* Returns None if price information is currently unavailable.
146+
* Returns a struct containing the current price, confidence interval, and the exponent for both
147+
* numbers. Returns None if price information is currently unavailable.
144148
*/
145-
pub fn get_current_price(&self) -> Option<(i64, u64, i32)> {
149+
pub fn get_current_price(&self) -> Option<PriceConf> {
146150
if !matches!(self.agg.status, PriceStatus::Trading) {
147151
None
148152
} else {
149-
Some((self.agg.price, self.agg.conf, self.expo))
153+
Some(PriceConf {
154+
price: self.agg.price,
155+
conf: self.agg.conf,
156+
expo: self.expo
157+
})
150158
}
151159
}
152160

153161
/**
154-
* Get the time-weighted average price (TWAP) as a fixed point number of the form a * 10^e.
155-
* Returns a tuple of the current twap and its exponent. For example:
156-
*
157-
* get_twap() -> Some((123, -2)) // represents 1.23
158-
* get_twap() -> Some((45, 3)) // represents 45000
159-
*
162+
* Get the time-weighted average price (TWAP) and a confidence interval on the result.
160163
* Returns None if the twap is currently unavailable.
164+
*
165+
* At the moment, the confidence interval returned by this method is computed in
166+
* a somewhat questionable way, so we do not recommend using it for high-value applications.
161167
*/
162-
pub fn get_twap(&self) -> Option<(i64, i32)> {
168+
pub fn get_twap(&self) -> Option<PriceConf> {
163169
// This method currently cannot return None, but may do so in the future.
164-
Some((self.twap.val, self.expo))
170+
// Note that the twac is a positive number in i64, so safe to cast to u64.
171+
Some(PriceConf { price: self.twap.val, conf: self.twac.val as u64, expo: self.expo })
172+
}
173+
174+
/**
175+
* Get the current price of this account in a different quote currency. If this account
176+
* represents the price of the product X/Z, and `quote` represents the price of the product Y/Z,
177+
* this method returns the price of X/Y. Use this method to get the price of e.g., mSOL/SOL from
178+
* the mSOL/USD and SOL/USD accounts.
179+
*
180+
* `result_expo` determines the exponent of the result, i.e., the number of digits below the decimal
181+
* point. This method returns `None` if either the price or confidence are too large to be
182+
* represented with the requested exponent.
183+
*/
184+
pub fn get_price_in_quote(&self, quote: &Price, result_expo: i32) -> Option<PriceConf> {
185+
return match (self.get_current_price(), quote.get_current_price()) {
186+
(Some(base_price_conf), Some(quote_price_conf)) =>
187+
base_price_conf.div(&quote_price_conf)?.scale_to_exponent(result_expo),
188+
(_, _) => None,
189+
}
190+
}
191+
192+
/**
193+
* Get the price of a basket of currencies. Each entry in `amounts` is of the form
194+
* `(price, qty, qty_expo)`, and the result is the sum of `price * qty * 10^qty_expo`.
195+
* The result is returned with exponent `result_expo`.
196+
*
197+
* An example use case for this function is to get the value of an LP token.
198+
*/
199+
pub fn price_basket(amounts: &[(Price, i64, i32)], result_expo: i32) -> Option<PriceConf> {
200+
assert!(amounts.len() > 0);
201+
let mut res = PriceConf { price: 0, conf: 0, expo: result_expo };
202+
for i in 0..amounts.len() {
203+
res = res.add(
204+
&amounts[i].0.get_current_price()?.cmul(amounts[i].1, amounts[i].2)?.scale_to_exponent(result_expo)?
205+
)?
206+
}
207+
Some(res)
165208
}
166209
}
167210

0 commit comments

Comments
 (0)