From 3321a3f9c9a6309abdee96032a5e8c63326c120f Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 5 Dec 2021 20:14:16 +0100 Subject: [PATCH] lang: Add ProgramData account (#1095) --- .github/actions/setup-solana/action.yaml | 2 + .github/workflows/tests.yaml | 42 ++++++ CHANGELOG.md | 1 + Cargo.lock | 7 +- lang/Cargo.toml | 1 + lang/src/bpf_upgradeable_state.rs | 82 ++++++++++++ lang/src/error.rs | 2 + lang/src/lib.rs | 9 +- lang/syn/src/codegen/program/handlers.rs | 8 +- lang/syn/src/lib.rs | 8 ++ lang/syn/src/parser/accounts/mod.rs | 2 + tests/bpf-upgradeable-state/.gitignore | 1 + tests/bpf-upgradeable-state/Anchor.toml | 12 ++ tests/bpf-upgradeable-state/Cargo.toml | 4 + .../bpf_upgradeable_state-keypair.json | 1 + .../migrations/deploy.ts | 12 ++ tests/bpf-upgradeable-state/package.json | 12 ++ .../program_with_different_programdata.json | 1 + .../programs/bpf-upgradeable-state/Cargo.toml | 18 +++ .../programs/bpf-upgradeable-state/Xargo.toml | 2 + .../programs/bpf-upgradeable-state/src/lib.rs | 53 ++++++++ .../tests/bpf-upgradable-state.ts | 125 ++++++++++++++++++ tests/bpf-upgradeable-state/tsconfig.json | 10 ++ tests/yarn.lock | 8 +- ts/src/error.ts | 5 + 25 files changed, 413 insertions(+), 15 deletions(-) create mode 100644 lang/src/bpf_upgradeable_state.rs create mode 100644 tests/bpf-upgradeable-state/.gitignore create mode 100644 tests/bpf-upgradeable-state/Anchor.toml create mode 100644 tests/bpf-upgradeable-state/Cargo.toml create mode 100644 tests/bpf-upgradeable-state/bpf_upgradeable_state-keypair.json create mode 100644 tests/bpf-upgradeable-state/migrations/deploy.ts create mode 100644 tests/bpf-upgradeable-state/package.json create mode 100644 tests/bpf-upgradeable-state/program_with_different_programdata.json create mode 100644 tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/Cargo.toml create mode 100644 tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/Xargo.toml create mode 100644 tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/src/lib.rs create mode 100644 tests/bpf-upgradeable-state/tests/bpf-upgradable-state.ts create mode 100644 tests/bpf-upgradeable-state/tsconfig.json diff --git a/.github/actions/setup-solana/action.yaml b/.github/actions/setup-solana/action.yaml index c0ab16ed..f24aa486 100644 --- a/.github/actions/setup-solana/action.yaml +++ b/.github/actions/setup-solana/action.yaml @@ -17,3 +17,5 @@ runs: shell: bash - run: solana-keygen new --no-bip39-passphrase shell: bash + - run: solana config set --url localhost + shell: bash diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 00433026..5060d25e 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -179,6 +179,48 @@ jobs: - uses: ./.github/actions/setup-solana/ - run: cd client/example && ./run-test.sh + test-bpf-upgradeable-state: + needs: setup-anchor-cli + name: Test tests/bpf-upgradeable-state + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + - uses: ./.github/actions/setup/ + - uses: ./.github/actions/setup-ts/ + - uses: ./.github/actions/setup-solana/ + + - uses: actions/cache@v2 + name: Cache Cargo registry + index + id: cache-anchor + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + ./target/ + key: cargo-${{ runner.os }}-anchor-${{ hashFiles('**/Cargo.lock') }} + + - uses: actions/download-artifact@v2 + with: + name: anchor-binary + path: ~/.cargo/bin/ + + - uses: actions/cache@v2 + name: Cache tests/bpf-upgradeable-state target + id: cache-test-target + with: + path: tests/bpf-upgradeable-state/target + key: cargo-${{ runner.os }}-tests/bpf-upgradeable-state-${{ env.ANCHOR_VERSION }} + + - run: solana-test-validator -r --quiet & + name: start validator + - run: cd tests/bpf-upgradeable-state && yarn + - run: cd tests/bpf-upgradeable-state && yarn link @project-serum/anchor + - run: cd tests/bpf-upgradeable-state && anchor build + - run: cd tests/bpf-upgradeable-state && solana program deploy --program-id program_with_different_programdata.json target/deploy/bpf_upgradeable_state.so + - run: cd tests/bpf-upgradeable-state && cp bpf_upgradeable_state-keypair.json target/deploy/bpf_upgradeable_state-keypair.json && anchor deploy && anchor test --skip-deploy --skip-build + test-programs: needs: setup-anchor-cli name: Test ${{ matrix.node.path }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 53c664b2..8427a2f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ incremented for features. * lang: Add `ErrorCode::AccountNotInitialized` error to separate the situation when the account has the wrong owner from when it does not exist (#[1024](https://github.com/project-serum/anchor/pull/1024)) * lang: Called instructions now log their name by default. This can be turned off with the `no-log-ix-name` flag ([#1057](https://github.com/project-serum/anchor/pull/1057)) +* lang: `ProgramData` and `UpgradableLoaderState` can now be passed into `Account` as generics. see [UpgradeableLoaderState](https://docs.rs/solana-program/latest/solana_program/bpf_loader_upgradeable/enum.UpgradeableLoaderState.html). `UpgradableLoaderState` can also be matched on to get `ProgramData`, but when `ProgramData` is used instead, anchor does the serialization and checking that it is actually program data for you ([#1095](https://github.com/project-serum/anchor/pull/1095)) * ts: Add better error msgs in the ts client if something wrong (i.e. not a pubkey or a string) is passed in as an account in an instruction accounts object ([#1098](https://github.com/project-serum/anchor/pull/1098)) ## [0.18.2] - 2021-11-14 diff --git a/Cargo.lock b/Cargo.lock index 3076b71f..4e16d850 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,11 +81,11 @@ dependencies = [ [[package]] name = "anchor-attribute-constant" -version = "0.18.0" +version = "0.18.2" dependencies = [ "anchor-syn", - "proc-macro2 1.0.29", - "syn 1.0.75", + "proc-macro2 1.0.32", + "syn 1.0.81", ] [[package]] @@ -213,6 +213,7 @@ dependencies = [ "anchor-attribute-state", "anchor-derive-accounts", "base64 0.13.0", + "bincode", "borsh", "bytemuck", "solana-program", diff --git a/lang/Cargo.toml b/lang/Cargo.toml index e97f56a2..2af4b3a0 100644 --- a/lang/Cargo.toml +++ b/lang/Cargo.toml @@ -38,3 +38,4 @@ borsh = "0.9" bytemuck = "1.4.0" solana-program = "1.8.0" thiserror = "1.0.20" +bincode = "1.3.3" diff --git a/lang/src/bpf_upgradeable_state.rs b/lang/src/bpf_upgradeable_state.rs new file mode 100644 index 00000000..d849798e --- /dev/null +++ b/lang/src/bpf_upgradeable_state.rs @@ -0,0 +1,82 @@ +use crate::{AccountDeserialize, AccountSerialize, Owner}; +use solana_program::{ + bpf_loader_upgradeable::UpgradeableLoaderState, program_error::ProgramError, pubkey::Pubkey, +}; + +#[derive(Clone)] +pub struct ProgramData { + pub slot: u64, + pub upgrade_authority_address: Option, +} + +impl AccountDeserialize for ProgramData { + fn try_deserialize( + buf: &mut &[u8], + ) -> Result { + ProgramData::try_deserialize_unchecked(buf) + } + + fn try_deserialize_unchecked( + buf: &mut &[u8], + ) -> Result { + let program_state = AccountDeserialize::try_deserialize_unchecked(buf)?; + + match program_state { + UpgradeableLoaderState::Uninitialized => { + Err(anchor_lang::error::ErrorCode::AccountNotProgramData.into()) + } + UpgradeableLoaderState::Buffer { + authority_address: _, + } => Err(anchor_lang::error::ErrorCode::AccountNotProgramData.into()), + UpgradeableLoaderState::Program { + programdata_address: _, + } => Err(anchor_lang::error::ErrorCode::AccountNotProgramData.into()), + UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address, + } => Ok(ProgramData { + slot, + upgrade_authority_address, + }), + } + } +} + +impl AccountSerialize for ProgramData { + fn try_serialize( + &self, + _writer: &mut W, + ) -> Result<(), solana_program::program_error::ProgramError> { + // no-op + Ok(()) + } +} + +impl Owner for ProgramData { + fn owner() -> solana_program::pubkey::Pubkey { + anchor_lang::solana_program::bpf_loader_upgradeable::ID + } +} + +impl Owner for UpgradeableLoaderState { + fn owner() -> Pubkey { + anchor_lang::solana_program::bpf_loader_upgradeable::ID + } +} + +impl AccountSerialize for UpgradeableLoaderState { + fn try_serialize(&self, _writer: &mut W) -> Result<(), ProgramError> { + // no-op + Ok(()) + } +} + +impl AccountDeserialize for UpgradeableLoaderState { + fn try_deserialize(buf: &mut &[u8]) -> Result { + UpgradeableLoaderState::try_deserialize_unchecked(buf) + } + + fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result { + bincode::deserialize(buf).map_err(|_| ProgramError::InvalidAccountData) + } +} diff --git a/lang/src/error.rs b/lang/src/error.rs index 93086d20..70a7ca97 100644 --- a/lang/src/error.rs +++ b/lang/src/error.rs @@ -76,6 +76,8 @@ pub enum ErrorCode { AccountNotSystemOwned, #[msg("The program expected this account to be already initialized")] AccountNotInitialized, + #[msg("The given account is not a program data account")] + AccountNotProgramData, // State. #[msg("The given state account does not have the correct address")] diff --git a/lang/src/lib.rs b/lang/src/lib.rs index 60b9b111..b570c22b 100644 --- a/lang/src/lib.rs +++ b/lang/src/lib.rs @@ -35,6 +35,7 @@ mod account; mod account_info; mod account_meta; mod boxed; +mod bpf_upgradeable_state; mod common; mod context; mod cpi_account; @@ -56,6 +57,7 @@ mod unchecked_account; mod vec; pub use crate::account::Account; +pub use crate::bpf_upgradeable_state::*; #[doc(hidden)] #[allow(deprecated)] pub use crate::context::CpiStateContext; @@ -252,9 +254,10 @@ impl Key for Pubkey { pub mod prelude { pub use super::{ access_control, account, constant, declare_id, emit, error, event, interface, program, - require, state, zero_copy, Account, AccountDeserialize, AccountLoader, AccountSerialize, - Accounts, AccountsExit, AnchorDeserialize, AnchorSerialize, Context, CpiContext, Id, Key, - Owner, Program, Signer, System, SystemAccount, Sysvar, ToAccountInfo, ToAccountInfos, + require, solana_program::bpf_loader_upgradeable::UpgradeableLoaderState, state, zero_copy, + Account, AccountDeserialize, AccountLoader, AccountSerialize, Accounts, AccountsExit, + AnchorDeserialize, AnchorSerialize, Context, CpiContext, Id, Key, Owner, Program, + ProgramData, Signer, System, SystemAccount, Sysvar, ToAccountInfo, ToAccountInfos, ToAccountMetas, UncheckedAccount, }; diff --git a/lang/syn/src/codegen/program/handlers.rs b/lang/syn/src/codegen/program/handlers.rs index f9c07809..3bfa47e1 100644 --- a/lang/syn/src/codegen/program/handlers.rs +++ b/lang/syn/src/codegen/program/handlers.rs @@ -486,11 +486,9 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { .methods .iter() .map(|ix| { - if state.is_zero_copy { - // Easy to implement. Just need to write a test. - // Feel free to open a PR. - panic!("Trait implementations not yet implemented for zero copy state structs. Please file an issue."); - } + // Easy to implement. Just need to write a test. + // Feel free to open a PR. + assert!(!state.is_zero_copy, "Trait implementations not yet implemented for zero copy state structs. Please file an issue."); let ix_arg_names: Vec<&syn::Ident> = ix.args.iter().map(|arg| &arg.name).collect(); diff --git a/lang/syn/src/lib.rs b/lang/syn/src/lib.rs index ba17f4ef..38033705 100644 --- a/lang/syn/src/lib.rs +++ b/lang/syn/src/lib.rs @@ -184,6 +184,9 @@ impl Field { Ty::Signer => quote! { Signer }, + Ty::ProgramData => quote! { + ProgramData + }, Ty::SystemAccount => quote! { SystemAccount }, @@ -298,6 +301,7 @@ impl Field { Ty::UncheckedAccount => quote! {}, Ty::Signer => quote! {}, Ty::SystemAccount => quote! {}, + Ty::ProgramData => quote! {}, } } @@ -316,6 +320,9 @@ impl Field { Ty::SystemAccount => quote! { SystemAccount }, + Ty::ProgramData => quote! { + ProgramData + }, Ty::ProgramAccount(ty) => { let ident = &ty.account_type_path; quote! { @@ -405,6 +412,7 @@ pub enum Ty { Program(ProgramTy), Signer, SystemAccount, + ProgramData, } #[derive(Debug, PartialEq)] diff --git a/lang/syn/src/parser/accounts/mod.rs b/lang/syn/src/parser/accounts/mod.rs index c50db1ab..1b8883c5 100644 --- a/lang/syn/src/parser/accounts/mod.rs +++ b/lang/syn/src/parser/accounts/mod.rs @@ -79,6 +79,7 @@ fn is_field_primitive(f: &syn::Field) -> ParseResult { | "Program" | "Signer" | "SystemAccount" + | "ProgramData" ); Ok(r) } @@ -102,6 +103,7 @@ fn parse_ty(f: &syn::Field) -> ParseResult { "Program" => Ty::Program(parse_program_ty(&path)?), "Signer" => Ty::Signer, "SystemAccount" => Ty::SystemAccount, + "ProgramData" => Ty::ProgramData, _ => return Err(ParseError::new(f.ty.span(), "invalid account type given")), }; diff --git a/tests/bpf-upgradeable-state/.gitignore b/tests/bpf-upgradeable-state/.gitignore new file mode 100644 index 00000000..8ee01d32 --- /dev/null +++ b/tests/bpf-upgradeable-state/.gitignore @@ -0,0 +1 @@ +yarn.lock diff --git a/tests/bpf-upgradeable-state/Anchor.toml b/tests/bpf-upgradeable-state/Anchor.toml new file mode 100644 index 00000000..84740b9e --- /dev/null +++ b/tests/bpf-upgradeable-state/Anchor.toml @@ -0,0 +1,12 @@ +[programs.localnet] +bpf_upgradeable_state = "Cum9tTyj5HwcEiAmhgaS7Bbj4UczCwsucrCkxRECzM4e" + +[registry] +url = "https://anchor.projectserum.com" + +[provider] +cluster = "localnet" +wallet = "~/.config/solana/id.json" + +[scripts] +test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" diff --git a/tests/bpf-upgradeable-state/Cargo.toml b/tests/bpf-upgradeable-state/Cargo.toml new file mode 100644 index 00000000..a60de986 --- /dev/null +++ b/tests/bpf-upgradeable-state/Cargo.toml @@ -0,0 +1,4 @@ +[workspace] +members = [ + "programs/*" +] diff --git a/tests/bpf-upgradeable-state/bpf_upgradeable_state-keypair.json b/tests/bpf-upgradeable-state/bpf_upgradeable_state-keypair.json new file mode 100644 index 00000000..ae39fe95 --- /dev/null +++ b/tests/bpf-upgradeable-state/bpf_upgradeable_state-keypair.json @@ -0,0 +1 @@ +[114,99,192,17,48,208,90,184,231,46,220,91,47,115,132,253,218,163,228,101,8,121,220,138,41,140,176,127,254,91,51,28,176,244,174,182,223,57,57,125,117,201,31,213,9,39,207,212,100,173,88,252,61,235,89,156,53,86,4,90,16,251,191,219] \ No newline at end of file diff --git a/tests/bpf-upgradeable-state/migrations/deploy.ts b/tests/bpf-upgradeable-state/migrations/deploy.ts new file mode 100644 index 00000000..325cf3d0 --- /dev/null +++ b/tests/bpf-upgradeable-state/migrations/deploy.ts @@ -0,0 +1,12 @@ +// Migrations are an early feature. Currently, they're nothing more than this +// single deploy script that's invoked from the CLI, injecting a provider +// configured from the workspace's Anchor.toml. + +const anchor = require("@project-serum/anchor"); + +module.exports = async function (provider) { + // Configure client to use the provider. + anchor.setProvider(provider); + + // Add your deploy script here. +} diff --git a/tests/bpf-upgradeable-state/package.json b/tests/bpf-upgradeable-state/package.json new file mode 100644 index 00000000..ada948fd --- /dev/null +++ b/tests/bpf-upgradeable-state/package.json @@ -0,0 +1,12 @@ +{ + "dependencies": { + "@project-serum/anchor": "^0.18.2" + }, + "devDependencies": { + "chai": "^4.3.4", + "mocha": "^9.0.3", + "ts-mocha": "^8.0.0", + "@types/mocha": "^9.0.0", + "typescript": "^4.3.5" + } +} diff --git a/tests/bpf-upgradeable-state/program_with_different_programdata.json b/tests/bpf-upgradeable-state/program_with_different_programdata.json new file mode 100644 index 00000000..8825b0f8 --- /dev/null +++ b/tests/bpf-upgradeable-state/program_with_different_programdata.json @@ -0,0 +1 @@ +[86,234,116,86,82,140,116,250,254,32,75,217,35,39,9,238,39,98,242,254,25,216,201,66,1,239,93,12,81,19,34,108,219,67,158,98,245,234,81,126,228,157,205,206,130,5,14,54,1,21,88,246,128,124,240,93,157,49,102,19,253,19,205,178] \ No newline at end of file diff --git a/tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/Cargo.toml b/tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/Cargo.toml new file mode 100644 index 00000000..ac3164c1 --- /dev/null +++ b/tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "bpf-upgradeable-state" +version = "0.1.0" +description = "Created with Anchor" +edition = "2018" + +[lib] +crate-type = ["cdylib", "lib"] +name = "bpf_upgradeable_state" + +[features] +no-entrypoint = [] +no-idl = [] +cpi = ["no-entrypoint"] +default = [] + +[dependencies] +anchor-lang = { path = "../../../../lang" } diff --git a/tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/Xargo.toml b/tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/Xargo.toml new file mode 100644 index 00000000..475fb71e --- /dev/null +++ b/tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] 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 new file mode 100644 index 00000000..d7405cef --- /dev/null +++ b/tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/src/lib.rs @@ -0,0 +1,53 @@ +use anchor_lang::prelude::*; + +declare_id!("Cum9tTyj5HwcEiAmhgaS7Bbj4UczCwsucrCkxRECzM4e"); + +// TODO: Once anchor can deserialize data of programs (=programdata_address) automatically, add another test to this file. +// Instead of using UpgradeableLoaderState, it should use Program<'info, MY_PROGRAM> + +#[program] +pub mod bpf_upgradeable_state { + use super::*; + pub fn set_admin_settings(ctx: Context, admin_data: u64) -> ProgramResult { + match *ctx.accounts.program { + UpgradeableLoaderState::Program { + programdata_address, + } => { + if programdata_address != ctx.accounts.program_data.key() { + return Err(CustomError::InvalidProgramDataAddress.into()); + } + } + _ => { + return Err(CustomError::AccountNotProgram.into()); + } + }; + ctx.accounts.settings.admin_data = admin_data; + Ok(()) + } +} + +#[account] +#[derive(Default, Debug)] +pub struct Settings { + admin_data: u64, +} + +#[error] +pub enum CustomError { + InvalidProgramDataAddress, + AccountNotProgram, +} + +#[derive(Accounts)] +#[instruction(admin_data: u64)] +pub struct SetAdminSettings<'info> { + #[account(init, payer = authority)] + pub settings: Account<'info, Settings>, + #[account(mut)] + pub authority: Signer<'info>, + #[account(address = crate::ID)] + pub program: Account<'info, UpgradeableLoaderState>, + #[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 new file mode 100644 index 00000000..eef41fe0 --- /dev/null +++ b/tests/bpf-upgradeable-state/tests/bpf-upgradable-state.ts @@ -0,0 +1,125 @@ +import * as anchor from '@project-serum/anchor'; +import { Program } from '@project-serum/anchor'; +import { findProgramAddressSync } from '@project-serum/anchor/dist/cjs/utils/pubkey'; +import { PublicKey } from '@solana/web3.js'; +import assert from 'assert'; +import { BpfUpgradeableState } from '../target/types/bpf_upgradeable_state'; + +describe('bpf_upgradeable_state', () => { + const provider = anchor.Provider.env(); + // Configure the client to use the local cluster. + anchor.setProvider(provider); + + const program = anchor.workspace.BpfUpgradeableState as Program; + const programDataAddress = findProgramAddressSync( + [program.programId.toBytes()], + new anchor.web3.PublicKey("BPFLoaderUpgradeab1e11111111111111111111111") + )[0]; + + it('Reads ProgramData and sets field', async () => { + const settings = anchor.web3.Keypair.generate(); + const tx = await program.rpc.setAdminSettings(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); + + console.log("Your transaction signature", tx); + }); + + it('Validates constraint on ProgramData', async () => { + const settings = anchor.web3.Keypair.generate(); + try { + const authority = anchor.web3.Keypair.generate(); + await provider.connection.confirmTransaction( + await provider.connection.requestAirdrop(authority.publicKey, 10000000000), + "confirmed" + ); + await program.rpc.setAdminSettings(new anchor.BN(500), { + accounts: { + authority: authority.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + programData: programDataAddress, + settings: settings.publicKey, + program: program.programId, + }, + signers: [settings, authority] + }); + assert.ok(false); + } catch (err) { + assert.equal(err.code, 143); + assert.equal(err.msg, "A raw constraint was violated"); + } + }); + + it('Validates that account is ProgramData', async () => { + const settings = anchor.web3.Keypair.generate(); + try { + await program.rpc.setAdminSettings(new anchor.BN(500), { + accounts: { + authority: program.provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + programData: program.programId, + settings: settings.publicKey, + program: program.programId, + }, + signers: [settings] + }); + assert.ok(false); + } catch (err) { + assert.equal(err.code, 173); + assert.equal(err.msg, "The given account is not a program data account"); + } + }); + + it('Validates that account is owned by the upgradeable bpf loader', async () => { + const settings = anchor.web3.Keypair.generate(); + try { + await program.rpc.setAdminSettings(new anchor.BN(500), { + accounts: { + authority: program.provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + programData: program.provider.wallet.publicKey, + settings: settings.publicKey, + program: program.programId, + }, + signers: [settings] + }); + assert.ok(false); + } catch (err) { + assert.equal(err.code, 167); + assert.equal(err.msg, "The given account is not owned by the executing program"); + } + }); + + it('Deserializes UpgradableLoaderState 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.setAdminSettings(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, 300); + } + }); +}); diff --git a/tests/bpf-upgradeable-state/tsconfig.json b/tests/bpf-upgradeable-state/tsconfig.json new file mode 100644 index 00000000..cd5d2e3d --- /dev/null +++ b/tests/bpf-upgradeable-state/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "types": ["mocha", "chai"], + "typeRoots": ["./node_modules/@types"], + "lib": ["es2015"], + "module": "commonjs", + "target": "es6", + "esModuleInterop": true + } +} diff --git a/tests/yarn.lock b/tests/yarn.lock index 78c29e31..5528c10c 100644 --- a/tests/yarn.lock +++ b/tests/yarn.lock @@ -50,10 +50,10 @@ snake-case "^3.0.4" toml "^3.0.0" -"@project-serum/anchor@^0.18.0": - version "0.18.0" - resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.18.0.tgz#867144282e59482230f797f73ee9f5634f846061" - integrity sha512-WTm+UB93MoxyCbjnHIibv/uUEoO/5gL4GEtE/aMioLF8Z4i0vCMPnvAN0xpk9VBu3t7ld2DcCE/L+6Z7dwU++w== +"@project-serum/anchor@^0.18.2": + version "0.18.2" + resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.18.2.tgz#0f13b5c2046446b7c24cf28763eec90febb28485" + integrity sha512-uyjiN/3Ipp+4hrZRm/hG18HzGLZyvP790LXrCsGO3IWxSl28YRhiGEpKnZycfMW94R7nxdUoE3wY67V+ZHSQBQ== dependencies: "@project-serum/borsh" "^0.2.2" "@solana/web3.js" "^1.17.0" diff --git a/ts/src/error.ts b/ts/src/error.ts index b27a3192..d31fa5ad 100644 --- a/ts/src/error.ts +++ b/ts/src/error.ts @@ -90,6 +90,7 @@ const LangErrorCode = { AccountNotSigner: 170, AccountNotSystemOwned: 171, AccountNotInitialized: 172, + AccountNotProgramData: 173, // State. StateInvalidAddress: 180, @@ -180,6 +181,10 @@ const LangErrorMessage = new Map([ LangErrorCode.AccountNotInitialized, "The program expected this account to be already initialized", ], + [ + LangErrorCode.AccountNotProgramData, + "The given account is not a program data account", + ], // State. [