Forráskód Böngészése

feat: separate write-authority from payer (#1373)

* feat: separate write-authority from payer

* test: update tests

* fix: typo
guibescos 1 éve
szülő
commit
f79f205895

+ 2 - 0
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(),

+ 12 - 5
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<PostUpdate>, 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;
         }

+ 19 - 4
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,

+ 79 - 6
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::<PriceUpdateV1>(price_update_keypair.pubkey())
+        .await
+        .unwrap();
+
+    assert_eq!(price_update_account.write_authority, poster.pubkey());
+    assert_eq!(
+        price_update_account.verification_level,
+        VerificationLevel::Full
+    );
+    assert_eq!(
+        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)
+    );
 }

+ 4 - 0
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(),

+ 14 - 0
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,