From 0e3dd8d9ab5e7d6b610b25d1d7a805ad432b5f8c Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 29 Jan 2024 14:14:46 +0000 Subject: [PATCH 01/18] Checkpoint --- pythnet/pythnet_sdk/src/test_utils/mod.rs | 45 +++++------------------ 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/pythnet/pythnet_sdk/src/test_utils/mod.rs b/pythnet/pythnet_sdk/src/test_utils/mod.rs index bdd3383894..df1d75cacb 100644 --- a/pythnet/pythnet_sdk/src/test_utils/mod.rs +++ b/pythnet/pythnet_sdk/src/test_utils/mod.rs @@ -37,8 +37,6 @@ use { wormhole_sdk::{ vaa::{ Body, - Header, - }, Address, Chain, Vaa, @@ -181,17 +179,8 @@ pub fn create_vaa_from_payload( emitter_chain: Chain, sequence: u64, ) -> Vaa> { - let guardians = dummy_guardians(); - - let body: Body> = Body { - emitter_chain, - emitter_address, - sequence, - payload: >::from(payload.to_vec()), - ..Default::default() - }; - - let digest = libsecp256k1Message::parse_slice(&body.digest().unwrap().secp256k_hash).unwrap(); + let digest = libsecp256k1Message::parse_slice(&digest(payload).unwrap().secp256k_hash).unwrap(); + let guardians = create_dummy_guardians(); let signatures: Vec<(Signature, RecoveryId)> = guardians .iter() @@ -211,30 +200,14 @@ pub fn create_vaa_from_payload( }) .collect(); - let mut wormhole_signatures_subset: Vec = wormhole_signatures - .choose_multiple(&mut thread_rng(), DEFAULT_NUM_SIGNATURES) - .cloned() - .collect(); - - wormhole_signatures_subset.sort_by(|a, b| a.index.cmp(&b.index)); - - let header = Header { - version: 1, - signatures: wormhole_signatures_subset, + let vaa: Vaa> = Vaa { + emitter_chain: emitter_chain, + emitter_address: emitter_address, + sequence, + payload: >::from(payload.to_vec()), + signatures: wormhole_signatures, ..Default::default() }; - - (header, body).into() -} - -pub fn trim_vaa_signatures(vaa: Vaa<&RawMessage>, n: u8) -> Vaa<&RawMessage> { - let mut vaa_copy = vaa.clone(); - vaa_copy.signatures = vaa - .signatures - .choose_multiple(&mut thread_rng(), n.into()) - .cloned() - .collect(); - vaa_copy.signatures.sort_by(|a, b| a.index.cmp(&b.index)); - vaa_copy + vaa } From 31143adec7ce412a40b97e4680a90bbd9a02911b Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 29 Jan 2024 14:21:23 +0000 Subject: [PATCH 02/18] Checkpoint --- pythnet/pythnet_sdk/src/test_utils/mod.rs | 2 +- target_chains/solana/cli/src/main.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pythnet/pythnet_sdk/src/test_utils/mod.rs b/pythnet/pythnet_sdk/src/test_utils/mod.rs index df1d75cacb..75d4ae0ed5 100644 --- a/pythnet/pythnet_sdk/src/test_utils/mod.rs +++ b/pythnet/pythnet_sdk/src/test_utils/mod.rs @@ -182,7 +182,7 @@ pub fn create_vaa_from_payload( let digest = libsecp256k1Message::parse_slice(&digest(payload).unwrap().secp256k_hash).unwrap(); let guardians = create_dummy_guardians(); - let signatures: Vec<(Signature, RecoveryId)> = guardians + let signatures: Vec<(Signature, RecoveryId)> = guardians[0..NUM_SIGNATURES] .iter() .map(|x| libsecp256k1::sign(&digest, &x)) .collect(); diff --git a/target_chains/solana/cli/src/main.rs b/target_chains/solana/cli/src/main.rs index bed6f2cc67..3c4863de78 100644 --- a/target_chains/solana/cli/src/main.rs +++ b/target_chains/solana/cli/src/main.rs @@ -280,6 +280,7 @@ pub fn process_post_price_update_atomic( } fn trim_signatures(header: &mut Header, n_signatures: usize) { + println!("There are {} signatures", header.signatures.len()); header.signatures = header.signatures[..(n_signatures)].to_vec(); } From 299dbc09bddaa4b77d6c10e102cd2d743c4e97b1 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 29 Jan 2024 16:14:19 +0000 Subject: [PATCH 03/18] Cleanup --- .../programs/pyth-solana-receiver/tests/test_post_updates.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs index fd22e4773c..071e9cb3d7 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs @@ -50,7 +50,7 @@ async fn test_post_updates() { poster.pubkey(), encoded_vaa_addresses[0], price_update_keypair.pubkey(), - merkle_price_updates[0].clone(), + accumulator_update_data.1[0].clone(), ), &vec![&poster, &price_update_keypair], None, @@ -81,7 +81,7 @@ async fn test_post_updates() { poster.pubkey(), encoded_vaa_addresses[0], price_update_keypair.pubkey(), - merkle_price_updates[1].clone(), + accumulator_update_data.1[1].clone(), ), &vec![&poster, &price_update_keypair], None, From 71356ceceb081ba6573510e8d88aa050aef744e5 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 29 Jan 2024 17:04:08 +0000 Subject: [PATCH 04/18] Checkpoint, debug --- pythnet/pythnet_sdk/src/test_utils/mod.rs | 5 +++-- target_chains/solana/cli/src/main.rs | 1 - .../programs/pyth-solana-receiver/tests/common/mod.rs | 6 ++++++ .../pyth-solana-receiver/tests/test_post_updates.rs | 4 ++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pythnet/pythnet_sdk/src/test_utils/mod.rs b/pythnet/pythnet_sdk/src/test_utils/mod.rs index 75d4ae0ed5..5a48434092 100644 --- a/pythnet/pythnet_sdk/src/test_utils/mod.rs +++ b/pythnet/pythnet_sdk/src/test_utils/mod.rs @@ -201,8 +201,9 @@ pub fn create_vaa_from_payload( .collect(); let vaa: Vaa> = Vaa { - emitter_chain: emitter_chain, - emitter_address: emitter_address, + version: 1, + emitter_chain, + emitter_address, sequence, payload: >::from(payload.to_vec()), signatures: wormhole_signatures, diff --git a/target_chains/solana/cli/src/main.rs b/target_chains/solana/cli/src/main.rs index 3c4863de78..bed6f2cc67 100644 --- a/target_chains/solana/cli/src/main.rs +++ b/target_chains/solana/cli/src/main.rs @@ -280,7 +280,6 @@ pub fn process_post_price_update_atomic( } fn trim_signatures(header: &mut Header, n_signatures: usize) { - println!("There are {} signatures", header.signatures.len()); header.signatures = header.signatures[..(n_signatures)].to_vec(); } diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/common/mod.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/common/mod.rs index 631158c812..e8854a2ddd 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/common/mod.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/common/mod.rs @@ -170,3 +170,9 @@ pub async fn setup_pyth_receiver(vaas: Vec>) -> ProgramTestFixt encoded_vaa_addresses, } } + +pub fn trim_vaa_signatures(vaa: Vec, n: u8) -> Vec { + let mut parsed_vaa: Vaa<&RawMessage> = serde_wormhole::from_slice(vaa.as_slice()).unwrap(); + parsed_vaa.signatures = parsed_vaa.signatures[0..n as usize].to_vec(); + serde_wormhole::to_vec(&parsed_vaa).unwrap() +} diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs index 071e9cb3d7..fd22e4773c 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs @@ -50,7 +50,7 @@ async fn test_post_updates() { poster.pubkey(), encoded_vaa_addresses[0], price_update_keypair.pubkey(), - accumulator_update_data.1[0].clone(), + merkle_price_updates[0].clone(), ), &vec![&poster, &price_update_keypair], None, @@ -81,7 +81,7 @@ async fn test_post_updates() { poster.pubkey(), encoded_vaa_addresses[0], price_update_keypair.pubkey(), - accumulator_update_data.1[1].clone(), + merkle_price_updates[1].clone(), ), &vec![&poster, &price_update_keypair], None, From 422c2dbb5313a05aaf9e1d84eebdb6893e907a27 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 29 Jan 2024 17:47:32 +0000 Subject: [PATCH 05/18] Go --- pythnet/pythnet_sdk/src/test_utils/mod.rs | 22 +++++++++++++------ .../tests/test_post_updates_atomic.rs | 15 +++++++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/pythnet/pythnet_sdk/src/test_utils/mod.rs b/pythnet/pythnet_sdk/src/test_utils/mod.rs index 5a48434092..5098d3e525 100644 --- a/pythnet/pythnet_sdk/src/test_utils/mod.rs +++ b/pythnet/pythnet_sdk/src/test_utils/mod.rs @@ -37,6 +37,7 @@ use { wormhole_sdk::{ vaa::{ Body, + }, Address, Chain, Vaa, @@ -179,9 +180,18 @@ pub fn create_vaa_from_payload( emitter_chain: Chain, sequence: u64, ) -> Vaa> { - let digest = libsecp256k1Message::parse_slice(&digest(payload).unwrap().secp256k_hash).unwrap(); let guardians = create_dummy_guardians(); + let body: Body> = Body { + emitter_chain, + emitter_address, + sequence, + payload: >::from(payload.to_vec()), + ..Default::default() + }; + + let digest = libsecp256k1Message::parse_slice(&body.digest().unwrap().secp256k_hash).unwrap(); + let signatures: Vec<(Signature, RecoveryId)> = guardians[0..NUM_SIGNATURES] .iter() .map(|x| libsecp256k1::sign(&digest, &x)) @@ -200,15 +210,13 @@ pub fn create_vaa_from_payload( }) .collect(); - let vaa: Vaa> = Vaa { + + let header = Header { version: 1, - emitter_chain, - emitter_address, - sequence, - payload: >::from(payload.to_vec()), signatures: wormhole_signatures, ..Default::default() }; - vaa + + (header, body).into() } diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs index 972692ffe4..881bfcd32c 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs @@ -1,5 +1,12 @@ use { +<<<<<<< HEAD crate::common::DEFAULT_GUARDIAN_SET_INDEX, +======= + crate::common::{ + trim_vaa_signatures, + DEFAULT_GUARDIAN_SET_INDEX, + }, +>>>>>>> c6431163 (Go) common::{ setup_pyth_receiver, ProgramTestFixtures, @@ -17,7 +24,10 @@ use { test_utils::{ create_accumulator_message, create_dummy_price_feed_message, +<<<<<<< HEAD trim_vaa_signatures, +======= +>>>>>>> c6431163 (Go) }, }, solana_sdk::{ @@ -36,11 +46,16 @@ async fn test_post_updates_atomic() { let feed_2 = create_dummy_price_feed_message(200); let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false); let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(message).unwrap(); +<<<<<<< HEAD let vaa = serde_wormhole::to_vec(&trim_vaa_signatures( serde_wormhole::from_slice(&vaa).unwrap(), 5, )) .unwrap(); +======= + + let vaa = trim_vaa_signatures(vaa, 5); +>>>>>>> c6431163 (Go) let ProgramTestFixtures { mut program_simulator, From 8e810fc9832eb6c4c0a58e1ef279b3ef36ab9c23 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 29 Jan 2024 19:26:17 +0000 Subject: [PATCH 06/18] Checkpoint --- target_chains/solana/Cargo.lock | 1 + .../solana/program_simulator/Cargo.toml | 1 + .../solana/program_simulator/src/lib.rs | 27 ++- .../pyth-solana-receiver/src/error.rs | 2 + .../programs/pyth-solana-receiver/src/lib.rs | 4 +- .../tests/test_post_updates_atomic.rs | 186 +++++++++++++++++- 6 files changed, 214 insertions(+), 7 deletions(-) diff --git a/target_chains/solana/Cargo.lock b/target_chains/solana/Cargo.lock index 402f6f20c2..3ce3971cd7 100644 --- a/target_chains/solana/Cargo.lock +++ b/target_chains/solana/Cargo.lock @@ -2981,6 +2981,7 @@ dependencies = [ name = "program-simulator" version = "0.1.0" dependencies = [ + "anchor-lang", "bincode", "borsh 0.10.3", "solana-client", diff --git a/target_chains/solana/program_simulator/Cargo.toml b/target_chains/solana/program_simulator/Cargo.toml index 58c10329fa..44d0832e4e 100644 --- a/target_chains/solana/program_simulator/Cargo.toml +++ b/target_chains/solana/program_simulator/Cargo.toml @@ -15,3 +15,4 @@ solana-program-test = "1.16.20" solana-program = "1.16.20" bincode = "1.3.3" borsh = "0.10.3" +anchor-lang = "0.28.0" diff --git a/target_chains/solana/program_simulator/src/lib.rs b/target_chains/solana/program_simulator/src/lib.rs index e0594dff3e..ac47390cea 100644 --- a/target_chains/solana/program_simulator/src/lib.rs +++ b/target_chains/solana/program_simulator/src/lib.rs @@ -2,8 +2,12 @@ use { borsh::BorshDeserialize, solana_program::{ hash::Hash, - instruction::Instruction, + instruction::{ + Instruction, + InstructionError, + }, native_token::LAMPORTS_PER_SOL, + program_error::ProgramError, pubkey::Pubkey, system_instruction, }, @@ -14,11 +18,15 @@ use { ProgramTestBanksClientExt, }, solana_sdk::{ + compute_budget, signature::{ Keypair, Signer, }, - transaction::Transaction, + transaction::{ + Transaction, + TransactionError, + }, }, }; @@ -49,9 +57,13 @@ impl ProgramSimulator { signers: &Vec<&Keypair>, payer: Option<&Keypair>, ) -> Result<(), BanksClientError> { + let compute_units_ixs = + compute_budget::ComputeBudgetInstruction::set_compute_unit_limit(2000000); let actual_payer = payer.unwrap_or(&self.genesis_keypair); - let mut transaction = - Transaction::new_with_payer(&[instruction], Some(&actual_payer.pubkey())); + let mut transaction = Transaction::new_with_payer( + &[instruction, compute_units_ixs], + Some(&actual_payer.pubkey()), + ); let blockhash = self .banks_client @@ -94,3 +106,10 @@ impl ProgramSimulator { Ok(T::deserialize(&mut &account.data[8..])?) } } + +pub fn into_transation_error>(error: T) -> TransactionError { + TransactionError::InstructionError( + 0, + InstructionError::try_from(u64::from(ProgramError::from(error.into()))).unwrap(), + ) +} diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/error.rs b/target_chains/solana/programs/pyth-solana-receiver/src/error.rs index 80b93eed22..220d814a16 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/error.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/error.rs @@ -43,4 +43,6 @@ pub enum ReceiverError { InvalidSignature, #[msg("The recovered guardian public key doesn't match the guardian set")] InvalidGuardianKeyRecovery, + #[msg("The guardian set account is owned by the wrong program")] + WrongGuardianSetOwner, } diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs b/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs index 3f97c263c2..651660cc6f 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs @@ -258,7 +258,7 @@ pub struct AuthorizeGovernanceAuthorityTransfer<'info> { pub struct PostUpdates<'info> { #[account(mut)] pub payer: Signer<'info>, - #[account(owner = config.wormhole)] + #[account(owner = config.wormhole @ ReceiverError::WrongVaaOwner)] /// CHECK: We aren't deserializing the VAA here but later with VaaAccount::load, which is the recommended way pub encoded_vaa: AccountInfo<'info>, #[account(seeds = [CONFIG_SEED.as_ref()], bump)] @@ -281,7 +281,7 @@ pub struct PostUpdatesAtomic<'info> { /// CHECK: We can't use AccountVariant:: here because its owner is hardcoded as the "official" Wormhole program and we want to get the wormhole address from the config. /// Instead we do the same steps in deserialize_guardian_set_checked. #[account( - owner = config.wormhole)] + owner = config.wormhole @ ReceiverError::WrongGuardianSetOwner)] pub guardian_set: AccountInfo<'info>, #[account(seeds = [CONFIG_SEED.as_ref()], bump)] pub config: Account<'info, Config>, diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs index 881bfcd32c..65a93286c8 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs @@ -11,9 +11,14 @@ use { setup_pyth_receiver, ProgramTestFixtures, }, + program_simulator::into_transation_error, pyth_solana_receiver::{ + error::ReceiverError, instruction::PostUpdatesAtomic, - sdk::deserialize_accumulator_update_data, + sdk::{ + deserialize_accumulator_update_data, + get_guardian_set_address, + }, state::price_update::{ PriceUpdateV1, VerificationLevel, @@ -30,11 +35,13 @@ use { >>>>>>> c6431163 (Go) }, }, + serde_wormhole::RawMessage, solana_sdk::{ signature::Keypair, signer::Signer, }, wormhole_core_bridge_solana::ID as BRIDGE_ID, + wormhole_sdk::Vaa, }; mod common; @@ -131,3 +138,180 @@ async fn test_post_updates_atomic() { feed_2 ); } + +#[tokio::test] +async fn test_post_updates_atomic_wrong_vaa() { + let feed_1 = create_dummy_price_feed_message(100); + let feed_2 = create_dummy_price_feed_message(200); + let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false); + let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(message).unwrap(); + + let ProgramTestFixtures { + mut program_simulator, + encoded_vaa_addresses: _, + } = setup_pyth_receiver(vec![]).await; + + let poster = program_simulator.get_funded_keypair().await.unwrap(); + let price_update_keypair = Keypair::new(); + + let vaa_wrong_num_signatures = trim_vaa_signatures(vaa.clone(), 4); + assert_eq!( + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + vaa_wrong_num_signatures.clone(), + merkle_price_updates[0].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::InsufficientGuardianSignatures) + ); + + let mut vaa_copy: Vaa<&RawMessage> = serde_wormhole::from_slice(&vaa).unwrap(); + vaa_copy.version = 0; + + assert_eq!( + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + serde_wormhole::to_vec(&vaa_copy).unwrap(), + merkle_price_updates[0].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::InvalidVaaVersion) + ); + + let mut vaa_copy: Vaa<&RawMessage> = serde_wormhole::from_slice(&vaa).unwrap(); + vaa_copy.guardian_set_index = 1; + + assert_eq!( + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + serde_wormhole::to_vec(&vaa_copy).unwrap(), + merkle_price_updates[0].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::GuardianSetMismatch) + ); + + let mut vaa_copy: Vaa<&RawMessage> = serde_wormhole::from_slice(&vaa).unwrap(); + vaa_copy.signatures[0].index = 20; + + assert_eq!( + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + serde_wormhole::to_vec(&vaa_copy).unwrap(), + merkle_price_updates[0].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::InvalidGuardianIndex) + ); + + let mut vaa_copy: Vaa<&RawMessage> = serde_wormhole::from_slice(&vaa).unwrap(); + vaa_copy.signatures[0].signature[64] = 5; + + assert_eq!( + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + serde_wormhole::to_vec(&vaa_copy).unwrap(), + merkle_price_updates[0].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::InvalidSignature) + ); + + + let mut vaa_copy: Vaa<&RawMessage> = serde_wormhole::from_slice(&vaa).unwrap(); + vaa_copy.signatures[0].signature = vaa_copy.signatures[1].signature; + + assert_eq!( + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + serde_wormhole::to_vec(&vaa_copy).unwrap(), + merkle_price_updates[0].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::InvalidGuardianKeyRecovery) + ); + + let mut wrong_instruction = PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + vaa.clone(), + merkle_price_updates[0].clone(), + ); + + let wrong_guardian_set = get_guardian_set_address(BRIDGE_ID, 1); + wrong_instruction.accounts[1].pubkey = wrong_guardian_set; + assert_eq!( + program_simulator + .process_ix( + wrong_instruction, + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::WrongGuardianSetOwner) + ); +} From 4ba2740183bed849ac47becaf7ef8c1fc95d9075 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 30 Jan 2024 14:15:17 +0000 Subject: [PATCH 07/18] Fix --- pythnet/pythnet_sdk/src/test_utils/mod.rs | 24 ++++++++++++++++--- .../tests/test_post_updates_atomic.rs | 21 ++++------------ 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/pythnet/pythnet_sdk/src/test_utils/mod.rs b/pythnet/pythnet_sdk/src/test_utils/mod.rs index 5098d3e525..bdd3383894 100644 --- a/pythnet/pythnet_sdk/src/test_utils/mod.rs +++ b/pythnet/pythnet_sdk/src/test_utils/mod.rs @@ -37,6 +37,7 @@ use { wormhole_sdk::{ vaa::{ Body, + Header, }, Address, Chain, @@ -180,7 +181,7 @@ pub fn create_vaa_from_payload( emitter_chain: Chain, sequence: u64, ) -> Vaa> { - let guardians = create_dummy_guardians(); + let guardians = dummy_guardians(); let body: Body> = Body { emitter_chain, @@ -192,7 +193,7 @@ pub fn create_vaa_from_payload( let digest = libsecp256k1Message::parse_slice(&body.digest().unwrap().secp256k_hash).unwrap(); - let signatures: Vec<(Signature, RecoveryId)> = guardians[0..NUM_SIGNATURES] + let signatures: Vec<(Signature, RecoveryId)> = guardians .iter() .map(|x| libsecp256k1::sign(&digest, &x)) .collect(); @@ -210,13 +211,30 @@ pub fn create_vaa_from_payload( }) .collect(); + let mut wormhole_signatures_subset: Vec = wormhole_signatures + .choose_multiple(&mut thread_rng(), DEFAULT_NUM_SIGNATURES) + .cloned() + .collect(); + + wormhole_signatures_subset.sort_by(|a, b| a.index.cmp(&b.index)); let header = Header { version: 1, - signatures: wormhole_signatures, + signatures: wormhole_signatures_subset, ..Default::default() }; (header, body).into() } + +pub fn trim_vaa_signatures(vaa: Vaa<&RawMessage>, n: u8) -> Vaa<&RawMessage> { + let mut vaa_copy = vaa.clone(); + vaa_copy.signatures = vaa + .signatures + .choose_multiple(&mut thread_rng(), n.into()) + .cloned() + .collect(); + vaa_copy.signatures.sort_by(|a, b| a.index.cmp(&b.index)); + vaa_copy +} diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs index 65a93286c8..17465fddbf 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs @@ -1,12 +1,5 @@ use { -<<<<<<< HEAD crate::common::DEFAULT_GUARDIAN_SET_INDEX, -======= - crate::common::{ - trim_vaa_signatures, - DEFAULT_GUARDIAN_SET_INDEX, - }, ->>>>>>> c6431163 (Go) common::{ setup_pyth_receiver, ProgramTestFixtures, @@ -29,10 +22,7 @@ use { test_utils::{ create_accumulator_message, create_dummy_price_feed_message, -<<<<<<< HEAD trim_vaa_signatures, -======= ->>>>>>> c6431163 (Go) }, }, serde_wormhole::RawMessage, @@ -53,16 +43,11 @@ async fn test_post_updates_atomic() { let feed_2 = create_dummy_price_feed_message(200); let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false); let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(message).unwrap(); -<<<<<<< HEAD let vaa = serde_wormhole::to_vec(&trim_vaa_signatures( serde_wormhole::from_slice(&vaa).unwrap(), 5, )) .unwrap(); -======= - - let vaa = trim_vaa_signatures(vaa, 5); ->>>>>>> c6431163 (Go) let ProgramTestFixtures { mut program_simulator, @@ -154,7 +139,11 @@ async fn test_post_updates_atomic_wrong_vaa() { let poster = program_simulator.get_funded_keypair().await.unwrap(); let price_update_keypair = Keypair::new(); - let vaa_wrong_num_signatures = trim_vaa_signatures(vaa.clone(), 4); + let vaa_wrong_num_signatures = serde_wormhole::to_vec(&trim_vaa_signatures( + serde_wormhole::from_slice(&vaa).unwrap(), + 4, + )) + .unwrap(); assert_eq!( program_simulator .process_ix( From 860b4a9edc10997e1148498435db47528570622b Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 30 Jan 2024 14:19:23 +0000 Subject: [PATCH 08/18] Add new error and test --- .../pyth-solana-receiver/src/error.rs | 2 ++ .../programs/pyth-solana-receiver/src/lib.rs | 2 +- .../tests/test_post_updates_atomic.rs | 24 +++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/error.rs b/target_chains/solana/programs/pyth-solana-receiver/src/error.rs index 220d814a16..ee40471168 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/error.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/error.rs @@ -37,6 +37,8 @@ pub enum ReceiverError { InvalidVaaVersion, #[msg("Guardian set version in the VAA doesn't match the guardian set passed")] GuardianSetMismatch, + #[msg("Guardian signature indices must be increasing")] + InvalidGuardianOrder, #[msg("Guardian index exceeds the number of guardians in the set")] InvalidGuardianIndex, #[msg("A VAA signature is invalid")] diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs b/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs index 651660cc6f..ebef649782 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs @@ -143,7 +143,7 @@ pub mod pyth_solana_receiver { // We do not allow for non-increasing guardian signature indices. let index = usize::from(sig.guardian_index()); if let Some(last_index) = last_guardian_index { - require!(index > last_index, ReceiverError::InvalidGuardianIndex); + require!(index > last_index, ReceiverError::InvalidGuardianOrder); } // Does this guardian index exist in this guardian set? diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs index 17465fddbf..9d4c3e0afa 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs @@ -210,6 +210,30 @@ async fn test_post_updates_atomic_wrong_vaa() { into_transation_error(ReceiverError::GuardianSetMismatch) ); + let mut vaa_copy: Vaa<&RawMessage> = serde_wormhole::from_slice(&vaa).unwrap(); + vaa_copy.signatures[0] = vaa_copy.signatures[1]; + + assert_eq!( + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + serde_wormhole::to_vec(&vaa_copy).unwrap(), + merkle_price_updates[0].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::InvalidGuardianOrder) + ); + + let mut vaa_copy: Vaa<&RawMessage> = serde_wormhole::from_slice(&vaa).unwrap(); vaa_copy.signatures[0].index = 20; From 6ea2a86b0a730a12e4ed94eebfa0c3e9a64561f2 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 30 Jan 2024 14:20:18 +0000 Subject: [PATCH 09/18] Cleanup --- .../programs/pyth-solana-receiver/tests/common/mod.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/common/mod.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/common/mod.rs index e8854a2ddd..631158c812 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/common/mod.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/common/mod.rs @@ -170,9 +170,3 @@ pub async fn setup_pyth_receiver(vaas: Vec>) -> ProgramTestFixt encoded_vaa_addresses, } } - -pub fn trim_vaa_signatures(vaa: Vec, n: u8) -> Vec { - let mut parsed_vaa: Vaa<&RawMessage> = serde_wormhole::from_slice(vaa.as_slice()).unwrap(); - parsed_vaa.signatures = parsed_vaa.signatures[0..n as usize].to_vec(); - serde_wormhole::to_vec(&parsed_vaa).unwrap() -} From e491fb95ca890b8d7fea888e6e814c358d967987 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 30 Jan 2024 14:41:34 +0000 Subject: [PATCH 10/18] Add another test --- .../pyth-solana-receiver/src/error.rs | 33 ++++++++--------- .../tests/test_post_updates.rs | 37 +++++++++++++++++++ 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/error.rs b/target_chains/solana/programs/pyth-solana-receiver/src/error.rs index ee40471168..005dd410ff 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/error.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/error.rs @@ -2,16 +2,9 @@ use anchor_lang::prelude::*; #[error_code] pub enum ReceiverError { + // Pyth payload errors #[msg("The tuple emitter chain, emitter doesn't match one of the valid data sources.")] InvalidDataSource, - #[msg("The posted VAA account has the wrong owner.")] - WrongVaaOwner, - #[msg("The posted VAA has wrong magic number.")] - PostedVaaHeaderWrongMagicNumber, - #[msg("An error occurred when deserializing the VAA.")] - DeserializeVaaFailed, - #[msg("An error occurred when deserializing the updates.")] - DeserializeUpdateFailed, #[msg("An error occurred when deserializing the message")] DeserializeMessageFailed, #[msg("Received an invalid wormhole message")] @@ -20,19 +13,18 @@ pub enum ReceiverError { InvalidPriceUpdate, #[msg("This type of message is not supported currently")] UnsupportedMessageType, - #[msg("The signer is not authorized to perform this governance action")] - GovernanceAuthorityMismatch, - #[msg("The signer is not authorized to accept the governance authority")] - TargetGovernanceAuthorityMismatch, - #[msg("The governance authority needs to request a transfer first")] - NonexistentGovernanceAuthorityTransferRequest, #[msg("Funds are insufficient to pay the receiving fee")] InsufficientFunds, - #[msg("The number of guardian signatures is below the minimum")] - InsufficientGuardianSignatures, + // Wormhole contract encoded vaa error (from post_updates) + #[msg("The posted VAA account has the wrong owner.")] + WrongVaaOwner, + // Wormhole signatures verification errors (from post_updates_atomic) + #[msg("An error occurred when deserializing the VAA.")] + DeserializeVaaFailed, #[msg("The Guardian Set account doesn't match the PDA derivation")] InvalidGuardianSetPda, - // Wormhole errors + #[msg("The number of guardian signatures is below the minimum")] + InsufficientGuardianSignatures, #[msg("Invalid VAA version")] InvalidVaaVersion, #[msg("Guardian set version in the VAA doesn't match the guardian set passed")] @@ -47,4 +39,11 @@ pub enum ReceiverError { InvalidGuardianKeyRecovery, #[msg("The guardian set account is owned by the wrong program")] WrongGuardianSetOwner, + // Governance errors + #[msg("The signer is not authorized to perform this governance action")] + GovernanceAuthorityMismatch, + #[msg("The signer is not authorized to accept the governance authority")] + TargetGovernanceAuthorityMismatch, + #[msg("The governance authority needs to request a transfer first")] + NonexistentGovernanceAuthorityTransferRequest, } diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs index fd22e4773c..c7398626ed 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs @@ -3,7 +3,9 @@ use { setup_pyth_receiver, ProgramTestFixtures, }, + program_simulator::into_transation_error, pyth_solana_receiver::{ + error::ReceiverError, instruction::PostUpdates, sdk::deserialize_accumulator_update_data, state::price_update::{ @@ -18,6 +20,7 @@ use { create_dummy_price_feed_message, }, }, + solana_program::pubkey::Pubkey, solana_sdk::{ signature::Keypair, signer::Signer, @@ -105,3 +108,37 @@ async fn test_post_updates() { feed_2 ); } + +#[tokio::test] +async fn test_post_updates_wrong_vaa() { + let feed_1 = create_dummy_price_feed_message(100); + let feed_2 = create_dummy_price_feed_message(200); + let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false); + let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(message).unwrap(); + + let ProgramTestFixtures { + mut program_simulator, + encoded_vaa_addresses: _, + } = setup_pyth_receiver(vec![serde_wormhole::from_slice(&vaa).unwrap()]).await; + + let poster = program_simulator.get_funded_keypair().await.unwrap(); + let price_update_keypair = Keypair::new(); + + assert_eq!( + program_simulator + .process_ix( + PostUpdates::populate( + poster.pubkey(), + Pubkey::new_unique(), + price_update_keypair.pubkey(), + merkle_price_updates[0].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::WrongVaaOwner) + ); +} From b3510b02dc8b3e580a7ba727a074f899a023e5ec Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 30 Jan 2024 15:06:32 +0000 Subject: [PATCH 11/18] Keep adding errors --- .../pyth-solana-receiver/src/error.rs | 6 +- .../programs/pyth-solana-receiver/src/lib.rs | 6 ++ .../pyth-solana-receiver/tests/common/mod.rs | 54 +++++++++++--- .../tests/test_post_updates.rs | 53 +++++++++++++- .../tests/test_post_updates_atomic.rs | 70 ++++++++++++++++++- 5 files changed, 173 insertions(+), 16 deletions(-) diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/error.rs b/target_chains/solana/programs/pyth-solana-receiver/src/error.rs index 005dd410ff..ff8b5fa036 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/error.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/error.rs @@ -21,8 +21,6 @@ pub enum ReceiverError { // Wormhole signatures verification errors (from post_updates_atomic) #[msg("An error occurred when deserializing the VAA.")] DeserializeVaaFailed, - #[msg("The Guardian Set account doesn't match the PDA derivation")] - InvalidGuardianSetPda, #[msg("The number of guardian signatures is below the minimum")] InsufficientGuardianSignatures, #[msg("Invalid VAA version")] @@ -39,6 +37,10 @@ pub enum ReceiverError { InvalidGuardianKeyRecovery, #[msg("The guardian set account is owned by the wrong program")] WrongGuardianSetOwner, + #[msg("The Guardian Set account doesn't match the PDA derivation")] + InvalidGuardianSetPda, + #[msg("The Guardian Set is expired")] + GuardianSetExpired, // Governance errors #[msg("The signer is not authorized to perform this governance action")] GovernanceAuthorityMismatch, diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs b/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs index ebef649782..5e030ac7d7 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs @@ -324,6 +324,12 @@ fn deserialize_guardian_set_checked( ReceiverError::InvalidGuardianSetPda ); + let timestamp = Clock::get().map(Into::into)?; + require!( + guardian_set.inner().is_active(×tamp), + ReceiverError::GuardianSetExpired + ); + Ok(guardian_set) } diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/common/mod.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/common/mod.rs index 631158c812..3bff972b8d 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/common/mod.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/common/mod.rs @@ -44,6 +44,7 @@ use { }; pub const DEFAULT_GUARDIAN_SET_INDEX: u32 = 0; +pub const WRONG_GUARDIAN_SET_INDEX: u32 = 1; pub fn default_receiver_config() -> Config { Config { @@ -65,11 +66,20 @@ pub struct ProgramTestFixtures { pub encoded_vaa_addresses: Vec, } -pub fn build_encoded_vaa_account_from_vaa(vaa: Vaa<&RawMessage>) -> Account { +pub fn build_encoded_vaa_account_from_vaa( + vaa: Vaa<&RawMessage>, + wrong_setup_option: WrongSetupOption, +) -> Account { let encoded_vaa_data = ( ::DISCRIMINATOR, Header { - status: ProcessingStatus::Verified, + status: { + if matches!(wrong_setup_option, WrongSetupOption::UnverifiedEncodedVaa) { + ProcessingStatus::Writing + } else { + ProcessingStatus::Verified + } + }, write_authority: Pubkey::new_unique(), version: 1, }, @@ -87,9 +97,15 @@ pub fn build_encoded_vaa_account_from_vaa(vaa: Vaa<&RawMessage>) -> Account { } } -pub fn build_guardian_set_account() -> Account { +pub fn build_guardian_set_account(wrong_setup_option: WrongSetupOption) -> Account { let guardian_set = GuardianSet { - index: DEFAULT_GUARDIAN_SET_INDEX, + index: { + if matches!(wrong_setup_option, WrongSetupOption::GuardianSetWrongIndex) { + WRONG_GUARDIAN_SET_INDEX + } else { + DEFAULT_GUARDIAN_SET_INDEX + } + }, keys: dummy_guardians() .iter() .map(|x| { @@ -101,7 +117,14 @@ pub fn build_guardian_set_account() -> Account { }) .collect::>(), creation_time: 0.into(), - expiration_time: 0.into(), + expiration_time: { + if matches!(wrong_setup_option, WrongSetupOption::GuardianSetExpired) { + 1 + } else { + 0 + } + } + .into(), }; let guardian_set_data = ( @@ -119,12 +142,24 @@ pub fn build_guardian_set_account() -> Account { rent_epoch: 0, } } + +#[derive(Copy, Clone)] +pub enum WrongSetupOption { + None, + GuardianSetExpired, + GuardianSetWrongIndex, + UnverifiedEncodedVaa, +} + /** * Setup to test the Pyth Receiver. The return values are a tuple composed of : * - The program simulator, which is used to send transactions * - The pubkeys of the encoded VAA accounts corresponding to the VAAs passed as argument, these accounts are prepopulated and can be used to test post_updates */ -pub async fn setup_pyth_receiver(vaas: Vec>) -> ProgramTestFixtures { +pub async fn setup_pyth_receiver( + vaas: Vec>, + wrong_setup_option: WrongSetupOption, +) -> ProgramTestFixtures { let mut program_test = ProgramTest::default(); program_test.add_program("pyth_solana_receiver", ID, None); @@ -132,11 +167,14 @@ pub async fn setup_pyth_receiver(vaas: Vec>) -> ProgramTestFixt for vaa in vaas { let encoded_vaa_address = Pubkey::new_unique(); encoded_vaa_addresses.push(encoded_vaa_address); - program_test.add_account(encoded_vaa_address, build_encoded_vaa_account_from_vaa(vaa)); + program_test.add_account( + encoded_vaa_address, + build_encoded_vaa_account_from_vaa(vaa, wrong_setup_option), + ); } program_test.add_account( get_guardian_set_address(BRIDGE_ID, DEFAULT_GUARDIAN_SET_INDEX), - build_guardian_set_account(), + build_guardian_set_account(wrong_setup_option), ); let mut program_simulator = ProgramSimulator::start_from_program_test(program_test).await; diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs index c7398626ed..6143e3b5fc 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs @@ -1,4 +1,5 @@ use { + crate::common::WrongSetupOption, common::{ setup_pyth_receiver, ProgramTestFixtures, @@ -41,7 +42,11 @@ async fn test_post_updates() { let ProgramTestFixtures { mut program_simulator, encoded_vaa_addresses, - } = setup_pyth_receiver(vec![serde_wormhole::from_slice(&vaa).unwrap()]).await; + } = setup_pyth_receiver( + vec![serde_wormhole::from_slice(&vaa).unwrap()], + WrongSetupOption::None, + ) + .await; let poster = program_simulator.get_funded_keypair().await.unwrap(); let price_update_keypair = Keypair::new(); @@ -110,7 +115,7 @@ async fn test_post_updates() { } #[tokio::test] -async fn test_post_updates_wrong_vaa() { +async fn test_post_updates_wrong_encoded_vaa_owner() { let feed_1 = create_dummy_price_feed_message(100); let feed_2 = create_dummy_price_feed_message(200); let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false); @@ -119,7 +124,11 @@ async fn test_post_updates_wrong_vaa() { let ProgramTestFixtures { mut program_simulator, encoded_vaa_addresses: _, - } = setup_pyth_receiver(vec![serde_wormhole::from_slice(&vaa).unwrap()]).await; + } = setup_pyth_receiver( + vec![serde_wormhole::from_slice(&vaa).unwrap()], + WrongSetupOption::None, + ) + .await; let poster = program_simulator.get_funded_keypair().await.unwrap(); let price_update_keypair = Keypair::new(); @@ -142,3 +151,41 @@ async fn test_post_updates_wrong_vaa() { into_transation_error(ReceiverError::WrongVaaOwner) ); } + +#[tokio::test] +async fn test_post_updates_wrong_setup() { + let feed_1 = create_dummy_price_feed_message(100); + let feed_2 = create_dummy_price_feed_message(200); + let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false); + let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(message).unwrap(); + + let ProgramTestFixtures { + mut program_simulator, + encoded_vaa_addresses, + } = setup_pyth_receiver( + vec![serde_wormhole::from_slice(&vaa).unwrap()], + WrongSetupOption::UnverifiedEncodedVaa, + ) + .await; + + let poster = program_simulator.get_funded_keypair().await.unwrap(); + let price_update_keypair = Keypair::new(); + + assert_eq!( + program_simulator + .process_ix( + PostUpdates::populate( + poster.pubkey(), + encoded_vaa_addresses[0], + price_update_keypair.pubkey(), + merkle_price_updates[0].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(wormhole_core_bridge_solana::error::CoreBridgeError::UnverifiedVaa) + ); +} diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs index 9d4c3e0afa..4f036f4d9b 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs @@ -1,5 +1,8 @@ use { - crate::common::DEFAULT_GUARDIAN_SET_INDEX, + crate::common::{ + WrongSetupOption, + DEFAULT_GUARDIAN_SET_INDEX, + }, common::{ setup_pyth_receiver, ProgramTestFixtures, @@ -52,7 +55,7 @@ async fn test_post_updates_atomic() { let ProgramTestFixtures { mut program_simulator, encoded_vaa_addresses: _, - } = setup_pyth_receiver(vec![]).await; + } = setup_pyth_receiver(vec![], WrongSetupOption::None).await; let poster = program_simulator.get_funded_keypair().await.unwrap(); let price_update_keypair = Keypair::new(); @@ -134,7 +137,7 @@ async fn test_post_updates_atomic_wrong_vaa() { let ProgramTestFixtures { mut program_simulator, encoded_vaa_addresses: _, - } = setup_pyth_receiver(vec![]).await; + } = setup_pyth_receiver(vec![], WrongSetupOption::None).await; let poster = program_simulator.get_funded_keypair().await.unwrap(); let price_update_keypair = Keypair::new(); @@ -328,3 +331,64 @@ async fn test_post_updates_atomic_wrong_vaa() { into_transation_error(ReceiverError::WrongGuardianSetOwner) ); } + + +#[tokio::test] +async fn test_post_updates_atomic_wrong_setup() { + let feed_1 = create_dummy_price_feed_message(100); + let feed_2 = create_dummy_price_feed_message(200); + let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false); + let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(message).unwrap(); + let price_update_keypair = Keypair::new(); + + let ProgramTestFixtures { + mut program_simulator, + encoded_vaa_addresses: _, + } = setup_pyth_receiver(vec![], WrongSetupOption::GuardianSetWrongIndex).await; + let poster: Keypair = program_simulator.get_funded_keypair().await.unwrap(); + assert_eq!( + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + vaa.clone(), + merkle_price_updates[0].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::InvalidGuardianSetPda) + ); + + + let ProgramTestFixtures { + mut program_simulator, + encoded_vaa_addresses: _, + } = setup_pyth_receiver(vec![], WrongSetupOption::GuardianSetExpired).await; + let poster = program_simulator.get_funded_keypair().await.unwrap(); + assert_eq!( + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + vaa.clone(), + merkle_price_updates[0].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::GuardianSetExpired) + ); +} From e01e64b52ef8d78afe9d4e36a452da4adce5732f Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 30 Jan 2024 15:18:20 +0000 Subject: [PATCH 12/18] Another test --- .../tests/test_post_updates_atomic.rs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs index 4f036f4d9b..94edebc62c 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs @@ -142,6 +142,29 @@ async fn test_post_updates_atomic_wrong_vaa() { let poster = program_simulator.get_funded_keypair().await.unwrap(); let price_update_keypair = Keypair::new(); + let mut vaa_buffer_copy: Vec = vaa.clone(); + // Mess up with the length of signatures + vaa_buffer_copy[5] = 255; + assert_eq!( + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + vaa_buffer_copy, + merkle_price_updates[0].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::DeserializeVaaFailed) + ); + let vaa_wrong_num_signatures = serde_wormhole::to_vec(&trim_vaa_signatures( serde_wormhole::from_slice(&vaa).unwrap(), 4, @@ -167,6 +190,7 @@ async fn test_post_updates_atomic_wrong_vaa() { into_transation_error(ReceiverError::InsufficientGuardianSignatures) ); + let mut vaa_copy: Vaa<&RawMessage> = serde_wormhole::from_slice(&vaa).unwrap(); vaa_copy.version = 0; From d2c92c46a3b9a793dc1b1931325cb1a71f964b34 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 30 Jan 2024 15:43:16 +0000 Subject: [PATCH 13/18] Add comment --- .../programs/pyth-solana-receiver/tests/test_post_updates.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs index 6143e3b5fc..f8bc560825 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs @@ -138,7 +138,7 @@ async fn test_post_updates_wrong_encoded_vaa_owner() { .process_ix( PostUpdates::populate( poster.pubkey(), - Pubkey::new_unique(), + Pubkey::new_unique(), // Random pubkey instead of the encoded VAA address price_update_keypair.pubkey(), merkle_price_updates[0].clone(), ), From 14a8df567cb6ab1efddadbaa4e05774e10c0b6c2 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 30 Jan 2024 17:10:25 +0000 Subject: [PATCH 14/18] More --- pythnet/pythnet_sdk/src/test_utils/mod.rs | 50 +++++++++++++++--- .../cosmwasm/contracts/pyth/src/contract.rs | 51 +++++++++++++------ .../near/receiver/tests/workspaces.rs | 2 +- .../solana/program_simulator/src/lib.rs | 5 ++ .../pyth-solana-receiver/src/error.rs | 11 ++-- .../programs/pyth-solana-receiver/src/lib.rs | 4 +- .../programs/pyth-solana-receiver/src/sdk.rs | 34 ++++++++++++- .../pyth-solana-receiver/tests/common/mod.rs | 32 +++++++++--- .../tests/test_post_updates.rs | 18 +++++-- .../tests/test_post_updates_atomic.rs | 15 ++++-- 10 files changed, 179 insertions(+), 43 deletions(-) diff --git a/pythnet/pythnet_sdk/src/test_utils/mod.rs b/pythnet/pythnet_sdk/src/test_utils/mod.rs index bdd3383894..80810bb4e3 100644 --- a/pythnet/pythnet_sdk/src/test_utils/mod.rs +++ b/pythnet/pythnet_sdk/src/test_utils/mod.rs @@ -8,6 +8,7 @@ use { messages::{ Message, PriceFeedMessage, + TwapMessage, }, wire::{ to_vec, @@ -110,24 +111,61 @@ pub fn create_dummy_price_feed_message(value: i64) -> Message { Message::PriceFeedMessage(msg) } +pub fn create_dummy_twap_message() -> Message { + let msg = TwapMessage { + feed_id: [0; 32], + cumulative_price: 0, + cumulative_conf: 0, + num_down_slots: 0, + exponent: 0, + publish_time: 0, + prev_publish_time: 0, + publish_slot: 0, + }; + Message::TwapMessage(msg) +} + pub fn create_accumulator_message( all_feeds: &[Message], updates: &[Message], corrupt_wormhole_message: bool, + corrupt_messages: bool, ) -> Vec { - let all_feeds_bytes: Vec<_> = all_feeds + let mut all_feeds_bytes: Vec<_> = all_feeds .iter() .map(|f| to_vec::<_, BigEndian>(f).unwrap()) .collect(); + + let mut updates_bytes: Vec<_> = updates + .iter() + .map(|f| to_vec::<_, BigEndian>(f).unwrap()) + .collect(); + + if corrupt_messages { + all_feeds_bytes = all_feeds_bytes + .iter() + .map(|f| { + let mut f_copy = f.clone(); + f_copy[0] = 255; + f_copy + }) + .collect(); + updates_bytes = updates_bytes + .iter() + .map(|f| { + let mut f_copy = f.clone(); + f_copy[0] = 255; + f_copy + }) + .collect(); + } let all_feeds_bytes_refs: Vec<_> = all_feeds_bytes.iter().map(|f| f.as_ref()).collect(); let tree = MerkleTree::::new(all_feeds_bytes_refs.as_slice()).unwrap(); let mut price_updates: Vec = vec![]; - for update in updates { - let proof = tree - .prove(&to_vec::<_, BigEndian>(update).unwrap()) - .unwrap(); + for update in updates_bytes { + let proof = tree.prove(&update).unwrap(); price_updates.push(MerklePriceUpdate { - message: PrefixedVec::from(to_vec::<_, BigEndian>(update).unwrap()), + message: PrefixedVec::from(update), proof, }); } diff --git a/target_chains/cosmwasm/contracts/pyth/src/contract.rs b/target_chains/cosmwasm/contracts/pyth/src/contract.rs index 6dc3398060..42a48a5a2a 100644 --- a/target_chains/cosmwasm/contracts/pyth/src/contract.rs +++ b/target_chains/cosmwasm/contracts/pyth/src/contract.rs @@ -1155,7 +1155,7 @@ mod test { let feed1 = create_dummy_price_feed_message(100); let feed2 = create_dummy_price_feed_message(200); let feed3 = create_dummy_price_feed_message(300); - let data = create_accumulator_message(&[feed1, feed2, feed3], &[feed1], false); + let data = create_accumulator_message(&[feed1, feed2, feed3], &[feed1], false, false); check_sufficient_fee(&deps.as_ref(), &[data.into()]) } @@ -1246,13 +1246,13 @@ mod test { let feed2 = create_dummy_price_feed_message(200); let feed3 = create_dummy_price_feed_message(300); - let msg = create_accumulator_message(&[feed1, feed2, feed3], &[feed1, feed3], false); + let msg = create_accumulator_message(&[feed1, feed2, feed3], &[feed1, feed3], false, false); assert_eq!( get_update_fee_amount(&deps.as_ref(), &[msg.into()]).unwrap(), 200 ); - let msg = create_accumulator_message(&[feed1, feed2, feed3], &[feed1], false); + let msg = create_accumulator_message(&[feed1, feed2, feed3], &[feed1], false, false); assert_eq!( get_update_fee_amount(&deps.as_ref(), &[msg.into()]).unwrap(), 100 @@ -1262,6 +1262,7 @@ mod test { &[feed1, feed2, feed3], &[feed1, feed2, feed3, feed1, feed3], false, + false, ); assert_eq!( get_update_fee_amount(&deps.as_ref(), &[msg.into()]).unwrap(), @@ -1270,7 +1271,12 @@ mod test { let batch_msg = create_batch_price_update_msg_from_attestations(vec![PriceAttestation::default()]); - let msg = create_accumulator_message(&[feed1, feed2, feed3], &[feed1, feed2, feed3], false); + let msg = create_accumulator_message( + &[feed1, feed2, feed3], + &[feed1, feed2, feed3], + false, + false, + ); assert_eq!( get_update_fee_amount(&deps.as_ref(), &[msg.into(), batch_msg]).unwrap(), 400 @@ -1287,7 +1293,7 @@ mod test { let feed1 = create_dummy_price_feed_message(100); let feed2 = create_dummy_price_feed_message(200); - let msg = create_accumulator_message(&[feed1, feed2], &[feed1], false); + let msg = create_accumulator_message(&[feed1, feed2], &[feed1], false, false); let info = mock_info("123", &[]); let result = update_price_feeds(deps.as_mut(), env, info, &[msg.into()]); assert!(result.is_ok()); @@ -1304,7 +1310,7 @@ mod test { for i in 0..10000 { all_feeds.push(create_dummy_price_feed_message(i)); } - let msg = create_accumulator_message(&all_feeds, &all_feeds[100..110], false); + let msg = create_accumulator_message(&all_feeds, &all_feeds[100..110], false, false); let info = mock_info("123", &[]); let result = update_price_feeds(deps.as_mut(), env, info, &[msg.into()]); assert!(result.is_ok()); @@ -1331,15 +1337,24 @@ mod test { let mut feed1 = create_dummy_price_feed_message(100); let mut feed2 = create_dummy_price_feed_message(200); let mut feed3 = create_dummy_price_feed_message(300); - let msg = create_accumulator_message(&[feed1, feed2, feed3], &[feed1, feed2, feed3], false); + let msg = create_accumulator_message( + &[feed1, feed2, feed3], + &[feed1, feed2, feed3], + false, + false, + ); as_mut_price_feed(&mut feed1).publish_time += 1; as_mut_price_feed(&mut feed2).publish_time += 1; as_mut_price_feed(&mut feed3).publish_time += 1; as_mut_price_feed(&mut feed1).price *= 2; as_mut_price_feed(&mut feed2).price *= 2; as_mut_price_feed(&mut feed3).price *= 2; - let msg2 = - create_accumulator_message(&[feed1, feed2, feed3], &[feed1, feed2, feed3], false); + let msg2 = create_accumulator_message( + &[feed1, feed2, feed3], + &[feed1, feed2, feed3], + false, + false, + ); let info = mock_info("123", &[]); let result = update_price_feeds(deps.as_mut(), env, info, &[msg.into(), msg2.into()]); @@ -1360,7 +1375,12 @@ mod test { let feed3 = create_dummy_price_feed_message(300); as_mut_price_feed(&mut feed2).publish_time -= 1; as_mut_price_feed(&mut feed2).price *= 2; - let msg = create_accumulator_message(&[feed1, feed2, feed3], &[feed1, feed2, feed3], false); + let msg = create_accumulator_message( + &[feed1, feed2, feed3], + &[feed1, feed2, feed3], + false, + false, + ); let info = mock_info("123", &[]); let result = update_price_feeds(deps.as_mut(), env, info, &[msg.into()]); @@ -1380,9 +1400,10 @@ mod test { let feed3 = create_dummy_price_feed_message(300); as_mut_price_feed(&mut feed2).publish_time -= 1; as_mut_price_feed(&mut feed2).price *= 2; - let msg = create_accumulator_message(&[feed1, feed2, feed3], &[feed1, feed3], false); + let msg = create_accumulator_message(&[feed1, feed2, feed3], &[feed1, feed3], false, false); - let msg2 = create_accumulator_message(&[feed1, feed2, feed3], &[feed2, feed3], false); + let msg2 = + create_accumulator_message(&[feed1, feed2, feed3], &[feed2, feed3], false, false); let info = mock_info("123", &[]); let result = update_price_feeds(deps.as_mut(), env, info, &[msg.into(), msg2.into()]); @@ -1399,7 +1420,7 @@ mod test { .unwrap(); let feed1 = create_dummy_price_feed_message(100); - let mut msg = create_accumulator_message(&[feed1], &[feed1], false); + let mut msg = create_accumulator_message(&[feed1], &[feed1], false, false); msg[4] = 3; // major version let info = mock_info("123", &[]); let result = update_price_feeds(deps.as_mut(), env, info, &[msg.into()]); @@ -1418,7 +1439,7 @@ mod test { .unwrap(); let feed1 = create_dummy_price_feed_message(100); - let msg = create_accumulator_message(&[feed1], &[feed1], true); + let msg = create_accumulator_message(&[feed1], &[feed1], true, false); let info = mock_info("123", &[]); let result = update_price_feeds(deps.as_mut(), env, info, &[msg.into()]); assert!(result.is_err()); @@ -1446,7 +1467,7 @@ mod test { prev_publish_time: 0, publish_slot: 0, }); - let msg = create_accumulator_message(&[feed1], &[feed1], false); + let msg = create_accumulator_message(&[feed1], &[feed1], false, false); let info = mock_info("123", &[]); let result = update_price_feeds(deps.as_mut(), env, info, &[msg.into()]); assert!(result.is_err()); diff --git a/target_chains/near/receiver/tests/workspaces.rs b/target_chains/near/receiver/tests/workspaces.rs index a56f522162..ee2281d03a 100644 --- a/target_chains/near/receiver/tests/workspaces.rs +++ b/target_chains/near/receiver/tests/workspaces.rs @@ -863,7 +863,7 @@ async fn test_accumulator_updates() { // Create a couple of test feeds. let feed_1 = create_dummy_price_feed_message(100); let feed_2 = create_dummy_price_feed_message(200); - let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1], false); + let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1], false, false); let message = hex::encode(message); // Call the usual UpdatePriceFeed function. diff --git a/target_chains/solana/program_simulator/src/lib.rs b/target_chains/solana/program_simulator/src/lib.rs index ac47390cea..e0909010ae 100644 --- a/target_chains/solana/program_simulator/src/lib.rs +++ b/target_chains/solana/program_simulator/src/lib.rs @@ -105,6 +105,11 @@ impl ProgramSimulator { Ok(T::deserialize(&mut &account.data[8..])?) } + + pub async fn get_balance(&mut self, pubkey: Pubkey) -> Result { + let lamports = self.banks_client.get_balance(pubkey).await.unwrap(); + Ok(lamports) + } } pub fn into_transation_error>(error: T) -> TransactionError { diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/error.rs b/target_chains/solana/programs/pyth-solana-receiver/src/error.rs index ff8b5fa036..7ad666efde 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/error.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/error.rs @@ -3,16 +3,16 @@ use anchor_lang::prelude::*; #[error_code] pub enum ReceiverError { // Pyth payload errors - #[msg("The tuple emitter chain, emitter doesn't match one of the valid data sources.")] - InvalidDataSource, - #[msg("An error occurred when deserializing the message")] - DeserializeMessageFailed, #[msg("Received an invalid wormhole message")] InvalidWormholeMessage, #[msg("Received an invalid price update")] InvalidPriceUpdate, + #[msg("An error occurred when deserializing the message")] + DeserializeMessageFailed, #[msg("This type of message is not supported currently")] UnsupportedMessageType, + #[msg("The tuple emitter chain, emitter doesn't match one of the valid data sources.")] + InvalidDataSource, #[msg("Funds are insufficient to pay the receiving fee")] InsufficientFunds, // Wormhole contract encoded vaa error (from post_updates) @@ -48,4 +48,7 @@ pub enum ReceiverError { TargetGovernanceAuthorityMismatch, #[msg("The governance authority needs to request a transfer first")] NonexistentGovernanceAuthorityTransferRequest, + // Price account permissions + #[msg("This signer can't write to price update account")] + WrongWriteAuthority, } diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs b/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs index 5e030ac7d7..178d135d47 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs @@ -269,7 +269,7 @@ pub struct PostUpdates<'info> { pub treasury: AccountInfo<'info>, /// The contraint is such that either the price_update_account is uninitialized or the payer is the write_authority. /// Pubkey::default() is the SystemProgram on Solana and it can't sign so it's impossible that price_update_account.write_authority == Pubkey::default() once the account is initialized - #[account(init_if_needed, constraint = price_update_account.write_authority == Pubkey::default() || price_update_account.write_authority == payer.key(), payer =payer, space = PriceUpdateV1::LEN)] + #[account(init_if_needed, constraint = price_update_account.write_authority == Pubkey::default() || price_update_account.write_authority == payer.key() @ ReceiverError::WrongWriteAuthority , payer =payer, space = PriceUpdateV1::LEN)] pub price_update_account: Account<'info, PriceUpdateV1>, pub system_program: Program<'info, System>, } @@ -290,7 +290,7 @@ pub struct PostUpdatesAtomic<'info> { pub treasury: AccountInfo<'info>, /// The contraint is such that either the price_update_account is uninitialized or the payer is the write_authority. /// Pubkey::default() is the SystemProgram on Solana and it can't sign so it's impossible that price_update_account.write_authority == Pubkey::default() once the account is initialized - #[account(init_if_needed, constraint = price_update_account.write_authority == Pubkey::default() || price_update_account.write_authority == payer.key(), payer = payer, space = PriceUpdateV1::LEN)] + #[account(init_if_needed, constraint = price_update_account.write_authority == Pubkey::default() || price_update_account.write_authority == payer.key() @ ReceiverError::WrongWriteAuthority, payer = payer, space = PriceUpdateV1::LEN)] pub price_update_account: Account<'info, PriceUpdateV1>, pub system_program: Program<'info, System>, } diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs b/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs index c536d8c166..b5e1abeedd 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs @@ -2,7 +2,10 @@ use { crate::{ accounts, instruction, - state::config::Config, + state::config::{ + Config, + DataSource, + }, PostUpdatesAtomicParams, CONFIG_SEED, ID, @@ -141,6 +144,35 @@ impl instruction::PostUpdatesAtomic { } +impl instruction::SetDataSources { + pub fn populate(payer: Pubkey, data_sources: Vec) -> Instruction { + let governance_accounts = accounts::Governance::populate(payer).to_account_metas(None); + Instruction { + program_id: ID, + accounts: governance_accounts, + data: instruction::SetDataSources { + valid_data_sources: data_sources, + } + .data(), + } + } +} + +impl instruction::SetFee { + pub fn populate(payer: Pubkey, fee: u64) -> Instruction { + let governance_accounts = accounts::Governance::populate(payer).to_account_metas(None); + Instruction { + program_id: ID, + accounts: governance_accounts, + data: instruction::SetFee { + single_update_fee_in_lamports: fee, + } + .data(), + } + } +} + + pub fn get_treasury_address() -> Pubkey { Pubkey::find_program_address(&[TREASURY_SEED.as_ref()], &ID).0 } diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/common/mod.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/common/mod.rs index 3bff972b8d..57234c3f08 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/common/mod.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/common/mod.rs @@ -46,17 +46,17 @@ use { pub const DEFAULT_GUARDIAN_SET_INDEX: u32 = 0; pub const WRONG_GUARDIAN_SET_INDEX: u32 = 1; -pub fn default_receiver_config() -> Config { +pub fn default_receiver_config(governance_authority: Pubkey) -> Config { Config { - governance_authority: Pubkey::new_unique(), - target_governance_authority: None, - wormhole: BRIDGE_ID, - valid_data_sources: vec![DataSource { + governance_authority, + target_governance_authority: None, + wormhole: BRIDGE_ID, + valid_data_sources: vec![DataSource { chain: DEFAULT_DATA_SOURCE.chain.into(), emitter: Pubkey::from(DEFAULT_DATA_SOURCE.address.0), }], single_update_fee_in_lamports: 1, - minimum_signatures: 5, + minimum_signatures: 5, } } @@ -64,6 +64,7 @@ pub fn default_receiver_config() -> Config { pub struct ProgramTestFixtures { pub program_simulator: ProgramSimulator, pub encoded_vaa_addresses: Vec, + pub governance_authority: Keypair, } pub fn build_encoded_vaa_account_from_vaa( @@ -179,8 +180,9 @@ pub async fn setup_pyth_receiver( let mut program_simulator = ProgramSimulator::start_from_program_test(program_test).await; - let initial_config = default_receiver_config(); let setup_keypair: Keypair = program_simulator.get_funded_keypair().await.unwrap(); + let initial_config = default_receiver_config(setup_keypair.pubkey()); + program_simulator .process_ix( @@ -206,5 +208,21 @@ pub async fn setup_pyth_receiver( ProgramTestFixtures { program_simulator, encoded_vaa_addresses, + governance_authority: setup_keypair, } } + +pub async fn assert_treasury_balance( + program_simulator: &mut ProgramSimulator, + expected_balance: u64, +) { + let treasury_balance = program_simulator + .get_balance(get_treasury_address()) + .await + .unwrap(); + + assert_eq!( + treasury_balance, + expected_balance + Rent::default().minimum_balance(0) + ); +} diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs index f8bc560825..a659a636a1 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs @@ -1,5 +1,8 @@ use { - crate::common::WrongSetupOption, + crate::common::{ + assert_treasury_balance, + WrongSetupOption, + }, common::{ setup_pyth_receiver, ProgramTestFixtures, @@ -35,19 +38,22 @@ mod common; async fn test_post_updates() { let feed_1 = create_dummy_price_feed_message(100); let feed_2 = create_dummy_price_feed_message(200); - let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false); + let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false, false); let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(message).unwrap(); let ProgramTestFixtures { mut program_simulator, encoded_vaa_addresses, + governance_authority: _, } = setup_pyth_receiver( vec![serde_wormhole::from_slice(&vaa).unwrap()], WrongSetupOption::None, ) .await; + assert_treasury_balance(&mut program_simulator, 0).await; + let poster = program_simulator.get_funded_keypair().await.unwrap(); let price_update_keypair = Keypair::new(); @@ -66,6 +72,7 @@ async fn test_post_updates() { .await .unwrap(); + assert_treasury_balance(&mut program_simulator, 1).await; let mut price_update_account = program_simulator .get_anchor_account_data::(price_update_keypair.pubkey()) @@ -97,6 +104,7 @@ async fn test_post_updates() { .await .unwrap(); + assert_treasury_balance(&mut program_simulator, 2).await; price_update_account = program_simulator .get_anchor_account_data::(price_update_keypair.pubkey()) @@ -118,12 +126,13 @@ async fn test_post_updates() { async fn test_post_updates_wrong_encoded_vaa_owner() { let feed_1 = create_dummy_price_feed_message(100); let feed_2 = create_dummy_price_feed_message(200); - let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false); + let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false, false); let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(message).unwrap(); let ProgramTestFixtures { mut program_simulator, encoded_vaa_addresses: _, + governance_authority: _, } = setup_pyth_receiver( vec![serde_wormhole::from_slice(&vaa).unwrap()], WrongSetupOption::None, @@ -156,12 +165,13 @@ async fn test_post_updates_wrong_encoded_vaa_owner() { async fn test_post_updates_wrong_setup() { let feed_1 = create_dummy_price_feed_message(100); let feed_2 = create_dummy_price_feed_message(200); - let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false); + let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false, false); let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(message).unwrap(); let ProgramTestFixtures { mut program_simulator, encoded_vaa_addresses, + governance_authority: _, } = setup_pyth_receiver( vec![serde_wormhole::from_slice(&vaa).unwrap()], WrongSetupOption::UnverifiedEncodedVaa, diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs index 94edebc62c..55fc27be55 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs @@ -1,5 +1,6 @@ use { crate::common::{ + assert_treasury_balance, WrongSetupOption, DEFAULT_GUARDIAN_SET_INDEX, }, @@ -44,7 +45,7 @@ mod common; async fn test_post_updates_atomic() { let feed_1 = create_dummy_price_feed_message(100); let feed_2 = create_dummy_price_feed_message(200); - let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false); + let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false, false); let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(message).unwrap(); let vaa = serde_wormhole::to_vec(&trim_vaa_signatures( serde_wormhole::from_slice(&vaa).unwrap(), @@ -55,11 +56,14 @@ async fn test_post_updates_atomic() { let ProgramTestFixtures { mut program_simulator, encoded_vaa_addresses: _, + governance_authority: _, } = setup_pyth_receiver(vec![], WrongSetupOption::None).await; let poster = program_simulator.get_funded_keypair().await.unwrap(); let price_update_keypair = Keypair::new(); + assert_treasury_balance(&mut program_simulator, 0).await; + // post one update atomically program_simulator .process_ix( @@ -77,6 +81,7 @@ async fn test_post_updates_atomic() { .await .unwrap(); + assert_treasury_balance(&mut program_simulator, 1).await; let mut price_update_account = program_simulator .get_anchor_account_data::(price_update_keypair.pubkey()) @@ -110,6 +115,7 @@ async fn test_post_updates_atomic() { .await .unwrap(); + assert_treasury_balance(&mut program_simulator, 2).await; price_update_account = program_simulator .get_anchor_account_data::(price_update_keypair.pubkey()) @@ -131,12 +137,13 @@ async fn test_post_updates_atomic() { async fn test_post_updates_atomic_wrong_vaa() { let feed_1 = create_dummy_price_feed_message(100); let feed_2 = create_dummy_price_feed_message(200); - let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false); + let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false, false); let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(message).unwrap(); let ProgramTestFixtures { mut program_simulator, encoded_vaa_addresses: _, + governance_authority: _, } = setup_pyth_receiver(vec![], WrongSetupOption::None).await; let poster = program_simulator.get_funded_keypair().await.unwrap(); @@ -361,13 +368,14 @@ async fn test_post_updates_atomic_wrong_vaa() { async fn test_post_updates_atomic_wrong_setup() { let feed_1 = create_dummy_price_feed_message(100); let feed_2 = create_dummy_price_feed_message(200); - let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false); + let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false, false); let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(message).unwrap(); let price_update_keypair = Keypair::new(); let ProgramTestFixtures { mut program_simulator, encoded_vaa_addresses: _, + governance_authority: _, } = setup_pyth_receiver(vec![], WrongSetupOption::GuardianSetWrongIndex).await; let poster: Keypair = program_simulator.get_funded_keypair().await.unwrap(); assert_eq!( @@ -394,6 +402,7 @@ async fn test_post_updates_atomic_wrong_setup() { let ProgramTestFixtures { mut program_simulator, encoded_vaa_addresses: _, + governance_authority: _, } = setup_pyth_receiver(vec![], WrongSetupOption::GuardianSetExpired).await; let poster = program_simulator.get_funded_keypair().await.unwrap(); assert_eq!( From 336eb34162f238411091022fea18afd79a96c315 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 30 Jan 2024 17:17:20 +0000 Subject: [PATCH 15/18] Do it --- .../solana/programs/pyth-solana-receiver/src/error.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/error.rs b/target_chains/solana/programs/pyth-solana-receiver/src/error.rs index 7ad666efde..94aa33170c 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/error.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/error.rs @@ -5,16 +5,19 @@ pub enum ReceiverError { // Pyth payload errors #[msg("Received an invalid wormhole message")] InvalidWormholeMessage, - #[msg("Received an invalid price update")] - InvalidPriceUpdate, #[msg("An error occurred when deserializing the message")] DeserializeMessageFailed, + #[msg("Received an invalid price update")] + InvalidPriceUpdate, #[msg("This type of message is not supported currently")] UnsupportedMessageType, #[msg("The tuple emitter chain, emitter doesn't match one of the valid data sources.")] InvalidDataSource, #[msg("Funds are insufficient to pay the receiving fee")] InsufficientFunds, + // Price account permissions + #[msg("This signer can't write to price update account")] + WrongWriteAuthority, // Wormhole contract encoded vaa error (from post_updates) #[msg("The posted VAA account has the wrong owner.")] WrongVaaOwner, @@ -48,7 +51,4 @@ pub enum ReceiverError { TargetGovernanceAuthorityMismatch, #[msg("The governance authority needs to request a transfer first")] NonexistentGovernanceAuthorityTransferRequest, - // Price account permissions - #[msg("This signer can't write to price update account")] - WrongWriteAuthority, } From 7596a4a085f03612a5f277943f08975c1b9e29f3 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 30 Jan 2024 17:18:52 +0000 Subject: [PATCH 16/18] Again --- .../tests/test_post_price_update_from_vaa.rs | 460 ++++++++++++++++++ 1 file changed, 460 insertions(+) create mode 100644 target_chains/solana/programs/pyth-solana-receiver/tests/test_post_price_update_from_vaa.rs diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_price_update_from_vaa.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_price_update_from_vaa.rs new file mode 100644 index 0000000000..6173504dcc --- /dev/null +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_price_update_from_vaa.rs @@ -0,0 +1,460 @@ +use { + crate::common::{ + assert_treasury_balance, + WrongSetupOption, + DEFAULT_GUARDIAN_SET_INDEX, + }, + common::{ + setup_pyth_receiver, + ProgramTestFixtures, + }, + program_simulator::into_transation_error, + pyth_solana_receiver::{ + error::ReceiverError, + instruction::{ + PostUpdatesAtomic, + SetDataSources, + SetFee, + }, + sdk::deserialize_accumulator_update_data, + state::{ + config::DataSource, + price_update::{ + PriceUpdateV1, + VerificationLevel, + }, + }, + }, + pythnet_sdk::{ + messages::Message, + test_utils::{ + create_accumulator_message, + create_dummy_price_feed_message, + create_dummy_twap_message, + DEFAULT_DATA_SOURCE, + SECONDARY_DATA_SOURCE, + }, + }, + solana_program::{ + native_token::LAMPORTS_PER_SOL, + pubkey::Pubkey, + }, + solana_sdk::{ + signature::Keypair, + signer::Signer, + }, + wormhole_core_bridge_solana::ID as BRIDGE_ID, +}; + +mod common; + + +// This file is meant to test the errors that can be thrown by post_price_update_from_vaa +#[tokio::test] +async fn test_invalid_wormhole_message() { + let feed_1 = create_dummy_price_feed_message(100); + let feed_2 = create_dummy_price_feed_message(200); + + let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], true, false); + let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(message).unwrap(); + + let ProgramTestFixtures { + mut program_simulator, + encoded_vaa_addresses: _, + governance_authority: _, + } = setup_pyth_receiver( + vec![serde_wormhole::from_slice(&vaa).unwrap()], + WrongSetupOption::None, + ) + .await; + + let poster = program_simulator.get_funded_keypair().await.unwrap(); + let price_update_keypair = Keypair::new(); + + // corrupted wormhole message + assert_eq!( + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + vaa, + merkle_price_updates[0].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::InvalidWormholeMessage) + ); +} + +#[tokio::test] +async fn test_invalid_update_message() { + let feed_1 = create_dummy_price_feed_message(100); + let feed_2 = create_dummy_price_feed_message(200); + + let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false, true); + let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(message).unwrap(); + + + let ProgramTestFixtures { + mut program_simulator, + encoded_vaa_addresses: _, + governance_authority: _, + } = setup_pyth_receiver( + vec![serde_wormhole::from_slice(&vaa).unwrap()], + WrongSetupOption::None, + ) + .await; + + let poster = program_simulator.get_funded_keypair().await.unwrap(); + let price_update_keypair = Keypair::new(); + + // corrupted wormhole message + assert_eq!( + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + vaa, + merkle_price_updates[0].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::DeserializeMessageFailed) + ); +} + + +#[tokio::test] +async fn test_post_price_update_from_vaa() { + let feed_1 = create_dummy_price_feed_message(100); + let feed_2 = create_dummy_price_feed_message(200); + let feed_3 = create_dummy_price_feed_message(300); + let twap_1 = create_dummy_twap_message(); + + let message = create_accumulator_message( + &[feed_1, feed_2, twap_1], + &[feed_1, feed_2, twap_1], + false, + false, + ); + + let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(message).unwrap(); + + let message2 = create_accumulator_message(&[feed_2, feed_3], &[feed_3], false, false); + let (_, merkle_price_updates2) = deserialize_accumulator_update_data(message2).unwrap(); + + let ProgramTestFixtures { + mut program_simulator, + encoded_vaa_addresses: _, + governance_authority, + } = setup_pyth_receiver( + vec![serde_wormhole::from_slice(&vaa).unwrap()], + WrongSetupOption::None, + ) + .await; + + assert_treasury_balance(&mut program_simulator, 0).await; + + let poster = program_simulator.get_funded_keypair().await.unwrap(); + let price_update_keypair = Keypair::new(); + + // this update is not in the proof + assert_eq!( + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + vaa.clone(), + merkle_price_updates2[0].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::InvalidPriceUpdate) + ); + + // this update is a twap + assert_eq!( + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + vaa.clone(), + merkle_price_updates[2].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::UnsupportedMessageType) + ); + + + // change the data source + program_simulator + .process_ix( + SetDataSources::populate( + governance_authority.pubkey(), + vec![DataSource { + chain: SECONDARY_DATA_SOURCE.chain.into(), + emitter: Pubkey::from(DEFAULT_DATA_SOURCE.address.0), + }], + ), + &vec![&governance_authority], + None, + ) + .await + .unwrap(); + + // Now this should fail! + assert_eq!( + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + vaa.clone(), + merkle_price_updates[0].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::InvalidDataSource) + ); + + // change again, this time the emitter field + program_simulator + .process_ix( + SetDataSources::populate( + governance_authority.pubkey(), + vec![DataSource { + chain: DEFAULT_DATA_SOURCE.chain.into(), + emitter: Pubkey::from(SECONDARY_DATA_SOURCE.address.0), + }], + ), + &vec![&governance_authority], + None, + ) + .await + .unwrap(); + + + assert_eq!( + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + vaa.clone(), + merkle_price_updates[0].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::InvalidDataSource) + ); + + // change back + program_simulator + .process_ix( + SetDataSources::populate( + governance_authority.pubkey(), + vec![DataSource { + chain: DEFAULT_DATA_SOURCE.chain.into(), + emitter: Pubkey::from(DEFAULT_DATA_SOURCE.address.0), + }], + ), + &vec![&governance_authority], + None, + ) + .await + .unwrap(); + + // Now it works + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + vaa.clone(), + merkle_price_updates[0].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap(); + + assert_treasury_balance(&mut program_simulator, 1).await; + + let mut price_update_account = program_simulator + .get_anchor_account_data::(price_update_keypair.pubkey()) + .await + .unwrap(); + + assert_eq!(price_update_account.write_authority, poster.pubkey()); + assert_eq!( + price_update_account.verification_level, + VerificationLevel::Partial(13) + ); + assert_eq!( + Message::PriceFeedMessage(price_update_account.price_message), + feed_1 + ); + + + // Now change the fee! + program_simulator + .process_ix( + SetFee::populate(governance_authority.pubkey(), LAMPORTS_PER_SOL), + &vec![&governance_authority], + None, + ) + .await + .unwrap(); + + + assert_eq!( + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + vaa.clone(), + merkle_price_updates[1].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::InsufficientFunds) + ); + + assert_treasury_balance(&mut program_simulator, 1).await; + + price_update_account = program_simulator + .get_anchor_account_data::(price_update_keypair.pubkey()) + .await + .unwrap(); + assert_eq!(price_update_account.write_authority, poster.pubkey()); + assert_eq!( + price_update_account.verification_level, + VerificationLevel::Partial(13) + ); + assert_eq!( + Message::PriceFeedMessage(price_update_account.price_message), + feed_1 + ); + + + // Airdrop more + program_simulator + .airdrop(&poster.pubkey(), LAMPORTS_PER_SOL) + .await + .unwrap(); + program_simulator + .process_ix( + SetFee::populate(governance_authority.pubkey(), LAMPORTS_PER_SOL), + &vec![&governance_authority], + None, + ) + .await + .unwrap(); + + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + vaa.clone(), + merkle_price_updates[1].clone(), + ), + &vec![&poster, &price_update_keypair], + None, + ) + .await + .unwrap(); + + assert_treasury_balance(&mut program_simulator, LAMPORTS_PER_SOL + 1).await; + + price_update_account = program_simulator + .get_anchor_account_data::(price_update_keypair.pubkey()) + .await + .unwrap(); + assert_eq!(price_update_account.write_authority, poster.pubkey()); + assert_eq!( + price_update_account.verification_level, + VerificationLevel::Partial(13) + ); + assert_eq!( + Message::PriceFeedMessage(price_update_account.price_message), + feed_2 + ); + + + // poster_2 can't write to this price update account + let poster_2 = program_simulator.get_funded_keypair().await.unwrap(); + + assert_eq!( + program_simulator + .process_ix( + PostUpdatesAtomic::populate( + poster_2.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + vaa.clone(), + merkle_price_updates[0].clone(), + ), + &vec![&poster_2, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::WrongWriteAuthority) + ); +} From 873ead96a4effb860e90d7d12112545ec631a687 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 30 Jan 2024 18:12:32 +0000 Subject: [PATCH 17/18] Nice --- .../programs/pyth-solana-receiver/src/lib.rs | 8 +- .../programs/pyth-solana-receiver/src/sdk.rs | 57 +++ .../tests/test_governance.rs | 406 ++++++++++++++++++ 3 files changed, 467 insertions(+), 4 deletions(-) create mode 100644 target_chains/solana/programs/pyth-solana-receiver/tests/test_governance.rs diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs b/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs index 178d135d47..5d711ba518 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs @@ -68,8 +68,8 @@ pub mod pyth_solana_receiver { Ok(()) } - pub fn authorize_governance_authority_transfer( - ctx: Context, + pub fn accept_governance_authority_transfer( + ctx: Context, ) -> Result<()> { let config = &mut ctx.accounts.config; config.governance_authority = config.target_governance_authority.ok_or(error!( @@ -244,13 +244,13 @@ pub struct Governance<'info> { } #[derive(Accounts)] -pub struct AuthorizeGovernanceAuthorityTransfer<'info> { +pub struct AcceptGovernanceAuthorityTransfer<'info> { #[account(constraint = payer.key() == config.target_governance_authority.ok_or(error!(ReceiverError::NonexistentGovernanceAuthorityTransferRequest))? @ ReceiverError::TargetGovernanceAuthorityMismatch )] pub payer: Signer<'info>, - #[account(seeds = [CONFIG_SEED.as_ref()], bump)] + #[account(mut, seeds = [CONFIG_SEED.as_ref()], bump)] pub config: Account<'info, Config>, } diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs b/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs index b5e1abeedd..58228d7b40 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs @@ -81,6 +81,13 @@ impl accounts::Governance { } } +impl accounts::AcceptGovernanceAuthorityTransfer { + pub fn populate(payer: Pubkey) -> Self { + let config = get_config_address(); + accounts::AcceptGovernanceAuthorityTransfer { payer, config } + } +} + impl instruction::Initialize { pub fn populate(payer: &Pubkey, initial_config: Config) -> Instruction { Instruction { @@ -173,6 +180,56 @@ impl instruction::SetFee { } +impl instruction::SetWormholeAddress { + pub fn populate(payer: Pubkey, wormhole: Pubkey) -> Instruction { + let governance_accounts = accounts::Governance::populate(payer).to_account_metas(None); + Instruction { + program_id: ID, + accounts: governance_accounts, + data: instruction::SetWormholeAddress { wormhole }.data(), + } + } +} + + +impl instruction::SetMinimumSignatures { + pub fn populate(payer: Pubkey, minimum_signatures: u8) -> Instruction { + let governance_accounts = accounts::Governance::populate(payer).to_account_metas(None); + Instruction { + program_id: ID, + accounts: governance_accounts, + data: instruction::SetMinimumSignatures { minimum_signatures }.data(), + } + } +} + +impl instruction::RequestGovernanceAuthorityTransfer { + pub fn populate(payer: Pubkey, target_governance_authority: Pubkey) -> Instruction { + let governance_accounts = accounts::Governance::populate(payer).to_account_metas(None); + Instruction { + program_id: ID, + accounts: governance_accounts, + data: instruction::RequestGovernanceAuthorityTransfer { + target_governance_authority, + } + .data(), + } + } +} + +impl instruction::AcceptGovernanceAuthorityTransfer { + pub fn populate(payer: Pubkey) -> Instruction { + let governance_accounts = + accounts::AcceptGovernanceAuthorityTransfer::populate(payer).to_account_metas(None); + Instruction { + program_id: ID, + accounts: governance_accounts, + data: instruction::AcceptGovernanceAuthorityTransfer {}.data(), + } + } +} + + pub fn get_treasury_address() -> Pubkey { Pubkey::find_program_address(&[TREASURY_SEED.as_ref()], &ID).0 } diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_governance.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_governance.rs new file mode 100644 index 0000000000..df4998844c --- /dev/null +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/test_governance.rs @@ -0,0 +1,406 @@ +use { + crate::common::WrongSetupOption, + common::{ + setup_pyth_receiver, + ProgramTestFixtures, + }, + program_simulator::into_transation_error, + pyth_solana_receiver::{ + error::ReceiverError, + instruction::{ + AcceptGovernanceAuthorityTransfer, + RequestGovernanceAuthorityTransfer, + SetDataSources, + SetFee, + SetMinimumSignatures, + SetWormholeAddress, + }, + sdk::get_config_address, + state::config::{ + Config, + DataSource, + }, + }, + pythnet_sdk::test_utils::SECONDARY_DATA_SOURCE, + solana_program::{ + native_token::LAMPORTS_PER_SOL, + pubkey::Pubkey, + }, + solana_sdk::signer::Signer, +}; + +mod common; + + +#[tokio::test] +async fn test_governance() { + let ProgramTestFixtures { + mut program_simulator, + encoded_vaa_addresses: _, + governance_authority, + } = setup_pyth_receiver(vec![], WrongSetupOption::None).await; + + let new_governance_authority = program_simulator.get_funded_keypair().await.unwrap(); + + let initial_config = program_simulator + .get_anchor_account_data::(get_config_address()) + .await + .unwrap(); + + let new_config = Config { + governance_authority: new_governance_authority.pubkey(), + target_governance_authority: None, + wormhole: Pubkey::new_unique(), + valid_data_sources: vec![DataSource { + chain: SECONDARY_DATA_SOURCE.chain.into(), + emitter: Pubkey::from(SECONDARY_DATA_SOURCE.address.0), + }], + single_update_fee_in_lamports: LAMPORTS_PER_SOL, + minimum_signatures: 20, + }; + + + // this authority is not allowed to do anything + assert_eq!( + program_simulator + .process_ix( + SetDataSources::populate( + new_governance_authority.pubkey(), + new_config.valid_data_sources.clone() + ), + &vec![&new_governance_authority], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::GovernanceAuthorityMismatch) + ); + + assert_eq!( + program_simulator + .process_ix( + SetFee::populate( + new_governance_authority.pubkey(), + new_config.single_update_fee_in_lamports + ), + &vec![&new_governance_authority], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::GovernanceAuthorityMismatch) + ); + + assert_eq!( + program_simulator + .process_ix( + SetWormholeAddress::populate( + new_governance_authority.pubkey(), + new_config.wormhole + ), + &vec![&new_governance_authority], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::GovernanceAuthorityMismatch) + ); + + assert_eq!( + program_simulator + .process_ix( + SetMinimumSignatures::populate( + new_governance_authority.pubkey(), + new_config.minimum_signatures, + ), + &vec![&new_governance_authority], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::GovernanceAuthorityMismatch) + ); + + assert_eq!( + program_simulator + .process_ix( + RequestGovernanceAuthorityTransfer::populate( + new_governance_authority.pubkey(), + new_config.governance_authority, + ), + &vec![&new_governance_authority], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::GovernanceAuthorityMismatch) + ); + + assert_eq!( + program_simulator + .get_anchor_account_data::(get_config_address()) + .await + .unwrap(), + initial_config + ); + + + // Now start changing for real + program_simulator + .process_ix( + SetDataSources::populate( + governance_authority.pubkey(), + new_config.valid_data_sources.clone(), + ), + &vec![&governance_authority], + None, + ) + .await + .unwrap(); + + let mut current_config = program_simulator + .get_anchor_account_data::(get_config_address()) + .await + .unwrap(); + assert_eq!( + current_config.governance_authority, + initial_config.governance_authority + ); + assert_eq!(current_config.target_governance_authority, None); + assert_eq!(current_config.wormhole, initial_config.wormhole); + assert_eq!( + current_config.valid_data_sources, + new_config.valid_data_sources + ); + assert_eq!( + current_config.single_update_fee_in_lamports, + initial_config.single_update_fee_in_lamports + ); + assert_eq!( + current_config.minimum_signatures, + initial_config.minimum_signatures + ); + + program_simulator + .process_ix( + SetFee::populate( + governance_authority.pubkey(), + new_config.single_update_fee_in_lamports, + ), + &vec![&governance_authority], + None, + ) + .await + .unwrap(); + + current_config = program_simulator + .get_anchor_account_data::(get_config_address()) + .await + .unwrap(); + assert_eq!( + current_config.governance_authority, + initial_config.governance_authority + ); + assert_eq!(current_config.target_governance_authority, None); + assert_eq!(current_config.wormhole, initial_config.wormhole); + assert_eq!( + current_config.valid_data_sources, + new_config.valid_data_sources + ); + assert_eq!( + current_config.single_update_fee_in_lamports, + new_config.single_update_fee_in_lamports + ); + assert_eq!( + current_config.minimum_signatures, + initial_config.minimum_signatures + ); + + program_simulator + .process_ix( + SetWormholeAddress::populate(governance_authority.pubkey(), new_config.wormhole), + &vec![&governance_authority], + None, + ) + .await + .unwrap(); + + current_config = program_simulator + .get_anchor_account_data::(get_config_address()) + .await + .unwrap(); + assert_eq!( + current_config.governance_authority, + initial_config.governance_authority + ); + assert_eq!(current_config.target_governance_authority, None); + assert_eq!(current_config.wormhole, new_config.wormhole); + assert_eq!( + current_config.valid_data_sources, + new_config.valid_data_sources + ); + assert_eq!( + current_config.single_update_fee_in_lamports, + new_config.single_update_fee_in_lamports + ); + assert_eq!( + current_config.minimum_signatures, + initial_config.minimum_signatures + ); + + program_simulator + .process_ix( + SetMinimumSignatures::populate( + governance_authority.pubkey(), + new_config.minimum_signatures, + ), + &vec![&governance_authority], + None, + ) + .await + .unwrap(); + + current_config = program_simulator + .get_anchor_account_data::(get_config_address()) + .await + .unwrap(); + assert_eq!( + current_config.governance_authority, + initial_config.governance_authority + ); + assert_eq!(current_config.target_governance_authority, None); + assert_eq!(current_config.wormhole, new_config.wormhole); + assert_eq!( + current_config.valid_data_sources, + new_config.valid_data_sources + ); + assert_eq!( + current_config.single_update_fee_in_lamports, + new_config.single_update_fee_in_lamports + ); + assert_eq!( + current_config.minimum_signatures, + new_config.minimum_signatures + ); + + // Target is not defined yet + assert_eq!( + program_simulator + .process_ix( + AcceptGovernanceAuthorityTransfer::populate(new_governance_authority.pubkey()), + &vec![&new_governance_authority], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::NonexistentGovernanceAuthorityTransferRequest) + ); + + // Request transfer + program_simulator + .process_ix( + RequestGovernanceAuthorityTransfer::populate( + governance_authority.pubkey(), + new_governance_authority.pubkey(), + ), + &vec![&governance_authority], + None, + ) + .await + .unwrap(); + + + current_config = program_simulator + .get_anchor_account_data::(get_config_address()) + .await + .unwrap(); + assert_eq!( + current_config.governance_authority, + initial_config.governance_authority + ); + assert_eq!( + current_config.target_governance_authority, + Some(new_governance_authority.pubkey()) + ); + assert_eq!(current_config.wormhole, new_config.wormhole); + assert_eq!( + current_config.valid_data_sources, + new_config.valid_data_sources + ); + assert_eq!( + current_config.single_update_fee_in_lamports, + new_config.single_update_fee_in_lamports + ); + assert_eq!( + current_config.minimum_signatures, + new_config.minimum_signatures + ); + + let poster = program_simulator.get_funded_keypair().await.unwrap(); + // Random guy can't accept + + assert_eq!( + program_simulator + .process_ix( + AcceptGovernanceAuthorityTransfer::populate(poster.pubkey()), + &vec![&poster], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::TargetGovernanceAuthorityMismatch) + ); + + // New authority can accept + program_simulator + .process_ix( + AcceptGovernanceAuthorityTransfer::populate(new_governance_authority.pubkey()), + &vec![&new_governance_authority], + None, + ) + .await + .unwrap(); + + current_config = program_simulator + .get_anchor_account_data::(get_config_address()) + .await + .unwrap(); + assert_eq!(current_config, new_config); + + // Now the new authority can do stuff + program_simulator + .process_ix( + SetFee::populate(new_governance_authority.pubkey(), 9), + &vec![&new_governance_authority], + None, + ) + .await + .unwrap(); + + current_config = program_simulator + .get_anchor_account_data::(get_config_address()) + .await + .unwrap(); + assert_eq!( + current_config.governance_authority, + new_config.governance_authority + ); + assert_eq!(current_config.target_governance_authority, None); + assert_eq!(current_config.wormhole, new_config.wormhole); + assert_eq!( + current_config.valid_data_sources, + new_config.valid_data_sources + ); + assert_eq!(current_config.single_update_fee_in_lamports, 9); + assert_eq!( + current_config.minimum_signatures, + new_config.minimum_signatures + ); +} From ee10fef0336ff995109fa516da7e7515d38ea8f4 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 30 Jan 2024 18:47:25 +0000 Subject: [PATCH 18/18] Ship it --- .../programs/pyth-solana-receiver/src/lib.rs | 12 ++++++ .../programs/pyth-solana-receiver/src/sdk.rs | 22 +++++++++++ .../tests/test_post_updates.rs | 37 ++++++++++++++++++- 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs b/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs index 5d711ba518..a2afbba760 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs @@ -217,6 +217,10 @@ pub mod pyth_solana_receiver { Ok(()) } + + pub fn reclaim_rent(_ctx: Context) -> Result<()> { + Ok(()) + } } pub const CONFIG_SEED: &str = "config"; @@ -295,6 +299,14 @@ pub struct PostUpdatesAtomic<'info> { pub system_program: Program<'info, System>, } +#[derive(Accounts)] +pub struct ReclaimRent<'info> { + #[account(mut)] + pub payer: Signer<'info>, + #[account(mut, close = payer, constraint = price_update_account.write_authority == payer.key() @ ReceiverError::WrongWriteAuthority)] + pub price_update_account: Account<'info, PriceUpdateV1>, +} + #[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone)] pub struct PostUpdatesAtomicParams { pub vaa: Vec, diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs b/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs index 58228d7b40..69bc94bc1c 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs @@ -88,6 +88,16 @@ impl accounts::AcceptGovernanceAuthorityTransfer { } } +impl accounts::ReclaimRent { + pub fn populate(payer: Pubkey, price_update_account: Pubkey) -> Self { + let _config = get_config_address(); + accounts::ReclaimRent { + payer, + price_update_account, + } + } +} + impl instruction::Initialize { pub fn populate(payer: &Pubkey, initial_config: Config) -> Instruction { Instruction { @@ -229,6 +239,18 @@ impl instruction::AcceptGovernanceAuthorityTransfer { } } +impl instruction::ReclaimRent { + pub fn populate(payer: Pubkey, price_update_account: Pubkey) -> Instruction { + let governance_accounts = + accounts::ReclaimRent::populate(payer, price_update_account).to_account_metas(None); + Instruction { + program_id: ID, + accounts: governance_accounts, + data: instruction::ReclaimRent {}.data(), + } + } +} + pub fn get_treasury_address() -> Pubkey { Pubkey::find_program_address(&[TREASURY_SEED.as_ref()], &ID).0 diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs index a659a636a1..4b80c33fd9 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs @@ -10,7 +10,10 @@ use { program_simulator::into_transation_error, pyth_solana_receiver::{ error::ReceiverError, - instruction::PostUpdates, + instruction::{ + PostUpdates, + ReclaimRent, + }, sdk::deserialize_accumulator_update_data, state::price_update::{ PriceUpdateV1, @@ -120,6 +123,38 @@ async fn test_post_updates() { Message::PriceFeedMessage(price_update_account.price_message), feed_2 ); + + // This poster doesn't have the write authority + let poster_2 = program_simulator.get_funded_keypair().await.unwrap(); + assert_eq!( + program_simulator + .process_ix( + ReclaimRent::populate(poster_2.pubkey(), price_update_keypair.pubkey()), + &vec![&poster_2], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transation_error(ReceiverError::WrongWriteAuthority) + ); + + program_simulator + .process_ix( + ReclaimRent::populate(poster.pubkey(), price_update_keypair.pubkey()), + &vec![&poster], + None, + ) + .await + .unwrap(); + + assert_eq!( + program_simulator + .get_balance(price_update_keypair.pubkey()) + .await + .unwrap(), + 0 + ); } #[tokio::test]