Skip to content

Add support of anchor deserialzation #93

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 5, 2023
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
1 change: 1 addition & 0 deletions examples/sol-anchor-contract/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ target
**/*.rs.bk
node_modules
test-ledger
program_address.json
2 changes: 1 addition & 1 deletion examples/sol-anchor-contract/Anchor.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[programs.devnet]
example_sol_anchor_contract = "9azQ2ePzPvMPQgHric53kdSNmwjVM5KijDE4ANFCE9D4"
example_sol_anchor_contract = "BZh3CP454Ca1C9yBp2tpGAkXoKFti9x8ShJLSxNDpoxa"

[provider]
cluster = "devnet"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ default = []

[dependencies]
anchor-lang = "0.25.0"
pyth-sdk = { path = "../../../../pyth-sdk", version = "0.7.0" }
pyth-sdk-solana = { path = "../../../../pyth-sdk-solana", version = "0.7.0" }
solana-program = ">= 1.10, < 1.15"
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use anchor_lang::prelude::*;

#[error_code]
pub enum ErrorCode {
#[msg("You are not authorized to perform this action.")]
Unauthorized,
#[msg("The config has already been initialized.")]
ReInitialize,
#[msg("The config has not been initialized.")]
UnInitialize,
#[msg("Argument is invalid.")]
InvalidArgument,
#[msg("An overflow occurs.")]
Overflow,
#[msg("Pyth has an internal error.")]
PythError,
#[msg("Pyth price oracle is offline.")]
PythOffline,
#[msg("The loan value is higher than the collateral value.")]
LoanValueTooHigh,
#[msg("Program should not try to serialize a price account.")]
TryToSerializePriceAccount,
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use std::mem::size_of;
use anchor_lang::prelude::*;
use solana_program::account_info::AccountInfo;
use pyth_sdk_solana::load_price_feed_from_account_info;

declare_id!("9azQ2ePzPvMPQgHric53kdSNmwjVM5KijDE4ANFCE9D4");
pub mod state;
use state::PriceFeed;
use state::AdminConfig;

#[account]
pub struct AdminConfig {
pub loan_price_feed_id: Pubkey,
pub collateral_price_feed_id: Pubkey,
}
mod error;
use error::ErrorCode;

declare_id!("BZh3CP454Ca1C9yBp2tpGAkXoKFti9x8ShJLSxNDpoxa");

#[derive(Accounts)]
pub struct InitRequest<'info> {
Expand All @@ -25,12 +25,10 @@ pub struct InitRequest<'info> {
#[derive(Accounts)]
pub struct QueryRequest<'info> {
pub config: Account<'info, AdminConfig>,
/// CHECK: Pyth structs don't seem to support Anchor deserialization
#[account(address = config.loan_price_feed_id @ ErrorCode::InvalidArgument)]
pub pyth_loan_account: AccountInfo<'info>,
/// CHECK: Pyth structs don't seem to support Anchor deserialization
pub pyth_loan_account: Account<'info, PriceFeed>,
#[account(address = config.collateral_price_feed_id @ ErrorCode::InvalidArgument)]
pub pyth_collateral_account: AccountInfo<'info>,
pub pyth_collateral_account: Account<'info, PriceFeed>,
}

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

let pyth_loan_account = &ctx.accounts.pyth_loan_account;
let pyth_collateral_account = &ctx.accounts.pyth_collateral_account;

let loan_feed = &ctx.accounts.pyth_loan_account;
let collateral_feed = &ctx.accounts.pyth_collateral_account;
// With high confidence, the maximum value of the loan is
// (price + conf) * loan_qty * 10 ^ (expo).
// Here is more explanation on confidence interval in Pyth:
// https://docs.pyth.network/consume-data/best-practices
let feed1 = load_price_feed_from_account_info(pyth_loan_account)
.map_err(|_x| error!(ErrorCode::PythError))?;
let current_timestamp1 = Clock::get()?.unix_timestamp;
let result1 = feed1
let loan_price = loan_feed
.get_price_no_older_than(current_timestamp1, 60)
.ok_or(ErrorCode::PythOffline)?;
let loan_max_price = result1
let loan_max_price = loan_price
.price
.checked_add(result1.conf as i64)
.checked_add(loan_price.conf as i64)
.ok_or(ErrorCode::Overflow)?;
let mut loan_max_value = loan_max_price
.checked_mul(loan_qty)
.ok_or(ErrorCode::Overflow)?;
msg!(
"The maximum loan value is {} * 10^({}).",
loan_max_value,
result1.expo
loan_price.expo
);

// With high confidence, the minimum value of the collateral is
// (price - conf) * collateral_qty * 10 ^ (expo).
// Here is more explanation on confidence interval in Pyth:
// https://docs.pyth.network/consume-data/best-practices
let feed2 = load_price_feed_from_account_info(pyth_collateral_account)
.map_err(|_x| error!(ErrorCode::PythError))?;
let current_timestamp2 = Clock::get()?.unix_timestamp;
let result2 = feed2
let collateral_price = collateral_feed
.get_price_no_older_than(current_timestamp2, 60)
.ok_or(ErrorCode::PythOffline)?;
let collateral_min_price = result2
let collateral_min_price = collateral_price
.price
.checked_sub(result2.conf as i64)
.checked_sub(collateral_price.conf as i64)
.ok_or(ErrorCode::Overflow)?;
let mut collateral_min_value = collateral_min_price
.checked_mul(collateral_qty)
.ok_or(ErrorCode::Overflow)?;
msg!(
"The minimum collateral value is {} * 10^({}).",
collateral_min_value,
result2.expo
collateral_price.expo
);

// If the loan and collateral prices use different exponent,
// normalize the value.
if result1.expo > result2.expo {
if loan_price.expo > collateral_price.expo {
let normalize = (10 as i64)
.checked_pow((result1.expo - result2.expo) as u32)
.checked_pow((loan_price.expo - collateral_price.expo) as u32)
.ok_or(ErrorCode::Overflow)?;
collateral_min_value = collateral_min_value
.checked_mul(normalize)
.ok_or(ErrorCode::Overflow)?;
} else if result1.expo < result2.expo {
} else if loan_price.expo < collateral_price.expo {
let normalize = (10 as i64)
.checked_pow((result2.expo - result1.expo) as u32)
.checked_pow((collateral_price.expo - loan_price.expo) as u32)
.ok_or(ErrorCode::Overflow)?;
loan_max_value = loan_max_value
.checked_mul(normalize)
Expand All @@ -121,23 +116,3 @@ pub mod example_sol_anchor_contract {
}
}
}

#[error_code]
pub enum ErrorCode {
#[msg("You are not authorized to perform this action.")]
Unauthorized,
#[msg("The config has already been initialized.")]
ReInitialize,
#[msg("The config has not been initialized.")]
UnInitialize,
#[msg("Argument is invalid.")]
InvalidArgument,
#[msg("An overflow occurs.")]
Overflow,
#[msg("Pyth has an internal error.")]
PythError,
#[msg("Pyth price oracle is offline.")]
PythOffline,
#[msg("The loan value is higher than the collateral value.")]
LoanValueTooHigh,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use std::ops::Deref;
use std::str::FromStr;
use anchor_lang::prelude::*;
use pyth_sdk_solana::state::load_price_account;

use crate::ErrorCode;

#[account]
pub struct AdminConfig {
pub loan_price_feed_id: Pubkey,
pub collateral_price_feed_id: Pubkey,
}

#[derive(Clone)]
pub struct PriceFeed (pyth_sdk::PriceFeed);

impl anchor_lang::Owner for PriceFeed {
fn owner() -> Pubkey {
// Make sure the owner is the pyth oracle account on solana devnet
let oracle_addr = "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s";
return Pubkey::from_str(&oracle_addr).unwrap();
}
}

impl anchor_lang::AccountDeserialize for PriceFeed {
fn try_deserialize_unchecked(data: &mut &[u8]) -> Result<Self>{
let account = load_price_account(data)
.map_err(|_x| error!(ErrorCode::PythError))?;

// Use a dummy key since the key field will be removed from the SDK
let zeros: [u8; 32] = [0; 32];
let dummy_key = Pubkey::new(&zeros);
let feed = account.to_price_feed(&dummy_key);
return Ok(PriceFeed(feed));
}
}

impl anchor_lang::AccountSerialize for PriceFeed {
fn try_serialize<W: std::io::Write>(&self, _writer: &mut W,) -> std::result::Result<(), Error> {
Err(error!(ErrorCode::TryToSerializePriceAccount))
}
}

impl Deref for PriceFeed {
type Target = pyth_sdk::PriceFeed;

fn deref(&self) -> &Self::Target {
&self.0
}
}