Quellcode durchsuchen

[Solana] tests (#1226)

* Checkpoint

* Checkpoint

* Checkpoint

* Nice

* Finally works

* Cleanup

* Cleanup

* Cleanup

* Cleanup

* Checkpoint

* Works

* Final cleanup

* Remove async

* Continue
guibescos vor 1 Jahr
Ursprung
Commit
d3ed683b91

+ 32 - 7
target_chains/solana/Cargo.lock

@@ -2977,6 +2977,18 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "program-simulator"
+version = "0.1.0"
+dependencies = [
+ "bincode",
+ "borsh 0.10.3",
+ "solana-client",
+ "solana-program",
+ "solana-program-test",
+ "solana-sdk",
+]
+
 [[package]]
 name = "pyth-sdk"
 version = "0.8.0"
@@ -3017,16 +3029,19 @@ dependencies = [
  "byteorder",
  "lazy_static",
  "libsecp256k1 0.7.1",
+ "program-simulator",
  "pyth-sdk",
  "pyth-sdk-solana",
  "pythnet-sdk",
  "rand 0.8.5",
+ "serde_wormhole",
  "solana-program",
  "solana-program-test",
  "solana-sdk",
  "tokio",
- "wormhole-core-bridge-solana 0.0.1-alpha.3 (git+https://github.com/wormhole-foundation/wormhole?branch=wen/solana-rewrite)",
- "wormhole-raw-vaas",
+ "wormhole-core-bridge-solana 0.0.1-alpha.5",
+ "wormhole-raw-vaas 0.0.1-alpha.2",
+ "wormhole-sdk",
 ]
 
 [[package]]
@@ -3045,7 +3060,7 @@ dependencies = [
  "shellexpand",
  "solana-client",
  "solana-sdk",
- "wormhole-core-bridge-solana 0.0.1-alpha.3 (git+https://github.com/guibescos/wormhole?branch=variable-sigs)",
+ "wormhole-core-bridge-solana 0.0.1-alpha.3",
  "wormhole-sdk",
  "wormhole-solana",
 ]
@@ -6149,13 +6164,13 @@ dependencies = [
  "ruint",
  "solana-program",
  "wormhole-io",
- "wormhole-raw-vaas",
+ "wormhole-raw-vaas 0.0.1-alpha.2",
 ]
 
 [[package]]
 name = "wormhole-core-bridge-solana"
-version = "0.0.1-alpha.3"
-source = "git+https://github.com/wormhole-foundation/wormhole?branch=wen/solana-rewrite#1020390d1bf64f6fe2f3a4845df686ff2c5fea70"
+version = "0.0.1-alpha.5"
+source = "git+https://github.com/wormhole-foundation/wormhole?branch=wen/solana-rewrite#076ae39ea5227f2e7e454cdf3287c8db9dda197d"
 dependencies = [
  "anchor-lang",
  "cfg-if",
@@ -6163,7 +6178,7 @@ dependencies = [
  "ruint",
  "solana-program",
  "wormhole-io",
- "wormhole-raw-vaas",
+ "wormhole-raw-vaas 0.1.3",
 ]
 
 [[package]]
@@ -6182,6 +6197,16 @@ dependencies = [
  "ruint-macro",
 ]
 
+[[package]]
+name = "wormhole-raw-vaas"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d05bc16d11c8eb6f956c5de65bff05759ac2b961b16b054bef3666fe029db7c9"
+dependencies = [
+ "ruint",
+ "ruint-macro",
+]
+
 [[package]]
 name = "wormhole-sdk"
 version = "0.1.0"

+ 17 - 0
target_chains/solana/program_simulator/Cargo.toml

@@ -0,0 +1,17 @@
+[package]
+name = "program-simulator"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["lib"]
+name = "program_simulator"
+
+
+[dependencies]
+solana-sdk = "1.16.20"
+solana-client = "1.16.20"
+solana-program-test = "1.16.20"
+solana-program = "1.16.20"
+bincode = "1.3.3"
+borsh = "0.10.3"

+ 96 - 0
target_chains/solana/program_simulator/src/lib.rs

@@ -0,0 +1,96 @@
+use {
+    borsh::BorshDeserialize,
+    solana_program::{
+        hash::Hash,
+        instruction::Instruction,
+        native_token::LAMPORTS_PER_SOL,
+        pubkey::Pubkey,
+        system_instruction,
+    },
+    solana_program_test::{
+        BanksClient,
+        BanksClientError,
+        ProgramTest,
+        ProgramTestBanksClientExt,
+    },
+    solana_sdk::{
+        signature::{
+            Keypair,
+            Signer,
+        },
+        transaction::Transaction,
+    },
+};
+
+pub struct ProgramSimulator {
+    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,
+    genesis_keypair: Keypair,
+}
+
+impl ProgramSimulator {
+    pub async fn start_from_program_test(program_test: ProgramTest) -> ProgramSimulator {
+        let (banks_client, genesis_keypair, recent_blockhash) = program_test.start().await;
+        ProgramSimulator {
+            banks_client,
+            genesis_keypair,
+            last_blockhash: recent_blockhash,
+        }
+    }
+
+    /// 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: Option<&Keypair>,
+    ) -> Result<(), BanksClientError> {
+        let actual_payer = payer.unwrap_or(&self.genesis_keypair);
+        let mut transaction =
+            Transaction::new_with_payer(&[instruction], Some(&actual_payer.pubkey()));
+
+        let blockhash = self
+            .banks_client
+            .get_new_latest_blockhash(&self.last_blockhash)
+            .await
+            .unwrap();
+        self.last_blockhash = blockhash;
+
+        transaction.partial_sign(&[actual_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![], None).await
+    }
+
+    pub async fn get_funded_keypair(&mut self) -> Result<Keypair, BanksClientError> {
+        let keypair = Keypair::new();
+        self.airdrop(&keypair.pubkey(), LAMPORTS_PER_SOL).await?;
+        Ok(keypair)
+    }
+
+    pub async fn get_anchor_account_data<T: BorshDeserialize>(
+        &mut self,
+        pubkey: Pubkey,
+    ) -> Result<T, BanksClientError> {
+        let account = self
+            .banks_client
+            .get_account(pubkey)
+            .await
+            .unwrap()
+            .unwrap();
+
+        Ok(T::deserialize(&mut &account.data[8..])?)
+    }
+}

+ 3 - 0
target_chains/solana/programs/pyth-solana-receiver/Cargo.toml

@@ -33,3 +33,6 @@ bincode = "1.3.3"
 libsecp256k1 = "0.7.1"
 rand = "0.8.5"
 lazy_static = "1.4.0"
+program-simulator = { path = "../../program_simulator" }
+wormhole-sdk = { git = "https://github.com/wormhole-foundation/wormhole", tag = "v2.17.1" }
+serde_wormhole = { git = "https://github.com/wormhole-foundation/wormhole", tag = "v2.17.1"}

+ 2 - 67
target_chains/solana/programs/pyth-solana-receiver/src/lib.rs

@@ -1,9 +1,6 @@
 use {
     crate::error::ReceiverError,
-    anchor_lang::{
-        prelude::*,
-        system_program,
-    },
+    anchor_lang::prelude::*,
     pythnet_sdk::{
         accumulators::merkle::MerkleRoot,
         hashers::keccak256_160::Keccak160,
@@ -47,6 +44,7 @@ use {
 };
 
 pub mod error;
+pub mod sdk;
 pub mod state;
 
 declare_id!("rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ");
@@ -303,69 +301,6 @@ pub struct PostUpdatesAtomicParams {
     pub merkle_price_update: MerklePriceUpdate,
 }
 
-impl crate::accounts::Initialize {
-    pub fn populate(payer: &Pubkey) -> Self {
-        let config = Pubkey::find_program_address(&[CONFIG_SEED.as_ref()], &crate::ID).0;
-        crate::accounts::Initialize {
-            payer: *payer,
-            config,
-            system_program: system_program::ID,
-        }
-    }
-}
-
-impl crate::accounts::PostUpdatesAtomic {
-    pub fn populate(
-        payer: Pubkey,
-        price_update_account: Pubkey,
-        wormhole_address: Pubkey,
-        guardian_set_index: u32,
-    ) -> Self {
-        let config = Pubkey::find_program_address(&[CONFIG_SEED.as_ref()], &crate::ID).0;
-        let treasury = Pubkey::find_program_address(&[TREASURY_SEED.as_ref()], &crate::ID).0;
-
-        let guardian_set = Pubkey::find_program_address(
-            &[
-                GuardianSet::SEED_PREFIX,
-                guardian_set_index.to_be_bytes().as_ref(),
-            ],
-            &wormhole_address,
-        )
-        .0;
-
-        crate::accounts::PostUpdatesAtomic {
-            payer,
-            guardian_set,
-            config,
-            treasury,
-            price_update_account,
-            system_program: system_program::ID,
-        }
-    }
-}
-
-impl crate::accounts::PostUpdates {
-    pub fn populate(payer: Pubkey, encoded_vaa: Pubkey, price_update_account: Pubkey) -> Self {
-        let config = Pubkey::find_program_address(&[CONFIG_SEED.as_ref()], &crate::ID).0;
-        let treasury = Pubkey::find_program_address(&[TREASURY_SEED.as_ref()], &crate::ID).0;
-
-        crate::accounts::PostUpdates {
-            payer,
-            encoded_vaa,
-            config,
-            treasury,
-            price_update_account,
-            system_program: system_program::ID,
-        }
-    }
-}
-
-impl crate::accounts::Governance {
-    pub fn populate(payer: Pubkey) -> Self {
-        let config = Pubkey::find_program_address(&[CONFIG_SEED.as_ref()], &crate::ID).0;
-        crate::accounts::Governance { payer, config }
-    }
-}
 
 fn deserialize_guardian_set_checked(
     account_info: &AccountInfo<'_>,

+ 120 - 0
target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs

@@ -0,0 +1,120 @@
+use {
+    crate::{
+        accounts,
+        instruction,
+        state::config::Config,
+        CONFIG_SEED,
+        ID,
+        TREASURY_SEED,
+    },
+    anchor_lang::{
+        prelude::*,
+        system_program,
+        InstructionData,
+    },
+    pythnet_sdk::wire::v1::MerklePriceUpdate,
+    solana_program::instruction::Instruction,
+    wormhole_core_bridge_solana::state::GuardianSet,
+};
+
+impl accounts::Initialize {
+    pub fn populate(payer: &Pubkey) -> Self {
+        let config = get_config_address();
+        accounts::Initialize {
+            payer: *payer,
+            config,
+            system_program: system_program::ID,
+        }
+    }
+}
+
+impl accounts::PostUpdatesAtomic {
+    pub fn populate(
+        payer: Pubkey,
+        price_update_account: Pubkey,
+        wormhole_address: Pubkey,
+        guardian_set_index: u32,
+    ) -> Self {
+        let config = get_config_address();
+        let treasury = get_treasury_address();
+
+        let guardian_set = Pubkey::find_program_address(
+            &[
+                GuardianSet::SEED_PREFIX,
+                guardian_set_index.to_be_bytes().as_ref(),
+            ],
+            &wormhole_address,
+        )
+        .0;
+
+        accounts::PostUpdatesAtomic {
+            payer,
+            guardian_set,
+            config,
+            treasury,
+            price_update_account,
+            system_program: system_program::ID,
+        }
+    }
+}
+
+impl accounts::PostUpdates {
+    pub fn populate(payer: Pubkey, encoded_vaa: Pubkey, price_update_account: Pubkey) -> Self {
+        let config = get_config_address();
+        let treasury = get_treasury_address();
+        accounts::PostUpdates {
+            payer,
+            encoded_vaa,
+            config,
+            treasury,
+            price_update_account,
+            system_program: system_program::ID,
+        }
+    }
+}
+
+impl accounts::Governance {
+    pub fn populate(payer: Pubkey) -> Self {
+        let config = get_config_address();
+        accounts::Governance { payer, config }
+    }
+}
+
+impl instruction::Initialize {
+    pub fn populate(payer: &Pubkey, initial_config: Config) -> Instruction {
+        Instruction {
+            program_id: ID,
+            accounts:   accounts::Initialize::populate(payer).to_account_metas(None),
+            data:       instruction::Initialize { initial_config }.data(),
+        }
+    }
+}
+
+impl instruction::PostUpdates {
+    pub fn populate(
+        payer: Pubkey,
+        encoded_vaa: Pubkey,
+        price_update_account: Pubkey,
+        merkle_price_update: MerklePriceUpdate,
+    ) -> Instruction {
+        let post_update_accounts =
+            accounts::PostUpdates::populate(payer, encoded_vaa, price_update_account)
+                .to_account_metas(None);
+        Instruction {
+            program_id: ID,
+            accounts:   post_update_accounts,
+            data:       instruction::PostUpdates {
+                price_update: merkle_price_update,
+            }
+            .data(),
+        }
+    }
+}
+
+pub fn get_treasury_address() -> Pubkey {
+    Pubkey::find_program_address(&[TREASURY_SEED.as_ref()], &ID).0
+}
+
+pub fn get_config_address() -> Pubkey {
+    Pubkey::find_program_address(&[CONFIG_SEED.as_ref()], &ID).0
+}

+ 2 - 1
target_chains/solana/programs/pyth-solana-receiver/src/state/config.rs

@@ -4,6 +4,7 @@ use {
 };
 
 #[account]
+#[derive(Debug, PartialEq)]
 pub struct Config {
     pub governance_authority:          Pubkey, // This authority can update the other fields
     pub target_governance_authority:   Option<Pubkey>, // This field is used for a two-step governance authority transfer
@@ -13,7 +14,7 @@ pub struct Config {
     pub minimum_signatures:            u8, // The minimum number of signatures required to accept a VAA
 }
 
-#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq)]
+#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)]
 pub struct DataSource {
     pub chain:   u16,
     pub emitter: Pubkey,

+ 1 - 1
target_chains/solana/programs/pyth-solana-receiver/src/state/price_update.rs

@@ -12,7 +12,7 @@ use {
  * This enum represents how many guardian signatures were checked for a Pythnet price update
  * If full, guardian quorum has been attained
  * If partial, at least config.minimum signatures have been verified, but in the case config.minimum_signatures changes in the future we also include the number of signatures that were checked */
-#[derive(AnchorSerialize, AnchorDeserialize, Copy, Clone, PartialEq, BorshSchema)]
+#[derive(AnchorSerialize, AnchorDeserialize, Copy, Clone, PartialEq, BorshSchema, Debug)]
 pub enum VerificationLevel {
     Partial(u8),
     Full,

+ 0 - 1
target_chains/solana/programs/pyth-solana-receiver/tests/cases/mod.rs

@@ -1 +0,0 @@
-pub use super::program_test::*;

+ 231 - 0
target_chains/solana/programs/pyth-solana-receiver/tests/common/mod.rs

@@ -0,0 +1,231 @@
+use {
+    anchor_lang::AnchorSerialize,
+    program_simulator::ProgramSimulator,
+    pyth_solana_receiver::{
+        instruction::Initialize,
+        sdk::{
+            get_config_address,
+            get_treasury_address,
+        },
+        state::config::{
+            Config,
+            DataSource,
+        },
+        ID,
+    },
+    pythnet_sdk::{
+        accumulators::{
+            merkle::MerkleTree,
+            Accumulator,
+        },
+        hashers::keccak256_160::Keccak160,
+        messages::{
+            Message,
+            PriceFeedMessage,
+        },
+        wire::{
+            v1::MerklePriceUpdate,
+            PrefixedVec,
+        },
+    },
+    serde_wormhole::RawMessage,
+    solana_program::{
+        pubkey::Pubkey,
+        rent::Rent,
+    },
+    solana_program_test::ProgramTest,
+    solana_sdk::{
+        account::Account,
+        signature::Keypair,
+        signer::Signer,
+    },
+    wormhole_core_bridge_solana::{
+        state::{
+            EncodedVaa,
+            Header,
+            ProcessingStatus,
+        },
+        ID as BRIDGE_ID,
+    },
+    wormhole_sdk::{
+        Chain,
+        Vaa,
+    },
+};
+
+pub fn dummy_receiver_config(data_source: DataSource) -> Config {
+    Config {
+        governance_authority:          Pubkey::new_unique(),
+        target_governance_authority:   None,
+        wormhole:                      BRIDGE_ID,
+        valid_data_sources:            vec![DataSource {
+            chain:   data_source.chain,
+            emitter: data_source.emitter,
+        }],
+        single_update_fee_in_lamports: 1,
+        minimum_signatures:            5,
+    }
+}
+
+pub fn dummy_data_source() -> DataSource {
+    let emitter_address = Pubkey::new_unique().to_bytes();
+    let emitter_chain = Chain::Pythnet;
+    DataSource {
+        chain:   emitter_chain.into(),
+        emitter: Pubkey::from(emitter_address),
+    }
+}
+
+pub fn dummy_price_messages() -> Vec<PriceFeedMessage> {
+    vec![
+        PriceFeedMessage {
+            feed_id:           [0u8; 32],
+            price:             1,
+            conf:              2,
+            exponent:          3,
+            publish_time:      4,
+            prev_publish_time: 5,
+            ema_price:         6,
+            ema_conf:          7,
+        },
+        PriceFeedMessage {
+            feed_id:           [8u8; 32],
+            price:             9,
+            conf:              10,
+            exponent:          11,
+            publish_time:      12,
+            prev_publish_time: 13,
+            ema_price:         14,
+            ema_conf:          15,
+        },
+    ]
+}
+
+pub fn dummy_price_updates(
+    price_feed_messages: Vec<PriceFeedMessage>,
+) -> (MerkleTree<Keccak160>, Vec<MerklePriceUpdate>) {
+    let messages = price_feed_messages
+        .iter()
+        .map(|x| Message::PriceFeedMessage(*x))
+        .collect::<Vec<Message>>();
+    let price_feed_message_as_vec: Vec<Vec<u8>> = messages
+        .iter()
+        .map(|x| pythnet_sdk::wire::to_vec::<_, byteorder::BigEndian>(&x).unwrap())
+        .collect();
+
+    let merkle_tree_accumulator =
+        MerkleTree::<Keccak160>::from_set(price_feed_message_as_vec.iter().map(|x| x.as_ref()))
+            .unwrap();
+    let merkle_price_updates: Vec<MerklePriceUpdate> = price_feed_message_as_vec
+        .iter()
+        .map(|x| MerklePriceUpdate {
+            message: PrefixedVec::<u16, u8>::from(x.clone()),
+            proof:   merkle_tree_accumulator.prove(x.as_ref()).unwrap(),
+        })
+        .collect();
+
+    (merkle_tree_accumulator, merkle_price_updates)
+}
+
+
+pub fn build_merkle_root_encoded_vaa(
+    merkle_tree_accumulator: MerkleTree<Keccak160>,
+    data_source: &DataSource,
+) -> Account {
+    let merkle_tree_payload: Vec<u8> = merkle_tree_accumulator.serialize(1, 1);
+
+    let vaa_header: Vaa<Box<RawMessage>> = Vaa {
+        version:            1,
+        guardian_set_index: 0,
+        signatures:         vec![],
+        timestamp:          0,
+        nonce:              0,
+        emitter_chain:      Chain::from(data_source.chain),
+        emitter_address:    wormhole_sdk::Address(data_source.emitter.to_bytes()),
+        sequence:           0,
+        consistency_level:  0,
+        payload:            <Box<RawMessage>>::from(merkle_tree_payload),
+    };
+
+    let encoded_vaa_data = (
+        <EncodedVaa as anchor_lang::Discriminator>::DISCRIMINATOR,
+        Header {
+            status:          ProcessingStatus::Verified,
+            write_authority: Pubkey::new_unique(),
+            version:         1,
+        },
+        serde_wormhole::to_vec(&vaa_header).unwrap(),
+    )
+        .try_to_vec()
+        .unwrap();
+
+
+    Account {
+        lamports:   Rent::default().minimum_balance(encoded_vaa_data.len()),
+        data:       encoded_vaa_data,
+        owner:      BRIDGE_ID,
+        executable: false,
+        rent_epoch: 0,
+    }
+}
+
+
+pub struct ProgramTestFixtures {
+    pub program_simulator:    ProgramSimulator,
+    pub encoded_vaa_address:  Pubkey,
+    pub merkle_price_updates: Vec<MerklePriceUpdate>,
+}
+/**
+ * Setup to test the Pyth Receiver. The return values are a tuple composed of :
+ * - The program simulator, which is used to send transactions
+ * - The pubkey of an encoded VAA account, which is pre-populated and can be used to test post_updates
+ * - A vector of MerklePriceUpdate, corresponding to that VAA
+ */
+pub async fn setup_pyth_receiver(
+    price_feed_messages: Vec<PriceFeedMessage>,
+) -> ProgramTestFixtures {
+    let mut program_test = ProgramTest::default();
+    program_test.add_program("pyth_solana_receiver", ID, None);
+
+    let (merkle_tree_accumulator, merkle_price_updates) = dummy_price_updates(price_feed_messages);
+    let data_source = dummy_data_source();
+    let merkle_root_encoded_vaa =
+        build_merkle_root_encoded_vaa(merkle_tree_accumulator, &data_source);
+
+    let encoded_vaa_address = Pubkey::new_unique();
+    program_test.add_account(encoded_vaa_address, merkle_root_encoded_vaa);
+
+
+    let mut program_simulator = ProgramSimulator::start_from_program_test(program_test).await;
+
+    let initial_config = dummy_receiver_config(data_source);
+
+    let setup_keypair: Keypair = program_simulator.get_funded_keypair().await.unwrap();
+
+    program_simulator
+        .process_ix(
+            Initialize::populate(&setup_keypair.pubkey(), initial_config.clone()),
+            &vec![&setup_keypair],
+            None,
+        )
+        .await
+        .unwrap();
+
+    let config_account = program_simulator
+        .get_anchor_account_data::<Config>(get_config_address())
+        .await
+        .unwrap();
+    assert_eq!(config_account, initial_config);
+
+    program_simulator
+        .airdrop(&get_treasury_address(), Rent::default().minimum_balance(0))
+        .await
+        .unwrap();
+
+
+    ProgramTestFixtures {
+        program_simulator,
+        encoded_vaa_address,
+        merkle_price_updates,
+    }
+}

+ 0 - 184
target_chains/solana/programs/pyth-solana-receiver/tests/program_test/mod.rs

@@ -1,184 +0,0 @@
-use {
-    solana_program::{
-        bpf_loader_upgradeable::{
-            self,
-            UpgradeableLoaderState,
-        },
-        hash::Hash,
-        instruction::Instruction,
-        native_token::LAMPORTS_PER_SOL,
-        pubkey::Pubkey,
-        rent::Rent,
-        stake_history::Epoch,
-        system_instruction,
-    },
-    solana_program_test::{
-        read_file,
-        BanksClient,
-        BanksClientError,
-        ProgramTest,
-        ProgramTestBanksClientExt,
-    },
-    solana_sdk::{
-        account::Account,
-        signature::{
-            Keypair,
-            Signer,
-        },
-        transaction::Transaction,
-    },
-    std::path::PathBuf,
-};
-
-lazy_static::lazy_static! {
-    // Build the oracle binary and make it available to the
-    // simulator. lazy_static makes this happen only once per test
-    // run.
-    static ref PYTH_RECEIVER_PROGRAM_BINARY_PATH: PathBuf = {
-
-    // Detect features and pass them onto cargo-build-bpf.
-    // IMPORTANT: All features of this crate must have gates added to this vector.
-    let features: Vec<&str> = vec![
-    #[cfg(feature = "devnet")]
-        "devnet",
-    #[cfg(feature = "mainnet")]
-        "mainnet",
-    ];
-
-    let mut cmd = std::process::Command::new("cargo");
-    cmd.arg("build-bpf");
-
-    if !features.is_empty() {
-        cmd.arg("--features");
-        cmd.args(features);
-    }
-
-    let status = cmd.status().unwrap();
-
-    if !status.success() {
-        panic!(
-        "cargo-build-bpf did not exit with 0 (code {:?})",
-        status.code()
-        );
-    }
-
-    let target_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/../../target");
-
-    PathBuf::from(target_dir).join("deploy/pyth_solana_receiver.so")
-    };
-}
-
-/// 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,
-    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(&*PYTH_RECEIVER_PROGRAM_BINARY_PATH);
-
-        let mut program_test = ProgramTest::default();
-
-        // 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_address, _) = Pubkey::find_program_address(
-            &[&pyth_solana_receiver::ID.to_bytes()],
-            &bpf_loader_upgradeable::id(),
-        );
-
-        let upgrade_authority_keypair = Keypair::new();
-
-        let program_deserialized = UpgradeableLoaderState::Program {
-            programdata_address,
-        };
-        let programdata_deserialized = UpgradeableLoaderState::ProgramData {
-            slot:                      1,
-            upgrade_authority_address: Some(upgrade_authority_keypair.pubkey()),
-        };
-
-        // Program contains a pointer to programdata
-        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(pyth_solana_receiver::ID, program_account);
-        program_test.add_account(programdata_address, programdata_account);
-
-        // Start validator
-        let (banks_client, genesis_keypair, recent_blockhash) = program_test.start().await;
-
-        let mut result = ProgramSimulator {
-            program_id: pyth_solana_receiver::ID,
-            banks_client,
-            last_blockhash: recent_blockhash,
-            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
-    }
-}

+ 0 - 3
target_chains/solana/programs/pyth-solana-receiver/tests/test_all.rs

@@ -1,3 +0,0 @@
-mod cases;
-
-pub mod program_test;

+ 91 - 0
target_chains/solana/programs/pyth-solana-receiver/tests/test_initialize.rs

@@ -0,0 +1,91 @@
+use {
+    crate::common::dummy_price_messages,
+    common::{
+        setup_pyth_receiver,
+        ProgramTestFixtures,
+    },
+    pyth_solana_receiver::{
+        instruction::PostUpdates,
+        state::price_update::{
+            PriceUpdateV1,
+            VerificationLevel,
+        },
+    },
+    solana_sdk::{
+        signature::Keypair,
+        signer::Signer,
+    },
+};
+
+mod common;
+
+
+#[tokio::test]
+async fn test_post_updates() {
+    let dummy_price_messages = dummy_price_messages();
+
+    let ProgramTestFixtures {
+        mut program_simulator,
+        encoded_vaa_address,
+        merkle_price_updates,
+    } = setup_pyth_receiver(dummy_price_messages.clone()).await;
+
+    let poster = program_simulator.get_funded_keypair().await.unwrap();
+    let price_update_keypair = Keypair::new();
+
+    // post one update
+    program_simulator
+        .process_ix(
+            PostUpdates::populate(
+                poster.pubkey(),
+                encoded_vaa_address,
+                price_update_keypair.pubkey(),
+                merkle_price_updates[0].clone(),
+            ),
+            &vec![&poster, &price_update_keypair],
+            None,
+        )
+        .await
+        .unwrap();
+
+
+    let mut price_update_account = program_simulator
+        .get_anchor_account_data::<PriceUpdateV1>(price_update_keypair.pubkey())
+        .await
+        .unwrap();
+
+    assert_eq!(price_update_account.write_authority, poster.pubkey());
+    assert_eq!(
+        price_update_account.verification_level,
+        VerificationLevel::Full
+    );
+    assert_eq!(price_update_account.price_message, dummy_price_messages[0]);
+
+    // post another update to the same account
+    program_simulator
+        .process_ix(
+            PostUpdates::populate(
+                poster.pubkey(),
+                encoded_vaa_address,
+                price_update_keypair.pubkey(),
+                merkle_price_updates[1].clone(),
+            ),
+            &vec![&poster, &price_update_keypair],
+            None,
+        )
+        .await
+        .unwrap();
+
+
+    price_update_account = program_simulator
+        .get_anchor_account_data::<PriceUpdateV1>(price_update_keypair.pubkey())
+        .await
+        .unwrap();
+
+    assert_eq!(price_update_account.write_authority, poster.pubkey());
+    assert_eq!(
+        price_update_account.verification_level,
+        VerificationLevel::Full
+    );
+    assert_eq!(price_update_account.price_message, dummy_price_messages[1]);
+}