From e660a2c9e24ce26c64cd88db94b548fc2e137380 Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Sat, 11 Dec 2021 00:58:51 +0100 Subject: [PATCH 1/8] lang: add programdata_address field to Program account --- lang/src/program.rs | 54 +++++++++++++++++-- .../programs/bpf-upgradeable-state/src/lib.rs | 23 ++++++++ .../tests/bpf-upgradable-state.ts | 40 +++++++++++++- 3 files changed, 113 insertions(+), 4 deletions(-) diff --git a/lang/src/program.rs b/lang/src/program.rs index 73a02196c0..0f2bc34ab1 100644 --- a/lang/src/program.rs +++ b/lang/src/program.rs @@ -1,17 +1,21 @@ 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; use std::fmt; use std::ops::Deref; +// TODO: can we remove _account? + /// Account container that checks ownership on deserialization. #[derive(Clone)] 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 +23,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 +50,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 => { + return Err(ErrorCode::InvalidProgramExecutable.into()); + } + UpgradeableLoaderState::Buffer { + authority_address: _, + } => { + return Err(ErrorCode::InvalidProgramExecutable.into()); + } + UpgradeableLoaderState::Program { + programdata_address, + } => Some(programdata_address), + UpgradeableLoaderState::ProgramData { + slot: _, + upgrade_authority_address: _, + } => { + return Err(ErrorCode::InvalidProgramExecutable.into()); + } + } + } 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); + } + }); }); From 89776aa21a91924427c2fe45649ed3bd1ed7406b Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Sat, 11 Dec 2021 01:13:18 +0100 Subject: [PATCH 2/8] lang: refactor program account --- lang/src/program.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lang/src/program.rs b/lang/src/program.rs index 0f2bc34ab1..e32edcd55f 100644 --- a/lang/src/program.rs +++ b/lang/src/program.rs @@ -54,24 +54,24 @@ impl<'a, T: Id + AccountDeserialize + Clone> Program<'a, T> { let mut data: &[u8] = &info.try_borrow_data()?; let upgradable_loader_state = UpgradeableLoaderState::try_deserialize_unchecked(&mut data)?; + match upgradable_loader_state { - UpgradeableLoaderState::Uninitialized => { - return Err(ErrorCode::InvalidProgramExecutable.into()); - } - UpgradeableLoaderState::Buffer { + UpgradeableLoaderState::Uninitialized + | UpgradeableLoaderState::Buffer { authority_address: _, - } => { - return Err(ErrorCode::InvalidProgramExecutable.into()); } - UpgradeableLoaderState::Program { - programdata_address, - } => Some(programdata_address), - UpgradeableLoaderState::ProgramData { + | UpgradeableLoaderState::ProgramData { slot: _, upgrade_authority_address: _, } => { - return Err(ErrorCode::InvalidProgramExecutable.into()); + // 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 From 3764b46d5aacd81fe3afea4789d7941a065d60ae Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Sat, 11 Dec 2021 01:20:57 +0100 Subject: [PATCH 3/8] docs: update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0d49a396e..7d223e84a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ incremented for features. ## [Unreleased] +### Features + +* 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)) + ## [0.19.0] - 2021-12-08 ### Fixes From 84a3c40fbcb1d967fd804a36af2c7bbf476c46d3 Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Mon, 13 Dec 2021 20:26:13 +0100 Subject: [PATCH 4/8] docs: add version to solana toolchain update changelog line --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5008fe689..4966e818de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ incremented for features. ### Features * 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([#1133](https://github.com/project-serum/anchor/pull/1133)) +* 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 From 1484150669ecdc2f7aad5da1ee59a985c526dcad Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Mon, 13 Dec 2021 15:36:38 -0500 Subject: [PATCH 5/8] Update lang/src/program.rs --- lang/src/program.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/src/program.rs b/lang/src/program.rs index e32edcd55f..84a79c3256 100644 --- a/lang/src/program.rs +++ b/lang/src/program.rs @@ -66,7 +66,7 @@ impl<'a, T: Id + AccountDeserialize + Clone> Program<'a, T> { } => { // unreachable because check above already // ensures that program is executable - // and therefore a program account + // and therefore a program account. unreachable!() } UpgradeableLoaderState::Program { From fa76935ce978c4f0c9f7dac9a3b3bdadb11d8deb Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Mon, 13 Dec 2021 15:36:42 -0500 Subject: [PATCH 6/8] Update lang/src/program.rs --- lang/src/program.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/src/program.rs b/lang/src/program.rs index 84a79c3256..21520e00a5 100644 --- a/lang/src/program.rs +++ b/lang/src/program.rs @@ -64,7 +64,7 @@ impl<'a, T: Id + AccountDeserialize + Clone> Program<'a, T> { slot: _, upgrade_authority_address: _, } => { - // unreachable because check above already + // Unreachable because check above already // ensures that program is executable // and therefore a program account. unreachable!() From f4f9536557c7c27ec615314574e5ebb4f6963539 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Mon, 13 Dec 2021 15:36:47 -0500 Subject: [PATCH 7/8] Update lang/src/program.rs --- lang/src/program.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/lang/src/program.rs b/lang/src/program.rs index 21520e00a5..e10aa36c23 100644 --- a/lang/src/program.rs +++ b/lang/src/program.rs @@ -9,7 +9,6 @@ use std::fmt; use std::ops::Deref; // TODO: can we remove _account? - /// Account container that checks ownership on deserialization. #[derive(Clone)] pub struct Program<'info, T: Id + AccountDeserialize + Clone> { From 65caeff21d39fc78bd50ffaade45d680c7f15064 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Mon, 13 Dec 2021 15:36:51 -0500 Subject: [PATCH 8/8] Update lang/src/program.rs --- lang/src/program.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/lang/src/program.rs b/lang/src/program.rs index e10aa36c23..7a6e1f1209 100644 --- a/lang/src/program.rs +++ b/lang/src/program.rs @@ -8,7 +8,6 @@ use solana_program::pubkey::Pubkey; use std::fmt; use std::ops::Deref; -// TODO: can we remove _account? /// Account container that checks ownership on deserialization. #[derive(Clone)] pub struct Program<'info, T: Id + AccountDeserialize + Clone> {