diff --git a/CHANGELOG.md b/CHANGELOG.md index c6e74f47d2..4966e818de 100644 --- a/CHANGELOG.md +++ b/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` 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 diff --git a/lang/src/program.rs b/lang/src/program.rs index 73a02196c0..7a6e1f1209 100644 --- a/lang/src/program.rs +++ b/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, } 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, + ) -> 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 { + self.programdata_address } } diff --git a/tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/src/lib.rs b/tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/src/lib.rs index d7405cef6a..6017db4ad3 100644 --- a/tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/src/lib.rs +++ b/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, + 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>, +} diff --git a/tests/bpf-upgradeable-state/tests/bpf-upgradable-state.ts b/tests/bpf-upgradeable-state/tests/bpf-upgradable-state.ts index c2a1efb483..21c2cbaf81 100644 --- a/tests/bpf-upgradeable-state/tests/bpf-upgradable-state.ts +++ b/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); + } + }); });