[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:
parent
b6c39ce7bb
commit
d3ed683b91
|
@ -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"
|
||||
|
|
|
@ -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"
|
|
@ -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..])?)
|
||||
}
|
||||
}
|
|
@ -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"}
|
||||
|
|
|
@ -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<'_>,
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
pub use super::program_test::*;
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
mod cases;
|
||||
|
||||
pub mod program_test;
|
|
@ -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]);
|
||||
}
|
Loading…
Reference in New Issue