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
This commit is contained in:
parent
1ad419e0ba
commit
c32f2d99b9
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
mod executor_simulator;
|
||||
mod test_create_account;
|
|
@ -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();
|
||||
}
|
Loading…
Reference in New Issue