|
|
@@ -0,0 +1,163 @@
|
|
|
+use {
|
|
|
+ crate::ID,
|
|
|
+ solana_program::{
|
|
|
+ bpf_loader_upgradeable::{
|
|
|
+ self,
|
|
|
+ UpgradeableLoaderState,
|
|
|
+ },
|
|
|
+ hash::hash,
|
|
|
+ hash::Hash,
|
|
|
+ instruction::{
|
|
|
+ AccountMeta,
|
|
|
+ Instruction,
|
|
|
+ },
|
|
|
+ native_token::LAMPORTS_PER_SOL,
|
|
|
+ pubkey::Pubkey,
|
|
|
+ rent::Rent,
|
|
|
+ stake_history::Epoch,
|
|
|
+ system_instruction,
|
|
|
+ system_program,
|
|
|
+ },
|
|
|
+ solana_program_test::{
|
|
|
+ BanksClient,
|
|
|
+ BanksClientError,
|
|
|
+ ProgramTest,
|
|
|
+ ProgramTestBanksClientExt,
|
|
|
+ read_file,
|
|
|
+ },
|
|
|
+ solana_sdk::{
|
|
|
+ account::Account,
|
|
|
+ signature::{
|
|
|
+ Keypair,
|
|
|
+ Signer,
|
|
|
+ },
|
|
|
+ transaction::Transaction,
|
|
|
+ },
|
|
|
+ std::{
|
|
|
+ mem::size_of,
|
|
|
+ path::Path,
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
+/// Simulator for the state of the target chain program on Solana. You can run solana transactions against
|
|
|
+/// this struct to test how pyth instructions execute in the Solana runtime.
|
|
|
+pub struct ProgramSimulator {
|
|
|
+ pub program_id: Pubkey,
|
|
|
+ banks_client: BanksClient,
|
|
|
+ /// Hash used to submit the last transaction. The hash must be advanced for each new
|
|
|
+ /// transaction; otherwise, replayed transactions in different states can return stale
|
|
|
+ /// results.
|
|
|
+ last_blockhash: Hash,
|
|
|
+ programdata_id: Pubkey,
|
|
|
+ pub upgrade_authority: Keypair,
|
|
|
+ pub genesis_keypair: Keypair,
|
|
|
+}
|
|
|
+
|
|
|
+impl ProgramSimulator {
|
|
|
+
|
|
|
+ /// Deploys the target chain contract as upgradable
|
|
|
+ pub async fn new() -> ProgramSimulator {
|
|
|
+ let mut bpf_data = read_file(
|
|
|
+ std::env::current_dir()
|
|
|
+ .unwrap()
|
|
|
+ .join(Path::new("../../target/deploy/pyth_solana_receiver.so")),
|
|
|
+ );
|
|
|
+
|
|
|
+
|
|
|
+ let mut program_test = ProgramTest::default();
|
|
|
+ let program_key = Pubkey::try_from(ID).unwrap();
|
|
|
+ // This PDA is the actual address in the real world
|
|
|
+ // https://docs.rs/solana-program/1.6.4/solana_program/bpf_loader_upgradeable/index.html
|
|
|
+ let (programdata_key, _) =
|
|
|
+ Pubkey::find_program_address(&[&program_key.to_bytes()], &bpf_loader_upgradeable::id());
|
|
|
+
|
|
|
+ let upgrade_authority_keypair = Keypair::new();
|
|
|
+
|
|
|
+ let program_deserialized = UpgradeableLoaderState::Program {
|
|
|
+ programdata_address: programdata_key,
|
|
|
+ };
|
|
|
+ let programdata_deserialized = UpgradeableLoaderState::ProgramData {
|
|
|
+ slot: 1,
|
|
|
+ upgrade_authority_address: Some(upgrade_authority_keypair.pubkey()),
|
|
|
+ };
|
|
|
+
|
|
|
+ // Program contains a pointer to progradata
|
|
|
+ let program_vec = bincode::serialize(&program_deserialized).unwrap();
|
|
|
+ // Programdata contains a header and the binary of the program
|
|
|
+ let mut programdata_vec = bincode::serialize(&programdata_deserialized).unwrap();
|
|
|
+ programdata_vec.append(&mut bpf_data);
|
|
|
+
|
|
|
+ let program_account = Account {
|
|
|
+ lamports: Rent::default().minimum_balance(program_vec.len()),
|
|
|
+ data: program_vec,
|
|
|
+ owner: bpf_loader_upgradeable::ID,
|
|
|
+ executable: true,
|
|
|
+ rent_epoch: Epoch::default(),
|
|
|
+ };
|
|
|
+ let programdata_account = Account {
|
|
|
+ lamports: Rent::default().minimum_balance(programdata_vec.len()),
|
|
|
+ data: programdata_vec,
|
|
|
+ owner: bpf_loader_upgradeable::ID,
|
|
|
+ executable: false,
|
|
|
+ rent_epoch: Epoch::default(),
|
|
|
+ };
|
|
|
+
|
|
|
+ // Add to both accounts to program test, now the program is deploy as upgradable
|
|
|
+ program_test.add_account(program_key, program_account);
|
|
|
+ program_test.add_account(programdata_key, programdata_account);
|
|
|
+
|
|
|
+
|
|
|
+ // Start validator
|
|
|
+ let (banks_client, genesis_keypair, recent_blockhash) = program_test.start().await;
|
|
|
+
|
|
|
+ let mut result = ProgramSimulator {
|
|
|
+ program_id: program_key,
|
|
|
+ banks_client,
|
|
|
+ last_blockhash: recent_blockhash,
|
|
|
+ programdata_id: programdata_key,
|
|
|
+ upgrade_authority: upgrade_authority_keypair,
|
|
|
+ genesis_keypair,
|
|
|
+ };
|
|
|
+
|
|
|
+ // Transfer money to upgrade_authority so it can call the instructions
|
|
|
+ result
|
|
|
+ .airdrop(&result.upgrade_authority.pubkey(), 1000 * LAMPORTS_PER_SOL)
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+
|
|
|
+ result
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /// Process a transaction containing `instruction` signed by `signers`.
|
|
|
+ /// `payer` is used to pay for and sign the transaction.
|
|
|
+ pub async fn process_ix(
|
|
|
+ &mut self,
|
|
|
+ instruction: Instruction,
|
|
|
+ signers: &Vec<&Keypair>,
|
|
|
+ payer: &Keypair,
|
|
|
+ ) -> Result<(), BanksClientError> {
|
|
|
+ let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
|
|
|
+
|
|
|
+ let blockhash = self
|
|
|
+ .banks_client
|
|
|
+ .get_new_latest_blockhash(&self.last_blockhash)
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ self.last_blockhash = blockhash;
|
|
|
+
|
|
|
+ transaction.partial_sign(&[payer], self.last_blockhash);
|
|
|
+ transaction.partial_sign(signers, self.last_blockhash);
|
|
|
+
|
|
|
+ self.banks_client.process_transaction(transaction).await
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Send `lamports` worth of SOL to the pubkey `to`.
|
|
|
+ pub async fn airdrop(&mut self, to: &Pubkey, lamports: u64) -> Result<(), BanksClientError> {
|
|
|
+ let instruction =
|
|
|
+ system_instruction::transfer(&self.genesis_keypair.pubkey(), to, lamports);
|
|
|
+
|
|
|
+ self.process_ix(instruction, &vec![], &self.genesis_keypair.insecure_clone())
|
|
|
+ .await
|
|
|
+ }
|
|
|
+}
|