Skip to content

Commit 5f06773

Browse files
authored
Add support of anchor deserialzation (#93)
* Add support of anchor deserialization * cleanup * cleanup * Remove the redundant automatically_derived * Use PriceAccount for anchor recommended deserialization * It seems to work! * It works * Use PriceFeed and implement Deref
1 parent dfc25fd commit 5f06773

File tree

6 files changed

+100
-50
lines changed

6 files changed

+100
-50
lines changed

examples/sol-anchor-contract/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ target
66
**/*.rs.bk
77
node_modules
88
test-ledger
9+
program_address.json

examples/sol-anchor-contract/Anchor.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[programs.devnet]
2-
example_sol_anchor_contract = "9azQ2ePzPvMPQgHric53kdSNmwjVM5KijDE4ANFCE9D4"
2+
example_sol_anchor_contract = "BZh3CP454Ca1C9yBp2tpGAkXoKFti9x8ShJLSxNDpoxa"
33

44
[provider]
55
cluster = "devnet"

examples/sol-anchor-contract/programs/example-sol-anchor-contract/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ default = []
1717

1818
[dependencies]
1919
anchor-lang = "0.25.0"
20+
pyth-sdk = { path = "../../../../pyth-sdk", version = "0.7.0" }
2021
pyth-sdk-solana = { path = "../../../../pyth-sdk-solana", version = "0.7.0" }
2122
solana-program = ">= 1.10, < 1.15"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use anchor_lang::prelude::*;
2+
3+
#[error_code]
4+
pub enum ErrorCode {
5+
#[msg("You are not authorized to perform this action.")]
6+
Unauthorized,
7+
#[msg("The config has already been initialized.")]
8+
ReInitialize,
9+
#[msg("The config has not been initialized.")]
10+
UnInitialize,
11+
#[msg("Argument is invalid.")]
12+
InvalidArgument,
13+
#[msg("An overflow occurs.")]
14+
Overflow,
15+
#[msg("Pyth has an internal error.")]
16+
PythError,
17+
#[msg("Pyth price oracle is offline.")]
18+
PythOffline,
19+
#[msg("The loan value is higher than the collateral value.")]
20+
LoanValueTooHigh,
21+
#[msg("Program should not try to serialize a price account.")]
22+
TryToSerializePriceAccount,
23+
}
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
use std::mem::size_of;
22
use anchor_lang::prelude::*;
33
use solana_program::account_info::AccountInfo;
4-
use pyth_sdk_solana::load_price_feed_from_account_info;
54

6-
declare_id!("9azQ2ePzPvMPQgHric53kdSNmwjVM5KijDE4ANFCE9D4");
5+
pub mod state;
6+
use state::PriceFeed;
7+
use state::AdminConfig;
78

8-
#[account]
9-
pub struct AdminConfig {
10-
pub loan_price_feed_id: Pubkey,
11-
pub collateral_price_feed_id: Pubkey,
12-
}
9+
mod error;
10+
use error::ErrorCode;
11+
12+
declare_id!("BZh3CP454Ca1C9yBp2tpGAkXoKFti9x8ShJLSxNDpoxa");
1313

1414
#[derive(Accounts)]
1515
pub struct InitRequest<'info> {
@@ -25,12 +25,10 @@ pub struct InitRequest<'info> {
2525
#[derive(Accounts)]
2626
pub struct QueryRequest<'info> {
2727
pub config: Account<'info, AdminConfig>,
28-
/// CHECK: Pyth structs don't seem to support Anchor deserialization
2928
#[account(address = config.loan_price_feed_id @ ErrorCode::InvalidArgument)]
30-
pub pyth_loan_account: AccountInfo<'info>,
31-
/// CHECK: Pyth structs don't seem to support Anchor deserialization
29+
pub pyth_loan_account: Account<'info, PriceFeed>,
3230
#[account(address = config.collateral_price_feed_id @ ErrorCode::InvalidArgument)]
33-
pub pyth_collateral_account: AccountInfo<'info>,
31+
pub pyth_collateral_account: Account<'info, PriceFeed>,
3432
}
3533

3634
#[program]
@@ -46,66 +44,63 @@ pub mod example_sol_anchor_contract {
4644
msg!("Loan quantity is {}.", loan_qty);
4745
msg!("Collateral quantity is {}.", collateral_qty);
4846

49-
let pyth_loan_account = &ctx.accounts.pyth_loan_account;
50-
let pyth_collateral_account = &ctx.accounts.pyth_collateral_account;
47+
48+
let loan_feed = &ctx.accounts.pyth_loan_account;
49+
let collateral_feed = &ctx.accounts.pyth_collateral_account;
5150
// With high confidence, the maximum value of the loan is
5251
// (price + conf) * loan_qty * 10 ^ (expo).
5352
// Here is more explanation on confidence interval in Pyth:
5453
// https://docs.pyth.network/consume-data/best-practices
55-
let feed1 = load_price_feed_from_account_info(pyth_loan_account)
56-
.map_err(|_x| error!(ErrorCode::PythError))?;
5754
let current_timestamp1 = Clock::get()?.unix_timestamp;
58-
let result1 = feed1
55+
let loan_price = loan_feed
5956
.get_price_no_older_than(current_timestamp1, 60)
6057
.ok_or(ErrorCode::PythOffline)?;
61-
let loan_max_price = result1
58+
let loan_max_price = loan_price
6259
.price
63-
.checked_add(result1.conf as i64)
60+
.checked_add(loan_price.conf as i64)
6461
.ok_or(ErrorCode::Overflow)?;
6562
let mut loan_max_value = loan_max_price
6663
.checked_mul(loan_qty)
6764
.ok_or(ErrorCode::Overflow)?;
6865
msg!(
6966
"The maximum loan value is {} * 10^({}).",
7067
loan_max_value,
71-
result1.expo
68+
loan_price.expo
7269
);
7370

7471
// With high confidence, the minimum value of the collateral is
7572
// (price - conf) * collateral_qty * 10 ^ (expo).
7673
// Here is more explanation on confidence interval in Pyth:
7774
// https://docs.pyth.network/consume-data/best-practices
78-
let feed2 = load_price_feed_from_account_info(pyth_collateral_account)
79-
.map_err(|_x| error!(ErrorCode::PythError))?;
8075
let current_timestamp2 = Clock::get()?.unix_timestamp;
81-
let result2 = feed2
76+
let collateral_price = collateral_feed
8277
.get_price_no_older_than(current_timestamp2, 60)
8378
.ok_or(ErrorCode::PythOffline)?;
84-
let collateral_min_price = result2
79+
let collateral_min_price = collateral_price
8580
.price
86-
.checked_sub(result2.conf as i64)
81+
.checked_sub(collateral_price.conf as i64)
8782
.ok_or(ErrorCode::Overflow)?;
8883
let mut collateral_min_value = collateral_min_price
8984
.checked_mul(collateral_qty)
9085
.ok_or(ErrorCode::Overflow)?;
9186
msg!(
9287
"The minimum collateral value is {} * 10^({}).",
9388
collateral_min_value,
94-
result2.expo
89+
collateral_price.expo
9590
);
9691

9792
// If the loan and collateral prices use different exponent,
9893
// normalize the value.
99-
if result1.expo > result2.expo {
94+
if loan_price.expo > collateral_price.expo {
10095
let normalize = (10 as i64)
101-
.checked_pow((result1.expo - result2.expo) as u32)
96+
.checked_pow((loan_price.expo - collateral_price.expo) as u32)
10297
.ok_or(ErrorCode::Overflow)?;
10398
collateral_min_value = collateral_min_value
10499
.checked_mul(normalize)
105100
.ok_or(ErrorCode::Overflow)?;
106-
} else if result1.expo < result2.expo {
101+
} else if loan_price.expo < collateral_price.expo {
107102
let normalize = (10 as i64)
108-
.checked_pow((result2.expo - result1.expo) as u32)
103+
.checked_pow((collateral_price.expo - loan_price.expo) as u32)
109104
.ok_or(ErrorCode::Overflow)?;
110105
loan_max_value = loan_max_value
111106
.checked_mul(normalize)
@@ -121,23 +116,3 @@ pub mod example_sol_anchor_contract {
121116
}
122117
}
123118
}
124-
125-
#[error_code]
126-
pub enum ErrorCode {
127-
#[msg("You are not authorized to perform this action.")]
128-
Unauthorized,
129-
#[msg("The config has already been initialized.")]
130-
ReInitialize,
131-
#[msg("The config has not been initialized.")]
132-
UnInitialize,
133-
#[msg("Argument is invalid.")]
134-
InvalidArgument,
135-
#[msg("An overflow occurs.")]
136-
Overflow,
137-
#[msg("Pyth has an internal error.")]
138-
PythError,
139-
#[msg("Pyth price oracle is offline.")]
140-
PythOffline,
141-
#[msg("The loan value is higher than the collateral value.")]
142-
LoanValueTooHigh,
143-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use std::ops::Deref;
2+
use std::str::FromStr;
3+
use anchor_lang::prelude::*;
4+
use pyth_sdk_solana::state::load_price_account;
5+
6+
use crate::ErrorCode;
7+
8+
#[account]
9+
pub struct AdminConfig {
10+
pub loan_price_feed_id: Pubkey,
11+
pub collateral_price_feed_id: Pubkey,
12+
}
13+
14+
#[derive(Clone)]
15+
pub struct PriceFeed (pyth_sdk::PriceFeed);
16+
17+
impl anchor_lang::Owner for PriceFeed {
18+
fn owner() -> Pubkey {
19+
// Make sure the owner is the pyth oracle account on solana devnet
20+
let oracle_addr = "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s";
21+
return Pubkey::from_str(&oracle_addr).unwrap();
22+
}
23+
}
24+
25+
impl anchor_lang::AccountDeserialize for PriceFeed {
26+
fn try_deserialize_unchecked(data: &mut &[u8]) -> Result<Self>{
27+
let account = load_price_account(data)
28+
.map_err(|_x| error!(ErrorCode::PythError))?;
29+
30+
// Use a dummy key since the key field will be removed from the SDK
31+
let zeros: [u8; 32] = [0; 32];
32+
let dummy_key = Pubkey::new(&zeros);
33+
let feed = account.to_price_feed(&dummy_key);
34+
return Ok(PriceFeed(feed));
35+
}
36+
}
37+
38+
impl anchor_lang::AccountSerialize for PriceFeed {
39+
fn try_serialize<W: std::io::Write>(&self, _writer: &mut W,) -> std::result::Result<(), Error> {
40+
Err(error!(ErrorCode::TryToSerializePriceAccount))
41+
}
42+
}
43+
44+
impl Deref for PriceFeed {
45+
type Target = pyth_sdk::PriceFeed;
46+
47+
fn deref(&self) -> &Self::Target {
48+
&self.0
49+
}
50+
}

0 commit comments

Comments
 (0)