From 03ce6e8095a46bb956304e2e6d14f9d3f91d16cd Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Wed, 21 Dec 2022 22:38:26 -0500 Subject: [PATCH 1/8] Add support of anchor deserialization --- examples/sol-anchor-contract/Anchor.toml | 2 +- .../example-sol-anchor-contract/Cargo.toml | 1 + .../example-sol-anchor-contract/src/error.rs | 23 ++++++++ .../example-sol-anchor-contract/src/lib.rs | 54 ++++++------------- .../example-sol-anchor-contract/src/state.rs | 46 ++++++++++++++++ 5 files changed, 86 insertions(+), 40 deletions(-) create mode 100644 examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/error.rs create mode 100644 examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs diff --git a/examples/sol-anchor-contract/Anchor.toml b/examples/sol-anchor-contract/Anchor.toml index e950329..9130414 100644 --- a/examples/sol-anchor-contract/Anchor.toml +++ b/examples/sol-anchor-contract/Anchor.toml @@ -1,5 +1,5 @@ [programs.devnet] -example_sol_anchor_contract = "9azQ2ePzPvMPQgHric53kdSNmwjVM5KijDE4ANFCE9D4" +example_sol_anchor_contract = "BZh3CP454Ca1C9yBp2tpGAkXoKFti9x8ShJLSxNDpoxa" [provider] cluster = "devnet" diff --git a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/Cargo.toml b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/Cargo.toml index bdac1d9..da23876 100644 --- a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/Cargo.toml +++ b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/Cargo.toml @@ -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" diff --git a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/error.rs b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/error.rs new file mode 100644 index 0000000..1e70ad9 --- /dev/null +++ b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/error.rs @@ -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, +} diff --git a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs index db7bd2f..166dda5 100644 --- a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs +++ b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs @@ -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::AdminConfig; +use state::PythPriceFeed; -#[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> { @@ -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, PythPriceFeed>, #[account(address = config.collateral_price_feed_id @ ErrorCode::InvalidArgument)] - pub pyth_collateral_account: AccountInfo<'info>, + pub pyth_collateral_account: Account<'info, PythPriceFeed>, } #[program] @@ -46,16 +44,16 @@ 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.feed; + let collateral_feed = &ctx.accounts.pyth_collateral_account.feed; // 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 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 result1 = loan_feed .get_price_no_older_than(current_timestamp1, 60) .ok_or(ErrorCode::PythOffline)?; let loan_max_price = result1 @@ -75,10 +73,8 @@ pub mod example_sol_anchor_contract { // (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 result2 = collateral_feed .get_price_no_older_than(current_timestamp2, 60) .ok_or(ErrorCode::PythOffline)?; let collateral_min_price = result2 @@ -121,23 +117,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, -} diff --git a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs new file mode 100644 index 0000000..880ba16 --- /dev/null +++ b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs @@ -0,0 +1,46 @@ +use std::str::FromStr; +use pyth_sdk::PriceFeed; +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 PythPriceFeed { + pub feed: PriceFeed, +} + +#[automatically_derived] +impl anchor_lang::AccountDeserialize for PythPriceFeed { + fn try_deserialize_unchecked(data: &mut &[u8]) -> Result{ + let account = load_price_account(data) + .map_err(|_x| error!(ErrorCode::PythError))?; + // CHECK: using a dummy key for constructing PriceFeed + let zeros: [u8; 32] = [0; 32]; + let dummy_key = Pubkey::new(&zeros); + let feed = account.to_price_feed(&dummy_key); + return Ok(PythPriceFeed {feed: feed}); + } +} + +#[automatically_derived] +impl anchor_lang::AccountSerialize for PythPriceFeed { + fn try_serialize(&self, writer: &mut W,) -> std::result::Result<(), Error> { + Err(error!(ErrorCode::TryToSerializePriceAccount)) + } +} + +#[automatically_derived] +impl anchor_lang::Owner for PythPriceFeed { + fn owner() -> Pubkey { + // CHECK: this is the pyth oracle address on solana devnet + let oracle_addr = "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s"; + return Pubkey::from_str(&oracle_addr).unwrap(); + } +} From c86cc96c6f5f8da3cc4684d5da68fc1663a725d6 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Wed, 21 Dec 2022 22:40:06 -0500 Subject: [PATCH 2/8] cleanup --- .../programs/example-sol-anchor-contract/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs index 166dda5..ae174eb 100644 --- a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs +++ b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs @@ -50,8 +50,6 @@ pub mod example_sol_anchor_contract { // (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 = loan_feed .get_price_no_older_than(current_timestamp1, 60) From d2629b8d44fbed11f0e83f9d1d1bca0307a07ac0 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Wed, 21 Dec 2022 22:50:31 -0500 Subject: [PATCH 3/8] cleanup --- examples/sol-anchor-contract/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/sol-anchor-contract/.gitignore b/examples/sol-anchor-contract/.gitignore index 52987f0..d7bff41 100644 --- a/examples/sol-anchor-contract/.gitignore +++ b/examples/sol-anchor-contract/.gitignore @@ -6,3 +6,4 @@ target **/*.rs.bk node_modules test-ledger +program_address.json From 8e7eea977fc7bc6f9d134ca4dde93169c25929f4 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Thu, 22 Dec 2022 16:40:12 -0500 Subject: [PATCH 4/8] Remove the redundant automatically_derived --- .../programs/example-sol-anchor-contract/src/state.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs index 880ba16..42a4998 100644 --- a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs +++ b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs @@ -16,7 +16,6 @@ pub struct PythPriceFeed { pub feed: PriceFeed, } -#[automatically_derived] impl anchor_lang::AccountDeserialize for PythPriceFeed { fn try_deserialize_unchecked(data: &mut &[u8]) -> Result{ let account = load_price_account(data) @@ -29,14 +28,12 @@ impl anchor_lang::AccountDeserialize for PythPriceFeed { } } -#[automatically_derived] impl anchor_lang::AccountSerialize for PythPriceFeed { - fn try_serialize(&self, writer: &mut W,) -> std::result::Result<(), Error> { + fn try_serialize(&self, _writer: &mut W,) -> std::result::Result<(), Error> { Err(error!(ErrorCode::TryToSerializePriceAccount)) } } -#[automatically_derived] impl anchor_lang::Owner for PythPriceFeed { fn owner() -> Pubkey { // CHECK: this is the pyth oracle address on solana devnet From 9ba7c80035079e792b187242ca65d2b90b19bbbc Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Mon, 2 Jan 2023 15:19:47 -0500 Subject: [PATCH 5/8] Use PriceAccount for anchor recommended deserialization --- .../example-sol-anchor-contract/src/lib.rs | 22 +++++++++---------- .../example-sol-anchor-contract/src/state.rs | 19 +++++++--------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs index ae174eb..6b9cbf9 100644 --- a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs +++ b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs @@ -4,7 +4,7 @@ use solana_program::account_info::AccountInfo; pub mod state; use state::AdminConfig; -use state::PythPriceFeed; +use state::PythPriceAccount; mod error; use error::ErrorCode; @@ -26,9 +26,9 @@ pub struct InitRequest<'info> { pub struct QueryRequest<'info> { pub config: Account<'info, AdminConfig>, #[account(address = config.loan_price_feed_id @ ErrorCode::InvalidArgument)] - pub pyth_loan_account: Account<'info, PythPriceFeed>, + pub pyth_loan_account: Account<'info, PythPriceAccount>, #[account(address = config.collateral_price_feed_id @ ErrorCode::InvalidArgument)] - pub pyth_collateral_account: Account<'info, PythPriceFeed>, + pub pyth_collateral_account: Account<'info, PythPriceAccount>, } #[program] @@ -44,15 +44,15 @@ pub mod example_sol_anchor_contract { msg!("Loan quantity is {}.", loan_qty); msg!("Collateral quantity is {}.", collateral_qty); - let loan_feed = &ctx.accounts.pyth_loan_account.feed; - let collateral_feed = &ctx.accounts.pyth_collateral_account.feed; + let loan_account = &ctx.accounts.pyth_loan_account.account; + let collateral_account = &ctx.accounts.pyth_collateral_account.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 current_timestamp1 = Clock::get()?.unix_timestamp; - let result1 = loan_feed - .get_price_no_older_than(current_timestamp1, 60) + //let current_timestamp1 = Clock::get()?.unix_timestamp; + let result1 = loan_account + .get_price_no_older_than(&Clock::get()?, 60) .ok_or(ErrorCode::PythOffline)?; let loan_max_price = result1 .price @@ -71,9 +71,9 @@ pub mod example_sol_anchor_contract { // (price - conf) * collateral_qty * 10 ^ (expo). // Here is more explanation on confidence interval in Pyth: // https://docs.pyth.network/consume-data/best-practices - let current_timestamp2 = Clock::get()?.unix_timestamp; - let result2 = collateral_feed - .get_price_no_older_than(current_timestamp2, 60) + //let current_timestamp2 = Clock::get()?.unix_timestamp; + let result2 = collateral_account + .get_price_no_older_than(&Clock::get()?, 60) .ok_or(ErrorCode::PythOffline)?; let collateral_min_price = result2 .price diff --git a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs index 42a4998..675a51a 100644 --- a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs +++ b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use pyth_sdk::PriceFeed; use anchor_lang::prelude::*; +use pyth_sdk_solana::state::PriceAccount; use pyth_sdk_solana::state::load_price_account; use crate::ErrorCode; @@ -12,29 +12,26 @@ pub struct AdminConfig { } #[derive(Clone)] -pub struct PythPriceFeed { - pub feed: PriceFeed, +pub struct PythPriceAccount { + pub account: PriceAccount, } -impl anchor_lang::AccountDeserialize for PythPriceFeed { +impl anchor_lang::AccountDeserialize for PythPriceAccount { fn try_deserialize_unchecked(data: &mut &[u8]) -> Result{ let account = load_price_account(data) .map_err(|_x| error!(ErrorCode::PythError))?; - // CHECK: using a dummy key for constructing PriceFeed - let zeros: [u8; 32] = [0; 32]; - let dummy_key = Pubkey::new(&zeros); - let feed = account.to_price_feed(&dummy_key); - return Ok(PythPriceFeed {feed: feed}); + // CHECK: any possibility of returning &PriceAccount here? + return Ok(PythPriceAccount {account: account}); } } -impl anchor_lang::AccountSerialize for PythPriceFeed { +impl anchor_lang::AccountSerialize for PythPriceAccount { fn try_serialize(&self, _writer: &mut W,) -> std::result::Result<(), Error> { Err(error!(ErrorCode::TryToSerializePriceAccount)) } } -impl anchor_lang::Owner for PythPriceFeed { +impl anchor_lang::Owner for PythPriceAccount { fn owner() -> Pubkey { // CHECK: this is the pyth oracle address on solana devnet let oracle_addr = "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s"; From 55e84b7514569951f94f69c10507a459c02c3335 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Mon, 2 Jan 2023 17:06:21 -0500 Subject: [PATCH 6/8] It seems to work! --- .../example-sol-anchor-contract/src/lib.rs | 18 +++++++----------- .../example-sol-anchor-contract/src/state.rs | 16 ++++++++-------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs index 6b9cbf9..bb6e6d1 100644 --- a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs +++ b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs @@ -4,7 +4,7 @@ use solana_program::account_info::AccountInfo; pub mod state; use state::AdminConfig; -use state::PythPriceAccount; +use state::PythPrice; mod error; use error::ErrorCode; @@ -26,9 +26,9 @@ pub struct InitRequest<'info> { pub struct QueryRequest<'info> { pub config: Account<'info, AdminConfig>, #[account(address = config.loan_price_feed_id @ ErrorCode::InvalidArgument)] - pub pyth_loan_account: Account<'info, PythPriceAccount>, + pub pyth_loan_account: Account<'info, PythPrice>, #[account(address = config.collateral_price_feed_id @ ErrorCode::InvalidArgument)] - pub pyth_collateral_account: Account<'info, PythPriceAccount>, + pub pyth_collateral_account: Account<'info, PythPrice>, } #[program] @@ -44,15 +44,13 @@ pub mod example_sol_anchor_contract { msg!("Loan quantity is {}.", loan_qty); msg!("Collateral quantity is {}.", collateral_qty); - let loan_account = &ctx.accounts.pyth_loan_account.account; - let collateral_account = &ctx.accounts.pyth_collateral_account.account; + let loan_price = &ctx.accounts.pyth_loan_account.price; + let collateral_price = &ctx.accounts.pyth_collateral_account.price; // 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 current_timestamp1 = Clock::get()?.unix_timestamp; - let result1 = loan_account - .get_price_no_older_than(&Clock::get()?, 60) + let result1 = loan_price .ok_or(ErrorCode::PythOffline)?; let loan_max_price = result1 .price @@ -71,9 +69,7 @@ pub mod example_sol_anchor_contract { // (price - conf) * collateral_qty * 10 ^ (expo). // Here is more explanation on confidence interval in Pyth: // https://docs.pyth.network/consume-data/best-practices - //let current_timestamp2 = Clock::get()?.unix_timestamp; - let result2 = collateral_account - .get_price_no_older_than(&Clock::get()?, 60) + let result2 = collateral_price .ok_or(ErrorCode::PythOffline)?; let collateral_min_price = result2 .price diff --git a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs index 675a51a..a5ca6eb 100644 --- a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs +++ b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs @@ -1,8 +1,8 @@ use std::str::FromStr; use anchor_lang::prelude::*; -use pyth_sdk_solana::state::PriceAccount; use pyth_sdk_solana::state::load_price_account; +pub use pyth_sdk::Price; use crate::ErrorCode; #[account] @@ -12,26 +12,26 @@ pub struct AdminConfig { } #[derive(Clone)] -pub struct PythPriceAccount { - pub account: PriceAccount, +pub struct PythPrice { + pub price: Option, } -impl anchor_lang::AccountDeserialize for PythPriceAccount { +impl anchor_lang::AccountDeserialize for PythPrice { fn try_deserialize_unchecked(data: &mut &[u8]) -> Result{ let account = load_price_account(data) .map_err(|_x| error!(ErrorCode::PythError))?; - // CHECK: any possibility of returning &PriceAccount here? - return Ok(PythPriceAccount {account: account}); + let price = account.get_price_no_older_than(&Clock::get()?, 60); + return Ok(PythPrice {price: price}); } } -impl anchor_lang::AccountSerialize for PythPriceAccount { +impl anchor_lang::AccountSerialize for PythPrice { fn try_serialize(&self, _writer: &mut W,) -> std::result::Result<(), Error> { Err(error!(ErrorCode::TryToSerializePriceAccount)) } } -impl anchor_lang::Owner for PythPriceAccount { +impl anchor_lang::Owner for PythPrice { fn owner() -> Pubkey { // CHECK: this is the pyth oracle address on solana devnet let oracle_addr = "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s"; From fa69289e583918b2d631735ae645d6f0243ff05f Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Mon, 2 Jan 2023 17:14:13 -0500 Subject: [PATCH 7/8] It works --- .../example-sol-anchor-contract/src/lib.rs | 24 ++++++++----------- .../example-sol-anchor-contract/src/state.rs | 7 +++--- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs index bb6e6d1..75b8e1b 100644 --- a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs +++ b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs @@ -50,11 +50,9 @@ pub mod example_sol_anchor_contract { // (price + conf) * loan_qty * 10 ^ (expo). // Here is more explanation on confidence interval in Pyth: // https://docs.pyth.network/consume-data/best-practices - let result1 = loan_price - .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) @@ -62,18 +60,16 @@ pub mod example_sol_anchor_contract { 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 result2 = collateral_price - .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) @@ -81,21 +77,21 @@ pub mod example_sol_anchor_contract { 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) diff --git a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs index a5ca6eb..c647944 100644 --- a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs +++ b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs @@ -2,8 +2,8 @@ use std::str::FromStr; use anchor_lang::prelude::*; use pyth_sdk_solana::state::load_price_account; -pub use pyth_sdk::Price; use crate::ErrorCode; +pub use pyth_sdk::Price; #[account] pub struct AdminConfig { @@ -13,14 +13,15 @@ pub struct AdminConfig { #[derive(Clone)] pub struct PythPrice { - pub price: Option, + pub price: Price, } impl anchor_lang::AccountDeserialize for PythPrice { fn try_deserialize_unchecked(data: &mut &[u8]) -> Result{ let account = load_price_account(data) .map_err(|_x| error!(ErrorCode::PythError))?; - let price = account.get_price_no_older_than(&Clock::get()?, 60); + let price = account.get_price_no_older_than(&Clock::get()?, 60) + .ok_or(error!(ErrorCode::PythOffline))?; return Ok(PythPrice {price: price}); } } From 5e168ce74df2714051bb29b0a9b78921d8af1ccd Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Wed, 4 Jan 2023 19:25:39 -0500 Subject: [PATCH 8/8] Use PriceFeed and implement Deref --- .../example-sol-anchor-contract/src/lib.rs | 19 +++++++--- .../example-sol-anchor-contract/src/state.rs | 35 ++++++++++++------- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs index 75b8e1b..63b95a4 100644 --- a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs +++ b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/lib.rs @@ -3,8 +3,8 @@ use anchor_lang::prelude::*; use solana_program::account_info::AccountInfo; pub mod state; +use state::PriceFeed; use state::AdminConfig; -use state::PythPrice; mod error; use error::ErrorCode; @@ -26,9 +26,9 @@ pub struct InitRequest<'info> { pub struct QueryRequest<'info> { pub config: Account<'info, AdminConfig>, #[account(address = config.loan_price_feed_id @ ErrorCode::InvalidArgument)] - pub pyth_loan_account: Account<'info, PythPrice>, + pub pyth_loan_account: Account<'info, PriceFeed>, #[account(address = config.collateral_price_feed_id @ ErrorCode::InvalidArgument)] - pub pyth_collateral_account: Account<'info, PythPrice>, + pub pyth_collateral_account: Account<'info, PriceFeed>, } #[program] @@ -44,12 +44,17 @@ pub mod example_sol_anchor_contract { msg!("Loan quantity is {}.", loan_qty); msg!("Collateral quantity is {}.", collateral_qty); - let loan_price = &ctx.accounts.pyth_loan_account.price; - let collateral_price = &ctx.accounts.pyth_collateral_account.price; + + 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 current_timestamp1 = Clock::get()?.unix_timestamp; + let loan_price = loan_feed + .get_price_no_older_than(current_timestamp1, 60) + .ok_or(ErrorCode::PythOffline)?; let loan_max_price = loan_price .price .checked_add(loan_price.conf as i64) @@ -67,6 +72,10 @@ pub mod example_sol_anchor_contract { // (price - conf) * collateral_qty * 10 ^ (expo). // Here is more explanation on confidence interval in Pyth: // https://docs.pyth.network/consume-data/best-practices + let current_timestamp2 = Clock::get()?.unix_timestamp; + let collateral_price = collateral_feed + .get_price_no_older_than(current_timestamp2, 60) + .ok_or(ErrorCode::PythOffline)?; let collateral_min_price = collateral_price .price .checked_sub(collateral_price.conf as i64) diff --git a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs index c647944..42da274 100644 --- a/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs +++ b/examples/sol-anchor-contract/programs/example-sol-anchor-contract/src/state.rs @@ -1,9 +1,9 @@ +use std::ops::Deref; use std::str::FromStr; use anchor_lang::prelude::*; use pyth_sdk_solana::state::load_price_account; use crate::ErrorCode; -pub use pyth_sdk::Price; #[account] pub struct AdminConfig { @@ -12,30 +12,39 @@ pub struct AdminConfig { } #[derive(Clone)] -pub struct PythPrice { - pub price: Price, +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 PythPrice { +impl anchor_lang::AccountDeserialize for PriceFeed { fn try_deserialize_unchecked(data: &mut &[u8]) -> Result{ let account = load_price_account(data) .map_err(|_x| error!(ErrorCode::PythError))?; - let price = account.get_price_no_older_than(&Clock::get()?, 60) - .ok_or(error!(ErrorCode::PythOffline))?; - return Ok(PythPrice {price: price}); + + // 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 PythPrice { +impl anchor_lang::AccountSerialize for PriceFeed { fn try_serialize(&self, _writer: &mut W,) -> std::result::Result<(), Error> { Err(error!(ErrorCode::TryToSerializePriceAccount)) } } -impl anchor_lang::Owner for PythPrice { - fn owner() -> Pubkey { - // CHECK: this is the pyth oracle address on solana devnet - let oracle_addr = "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s"; - return Pubkey::from_str(&oracle_addr).unwrap(); +impl Deref for PriceFeed { + type Target = pyth_sdk::PriceFeed; + + fn deref(&self) -> &Self::Target { + &self.0 } }