Bläddra i källkod

accumulator-updater 8/x] - whitelist verification pda auth (#775)

* feat(accumulator-updater): change whitelist verification to use a signed PDA

* feat(accumulator-updater): address PR feedback

remove "auth" as seed
swimricky 2 år sedan
förälder
incheckning
5a90ad96a5

+ 8 - 5
accumulator_updater/programs/accumulator_updater/src/instructions/put_all.rs

@@ -22,7 +22,7 @@ pub fn put_all<'info>(
     base_account_key: Pubkey,
     messages: Vec<Vec<u8>>,
 ) -> Result<()> {
-    let cpi_caller = ctx.accounts.whitelist_verifier.is_allowed()?;
+    let cpi_caller_auth = ctx.accounts.whitelist_verifier.is_allowed()?;
     let accumulator_input_ai = ctx
         .remaining_accounts
         .first()
@@ -34,7 +34,7 @@ pub fn put_all<'info>(
         let accumulator_input = &mut (if is_uninitialized_account(accumulator_input_ai) {
             let (pda, bump) = Pubkey::find_program_address(
                 &[
-                    cpi_caller.as_ref(),
+                    cpi_caller_auth.as_ref(),
                     ACCUMULATOR.as_bytes(),
                     base_account_key.as_ref(),
                 ],
@@ -42,7 +42,7 @@ pub fn put_all<'info>(
             );
             require_keys_eq!(accumulator_input_ai.key(), pda);
             let signer_seeds = [
-                cpi_caller.as_ref(),
+                cpi_caller_auth.as_ref(),
                 ACCUMULATOR.as_bytes(),
                 base_account_key.as_ref(),
                 &[bump],
@@ -56,7 +56,6 @@ pub fn put_all<'info>(
                 accumulator_input_ai,
                 8 + AccumulatorInput::INIT_SPACE,
                 &ctx.accounts.fund,
-                // seeds,
                 &[signer_seeds.as_slice(), fund_signer_seeds.as_slice()],
                 &ctx.accounts.system_program,
             )?;
@@ -75,7 +74,11 @@ pub fn put_all<'info>(
         });
         // note: redundant for uninitialized code path but safer to check here.
         // compute budget cost should be minimal
-        accumulator_input.validate(accumulator_input_ai.key(), cpi_caller, base_account_key)?;
+        accumulator_input.validate(
+            accumulator_input_ai.key(),
+            cpi_caller_auth,
+            base_account_key,
+        )?;
 
 
         let (num_msgs, num_bytes) = accumulator_input.put_all(&messages);

+ 0 - 4
accumulator_updater/programs/accumulator_updater/src/state/accumulator_input.rs

@@ -66,10 +66,6 @@ impl AccumulatorHeader {
     }
 }
 impl AccumulatorInput {
-    pub fn size(&self) -> usize {
-        AccumulatorHeader::INIT_SPACE + 4 + self.messages.len()
-    }
-
     pub fn new(bump: u8) -> Self {
         let header = AccumulatorHeader::new(bump);
         Self {

+ 6 - 17
accumulator_updater/programs/accumulator_updater/src/state/whitelist.rs

@@ -1,12 +1,6 @@
 use {
     crate::AccumulatorUpdaterError,
-    anchor_lang::{
-        prelude::*,
-        solana_program::sysvar::{
-            self,
-            instructions::get_instruction_relative,
-        },
-    },
+    anchor_lang::prelude::*,
 };
 
 // Note: purposely not making this zero_copy
@@ -49,23 +43,18 @@ pub struct WhitelistVerifier<'info> {
     )]
     // Using a Box to move account from stack to heap
     pub whitelist: Box<Account<'info, Whitelist>>,
-    /// CHECK: Instruction introspection sysvar
-    #[account(address = sysvar::instructions::ID)]
-    pub ixs_sysvar: UncheckedAccount<'info>,
+    /// PDA representing authorized cpi caller
+    pub cpi_caller_auth: Signer<'info>,
 }
 
 impl<'info> WhitelistVerifier<'info> {
-    pub fn get_cpi_caller(&self) -> Result<Pubkey> {
-        let instruction = get_instruction_relative(0, &self.ixs_sysvar.to_account_info())?;
-        Ok(instruction.program_id)
-    }
     pub fn is_allowed(&self) -> Result<Pubkey> {
-        let cpi_caller = self.get_cpi_caller()?;
+        let auth = self.cpi_caller_auth.key();
         let whitelist = &self.whitelist;
         require!(
-            whitelist.allowed_programs.contains(&cpi_caller),
+            whitelist.allowed_programs.contains(&auth),
             AccumulatorUpdaterError::CallerNotAllowed
         );
-        Ok(cpi_caller)
+        Ok(auth)
     }
 }

+ 29 - 6
accumulator_updater/programs/mock-cpi-caller/src/instructions/add_price.rs

@@ -3,6 +3,7 @@ use {
         instructions::{
             sighash,
             ACCUMULATOR_UPDATER_IX_NAME,
+            CPI,
         },
         message::{
             get_schemas,
@@ -20,7 +21,7 @@ use {
     accumulator_updater::program::AccumulatorUpdater as AccumulatorUpdaterProgram,
     anchor_lang::{
         prelude::*,
-        solana_program::sysvar,
+        system_program,
     },
 };
 
@@ -63,7 +64,7 @@ impl<'info> AddPrice<'info> {
         let mut accounts = vec![
             AccountMeta::new(ctx.accounts.fund.key(), false),
             AccountMeta::new_readonly(ctx.accounts.accumulator_whitelist.key(), false),
-            AccountMeta::new_readonly(ctx.accounts.ixs_sysvar.key(), false),
+            AccountMeta::new_readonly(ctx.accounts.auth.key(), true),
             AccountMeta::new_readonly(ctx.accounts.system_program.key(), false),
         ];
         accounts.extend_from_slice(
@@ -86,7 +87,24 @@ impl<'info> AddPrice<'info> {
         };
         let account_infos = &mut ctx.accounts.to_account_infos();
         account_infos.extend_from_slice(ctx.remaining_accounts);
-        anchor_lang::solana_program::program::invoke(&create_inputs_ix, account_infos)?;
+        // using find_program_address here instead of ctx.bumps.get since
+        // that won't be available in the oracle program
+        let (_, bump) = Pubkey::find_program_address(
+            &[
+                ctx.accounts.accumulator_program.key().as_ref(),
+                CPI.as_bytes(),
+            ],
+            &crate::ID,
+        );
+        anchor_lang::solana_program::program::invoke_signed(
+            &create_inputs_ix,
+            account_infos,
+            &[&[
+                ctx.accounts.accumulator_program.key().as_ref(),
+                CPI.as_bytes(),
+                &[bump],
+            ]],
+        )?;
         Ok(())
     }
 }
@@ -119,9 +137,14 @@ pub struct AddPrice<'info> {
     pub system_program:        Program<'info, System>,
     /// CHECK: whitelist
     pub accumulator_whitelist: UncheckedAccount<'info>,
-    /// CHECK: instructions introspection sysvar
-    #[account(address = sysvar::instructions::ID)]
-    pub ixs_sysvar:            UncheckedAccount<'info>,
+    /// PDA representing this program's authority
+    /// to call the accumulator program
+    #[account(
+        seeds = [accumulator_program.key().as_ref(), b"cpi".as_ref()],
+        owner = system_program::System::id(),
+        bump,
+    )]
+    pub auth:                  SystemAccount<'info>,
     pub accumulator_program:   Program<'info, AccumulatorUpdaterProgram>,
     // Remaining Accounts
     // should all be new uninitialized accounts

+ 1 - 0
accumulator_updater/programs/mock-cpi-caller/src/instructions/mod.rs

@@ -24,3 +24,4 @@ pub fn sighash(namespace: &str, name: &str) -> [u8; 8] {
 }
 
 pub const ACCUMULATOR_UPDATER_IX_NAME: &str = "put_all";
+pub const CPI: &str = "cpi";

+ 27 - 6
accumulator_updater/programs/mock-cpi-caller/src/instructions/update_price.rs

@@ -3,6 +3,7 @@ use {
         instructions::{
             sighash,
             ACCUMULATOR_UPDATER_IX_NAME,
+            CPI,
         },
         message::{
             price::{
@@ -16,7 +17,7 @@ use {
     accumulator_updater::program::AccumulatorUpdater as AccumulatorUpdaterProgram,
     anchor_lang::{
         prelude::*,
-        solana_program::sysvar,
+        system_program,
     },
 };
 
@@ -47,9 +48,12 @@ pub struct UpdatePrice<'info> {
     pub system_program:        Program<'info, System>,
     /// CHECK: whitelist
     pub accumulator_whitelist: UncheckedAccount<'info>,
-    /// CHECK: instructions introspection sysvar
-    #[account(address = sysvar::instructions::ID)]
-    pub ixs_sysvar:            UncheckedAccount<'info>,
+    #[account(
+        seeds = [accumulator_program.key().as_ref(), b"cpi".as_ref()],
+        owner = system_program::System::id(),
+        bump,
+    )]
+    pub auth:                  SystemAccount<'info>,
     pub accumulator_program:   Program<'info, AccumulatorUpdaterProgram>,
 }
 
@@ -88,7 +92,7 @@ impl<'info> UpdatePrice<'info> {
         let mut accounts = vec![
             AccountMeta::new(ctx.accounts.fund.key(), false),
             AccountMeta::new_readonly(ctx.accounts.accumulator_whitelist.key(), false),
-            AccountMeta::new_readonly(ctx.accounts.ixs_sysvar.key(), false),
+            AccountMeta::new_readonly(ctx.accounts.auth.key(), true),
             AccountMeta::new_readonly(ctx.accounts.system_program.key(), false),
         ];
         accounts.extend_from_slice(
@@ -111,7 +115,24 @@ impl<'info> UpdatePrice<'info> {
         };
         let account_infos = &mut ctx.accounts.to_account_infos();
         account_infos.extend_from_slice(ctx.remaining_accounts);
-        anchor_lang::solana_program::program::invoke(&update_inputs_ix, account_infos)?;
+        // using find_program_address here instead of ctx.bumps.get since
+        // that won't be available in the oracle program
+        let (_, bump) = Pubkey::find_program_address(
+            &[
+                ctx.accounts.accumulator_program.key().as_ref(),
+                CPI.as_bytes(),
+            ],
+            &crate::ID,
+        );
+        anchor_lang::solana_program::program::invoke_signed(
+            &update_inputs_ix,
+            account_infos,
+            &[&[
+                ctx.accounts.accumulator_program.key().as_ref(),
+                CPI.as_bytes(),
+                &[bump],
+            ]],
+        )?;
         Ok(())
     }
 }

+ 34 - 24
accumulator_updater/tests/accumulator_updater.ts

@@ -15,6 +15,10 @@ const accumulatorUpdaterProgram = anchor.workspace
   .AccumulatorUpdater as Program<AccumulatorUpdater>;
 const mockCpiProg = anchor.workspace.MockCpiCaller as Program<MockCpiCaller>;
 let whitelistAuthority = anchor.web3.Keypair.generate();
+const [mockCpiCallerAuth] = anchor.web3.PublicKey.findProgramAddressSync(
+  [accumulatorUpdaterProgram.programId.toBuffer(), Buffer.from("cpi")],
+  mockCpiProg.programId
+);
 const [fundPda] = anchor.web3.PublicKey.findProgramAddressSync(
   [Buffer.from("fund")],
   accumulatorUpdaterProgram.programId
@@ -37,6 +41,15 @@ const [pythPriceAccountPk] = anchor.web3.PublicKey.findProgramAddressSync(
   mockCpiProg.programId
 );
 
+const [accumulatorPdaKey] = anchor.web3.PublicKey.findProgramAddressSync(
+  [
+    mockCpiCallerAuth.toBuffer(),
+    Buffer.from("accumulator"),
+    pythPriceAccountPk.toBuffer(),
+  ],
+  accumulatorUpdaterProgram.programId
+);
+
 let fundBalance = 100 * anchor.web3.LAMPORTS_PER_SOL;
 describe("accumulator_updater", () => {
   // Configure the client to use the local cluster.
@@ -70,9 +83,9 @@ describe("accumulator_updater", () => {
   });
 
   it("Sets allowed programs to the whitelist", async () => {
-    const allowedPrograms = [mockCpiProg.programId];
+    const allowedProgramAuthorities = [mockCpiCallerAuth];
     await accumulatorUpdaterProgram.methods
-      .setAllowedPrograms(allowedPrograms)
+      .setAllowedPrograms(allowedProgramAuthorities)
       .accounts({
         authority: whitelistAuthority.publicKey,
       })
@@ -87,7 +100,7 @@ describe("accumulator_updater", () => {
     );
     assert.deepEqual(
       whitelistAllowedPrograms,
-      allowedPrograms.map((p) => p.toString())
+      allowedProgramAuthorities.map((p) => p.toString())
     );
   });
 
@@ -115,21 +128,12 @@ describe("accumulator_updater", () => {
       .accounts({
         fund: fundPda,
         systemProgram: anchor.web3.SystemProgram.programId,
-        ixsSysvar: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY,
+        auth: mockCpiCallerAuth,
         accumulatorWhitelist: whitelistPubkey,
         accumulatorProgram: accumulatorUpdaterProgram.programId,
       })
       .pubkeys();
 
-    const accumulatorPdaKey = anchor.web3.PublicKey.findProgramAddressSync(
-      [
-        mockCpiProg.programId.toBuffer(),
-        Buffer.from("accumulator"),
-        pythPriceAccountPk.toBuffer(),
-      ],
-      accumulatorUpdaterProgram.programId
-    )[0];
-
     const accumulatorPdaMetas = [
       {
         pubkey: accumulatorPdaKey,
@@ -182,10 +186,6 @@ describe("accumulator_updater", () => {
     const pythPriceAccount = await provider.connection.getAccountInfo(
       mockCpiCallerAddPriceTxPubkeys.pythPriceAccount
     );
-    const pythPriceAcct = {
-      ...pythPriceAccount,
-      data: pythPriceAccount.data.toString("hex"),
-    };
 
     const accumulatorInput =
       await accumulatorUpdaterProgram.account.accumulatorInput.fetch(
@@ -242,13 +242,16 @@ describe("accumulator_updater", () => {
       emaExpo: new anchor.BN(8),
     };
 
-    let accumulatorPdaMeta = getAccumulatorPdaMeta(pythPriceAccountPk);
+    let accumulatorPdaMeta = getAccumulatorPdaMeta(
+      mockCpiCallerAuth,
+      pythPriceAccountPk
+    );
     await mockCpiProg.methods
       .updatePrice(updatePriceParams)
       .accounts({
         fund: fundPda,
         pythPriceAccount: pythPriceAccountPk,
-        ixsSysvar: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY,
+        auth: mockCpiCallerAuth,
         accumulatorWhitelist: whitelistPubkey,
         accumulatorProgram: accumulatorUpdaterProgram.programId,
       })
@@ -302,13 +305,16 @@ describe("accumulator_updater", () => {
         emaExpo: new anchor.BN(10 * i + 8),
       };
 
-      let accumulatorPdaMeta = getAccumulatorPdaMeta(pythPriceAccountPk);
+      let accumulatorPdaMeta = getAccumulatorPdaMeta(
+        mockCpiCallerAuth,
+        pythPriceAccountPk
+      );
       await mockCpiProg.methods
         .cpiMaxTest(updatePriceParams, testCase)
         .accounts({
           fund: fundPda,
           pythPriceAccount: pythPriceAccountPk,
-          ixsSysvar: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY,
+          auth: mockCpiCallerAuth,
           accumulatorWhitelist: whitelistPubkey,
           accumulatorProgram: accumulatorUpdaterProgram.programId,
         })
@@ -363,7 +369,10 @@ describe("accumulator_updater", () => {
         emaExpo: new anchor.BN(10 * i + 8),
       };
 
-      let accumulatorPdaMeta = getAccumulatorPdaMeta(pythPriceAccountPk);
+      let accumulatorPdaMeta = getAccumulatorPdaMeta(
+        mockCpiCallerAuth,
+        pythPriceAccountPk
+      );
       let errorThrown = false;
       try {
         await mockCpiProg.methods
@@ -371,7 +380,7 @@ describe("accumulator_updater", () => {
           .accounts({
             fund: fundPda,
             pythPriceAccount: pythPriceAccountPk,
-            ixsSysvar: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY,
+            auth: mockCpiCallerAuth,
             accumulatorWhitelist: whitelistPubkey,
             accumulatorProgram: accumulatorUpdaterProgram.programId,
           })
@@ -391,11 +400,12 @@ describe("accumulator_updater", () => {
 });
 
 export const getAccumulatorPdaMeta = (
+  cpiCallerAuth: anchor.web3.PublicKey,
   pythAccount: anchor.web3.PublicKey
 ): AccountMeta => {
   const accumulatorPdaKey = anchor.web3.PublicKey.findProgramAddressSync(
     [
-      mockCpiProg.programId.toBuffer(),
+      cpiCallerAuth.toBuffer(),
       Buffer.from("accumulator"),
       pythAccount.toBuffer(),
     ],