Browse Source

lang: programdata_address field for Program account (#1125)

Paul 3 years ago
parent
commit
911620ee27

+ 2 - 1
CHANGELOG.md

@@ -13,7 +13,8 @@ incremented for features.
 
 ### Features
 
-* lang,ts,ci,cli,docs: update solana toolchain([#1133](https://github.com/project-serum/anchor/pull/1133))
+* lang: Add `programdata_address: Option<Pubkey>` field to `Program` account. Will be populated if account is a program owned by the upgradable bpf loader ([#1125](https://github.com/project-serum/anchor/pull/1125))
+* lang,ts,ci,cli,docs: update solana toolchain to version 1.8.5([#1133](https://github.com/project-serum/anchor/pull/1133))
 
 ## [0.19.0] - 2021-12-08
 

+ 49 - 3
lang/src/program.rs

@@ -1,6 +1,7 @@
 use crate::error::ErrorCode;
 use crate::*;
 use solana_program::account_info::AccountInfo;
+use solana_program::bpf_loader_upgradeable::{self, UpgradeableLoaderState};
 use solana_program::instruction::AccountMeta;
 use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
@@ -12,6 +13,7 @@ use std::ops::Deref;
 pub struct Program<'info, T: Id + AccountDeserialize + Clone> {
     _account: T,
     info: AccountInfo<'info>,
+    programdata_address: Option<Pubkey>,
 }
 
 impl<'info, T: Id + AccountDeserialize + Clone + fmt::Debug> fmt::Debug for Program<'info, T> {
@@ -19,13 +21,22 @@ impl<'info, T: Id + AccountDeserialize + Clone + fmt::Debug> fmt::Debug for Prog
         f.debug_struct("Program")
             .field("account", &self._account)
             .field("info", &self.info)
+            .field("programdata_address", &self.programdata_address)
             .finish()
     }
 }
 
 impl<'a, T: Id + AccountDeserialize + Clone> Program<'a, T> {
-    fn new(info: AccountInfo<'a>, _account: T) -> Program<'a, T> {
-        Self { info, _account }
+    fn new(
+        info: AccountInfo<'a>,
+        _account: T,
+        programdata_address: Option<Pubkey>,
+    ) -> Program<'a, T> {
+        Self {
+            info,
+            _account,
+            programdata_address,
+        }
     }
 
     /// Deserializes the given `info` into a `Program`.
@@ -37,9 +48,44 @@ impl<'a, T: Id + AccountDeserialize + Clone> Program<'a, T> {
         if !info.executable {
             return Err(ErrorCode::InvalidProgramExecutable.into());
         }
+        let programdata_address = if *info.owner == bpf_loader_upgradeable::ID {
+            let mut data: &[u8] = &info.try_borrow_data()?;
+            let upgradable_loader_state =
+                UpgradeableLoaderState::try_deserialize_unchecked(&mut data)?;
+
+            match upgradable_loader_state {
+                UpgradeableLoaderState::Uninitialized
+                | UpgradeableLoaderState::Buffer {
+                    authority_address: _,
+                }
+                | UpgradeableLoaderState::ProgramData {
+                    slot: _,
+                    upgrade_authority_address: _,
+                } => {
+                    // Unreachable because check above already
+                    // ensures that program is executable
+                    // and therefore a program account.
+                    unreachable!()
+                }
+                UpgradeableLoaderState::Program {
+                    programdata_address,
+                } => Some(programdata_address),
+            }
+        } else {
+            None
+        };
+
         // Programs have no data so use an empty slice.
         let mut empty = &[][..];
-        Ok(Program::new(info.clone(), T::try_deserialize(&mut empty)?))
+        Ok(Program::new(
+            info.clone(),
+            T::try_deserialize(&mut empty)?,
+            programdata_address,
+        ))
+    }
+
+    pub fn programdata_address(&self) -> Option<Pubkey> {
+        self.programdata_address
     }
 }
 

+ 23 - 0
tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/src/lib.rs

@@ -24,6 +24,14 @@ pub mod bpf_upgradeable_state {
         ctx.accounts.settings.admin_data = admin_data;
         Ok(())
     }
+
+    pub fn set_admin_settings_use_program_state(
+        ctx: Context<SetAdminSettingsUseProgramState>,
+        admin_data: u64,
+    ) -> ProgramResult {
+        ctx.accounts.settings.admin_data = admin_data;
+        Ok(())
+    }
 }
 
 #[account]
@@ -36,6 +44,7 @@ pub struct Settings {
 pub enum CustomError {
     InvalidProgramDataAddress,
     AccountNotProgram,
+    AccountNotBpfUpgradableProgram,
 }
 
 #[derive(Accounts)]
@@ -51,3 +60,17 @@ pub struct SetAdminSettings<'info> {
     pub program_data: Account<'info, ProgramData>,
     pub system_program: Program<'info, System>,
 }
+
+#[derive(Accounts)]
+#[instruction(admin_data: u64)]
+pub struct SetAdminSettingsUseProgramState<'info> {
+    #[account(init, payer = authority)]
+    pub settings: Account<'info, Settings>,
+    #[account(mut)]
+    pub authority: Signer<'info>,
+    #[account(constraint = program.programdata_address() == Some(program_data.key()))]
+    pub program: Program<'info, crate::program::BpfUpgradeableState>,
+    #[account(constraint = program_data.upgrade_authority_address == Some(authority.key()))]
+    pub program_data: Account<'info, ProgramData>,
+    pub system_program: Program<'info, System>,
+}

+ 39 - 1
tests/bpf-upgradeable-state/tests/bpf-upgradable-state.ts

@@ -29,8 +29,21 @@ describe('bpf_upgradeable_state', () => {
       signers: [settings]
     });
     assert.equal((await program.account.settings.fetch(settings.publicKey)).adminData, 500);
+  });
 
-    console.log("Your transaction signature", tx);
+  it('Reads ProgramData and sets field, uses program state', async () => {
+    const settings = anchor.web3.Keypair.generate();
+    const tx = await program.rpc.setAdminSettingsUseProgramState(new anchor.BN(500), {
+      accounts: {
+        authority: program.provider.wallet.publicKey,
+        systemProgram: anchor.web3.SystemProgram.programId,
+        programData: programDataAddress,
+        program: program.programId,
+        settings: settings.publicKey
+      },
+      signers: [settings]
+    });
+    assert.equal((await program.account.settings.fetch(settings.publicKey)).adminData, 500);
   });
 
   it('Validates constraint on ProgramData', async () => {
@@ -122,4 +135,29 @@ describe('bpf_upgradeable_state', () => {
       assert.equal(err.code, 6000);
     }
   });
+
+  it('Deserializes Program and validates that programData is the expected account', async () => {
+    const secondProgramAddress = new PublicKey("Fkv67TwmbakfZw2PoW57wYPbqNexAH6vuxpyT8vmrc3B");
+    const secondProgramProgramDataAddress = findProgramAddressSync(
+      [secondProgramAddress.toBytes()],
+      new anchor.web3.PublicKey("BPFLoaderUpgradeab1e11111111111111111111111")
+    )[0];
+
+    const settings = anchor.web3.Keypair.generate();
+    try {
+      await program.rpc.setAdminSettingsUseProgramState(new anchor.BN(500), {
+        accounts: {
+          authority: program.provider.wallet.publicKey,
+          systemProgram: anchor.web3.SystemProgram.programId,
+          programData: secondProgramProgramDataAddress,
+          settings: settings.publicKey,
+          program: program.programId,
+        },
+        signers: [settings]
+      });
+      assert.ok(false);
+    } catch (err) {
+      assert.equal(err.code, 2003);
+    }
+  });
 });