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) + ); +}