Skip to content

Commit

Permalink
lang: programdata_address field for Program account (#1125)
Browse files Browse the repository at this point in the history
  • Loading branch information
paul-schaaf committed Dec 13, 2021
1 parent 96f7999 commit 911620e
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 5 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Expand Up @@ -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

Expand Down
52 changes: 49 additions & 3 deletions 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;
Expand All @@ -12,20 +13,30 @@ 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> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
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`.
Expand All @@ -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
}
}

Expand Down
Expand Up @@ -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]
Expand All @@ -36,6 +44,7 @@ pub struct Settings {
pub enum CustomError {
InvalidProgramDataAddress,
AccountNotProgram,
AccountNotBpfUpgradableProgram,
}

#[derive(Accounts)]
Expand All @@ -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>,
}
40 changes: 39 additions & 1 deletion tests/bpf-upgradeable-state/tests/bpf-upgradable-state.ts
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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);
}
});
});

0 comments on commit 911620e

Please sign in to comment.