diff --git a/target_chains/solana/cli/src/main.rs b/target_chains/solana/cli/src/main.rs index 72bd212f..84fdee48 100644 --- a/target_chains/solana/cli/src/main.rs +++ b/target_chains/solana/cli/src/main.rs @@ -264,6 +264,7 @@ pub fn process_post_price_update_atomic( let post_update_accounts = pyth_solana_receiver::accounts::PostUpdateAtomic::populate( + payer.pubkey(), payer.pubkey(), price_update_keypair.pubkey(), *wormhole, @@ -472,6 +473,7 @@ pub fn process_write_encoded_vaa_and_post_price_update( let price_update_keypair = Keypair::new(); let post_update_accounts = pyth_solana_receiver::accounts::PostUpdate::populate( + payer.pubkey(), payer.pubkey(), encoded_vaa_keypair.pubkey(), price_update_keypair.pubkey(), 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 e3d20d52..5b498e0c 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs @@ -191,6 +191,7 @@ pub mod pyth_solana_receiver { // End borrowed section let payer = &ctx.accounts.payer; + let write_authority: &Signer<'_> = &ctx.accounts.write_authority; let treasury = &ctx.accounts.treasury; let price_update_account = &mut ctx.accounts.price_update_account; @@ -203,6 +204,7 @@ pub mod pyth_solana_receiver { post_price_update_from_vaa( config, payer, + write_authority, treasury, price_update_account, &vaa_components, @@ -220,6 +222,7 @@ pub mod pyth_solana_receiver { pub fn post_update(ctx: Context, params: PostUpdateParams) -> Result<()> { let config = &ctx.accounts.config; let payer: &Signer<'_> = &ctx.accounts.payer; + let write_authority: &Signer<'_> = &ctx.accounts.write_authority; let encoded_vaa = VaaAccount::load(&ctx.accounts.encoded_vaa)?; // IMPORTANT: This line checks that the encoded_vaa has ProcessingStatus::Verified. This check is critical otherwise the program could be tricked into accepting unverified VAAs. let treasury: &AccountInfo<'_> = &ctx.accounts.treasury; let price_update_account: &mut Account<'_, PriceUpdateV1> = @@ -234,6 +237,7 @@ pub mod pyth_solana_receiver { post_price_update_from_vaa( config, payer, + write_authority, treasury, price_update_account, &vaa_components, @@ -297,11 +301,12 @@ pub struct PostUpdate<'info> { /// CHECK: This is just a PDA controlled by the program. There is currently no way to withdraw funds from it. #[account(mut, seeds = [TREASURY_SEED.as_ref(), &[params.treasury_id]], bump)] pub treasury: AccountInfo<'info>, - /// The contraint is such that either the price_update_account is uninitialized or the payer is the write_authority. + /// The constraint is such that either the price_update_account is uninitialized or the write_authority 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() @ ReceiverError::WrongWriteAuthority , payer =payer, space = PriceUpdateV1::LEN)] + #[account(init_if_needed, constraint = price_update_account.write_authority == Pubkey::default() || price_update_account.write_authority == write_authority.key() @ ReceiverError::WrongWriteAuthority , payer =payer, space = PriceUpdateV1::LEN)] pub price_update_account: Account<'info, PriceUpdateV1>, pub system_program: Program<'info, System>, + pub write_authority: Signer<'info>, } #[derive(Accounts)] @@ -319,11 +324,12 @@ pub struct PostUpdateAtomic<'info> { #[account(mut, seeds = [TREASURY_SEED.as_ref(), &[params.treasury_id]], bump)] /// CHECK: This is just a PDA controlled by the program. There is currently no way to withdraw funds from it. pub treasury: AccountInfo<'info>, - /// The contraint is such that either the price_update_account is uninitialized or the payer is the write_authority. + /// The constraint is such that either the price_update_account is uninitialized or the write_authority 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() @ ReceiverError::WrongWriteAuthority, payer = payer, space = PriceUpdateV1::LEN)] + #[account(init_if_needed, constraint = price_update_account.write_authority == Pubkey::default() || price_update_account.write_authority == write_authority.key() @ ReceiverError::WrongWriteAuthority, payer = payer, space = PriceUpdateV1::LEN)] pub price_update_account: Account<'info, PriceUpdateV1>, pub system_program: Program<'info, System>, + pub write_authority: Signer<'info>, } #[derive(Accounts)] @@ -388,6 +394,7 @@ struct VaaComponents { fn post_price_update_from_vaa<'info>( config: &Account<'info, Config>, payer: &Signer<'info>, + write_authority: &Signer<'info>, treasury: &AccountInfo<'info>, price_update_account: &mut Account<'_, PriceUpdateV1>, vaa_components: &VaaComponents, @@ -440,7 +447,7 @@ fn post_price_update_from_vaa<'info>( match message { Message::PriceFeedMessage(price_feed_message) => { - price_update_account.write_authority = payer.key(); + price_update_account.write_authority = write_authority.key(); price_update_account.verification_level = vaa_components.verification_level; price_update_account.price_message = price_feed_message; } 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 3a2ca1ed..37a5e4fd 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs @@ -43,6 +43,7 @@ impl accounts::Initialize { impl accounts::PostUpdateAtomic { pub fn populate( payer: Pubkey, + write_authority: Pubkey, price_update_account: Pubkey, wormhole_address: Pubkey, guardian_set_index: u32, @@ -60,12 +61,18 @@ impl accounts::PostUpdateAtomic { treasury, price_update_account, system_program: system_program::ID, + write_authority, } } } impl accounts::PostUpdate { - pub fn populate(payer: Pubkey, encoded_vaa: Pubkey, price_update_account: Pubkey) -> Self { + pub fn populate( + payer: Pubkey, + write_authority: Pubkey, + encoded_vaa: Pubkey, + price_update_account: Pubkey, + ) -> Self { let config = get_config_address(); let treasury = get_treasury_address(DEFAULT_TREASURY_ID); accounts::PostUpdate { @@ -75,6 +82,7 @@ impl accounts::PostUpdate { treasury, price_update_account, system_program: system_program::ID, + write_authority, } } } @@ -116,13 +124,18 @@ impl instruction::Initialize { impl instruction::PostUpdate { pub fn populate( payer: Pubkey, + write_authority: Pubkey, encoded_vaa: Pubkey, price_update_account: Pubkey, merkle_price_update: MerklePriceUpdate, ) -> Instruction { - let post_update_accounts = - accounts::PostUpdate::populate(payer, encoded_vaa, price_update_account) - .to_account_metas(None); + let post_update_accounts = accounts::PostUpdate::populate( + payer, + write_authority, + encoded_vaa, + price_update_account, + ) + .to_account_metas(None); Instruction { program_id: ID, accounts: post_update_accounts, @@ -141,6 +154,7 @@ impl instruction::PostUpdate { impl instruction::PostUpdateAtomic { pub fn populate( payer: Pubkey, + write_authority: Pubkey, price_update_account: Pubkey, wormhole_address: Pubkey, guardian_set_index: u32, @@ -150,6 +164,7 @@ impl instruction::PostUpdateAtomic { ) -> Instruction { let post_update_accounts = accounts::PostUpdateAtomic::populate( payer, + write_authority, price_update_account, wormhole_address, guardian_set_index, 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 index 462e3ef3..2b6c827a 100644 --- 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 @@ -81,6 +81,7 @@ async fn test_invalid_wormhole_message() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -126,6 +127,7 @@ async fn test_invalid_update_message() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -177,6 +179,9 @@ async fn test_post_price_update_from_vaa() { assert_treasury_balance(&mut program_simulator, 0, DEFAULT_TREASURY_ID).await; let poster = program_simulator.get_funded_keypair().await.unwrap(); + // poster_2 can't write to this price update account + let poster_2 = program_simulator.get_funded_keypair().await.unwrap(); + let price_update_keypair = Keypair::new(); // this update is not in the proof @@ -184,6 +189,7 @@ async fn test_post_price_update_from_vaa() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -206,6 +212,7 @@ async fn test_post_price_update_from_vaa() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -245,6 +252,7 @@ async fn test_post_price_update_from_vaa() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -283,6 +291,7 @@ async fn test_post_price_update_from_vaa() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -320,6 +329,7 @@ async fn test_post_price_update_from_vaa() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -356,6 +366,47 @@ async fn test_post_price_update_from_vaa() { feed_1 ); + // Now poster_2 will pay + program_simulator + .process_ix_with_default_compute_limit( + PostUpdateAtomic::populate( + poster_2.pubkey(), + poster.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + vaa.clone(), + merkle_price_updates[0].clone(), + DEFAULT_TREASURY_ID, + ), + &vec![&poster, &poster_2, &price_update_keypair], + None, + ) + .await + .unwrap(); + + assert_treasury_balance( + &mut program_simulator, + Rent::default().minimum_balance(0) + 1, + DEFAULT_TREASURY_ID, + ) + .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::Full + ); + assert_eq!( + Message::PriceFeedMessage(price_update_account.price_message), + feed_1 + ); + // Now change the fee! program_simulator @@ -378,6 +429,7 @@ async fn test_post_price_update_from_vaa() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -397,7 +449,7 @@ async fn test_post_price_update_from_vaa() { assert_treasury_balance( &mut program_simulator, - Rent::default().minimum_balance(0), + Rent::default().minimum_balance(0) + 1, DEFAULT_TREASURY_ID, ) .await; @@ -435,6 +487,7 @@ async fn test_post_price_update_from_vaa() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -451,7 +504,7 @@ async fn test_post_price_update_from_vaa() { assert_treasury_balance( &mut program_simulator, - Rent::default().minimum_balance(0) + LAMPORTS_PER_SOL, + Rent::default().minimum_balance(0) + 1 + LAMPORTS_PER_SOL, DEFAULT_TREASURY_ID, ) .await; @@ -470,14 +523,11 @@ async fn test_post_price_update_from_vaa() { 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_with_default_compute_limit( PostUpdateAtomic::populate( + poster_2.pubkey(), poster_2.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -494,4 +544,27 @@ async fn test_post_price_update_from_vaa() { .unwrap(), into_transaction_error(ReceiverError::WrongWriteAuthority) ); + + // poster_2 can't write to this price update account not even if poster pays + assert_eq!( + program_simulator + .process_ix_with_default_compute_limit( + PostUpdateAtomic::populate( + poster.pubkey(), + poster_2.pubkey(), + price_update_keypair.pubkey(), + BRIDGE_ID, + DEFAULT_GUARDIAN_SET_INDEX, + vaa.clone(), + merkle_price_updates[0].clone(), + DEFAULT_TREASURY_ID + ), + &vec![&poster, &poster_2, &price_update_keypair], + None, + ) + .await + .unwrap_err() + .unwrap(), + into_transaction_error(ReceiverError::WrongWriteAuthority) + ); } 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 04186c43..8b095e3f 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 @@ -68,6 +68,7 @@ async fn test_post_update() { program_simulator .process_ix_with_default_compute_limit( PostUpdate::populate( + poster.pubkey(), poster.pubkey(), encoded_vaa_addresses[0], price_update_keypair.pubkey(), @@ -105,6 +106,7 @@ async fn test_post_update() { program_simulator .process_ix_with_default_compute_limit( PostUpdate::populate( + poster.pubkey(), poster.pubkey(), encoded_vaa_addresses[0], price_update_keypair.pubkey(), @@ -195,6 +197,7 @@ async fn test_post_update_wrong_encoded_vaa_owner() { program_simulator .process_ix_with_default_compute_limit( PostUpdate::populate( + poster.pubkey(), poster.pubkey(), Pubkey::new_unique(), // Random pubkey instead of the encoded VAA address price_update_keypair.pubkey(), @@ -234,6 +237,7 @@ async fn test_post_update_wrong_setup() { program_simulator .process_ix_with_default_compute_limit( PostUpdate::populate( + poster.pubkey(), poster.pubkey(), encoded_vaa_addresses[0], price_update_keypair.pubkey(), 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 39122d23..a7ad8893 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 @@ -71,6 +71,7 @@ async fn test_post_update_atomic() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -111,6 +112,7 @@ async fn test_post_update_atomic() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -152,6 +154,7 @@ async fn test_post_update_atomic() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -217,6 +220,7 @@ async fn test_post_update_atomic_wrong_vaa() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -243,6 +247,7 @@ async fn test_post_update_atomic_wrong_vaa() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -268,6 +273,7 @@ async fn test_post_update_atomic_wrong_vaa() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -292,6 +298,7 @@ async fn test_post_update_atomic_wrong_vaa() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -316,6 +323,7 @@ async fn test_post_update_atomic_wrong_vaa() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -341,6 +349,7 @@ async fn test_post_update_atomic_wrong_vaa() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -365,6 +374,7 @@ async fn test_post_update_atomic_wrong_vaa() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -390,6 +400,7 @@ async fn test_post_update_atomic_wrong_vaa() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -408,6 +419,7 @@ async fn test_post_update_atomic_wrong_vaa() { ); let mut wrong_instruction = PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -452,6 +464,7 @@ async fn test_post_update_atomic_wrong_setup() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID, @@ -480,6 +493,7 @@ async fn test_post_update_atomic_wrong_setup() { program_simulator .process_ix_with_default_compute_limit( PostUpdateAtomic::populate( + poster.pubkey(), poster.pubkey(), price_update_keypair.pubkey(), BRIDGE_ID,