Skip to content

Commit

Permalink
[solana] Add wrong vaa tests (#1262)
Browse files Browse the repository at this point in the history
* Checkpoint

* Checkpoint

* Cleanup

* Checkpoint, debug

* Go

* Checkpoint

* Fix

* Add new error and test

* Cleanup

* Add another test

* Keep adding errors

* Another test

* Add comment

* Rename

* Rename
  • Loading branch information
guibescos authored Jan 31, 2024
1 parent 4edef3a commit 2ab72d9
Show file tree
Hide file tree
Showing 8 changed files with 498 additions and 43 deletions.
1 change: 1 addition & 0 deletions target_chains/solana/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions target_chains/solana/program_simulator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
32 changes: 26 additions & 6 deletions target_chains/solana/program_simulator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand All @@ -14,11 +18,15 @@ use {
ProgramTestBanksClientExt,
},
solana_sdk::{
compute_budget,
signature::{
Keypair,
Signer,
},
transaction::Transaction,
transaction::{
Transaction,
TransactionError,
},
},
};

Expand All @@ -43,15 +51,19 @@ impl ProgramSimulator {

/// Process a transaction containing `instruction` signed by `signers`.
/// `payer` is used to pay for and sign the transaction.
pub async fn process_ix(
pub async fn process_ix_with_default_compute_limit(
&mut self,
instruction: Instruction,
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
Expand All @@ -71,7 +83,8 @@ impl ProgramSimulator {
let instruction =
system_instruction::transfer(&self.genesis_keypair.pubkey(), to, lamports);

self.process_ix(instruction, &vec![], None).await
self.process_ix_with_default_compute_limit(instruction, &vec![], None)
.await
}

pub async fn get_funded_keypair(&mut self) -> Result<Keypair, BanksClientError> {
Expand All @@ -94,3 +107,10 @@ impl ProgramSimulator {
Ok(T::deserialize(&mut &account.data[8..])?)
}
}

pub fn into_transaction_error<T: Into<anchor_lang::prelude::Error>>(error: T) -> TransactionError {
TransactionError::InstructionError(
0,
InstructionError::try_from(u64::from(ProgramError::from(error.into()))).unwrap(),
)
}
39 changes: 22 additions & 17 deletions target_chains/solana/programs/pyth-solana-receiver/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand All @@ -20,27 +13,39 @@ 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,
// 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 number of guardian signatures is below the minimum")]
InsufficientGuardianSignatures,
#[msg("The Guardian Set account doesn't match the PDA derivation")]
InvalidGuardianSetPda,
// Wormhole errors
#[msg("Invalid VAA version")]
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")]
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,
#[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,
#[msg("The signer is not authorized to accept the governance authority")]
TargetGovernanceAuthorityMismatch,
#[msg("The governance authority needs to request a transfer first")]
NonexistentGovernanceAuthorityTransferRequest,
}
12 changes: 9 additions & 3 deletions target_chains/solana/programs/pyth-solana-receiver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down Expand Up @@ -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)]
Expand All @@ -281,7 +281,7 @@ pub struct PostUpdatesAtomic<'info> {
/// CHECK: We can't use AccountVariant::<GuardianSet> 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>,
Expand Down Expand Up @@ -324,6 +324,12 @@ fn deserialize_guardian_set_checked(
ReceiverError::InvalidGuardianSetPda
);

let timestamp = Clock::get().map(Into::into)?;
require!(
guardian_set.inner().is_active(&timestamp),
ReceiverError::GuardianSetExpired
);

Ok(guardian_set)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -65,11 +66,20 @@ pub struct ProgramTestFixtures {
pub encoded_vaa_addresses: Vec<Pubkey>,
}

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 = (
<EncodedVaa as anchor_lang::Discriminator>::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,
},
Expand All @@ -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| {
Expand All @@ -101,7 +117,14 @@ pub fn build_guardian_set_account() -> Account {
})
.collect::<Vec<[u8; 20]>>(),
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 = (
Expand All @@ -119,24 +142,39 @@ 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<Vaa<&RawMessage>>) -> ProgramTestFixtures {
pub async fn setup_pyth_receiver(
vaas: Vec<Vaa<&RawMessage>>,
wrong_setup_option: WrongSetupOption,
) -> ProgramTestFixtures {
let mut program_test = ProgramTest::default();
program_test.add_program("pyth_solana_receiver", ID, None);

let mut encoded_vaa_addresses: Vec<Pubkey> = vec![];
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;
Expand All @@ -145,7 +183,7 @@ pub async fn setup_pyth_receiver(vaas: Vec<Vaa<&RawMessage>>) -> ProgramTestFixt
let setup_keypair: Keypair = program_simulator.get_funded_keypair().await.unwrap();

program_simulator
.process_ix(
.process_ix_with_default_compute_limit(
Initialize::populate(&setup_keypair.pubkey(), initial_config.clone()),
&vec![&setup_keypair],
None,
Expand Down
Loading

0 comments on commit 2ab72d9

Please sign in to comment.