diff --git a/.gitignore b/.gitignore index ba4f6fe..4ccd6e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Generated by Cargo # will have compiled files and executables -/target/ +target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html @@ -14,3 +14,6 @@ Cargo.lock !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json + +.idea +.anchor diff --git a/Cargo.toml b/Cargo.toml index 85c8a16..c4156b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,26 +11,26 @@ anchor = ["anchor-lang"] pyth = ["pyth-sdk-solana"] [dependencies] -solana-program-test = "1.18" -solana-sdk = "1.18" -solana-program ="1.18" -solana-banks-client = "1.18" -solana-program-runtime = "1.18" -solana-client = "1.18" -spl-token = "4.0.0" -spl-associated-token-account = "1.1.2" -anchor-lang = { version = "0.30.0", optional = true } -async-trait = "0.1.52" +solana-program-test = "=1.18" +solana-sdk = "=1.18" +solana-program = "=1.18" +solana-banks-client = "=1.18" +solana-program-runtime = "=1.18" +solana-client = "=1.18" +spl-token = "=4.0.0" +spl-associated-token-account = "=2.3.0" +anchor-lang = { version = "0.30.1", optional = true } +async-trait = "0.1.81" futures = "0.3" -borsh = "0.9" +borsh = "0.10.3" bincode = "1.3.3" log = "0.4" chrono-humanize = "0.2" pyth-sdk-solana = { version = "0.10.1", optional = true } -serde = { version = "1.0.152"} -serde_json = { version = "1.0.91", optional = true } -thiserror = "1.0.38" +serde = { version = "1.0.207" } +serde_json = { version = "1.0.124", optional = true } +thiserror = "1.0.63" [dev-dependencies] -program-for-tests = {path="tests/artifacts/program_for_tests"} -solana-test-validator = "1.18" \ No newline at end of file +program-for-tests = { path = "tests/testsuite/artifacts/program_for_tests" } +solana-test-validator = "=1.18" diff --git a/README.md b/README.md index 83cf5b0..449976b 100644 --- a/README.md +++ b/README.md @@ -134,19 +134,6 @@ async fn create_associated_token_account(   -Deploy a final program - -```rust -async fn deploy_program( - &mut self, - path_to_program: &str, - program_keypair: &Keypair, - payer: &Keypair, -) -> Result<(), Box> -``` - -  - Deploy an upgradeable program ```rust diff --git a/src/extensions/client/banks_client.rs b/src/extensions/client/banks_client.rs index f4ee492..fd3d6cb 100644 --- a/src/extensions/client/banks_client.rs +++ b/src/extensions/client/banks_client.rs @@ -1,7 +1,7 @@ use super::*; #[cfg(feature = "pyth")] -use pyth_sdk_solana::state::PriceAccount; +use pyth_sdk_solana::state::SolanaPriceAccount; #[async_trait] impl ClientExtensions for BanksClient { @@ -26,35 +26,37 @@ impl ClientExtensions for BanksClient { &mut self, address: Pubkey, ) -> Result> { - self.get_account(address) - .map(|result| { - let account = result?.ok_or(BanksClientError::ClientError("Account not found"))?; - T::try_deserialize(&mut account.data.as_ref()) - .map_err(|_| BanksClientError::ClientError("Failed to deserialize account")) - }) - .await - .map_err(Into::into) + let account = self + .get_account(address) + .await? + .ok_or(BanksClientError::ClientError("Account not found"))?; + T::try_deserialize(&mut account.data.as_ref()).map_err(|_| { + Into::into(BanksClientError::ClientError( + "Failed to deserialize account", + )) + }) } async fn get_account_with_borsh( &mut self, address: Pubkey, ) -> Result> { - self.get_account(address) - .map(|result| { - let account = result?.ok_or(BanksClientError::ClientError("Account not found"))?; - T::deserialize(&mut account.data.as_ref()) - .map_err(|_| BanksClientError::ClientError("Failed to deserialize account")) - }) - .await - .map_err(Into::into) + let account = self + .get_account(address) + .await? + .ok_or(BanksClientError::ClientError("Account not found"))?; + T::deserialize(&mut account.data.as_ref()).map_err(|_| { + Into::into(BanksClientError::ClientError( + "Failed to deserialize account", + )) + }) } #[cfg(feature = "pyth")] async fn get_pyth_price_account( &mut self, address: Pubkey, - ) -> Result> { + ) -> Result> { let account = self.get_account(address).await?.unwrap(); let price_account = pyth_sdk_solana::state::load_price_account(account.data.as_ref()) @@ -168,7 +170,8 @@ impl ClientExtensions for BanksClient { token_program_id: &Pubkey, ) -> Result> { let latest_blockhash = self.get_latest_blockhash().await?; - let associated_token_account = get_associated_token_address(account, mint); + let associated_token_account = + get_associated_token_address_with_program_id(account, mint, token_program_id); let ix = create_associated_token_account_ix(&payer.pubkey(), account, mint, token_program_id); @@ -183,71 +186,6 @@ impl ClientExtensions for BanksClient { return Ok(associated_token_account); } - async fn deploy_program( - &mut self, - path_to_program: &str, - program_keypair: &Keypair, - payer: &Keypair, - ) -> Result<(), Box> { - let (buffer, buffer_len) = util::load_file_to_bytes(path_to_program); - - let program_data = buffer; - - // multiply by 2 so program can be updated later on - let program_len = buffer_len; - let minimum_balance = Rent::default().minimum_balance( - bpf_loader_upgradeable::UpgradeableLoaderState::programdata_len(program_len) - .expect("Cannot get program len"), - ); - let latest_blockhash = self.get_latest_blockhash().await?; - - // 1 Create account - self.process_transaction(system_transaction::create_account( - payer, - program_keypair, - latest_blockhash, - minimum_balance, - program_len as u64, - &bpf_loader::id(), - )) - .await - .unwrap(); - - // 2. Write to buffer - let deploy_ix = |offset: u32, bytes: Vec| { - loader_instruction::write(&program_keypair.pubkey(), &bpf_loader::id(), offset, bytes) - }; - - let chunk_size = util::calculate_chunk_size(deploy_ix, &vec![payer, program_keypair]); - - for (chunk, i) in program_data.chunks(chunk_size).zip(0..) { - let ix = deploy_ix(i * chunk_size as u32, chunk.to_vec()); - let tx = self - .transaction_from_instructions(&[ix], payer, vec![payer, program_keypair]) - .await - .unwrap(); - - self.process_transaction(tx).await?; - } - - // 3. Finalize - let finalize_tx = self - .transaction_from_instructions( - &[loader_instruction::finalize( - &program_keypair.pubkey(), - &bpf_loader::id(), - )], - payer, - vec![payer, program_keypair], - ) - .await - .unwrap(); - - self.process_transaction(finalize_tx).await?; - - return Ok(()); - } - async fn deploy_upgradable_program( &mut self, path_to_program: &str, @@ -263,8 +201,7 @@ impl ClientExtensions for BanksClient { // multiply by 2 so program can be updated later on let program_len = buffer_len * 2; let minimum_balance = Rent::default().minimum_balance( - bpf_loader_upgradeable::UpgradeableLoaderState::programdata_len(program_len) - .expect("Cannot get program len"), + bpf_loader_upgradeable::UpgradeableLoaderState::size_of_programdata(program_len), ); // 1 Create buffer diff --git a/src/extensions/client/mod.rs b/src/extensions/client/mod.rs index 84b661f..106dcd5 100644 --- a/src/extensions/client/mod.rs +++ b/src/extensions/client/mod.rs @@ -1,22 +1,17 @@ use async_trait::async_trait; use borsh::BorshDeserialize; -use futures::FutureExt; -use solana_program::{ - bpf_loader_upgradeable, - program_pack::Pack -}; +use solana_program::{bpf_loader_upgradeable, program_pack::Pack}; use solana_sdk::{ - bpf_loader, instruction::Instruction, - loader_instruction, pubkey::Pubkey, signature::{Keypair, Signer}, system_transaction, sysvar::rent::Rent, - transaction::Transaction + transaction::Transaction, }; use spl_associated_token_account::{ - get_associated_token_address, instruction::create_associated_token_account as create_associated_token_account_ix, + get_associated_token_address_with_program_id, + instruction::create_associated_token_account as create_associated_token_account_ix, }; #[cfg(feature = "anchor")] @@ -27,13 +22,15 @@ pub use solana_banks_client::{BanksClient, BanksClientError}; mod banks_client; mod rpc_client; +#[allow(unused_imports)] pub use banks_client::*; +#[allow(unused_imports)] pub use rpc_client::*; use crate::util; #[cfg(feature = "pyth")] -use pyth_sdk_solana::state::PriceAccount; +use pyth_sdk_solana::state::SolanaPriceAccount; /// Convenience functions for clients #[async_trait] @@ -68,12 +65,11 @@ pub trait ClientExtensions { unimplemented!(); } - #[cfg(feature = "pyth")] async fn get_pyth_price_account( &mut self, _address: Pubkey, - ) -> Result> { + ) -> Result> { unimplemented!(); } @@ -123,16 +119,6 @@ pub trait ClientExtensions { unimplemented!(); } - /// Deploy a program - async fn deploy_program( - &mut self, - _path_to_program: &str, - _program_keypair: &Keypair, - _payer: &Keypair, - ) -> Result<(), Box> { - unimplemented!(); - } - /// Deploy an upgradable program async fn deploy_upgradable_program( &mut self, @@ -144,4 +130,4 @@ pub trait ClientExtensions { ) -> Result<(), Box> { unimplemented!(); } -} \ No newline at end of file +} diff --git a/src/extensions/client/rpc_client.rs b/src/extensions/client/rpc_client.rs index fded2ee..c1b6578 100644 --- a/src/extensions/client/rpc_client.rs +++ b/src/extensions/client/rpc_client.rs @@ -1,8 +1,9 @@ use super::*; -use solana_client::rpc_client::RpcClient; +use futures::future::join_all; #[cfg(feature = "pyth")] -use pyth_sdk_solana::state::PriceAccount; +use pyth_sdk_solana::state::SolanaPriceAccount; +use solana_client::nonblocking::rpc_client::RpcClient; #[async_trait] impl ClientExtensions for RpcClient { @@ -12,7 +13,7 @@ impl ClientExtensions for RpcClient { payer: &Keypair, signers: Vec<&Keypair>, ) -> Result> { - let latest_blockhash = self.get_latest_blockhash()?; + let latest_blockhash = self.get_latest_blockhash().await?; Ok(Transaction::new_signed_with_payer( ixs, @@ -27,7 +28,7 @@ impl ClientExtensions for RpcClient { &mut self, address: Pubkey, ) -> Result> { - self.get_account_data(&address).map(|account_data| { + self.get_account_data(&address).await.map(|account_data| { T::try_deserialize(&mut account_data.as_ref()).map_err(Into::into) })? } @@ -37,6 +38,7 @@ impl ClientExtensions for RpcClient { address: Pubkey, ) -> Result> { self.get_account_data(&address) + .await .map(|account_data| T::deserialize(&mut account_data.as_ref()).map_err(Into::into))? } @@ -44,8 +46,8 @@ impl ClientExtensions for RpcClient { async fn get_pyth_price_account( &mut self, address: Pubkey, - ) -> Result> { - self.get_account_data(&address).map(|account_data| { + ) -> Result> { + self.get_account_data(&address).await.map(|account_data| { //PriceFeed::deserialize(&mut account_data.as_ref()).map_err(Into::into) let data = account_data; let price_account = @@ -64,7 +66,7 @@ impl ClientExtensions for RpcClient { space: u64, owner: Pubkey, ) -> Result<(), Box> { - let latest_blockhash = self.get_latest_blockhash()?; + let latest_blockhash = self.get_latest_blockhash().await?; self.send_and_confirm_transaction(&system_transaction::create_account( from, @@ -74,6 +76,7 @@ impl ClientExtensions for RpcClient { space, &owner, )) + .await .map(|_| ()) .map_err(Into::into) } @@ -86,7 +89,7 @@ impl ClientExtensions for RpcClient { decimals: u8, payer: &Keypair, ) -> Result<(), Box> { - let latest_blockhash = self.get_latest_blockhash()?; + let latest_blockhash = self.get_latest_blockhash().await?; self.send_and_confirm_transaction(&system_transaction::create_account( payer, mint, @@ -94,7 +97,8 @@ impl ClientExtensions for RpcClient { Rent::default().minimum_balance(spl_token::state::Mint::get_packed_len()), spl_token::state::Mint::get_packed_len() as u64, &spl_token::id(), - ))?; + )) + .await?; let tx = self .transaction_from_instructions( @@ -111,6 +115,7 @@ impl ClientExtensions for RpcClient { .await?; self.send_and_confirm_transaction(&tx) + .await .map(|_| ()) .map_err(Into::into) } @@ -122,7 +127,7 @@ impl ClientExtensions for RpcClient { mint: &Pubkey, payer: &Keypair, ) -> Result<(), Box> { - let latest_blockhash = self.get_latest_blockhash()?; + let latest_blockhash = self.get_latest_blockhash().await?; self.send_and_confirm_transaction(&system_transaction::create_account( payer, @@ -131,7 +136,8 @@ impl ClientExtensions for RpcClient { Rent::default().minimum_balance(spl_token::state::Account::get_packed_len()), spl_token::state::Account::get_packed_len() as u64, &spl_token::id(), - ))?; + )) + .await?; let tx = self .transaction_from_instructions( @@ -147,6 +153,7 @@ impl ClientExtensions for RpcClient { .await?; self.send_and_confirm_transaction(&tx) + .await .map(|_| ()) .map_err(Into::into) } @@ -158,7 +165,8 @@ impl ClientExtensions for RpcClient { payer: &Keypair, token_program_id: &Pubkey, ) -> Result> { - let associated_token_account = get_associated_token_address(account, mint); + let associated_token_account = + get_associated_token_address_with_program_id(account, mint, token_program_id); let tx = self .transaction_from_instructions( @@ -174,80 +182,8 @@ impl ClientExtensions for RpcClient { .await?; self.send_and_confirm_transaction(&tx) - .map(|_| associated_token_account) - .map_err(Into::into) - } - - async fn deploy_program( - &mut self, - path_to_program: &str, - program_keypair: &Keypair, - payer: &Keypair, - ) -> Result<(), Box> { - let (buffer, buffer_len) = util::load_file_to_bytes(path_to_program); - - let program_data = buffer; - - // multiply by 2 so program can be updated later on - let program_len = buffer_len; - let minimum_balance = Rent::default().minimum_balance( - bpf_loader_upgradeable::UpgradeableLoaderState::programdata_len(program_len) - .expect("Cannot get program len"), - ); - let latest_blockhash = self.get_latest_blockhash()?; - - // 1 Create account - self.send_and_confirm_transaction(&system_transaction::create_account( - payer, - program_keypair, - latest_blockhash, - minimum_balance, - program_len as u64, - &bpf_loader::id(), - ))?; - - // 2. Write to buffer - let deploy_ix = |offset: u32, bytes: Vec| { - loader_instruction::write(&program_keypair.pubkey(), &bpf_loader::id(), offset, bytes) - }; - - let chunk_size = util::calculate_chunk_size(deploy_ix, &vec![payer, program_keypair]); - - for (chunk, i) in program_data.chunks(chunk_size).zip(0..) { - let ix = deploy_ix(i * chunk_size as u32, chunk.to_vec()); - let tx = self - .transaction_from_instructions(&[ix], payer, vec![payer, program_keypair]) - .await - .unwrap(); - - self.send_and_confirm_transaction(&tx)?; - } - - // 3. Finalize - // let finalize_msg = Message::new_with_blockhash( - // &[loader_instruction::finalize( - // &program_keypair.pubkey(), - // &bpf_loader::id(), - // )], - // Some(&payer.pubkey()), - // &latest_blockhash, - // ); - // let finalize_tx = Transaction::new(&[payer, program_keypair], finalize_msg, latest_blockhash); - - let finalize_tx = self - .transaction_from_instructions( - &[loader_instruction::finalize( - &program_keypair.pubkey(), - &bpf_loader::id(), - )], - payer, - vec![payer, program_keypair], - ) .await - .unwrap(); - - self.send_and_confirm_transaction(&finalize_tx) - .map(|_| ()) + .map(|_| associated_token_account) .map_err(Into::into) } @@ -266,8 +202,7 @@ impl ClientExtensions for RpcClient { // multiply by 2 so program can be updated later on let program_len = buffer_len * 2; let minimum_balance = Rent::default().minimum_balance( - bpf_loader_upgradeable::UpgradeableLoaderState::programdata_len(program_len) - .expect("Cannot get program len"), + bpf_loader_upgradeable::UpgradeableLoaderState::size_of_programdata(program_len), ); // 1 Create buffer @@ -288,7 +223,7 @@ impl ClientExtensions for RpcClient { ) .await?; - self.send_and_confirm_transaction(&tx)?; + self.send_and_confirm_transaction(&tx).await?; // 2 Write to buffer let deploy_ix = |offset: u32, bytes: Vec| { @@ -303,17 +238,25 @@ impl ClientExtensions for RpcClient { let chunk_size = util::calculate_chunk_size(deploy_ix, &vec![payer, buffer_authority_signer]); + let mut txs = vec![]; + for (chunk, i) in program_data.chunks(chunk_size).zip(0..) { - let ix = deploy_ix(i * chunk_size as u32, chunk.to_vec()); + txs.push(Transaction::new_signed_with_payer( + &[deploy_ix(i * chunk_size as u32, chunk.to_vec())], + Some(&payer.pubkey()), + &vec![payer, buffer_authority_signer], + self.get_latest_blockhash().await?, + )); + } - tx = self - .transaction_from_instructions(&[ix], payer, vec![payer, buffer_authority_signer]) - .await - .unwrap(); + let mut futures = vec![]; - self.send_and_confirm_transaction(&tx)?; + for tx in txs.iter() { + futures.push(self.send_and_confirm_transaction(tx)); } + join_all(futures).await; + // 3. Finalize tx = self .transaction_from_instructions( @@ -333,6 +276,7 @@ impl ClientExtensions for RpcClient { .await?; self.send_and_confirm_transaction(&tx) + .await .map(|_| ()) .map_err(Into::into) } diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs index 9006f19..ec98ccf 100644 --- a/src/extensions/mod.rs +++ b/src/extensions/mod.rs @@ -1,7 +1,7 @@ mod client; -mod program_test_context; mod program_test; +mod program_test_context; pub use client::*; +pub use program_test::*; pub use program_test_context::*; -pub use program_test::*; \ No newline at end of file diff --git a/src/extensions/program_test.rs b/src/extensions/program_test.rs index aaa26a8..f81779d 100644 --- a/src/extensions/program_test.rs +++ b/src/extensions/program_test.rs @@ -23,7 +23,7 @@ use anchor_lang::{AnchorSerialize, Discriminator}; #[cfg(feature = "pyth")] use { crate::util::PriceAccountWrapper, - pyth_sdk_solana::state::{PriceAccount, PriceInfo}, + pyth_sdk_solana::state::{PriceInfo, SolanaPriceAccount}, solana_program_test::BanksClientError, }; @@ -79,6 +79,7 @@ pub trait ProgramTestExtension { ); /// Adds an SPL Token account to the test environment. + #[allow(clippy::too_many_arguments)] fn add_token_account( &mut self, pubkey: Pubkey, @@ -93,6 +94,7 @@ pub trait ProgramTestExtension { /// Adds an associated token account to the test environment. /// Returns the address of the created account. + #[allow(clippy::too_many_arguments)] fn add_associated_token_account( &mut self, mint: Pubkey, @@ -114,15 +116,15 @@ pub trait ProgramTestExtension { process_instruction: Option, ); - /// Adds a BPF program to the test environment. - /// The program is upgradeable if `Some` `program_authority` and then providing the program data account + /// Adds a BPF program to the test environment. + /// The program is upgradeable if `Some` `program_authority` and then providing the program data account /// This is useful for those programs which the program data has to be a spefic one, if not, use add_bpf_program fn add_bpf_program_with_program_data( &mut self, program_name: &str, program_id: Pubkey, program_authority: Option, - program_data: Pubkey, + program_data: Pubkey, process_instruction: Option, ); @@ -132,7 +134,7 @@ pub trait ProgramTestExtension { &mut self, oracle: Pubkey, program_id: Pubkey, - price_account: Option, + price_account: Option, price_info: Option, timestamp: Option, ) -> Result<(), BanksClientError>; @@ -178,7 +180,7 @@ impl ProgramTestExtension for ProgramTest { anchor_data: T, executable: bool, ) { - let discriminator = &T::discriminator(); + let discriminator = &T::DISCRIMINATOR; let data = anchor_data .try_to_vec() .expect("Cannot serialize provided anchor account"); @@ -188,7 +190,7 @@ impl ProgramTestExtension for ProgramTest { self.add_account_with_data(pubkey, owner, &v, executable); } - //Note that the total size is 8 (disciminator) + size + //Note that the total size is 8 (discriminator) + size #[cfg(feature = "anchor")] fn add_empty_account_with_anchor( &mut self, @@ -196,7 +198,7 @@ impl ProgramTestExtension for ProgramTest { owner: Pubkey, size: usize, ) { - let discriminator = &T::discriminator(); + let discriminator = &T::DISCRIMINATOR; let data = vec![0_u8; size]; let mut v = Vec::new(); v.extend_from_slice(discriminator); @@ -466,28 +468,26 @@ impl ProgramTestExtension for ProgramTest { &mut self, oracle: Pubkey, program_id: Pubkey, - price_account: Option, + price_account: Option, price_info: Option, timestamp: Option, ) -> Result<(), BanksClientError> { let data = if let Some(price_account) = price_account { bincode::serialize(&PriceAccountWrapper(&price_account)).unwrap() } else if let (Some(price_info), Some(timestamp)) = (price_info, timestamp) { - bincode::serialize(&PriceAccountWrapper( - &pyth_sdk_solana::state::PriceAccount { - magic: 0xa1b2c3d4, - ver: 2, - expo: 5, - atype: 3, - agg: price_info, - timestamp, - prev_timestamp: 100, - prev_price: 60, - prev_conf: 70, - prev_slot: 1, - ..Default::default() - }, - )) + bincode::serialize(&PriceAccountWrapper(&SolanaPriceAccount { + magic: 0xa1b2c3d4, + ver: 2, + expo: 5, + atype: 3, + agg: price_info, + timestamp, + prev_timestamp: 100, + prev_price: 60, + prev_conf: 70, + prev_slot: 1, + ..Default::default() + })) .unwrap() } else { return Err(BanksClientError::ClientError( diff --git a/src/extensions/program_test_context.rs b/src/extensions/program_test_context.rs index 5bcb1d8..db3f1b4 100644 --- a/src/extensions/program_test_context.rs +++ b/src/extensions/program_test_context.rs @@ -1,13 +1,14 @@ use async_trait::async_trait; -use solana_program::pubkey::Pubkey; use solana_program_test::{ProgramTestContext, ProgramTestError}; -use solana_sdk::{account::AccountSharedData, sysvar::clock::Clock}; +use solana_sdk::sysvar::clock::Clock; #[cfg(feature = "pyth")] use { crate::error::TestFrameWorkError, crate::util::PriceAccountWrapper, - pyth_sdk_solana::state::{PriceAccount, PriceInfo}, + pyth_sdk_solana::state::{PriceInfo, SolanaPriceAccount}, + solana_program::pubkey::Pubkey, + solana_sdk::account::AccountSharedData, }; #[async_trait] @@ -19,7 +20,7 @@ pub trait ProgramTestContextExtension { async fn update_pyth_oracle( &mut self, address: Pubkey, - price_account: Option, + price_account: Option, price_info: Option, timestamp: Option, valid_slots: Option, @@ -63,7 +64,7 @@ impl ProgramTestContextExtension for ProgramTestContext { async fn update_pyth_oracle( &mut self, address: Pubkey, - price_account: Option, + price_account: Option, price_info: Option, timestamp: Option, valid_slot: Option, diff --git a/src/lib.rs b/src/lib.rs index 1a664b6..c55e94f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,17 +7,26 @@ pub use solana_program_test::tokio; pub use solana_program_test::*; #[macro_export] -macro_rules! processor { - ($process_instruction:expr) => { - Some( - |first_instruction_account: usize, - invoke_context: &mut solana_program_test::InvokeContext| { - $crate::builtin_process_instruction( - $process_instruction, - first_instruction_account, - invoke_context, - ) - }, - ) +macro_rules! correct_entry { + ($correct_entry:ident, $entry:path) => { + fn $correct_entry( + program_id: &solana_program::pubkey::Pubkey, + accounts: &[solana_program::account_info::AccountInfo], + data: &[u8], + ) -> solana_program::entrypoint::ProgramResult { + $entry( + program_id, + unsafe { &*(accounts as *const [solana_program::account_info::AccountInfo]) }, + data, + ) + } }; } + +#[macro_export] +macro_rules! processor { + ($entry:path) => {{ + $crate::correct_entry!(__correct_entry, $entry); + solana_program_test::processor!(__correct_entry) + }}; +} diff --git a/src/util.rs b/src/util.rs index 315cb68..b07ef5c 100644 --- a/src/util.rs +++ b/src/util.rs @@ -7,7 +7,7 @@ use std::io::Read; #[cfg(feature = "pyth")] use { - pyth_sdk_solana::state::{PriceAccount, PriceComp, PriceInfo, PriceType, Rational}, + pyth_sdk_solana::state::{PriceComp, PriceInfo, PriceType, Rational, SolanaPriceAccount}, serde::{Deserialize, Serialize}, solana_sdk::pubkey::Pubkey, }; @@ -40,11 +40,11 @@ pub fn calculate_chunk_size) -> Instruction>( #[cfg(feature = "pyth")] #[derive(serde::Serialize)] -pub struct PriceAccountWrapper<'a>(#[serde(with = "PriceAccountDef")] pub &'a PriceAccount); +pub struct PriceAccountWrapper<'a>(#[serde(with = "PriceAccountDef")] pub &'a SolanaPriceAccount); #[cfg(feature = "pyth")] #[derive(Serialize, Deserialize)] -#[serde(remote = "PriceAccount")] +#[serde(remote = "SolanaPriceAccount")] #[repr(C)] pub struct PriceAccountDef { /// pyth magic number @@ -97,4 +97,6 @@ pub struct PriceAccountDef { pub agg: PriceInfo, /// price components one per quoter pub comp: [PriceComp; 32], + /// additional extended account data + pub extended: (), } diff --git a/tests/artifacts/program_for_tests.so b/tests/artifacts/program_for_tests.so deleted file mode 100755 index 8c6487a..0000000 Binary files a/tests/artifacts/program_for_tests.so and /dev/null differ diff --git a/tests/artifacts/program_for_tests/Anchor.toml b/tests/artifacts/program_for_tests/Anchor.toml deleted file mode 100644 index f60d23b..0000000 --- a/tests/artifacts/program_for_tests/Anchor.toml +++ /dev/null @@ -1,9 +0,0 @@ -[provider] -cluster = "localnet" -wallet = "~/.config/solana/id.json" - -[programs.localnet] -basic_4 = "CwrqeMj2U8tFr1Rhkgwc84tpAsqbt9pTt2a4taoTADPr" - -[scripts] -test = "yarn run mocha -t 1000000 tests/" diff --git a/tests/artifacts/program_for_tests/Cargo.toml b/tests/artifacts/program_for_tests/Cargo.toml deleted file mode 100644 index 827b0be..0000000 --- a/tests/artifacts/program_for_tests/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "program-for-tests" -version = "0.1.0" -description = "Created with Anchor" -rust-version = "1.56" -edition = "2021" - -[lib] -crate-type = ["cdylib", "lib"] -name = "program_for_tests" - -[features] -no-entrypoint = [] -cpi = ["no-entrypoint"] - -[dependencies] -anchor-lang = { version = "0.26.0" } diff --git a/tests/testsuite/artifacts/program_for_tests.so b/tests/testsuite/artifacts/program_for_tests.so new file mode 100755 index 0000000..d153bba Binary files /dev/null and b/tests/testsuite/artifacts/program_for_tests.so differ diff --git a/tests/testsuite/artifacts/program_for_tests/Anchor.toml b/tests/testsuite/artifacts/program_for_tests/Anchor.toml new file mode 100644 index 0000000..8a97016 --- /dev/null +++ b/tests/testsuite/artifacts/program_for_tests/Anchor.toml @@ -0,0 +1,19 @@ +[toolchain] +anchor_version = "0.30.1" + +[features] +seeds = false +skip-lint = false + +[registry] +url = "https://api.apr.dev" + +[provider] +cluster = "localnet" +wallet = "~/.config/solana/id.json" + +[programs.localnet] +program_for_tests = "CwrqeMj2U8tFr1Rhkgwc84tpAsqbt9pTt2a4taoTADPr" + +[scripts] +test = "yarn run mocha -t 1000000 tests/" diff --git a/tests/testsuite/artifacts/program_for_tests/Cargo.toml b/tests/testsuite/artifacts/program_for_tests/Cargo.toml new file mode 100644 index 0000000..2d029cc --- /dev/null +++ b/tests/testsuite/artifacts/program_for_tests/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "program-for-tests" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "program_for_tests" + +[features] +default = [] +cpi = ["no-entrypoint"] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +idl-build = ["anchor-lang/idl-build"] + +[dependencies] +anchor-lang = { version = "0.30.1", features = [] } + +[profile.release] +overflow-checks = true +lto = "fat" +codegen-units = 1 + +[profile.release.build-override] +opt-level = 3 +incremental = false +codegen-units = 1 diff --git a/tests/artifacts/program_for_tests/Xargo.toml b/tests/testsuite/artifacts/program_for_tests/Xargo.toml similarity index 100% rename from tests/artifacts/program_for_tests/Xargo.toml rename to tests/testsuite/artifacts/program_for_tests/Xargo.toml diff --git a/tests/artifacts/program_for_tests/src/lib.rs b/tests/testsuite/artifacts/program_for_tests/src/lib.rs similarity index 98% rename from tests/artifacts/program_for_tests/src/lib.rs rename to tests/testsuite/artifacts/program_for_tests/src/lib.rs index 1997d3a..64b821f 100644 --- a/tests/artifacts/program_for_tests/src/lib.rs +++ b/tests/testsuite/artifacts/program_for_tests/src/lib.rs @@ -9,7 +9,7 @@ declare_id!("CwrqeMj2U8tFr1Rhkgwc84tpAsqbt9pTt2a4taoTADPr"); pub mod basic_4 { use super::*; - #[state] + #[account] pub struct Counter { pub authority: Pubkey, pub count: u64, diff --git a/tests/artifacts/usdc_mint b/tests/testsuite/artifacts/usdc_mint similarity index 100% rename from tests/artifacts/usdc_mint rename to tests/testsuite/artifacts/usdc_mint diff --git a/tests/banks_client.rs b/tests/testsuite/banks_client.rs similarity index 90% rename from tests/banks_client.rs rename to tests/testsuite/banks_client.rs index c273ca0..12863e4 100644 --- a/tests/banks_client.rs +++ b/tests/testsuite/banks_client.rs @@ -10,10 +10,9 @@ use { spl_token::state::{Account as TokenAccount, Mint}, }; +use crate::helpers; use std::str::FromStr; -mod helpers; - #[tokio::test] async fn transaction_from_instructions() { let (mut program, _) = helpers::add_program(); @@ -60,7 +59,7 @@ async fn transaction_from_instructions() { async fn transaction_from_instructions_upgradeable() { let mut program = ProgramTest::default(); program.add_bpf_program( - "tests/artifacts/program_for_tests", + "tests/testsuite/artifacts/program_for_tests", Pubkey::from_str("CwrqeMj2U8tFr1Rhkgwc84tpAsqbt9pTt2a4taoTADPr").unwrap(), Some(Pubkey::from_str("CwrqeMj2U8tFr1Rhkgwc84tpAsqbt9pTt2a4taoTADPr").unwrap()), None, @@ -245,34 +244,6 @@ async fn create_associated_token_account() { assert_eq!(token_account_data.owner, payer.pubkey()); } -#[tokio::test] -async fn deploy_program() { - let (mut program, _) = helpers::add_program(); - let payer = helpers::add_payer(&mut program); - - let program_keypair = Keypair::new(); - - let (mut banks_client, _payer_keypair, mut _recent_blockhash) = program.start().await; - banks_client - .deploy_program( - "tests/artifacts/program_for_tests.so", - &program_keypair, - &payer, - ) - .await - .unwrap(); - let deployed_program_account = banks_client - .get_account(program_keypair.pubkey()) - .await - .unwrap() - .unwrap(); - - assert_eq!( - deployed_program_account.owner, - Pubkey::from_str("BPFLoader2111111111111111111111111111111111").unwrap() - ); -} - #[tokio::test] async fn deploy_upgradable_program() { let (mut program, _) = helpers::add_program(); @@ -285,7 +256,7 @@ async fn deploy_upgradable_program() { let (mut banks_client, _payer_keypair, mut _recent_blockhash) = program.start().await; banks_client .deploy_upgradable_program( - "tests/artifacts/program_for_tests.so", + "tests/testsuite/artifacts/program_for_tests.so", &buffer_keypair, &buffer_authority_signer, &program_keypair, diff --git a/tests/helpers.rs b/tests/testsuite/helpers.rs similarity index 97% rename from tests/helpers.rs rename to tests/testsuite/helpers.rs index a153532..885aa75 100644 --- a/tests/helpers.rs +++ b/tests/testsuite/helpers.rs @@ -29,5 +29,5 @@ pub fn add_payer(program: &mut ProgramTest) -> Keypair { }, ); - return payer; + payer } diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs new file mode 100644 index 0000000..8293a41 --- /dev/null +++ b/tests/testsuite/main.rs @@ -0,0 +1,5 @@ +mod banks_client; +mod helpers; +mod program_test; +mod program_test_context; +mod rpc_client; diff --git a/tests/program_test.rs b/tests/testsuite/program_test.rs similarity index 97% rename from tests/program_test.rs rename to tests/testsuite/program_test.rs index 851651d..94dabe4 100644 --- a/tests/program_test.rs +++ b/tests/testsuite/program_test.rs @@ -15,10 +15,9 @@ use borsh::BorshDeserialize; #[cfg(feature = "anchor")] use {anchor_lang::AccountDeserialize, program_for_tests::CountTracker}; +use crate::helpers; #[cfg(feature = "pyth")] -use pyth_sdk_solana::state::{PriceAccount, PriceInfo, PriceStatus}; - -mod helpers; +use pyth_sdk_solana::state::{PriceInfo, PriceStatus, SolanaPriceAccount}; #[tokio::test] async fn generate_accounts() { @@ -47,7 +46,8 @@ async fn add_account_with_data() { // USDC Mint from mainnet // got using solana account --output-file usdc_mint EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v - let (data, _) = solana_test_framework::util::load_file_to_bytes("tests/artifacts/usdc_mint"); + let (data, _) = + solana_test_framework::util::load_file_to_bytes("tests/testsuite/artifacts/usdc_mint"); program.add_account_with_data(acc_pubkey, owner, &data[..], false); @@ -256,7 +256,7 @@ async fn add_pyth_price_feed() { pub_slot: 3, ..Default::default() }; - let price_account = PriceAccount { + let price_account = SolanaPriceAccount { magic: 0xa1b2c3d4, ver: 2, expo: 5, diff --git a/tests/program_test_context.rs b/tests/testsuite/program_test_context.rs similarity index 93% rename from tests/program_test_context.rs rename to tests/testsuite/program_test_context.rs index e77d40d..18b7e8a 100644 --- a/tests/program_test_context.rs +++ b/tests/testsuite/program_test_context.rs @@ -4,10 +4,8 @@ use solana_sdk::{pubkey::Pubkey, sysvar::clock::Clock}; use std::str::FromStr; -mod helpers; - #[cfg(feature = "pyth")] -use pyth_sdk_solana::state::{PriceAccount, PriceInfo, PriceStatus}; +use pyth_sdk_solana::state::{PriceInfo, PriceStatus, SolanaPriceAccount}; #[tokio::test] async fn transaction_from_instructions() { @@ -40,7 +38,7 @@ async fn transaction_from_instructions() { #[cfg(feature = "pyth")] #[tokio::test] async fn update_pyth_oracle() { - let (mut program, program_id) = helpers::add_program(); + let (mut program, program_id) = crate::helpers::add_program(); let oracle = Pubkey::new_unique(); @@ -53,7 +51,7 @@ async fn update_pyth_oracle() { ..Default::default() }; let valid_slot = 10; - let price_account = PriceAccount { + let price_account = SolanaPriceAccount { magic: 0xa1b2c3d4, ver: 2, expo: 5, @@ -87,7 +85,7 @@ async fn update_pyth_oracle() { pub_slot: 3, ..Default::default() }; - let price_account2 = PriceAccount { + let price_account2 = SolanaPriceAccount { magic: 0xa1b2c3d4, ver: 2, expo: 5, diff --git a/tests/rpc_client.rs b/tests/testsuite/rpc_client.rs similarity index 70% rename from tests/rpc_client.rs rename to tests/testsuite/rpc_client.rs index 7ce6525..7dd4c86 100644 --- a/tests/rpc_client.rs +++ b/tests/testsuite/rpc_client.rs @@ -10,7 +10,7 @@ use { spl_token::state::Mint, }; -use solana_test_validator::{ProgramInfo, TestValidatorGenesis}; +use solana_test_validator::{TestValidatorGenesis, UpgradeableProgramInfo}; use spl_token::state::Account as TokenAccount; @@ -20,16 +20,17 @@ use std::str::FromStr; async fn transaction_from_instructions() { let mut genesis_config = TestValidatorGenesis::default(); let program_id = Pubkey::from_str("CwrqeMj2U8tFr1Rhkgwc84tpAsqbt9pTt2a4taoTADPr").unwrap(); - let program_path = "tests/artifacts/program_for_tests.so"; + let program_path = "tests/testsuite/artifacts/program_for_tests.so"; - genesis_config.add_programs_with_path(&[ProgramInfo { + genesis_config.add_upgradeable_programs_with_path(&[UpgradeableProgramInfo { program_id, - loader: solana_sdk::bpf_loader::id(), + loader: solana_sdk::bpf_loader_upgradeable::id(), + upgrade_authority: Default::default(), program_path: std::path::PathBuf::from(program_path), }]); let (test_validator, payer) = genesis_config.start_async().await; - let mut rpc_client = test_validator.get_rpc_client(); + let mut rpc_client = test_validator.get_async_rpc_client(); let acc_1 = Keypair::new(); let acc_2 = Keypair::new(); @@ -53,9 +54,9 @@ async fn transaction_from_instructions() { .await .unwrap(); - assert!(rpc_client.send_and_confirm_transaction(&tx).is_ok()); - let acc1_data = rpc_client.get_account(&acc_1.pubkey()).unwrap(); - let acc2_data = rpc_client.get_account(&acc_2.pubkey()).unwrap(); + assert!(rpc_client.send_and_confirm_transaction(&tx).await.is_ok()); + let acc1_data = rpc_client.get_account(&acc_1.pubkey()).await.unwrap(); + let acc2_data = rpc_client.get_account(&acc_2.pubkey()).await.unwrap(); assert_eq!(acc1_data.owner, acc_1.pubkey()); assert_eq!(acc2_data.owner, acc_2.pubkey()); } @@ -64,16 +65,17 @@ async fn transaction_from_instructions() { async fn create_account() { let mut genesis_config = TestValidatorGenesis::default(); let program_id = Pubkey::from_str("CwrqeMj2U8tFr1Rhkgwc84tpAsqbt9pTt2a4taoTADPr").unwrap(); - let program_path = "tests/artifacts/program_for_tests.so"; + let program_path = "tests/testsuite/artifacts/program_for_tests.so"; - genesis_config.add_programs_with_path(&[ProgramInfo { + genesis_config.add_upgradeable_programs_with_path(&[UpgradeableProgramInfo { program_id, - loader: solana_sdk::bpf_loader::id(), + loader: solana_sdk::bpf_loader_upgradeable::id(), + upgrade_authority: Default::default(), program_path: std::path::PathBuf::from(program_path), }]); let (test_validator, payer) = genesis_config.start_async().await; - let mut rpc_client = test_validator.get_rpc_client(); + let mut rpc_client = test_validator.get_async_rpc_client(); let lamports = 1_000_000; let new_acc = Keypair::new(); @@ -83,7 +85,7 @@ async fn create_account() { .await .unwrap(); - let acc = rpc_client.get_account(&new_acc.pubkey()).unwrap(); + let acc = rpc_client.get_account(&new_acc.pubkey()).await.unwrap(); assert_eq!(acc.lamports, lamports); } @@ -91,16 +93,17 @@ async fn create_account() { async fn create_token_mint() { let mut genesis_config = TestValidatorGenesis::default(); let program_id = Pubkey::from_str("CwrqeMj2U8tFr1Rhkgwc84tpAsqbt9pTt2a4taoTADPr").unwrap(); - let program_path = "tests/artifacts/program_for_tests.so"; + let program_path = "tests/testsuite/artifacts/program_for_tests.so"; - genesis_config.add_programs_with_path(&[ProgramInfo { + genesis_config.add_upgradeable_programs_with_path(&[UpgradeableProgramInfo { program_id, - loader: solana_sdk::bpf_loader::id(), + loader: solana_sdk::bpf_loader_upgradeable::id(), + upgrade_authority: Default::default(), program_path: std::path::PathBuf::from(program_path), }]); let (test_validator, payer) = genesis_config.start_async().await; - let mut rpc_client = test_validator.get_rpc_client(); + let mut rpc_client = test_validator.get_async_rpc_client(); let mint = Keypair::new(); let freeze_pubkey = Pubkey::new_unique(); @@ -118,7 +121,7 @@ async fn create_token_mint() { .await .unwrap(); //Test mint with defaults creation - let mint_acc = rpc_client.get_account(&mint.pubkey()).unwrap(); + let mint_acc = rpc_client.get_account(&mint.pubkey()).await.unwrap(); let mint_data = Mint::unpack(&mint_acc.data).unwrap(); assert_eq!(mint_data.freeze_authority.unwrap(), freeze_pubkey); assert_eq!(mint_data.decimals, decimals); @@ -129,16 +132,17 @@ async fn create_token_mint() { async fn create_token_account() { let mut genesis_config = TestValidatorGenesis::default(); let program_id = Pubkey::from_str("CwrqeMj2U8tFr1Rhkgwc84tpAsqbt9pTt2a4taoTADPr").unwrap(); - let program_path = "tests/artifacts/program_for_tests.so"; + let program_path = "tests/testsuite/artifacts/program_for_tests.so"; - genesis_config.add_programs_with_path(&[ProgramInfo { + genesis_config.add_upgradeable_programs_with_path(&[UpgradeableProgramInfo { program_id, - loader: solana_sdk::bpf_loader::id(), + loader: solana_sdk::bpf_loader_upgradeable::id(), + upgrade_authority: Default::default(), program_path: std::path::PathBuf::from(program_path), }]); let (test_validator, payer) = genesis_config.start_async().await; - let mut rpc_client = test_validator.get_rpc_client(); + let mut rpc_client = test_validator.get_async_rpc_client(); let mint = Keypair::new(); let freeze_pubkey = Pubkey::new_unique(); @@ -163,7 +167,10 @@ async fn create_token_account() { .await .unwrap(); - let token_account = rpc_client.get_account(&token_account.pubkey()).unwrap(); + let token_account = rpc_client + .get_account(&token_account.pubkey()) + .await + .unwrap(); let token_account_data = TokenAccount::unpack(&token_account.data).unwrap(); @@ -175,17 +182,18 @@ async fn create_token_account() { async fn create_associated_token_account() { let mut genesis_config = TestValidatorGenesis::default(); let program_id = Pubkey::from_str("CwrqeMj2U8tFr1Rhkgwc84tpAsqbt9pTt2a4taoTADPr").unwrap(); - let program_path = "tests/artifacts/program_for_tests.so"; + let program_path = "tests/testsuite/artifacts/program_for_tests.so"; let token_program_id = spl_token::ID; //could also use token-2022 ID - genesis_config.add_programs_with_path(&[ProgramInfo { + genesis_config.add_upgradeable_programs_with_path(&[UpgradeableProgramInfo { program_id, - loader: solana_sdk::bpf_loader::id(), + loader: solana_sdk::bpf_loader_upgradeable::id(), + upgrade_authority: Default::default(), program_path: std::path::PathBuf::from(program_path), }]); let (test_validator, payer) = genesis_config.start_async().await; - let mut rpc_client = test_validator.get_rpc_client(); + let mut rpc_client = test_validator.get_async_rpc_client(); let mint = Keypair::new(); let freeze_pubkey = Pubkey::new_unique(); @@ -208,7 +216,7 @@ async fn create_associated_token_account() { .await .unwrap(); - let token_account = rpc_client.get_account(&token_account).unwrap(); + let token_account = rpc_client.get_account(&token_account).await.unwrap(); let token_account_data = TokenAccount::unpack(&token_account.data).unwrap(); @@ -216,35 +224,11 @@ async fn create_associated_token_account() { assert_eq!(token_account_data.owner, payer.pubkey()); } -#[tokio::test(flavor = "multi_thread")] -async fn deploy_program() { - let genesis_config = TestValidatorGenesis::default(); - let program_keypair = Keypair::new(); - - let (test_validator, payer) = genesis_config.start_async().await; - let mut rpc_client = test_validator.get_rpc_client(); - - rpc_client - .deploy_program( - "tests/artifacts/program_for_tests.so", - &program_keypair, - &payer, - ) - .await - .unwrap(); - let deployed_program_account = rpc_client.get_account(&program_keypair.pubkey()).unwrap(); - - assert_eq!( - deployed_program_account.owner, - Pubkey::from_str("BPFLoader2111111111111111111111111111111111").unwrap() - ); -} - #[tokio::test(flavor = "multi_thread")] async fn deploy_upgradable_program() { let genesis_config = TestValidatorGenesis::default(); let (test_validator, payer) = genesis_config.start_async().await; - let mut rpc_client = test_validator.get_rpc_client(); + let mut rpc_client = test_validator.get_async_rpc_client(); let program_keypair = Keypair::new(); let buffer_keypair = Keypair::new(); @@ -252,7 +236,7 @@ async fn deploy_upgradable_program() { rpc_client .deploy_upgradable_program( - "tests/artifacts/program_for_tests.so", + "tests/testsuite/artifacts/program_for_tests.so", &buffer_keypair, &buffer_authority_signer, &program_keypair, @@ -260,7 +244,10 @@ async fn deploy_upgradable_program() { ) .await .unwrap(); - let deployed_program_account = rpc_client.get_account(&program_keypair.pubkey()).unwrap(); + let deployed_program_account = rpc_client + .get_account(&program_keypair.pubkey()) + .await + .unwrap(); assert_eq!( deployed_program_account.owner,