[Solana] tests (#1226)

* Checkpoint

* Checkpoint

* Checkpoint

* Nice

* Finally works

* Cleanup

* Cleanup

* Cleanup

* Cleanup

* Checkpoint

* Works

* Final cleanup

* Remove async

* Continue
This commit is contained in:
guibescos 2024-01-17 15:46:31 +00:00 committed by GitHub
parent b6c39ce7bb
commit d3ed683b91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 595 additions and 264 deletions

View File

@ -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"

View File

@ -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"

View File

@ -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..])?)
}
}

View File

@ -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"}

View File

@ -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<'_>,

View File

@ -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
}

View File

@ -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,

View File

@ -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,

View File

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

View File

@ -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,
}
}

View File

@ -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
}
}

View File

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

View File

@ -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]);
}