Pārlūkot izejas kodu

Setting up rust tests (#292)

* Add tests

* Add tests to CI

* Remote test from precommit hook

* New test

* Remove merge error

* Comments and increase seqno

* Remove unnecesary dep

* Fix rebase

* Fix feedback
guibescos 3 gadi atpakaļ
vecāks
revīzija
c32f2d99b9

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 3705 - 489
pythnet/remote-executor/Cargo.lock


+ 6 - 0
pythnet/remote-executor/programs/remote-executor/Cargo.toml

@@ -23,3 +23,9 @@ anchor-lang = {version = "0.25.0", features = ["init-if-needed"]}
 wormhole-solana = { git = "https://github.com/guibescos/wormhole", branch = "gbescos/sdk-solana"}
 wormhole-core = { git = "https://github.com/guibescos/wormhole", branch = "gbescos/sdk-solana"}
 boolinator = "2.4.0"
+
+[dev-dependencies]
+solana-program-test = "=1.10.31"
+tokio = "1.14.1"
+solana-sdk = "=1.10.31"
+bincode = "1.3.3"

+ 31 - 0
pythnet/remote-executor/programs/remote-executor/src/lib.rs

@@ -4,6 +4,7 @@
 use anchor_lang::{
     prelude::*,
     solana_program::borsh::get_packed_len,
+    system_program,
 };
 use state::{
     claim_record::ClaimRecord,
@@ -13,6 +14,9 @@ use state::{
 mod error;
 mod state;
 
+#[cfg(test)] //Conditional compilation of the tests
+mod tests;
+
 declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
 
 #[program]
@@ -78,3 +82,30 @@ pub struct ExecutePostedVaa<'info> {
     pub claim_record: Account<'info, ClaimRecord>,
     pub system_program: Program<'info, System>,
 }
+
+impl crate::accounts::ExecutePostedVaa {
+    pub fn populate(
+        program_id: &Pubkey,
+        payer: &Pubkey,
+        emitter: &Pubkey,
+        posted_vaa: &Pubkey,
+    ) -> Self {
+        let executor_key = Pubkey::find_program_address(
+            &[EXECUTOR_KEY_SEED.as_bytes(), &emitter.to_bytes()],
+            program_id,
+        )
+        .0;
+        let claim_record = Pubkey::find_program_address(
+            &[CLAIM_RECORD_SEED.as_bytes(), &emitter.to_bytes()],
+            program_id,
+        )
+        .0;
+        crate::accounts::ExecutePostedVaa {
+            payer: *payer,
+            executor_key,
+            claim_record,
+            posted_vaa: *posted_vaa,
+            system_program: system_program::ID,
+        }
+    }
+}

+ 18 - 24
pythnet/remote-executor/programs/remote-executor/src/state/governance_payload.rs

@@ -46,6 +46,20 @@ pub struct GovernanceHeader {
     pub chain: BigEndianU16,
 }
 
+impl GovernanceHeader {
+    #[allow(unused)] // Only used in tests right now
+    pub fn executor_governance_header() -> Self {
+        Self {
+            magic_number: MAGIC_NUMBER,
+            module: Module::Executor,
+            action: Action::ExecutePostedVaa,
+            chain: BigEndianU16 {
+                value: Chain::Pythnet.try_into().unwrap(),
+            },
+        }
+    }
+}
+
 /// Hack to get Borsh to deserialize, serialize this number with big endian order
 #[derive(Eq, PartialEq, Debug)]
 pub struct BigEndianU16 {
@@ -162,32 +176,18 @@ pub mod tests {
         state::governance_payload::InstructionData,
     };
 
-    use super::{
-        Action,
-        BigEndianU16,
-        ExecutorPayload,
-        Module,
-        MAGIC_NUMBER,
-    };
+    use super::ExecutorPayload;
     use anchor_lang::{
         prelude::Pubkey,
         AnchorDeserialize,
         AnchorSerialize,
     };
-    use wormhole::Chain;
 
     #[test]
     fn test_check_deserialization_serialization() {
         // No instructions
         let payload = ExecutorPayload {
-            header: super::GovernanceHeader {
-                magic_number: MAGIC_NUMBER,
-                module: Module::Executor,
-                action: Action::ExecutePostedVaa,
-                chain: BigEndianU16 {
-                    value: Chain::Pythnet.try_into().unwrap(),
-                },
-            },
+            header: super::GovernanceHeader::executor_governance_header(),
             instructions: vec![],
         };
 
@@ -202,14 +202,8 @@ pub mod tests {
 
         // One instruction
         let payload = ExecutorPayload {
-            header: super::GovernanceHeader {
-                magic_number: MAGIC_NUMBER,
-                module: Module::Executor,
-                action: Action::ExecutePostedVaa,
-                chain: BigEndianU16 {
-                    value: Chain::Pythnet.try_into().unwrap(),
-                },
-            },
+            header: super::GovernanceHeader::executor_governance_header(),
+
             instructions: vec![InstructionData::from(
                 &anchor_lang::solana_program::system_instruction::create_account(
                     &Pubkey::new_unique(),

+ 226 - 0
pythnet/remote-executor/programs/remote-executor/src/tests/executor_simulator.rs

@@ -0,0 +1,226 @@
+use std::collections::HashMap;
+
+use anchor_lang::{
+    prelude::{
+        Pubkey,
+        Rent,
+        UpgradeableLoaderState,
+    },
+    solana_program::hash::Hash,
+    AnchorSerialize,
+    InstructionData as AnchorInstructionData,
+    Key,
+    Owner,
+    ToAccountMetas,
+};
+use solana_program_test::{
+    find_file,
+    read_file,
+    BanksClient,
+    BanksClientError,
+    ProgramTest,
+    ProgramTestBanksClientExt,
+};
+use solana_sdk::{
+    account::Account,
+    bpf_loader_upgradeable,
+    instruction::Instruction,
+    signature::Keypair,
+    signer::Signer,
+    stake_history::Epoch,
+    system_instruction,
+    transaction::Transaction,
+};
+use wormhole::Chain;
+use wormhole_solana::VAA;
+
+use crate::state::{
+    governance_payload::{
+        ExecutorPayload,
+        GovernanceHeader,
+        InstructionData,
+    },
+    posted_vaa::AnchorVaa,
+};
+
+/// Bench for the tests, the goal of this struct is to be able to setup solana accounts before starting the local validator
+pub struct ExecutorBench {
+    program_test: ProgramTest,
+    program_id: Pubkey,
+    seqno: HashMap<Pubkey, u64>,
+}
+
+impl ExecutorBench {
+    /// Deploys the executor program as upgradable
+    pub fn new() -> ExecutorBench {
+        let mut bpf_data = read_file(find_file("remote_executor.so").unwrap_or_else(|| {
+            panic!("Unable to locate {}", "remote_executor.so");
+        }));
+
+        let mut program_test = ProgramTest::default();
+        let program_key = crate::id();
+        let programdata_key = Pubkey::new_unique();
+
+        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 both accounts to program test, now the program is deployed as upgradable
+        program_test.add_account(program_key, program_account);
+        program_test.add_account(programdata_key, programdata_account);
+
+        return ExecutorBench {
+            program_test,
+            program_id: program_key.key(),
+            seqno: HashMap::<Pubkey, u64>::new(),
+        };
+    }
+
+    /// Start local validator based on the current bench
+    pub async fn start(self) -> ExecutorSimulator {
+        // Start validator
+        let (banks_client, genesis_keypair, recent_blockhash) = self.program_test.start().await;
+
+        return ExecutorSimulator {
+            banks_client,
+            payer: genesis_keypair,
+            last_blockhash: recent_blockhash,
+            program_id: self.program_id,
+        };
+    }
+
+    /// Add VAA account with emitter and instructions for consumption by the remote_executor
+    pub fn add_vaa_account(&mut self, emitter: &Pubkey, instructions: &Vec<Instruction>) -> Pubkey {
+        let payload = ExecutorPayload {
+            header: GovernanceHeader::executor_governance_header(),
+            instructions: instructions
+                .iter()
+                .map(|x| InstructionData::from(x))
+                .collect(),
+        };
+
+        let payload_bytes = payload.try_to_vec().unwrap();
+
+        let vaa = VAA {
+            vaa_version: 0,
+            consistency_level: 0,
+            vaa_time: 0,
+            vaa_signature_account: Pubkey::new_unique(),
+            submission_time: 0,
+            nonce: 0,
+            sequence: self.seqno.get(&emitter).unwrap_or(&0) + 1,
+            emitter_chain: Chain::Solana.into(),
+            emitter_address: emitter.to_bytes(),
+            payload: payload_bytes,
+        };
+
+        *self.seqno.entry(*emitter).or_insert(0) += 1;
+
+        let vaa_bytes = vaa.try_to_vec().unwrap();
+
+        let vaa_account = Account {
+            lamports: Rent::default().minimum_balance(vaa_bytes.len()),
+            data: vaa_bytes,
+            owner: AnchorVaa::owner(),
+            executable: false,
+            rent_epoch: Epoch::default(),
+        };
+
+        let vaa_pubkey = Pubkey::new_unique();
+        self.program_test.add_account(vaa_pubkey, vaa_account);
+        return vaa_pubkey;
+    }
+}
+pub struct ExecutorSimulator {
+    banks_client: BanksClient,
+    payer: Keypair,
+    last_blockhash: Hash,
+    program_id: Pubkey,
+}
+
+impl ExecutorSimulator {
+    #[allow(dead_code)]
+    pub async fn airdrop(&mut self, to: &Pubkey, lamports: u64) -> Result<(), BanksClientError> {
+        let instruction = system_instruction::transfer(&self.payer.pubkey(), to, lamports);
+
+        self.process_ix(instruction, &vec![]).await
+    }
+
+    /// Process a transaction containing `instruction` signed by `signers`.
+    /// `payer` is used to pay for and sign the transaction.
+    async fn process_ix(
+        &mut self,
+        instruction: Instruction,
+        signers: &Vec<&Keypair>,
+    ) -> Result<(), BanksClientError> {
+        let mut transaction =
+            Transaction::new_with_payer(&[instruction], Some(&self.payer.pubkey()));
+
+        let blockhash = self
+            .banks_client
+            .get_new_latest_blockhash(&self.last_blockhash)
+            .await
+            .unwrap();
+        self.last_blockhash = blockhash;
+
+        transaction.partial_sign(&[&self.payer], self.last_blockhash);
+        transaction.partial_sign(signers, self.last_blockhash);
+
+        self.banks_client.process_transaction(transaction).await
+    }
+
+    /// Execute the payload contained in the VAA at posted_vaa_address
+    pub async fn execute_posted_vaa(
+        &mut self,
+        posted_vaa_address: &Pubkey,
+    ) -> Result<(), BanksClientError> {
+        let posted_vaa_data: VAA = self
+            .banks_client
+            .get_account_data_with_borsh(*posted_vaa_address)
+            .await
+            .unwrap();
+
+        let account_metas = crate::accounts::ExecutePostedVaa::populate(
+            &self.program_id,
+            &self.payer.pubkey(),
+            &Pubkey::new(&posted_vaa_data.emitter_address),
+            &posted_vaa_address,
+        )
+        .to_account_metas(None);
+
+        let instruction = Instruction {
+            program_id: self.program_id,
+            accounts: account_metas,
+            data: crate::instruction::ExecutePostedVaa.data(),
+        };
+
+        self.process_ix(instruction, &vec![]).await
+    }
+}

+ 2 - 0
pythnet/remote-executor/programs/remote-executor/src/tests/mod.rs

@@ -0,0 +1,2 @@
+mod executor_simulator;
+mod test_create_account;

+ 16 - 0
pythnet/remote-executor/programs/remote-executor/src/tests/test_create_account.rs

@@ -0,0 +1,16 @@
+use super::executor_simulator::ExecutorBench;
+use anchor_lang::prelude::Pubkey;
+
+#[tokio::test]
+async fn test_create_account() {
+    let mut bench = ExecutorBench::new();
+
+    let emitter = Pubkey::new_unique();
+    let instructions = vec![];
+
+    let vaa_account = bench.add_vaa_account(&emitter, &instructions);
+
+    let mut sim = bench.start().await;
+
+    sim.execute_posted_vaa(&vaa_account).await.unwrap();
+}

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels