From 3ddad6cbb61dd61fc62b83dc1681e63217cf9787 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 6 Jan 2022 22:30:44 +0100 Subject: [PATCH] lang: rest of accounts docs reference (#1231) --- lang/attribute/account/src/lib.rs | 2 +- lang/src/accounts/account.rs | 115 +++++++++++++++++- lang/src/accounts/account_info.rs | 4 + lang/src/accounts/boxed.rs | 15 +++ lang/src/accounts/loader_account.rs | 80 ++++++++++-- lang/src/accounts/program.rs | 49 +++++++- lang/src/accounts/signer.rs | 25 ++++ lang/src/accounts/system_account.rs | 7 ++ lang/src/accounts/sysvar.rs | 24 +++- lang/src/accounts/unchecked_account.rs | 6 +- .../programs/bpf-upgradeable-state/src/lib.rs | 11 +- 11 files changed, 321 insertions(+), 17 deletions(-) diff --git a/lang/attribute/account/src/lib.rs b/lang/attribute/account/src/lib.rs index a6e70eb0c8..da1dd8bd76 100644 --- a/lang/attribute/account/src/lib.rs +++ b/lang/attribute/account/src/lib.rs @@ -56,7 +56,7 @@ mod id; /// To facilitate this, all fields in an account must be constrained to be /// "plain old data", i.e., they must implement /// [`Pod`](../bytemuck/trait.Pod.html). Please review the -/// [`safety`](file:///home/armaniferrante/Documents/code/src/github.com/project-serum/anchor/target/doc/bytemuck/trait.Pod.html#safety) +/// [`safety`](../bytemuck/trait.Pod.html#safety) /// section before using. #[proc_macro_attribute] pub fn account( diff --git a/lang/src/accounts/account.rs b/lang/src/accounts/account.rs index 1b58dd2b81..22daea46a3 100644 --- a/lang/src/accounts/account.rs +++ b/lang/src/accounts/account.rs @@ -13,6 +13,13 @@ use std::ops::{Deref, DerefMut}; /// Wrapper around [`AccountInfo`](crate::solana_program::account_info::AccountInfo) /// that verifies program ownership and deserializes underlying data into a Rust type. /// +/// # Table of Contents +/// - [Basic Functionality](#basic-functionality) +/// - [Using Account with non-anchor types](#using-account-with-non-anchor-types) +/// - [Out of the box wrapper types](#out-of-the-box-wrapper-types) +/// +/// # Basic Functionality +/// /// Account checks that `Account.info.owner == T::owner()`. /// This means that the data type that Accounts wraps around (`=T`) needs to /// implement the [Owner trait](crate::Owner). @@ -79,7 +86,7 @@ use std::ops::{Deref, DerefMut}; /// functions `#[account]` generates. See the example below for the code you have /// to write. /// -/// The mint wrapper type Anchor provides out of the box for the token program ([source](https://github.com/project-serum/anchor/blob/master/spl/src/token.rs)) +/// The mint wrapper type that Anchor provides out of the box for the token program ([source](https://github.com/project-serum/anchor/blob/master/spl/src/token.rs)) /// ```ignore /// #[derive(Clone)] /// pub struct Mint(spl_token::state::Mint); @@ -121,6 +128,96 @@ use std::ops::{Deref, DerefMut}; /// } /// } /// ``` +/// +/// ## Out of the box wrapper types +/// +/// ### Accessing BPFUpgradeableLoader Data +/// +/// Anchor provides wrapper types to access data stored in programs owned by the BPFUpgradeableLoader +/// such as the upgrade authority. If you're interested in the data of a program account, you can use +/// ```ignore +/// Account<'info, BpfUpgradeableLoaderState> +/// ``` +/// and then match on its contents inside your instruction function. +/// +/// Alternatively, you can use +/// ```ignore +/// Account<'info, ProgramData> +/// ``` +/// to let anchor do the matching for you and return the ProgramData variant of BpfUpgradeableLoaderState. +/// +/// # Example +/// ```ignore +/// use anchor_lang::prelude::*; +/// use crate::program::MyProgram; +/// +/// declare_id!("Cum9tTyj5HwcEiAmhgaS7Bbj4UczCwsucrCkxRECzM4e"); +/// +/// #[program] +/// pub mod my_program { +/// use super::*; +/// +/// pub fn set_initial_admin( +/// ctx: Context, +/// admin_key: Pubkey +/// ) -> ProgramResult { +/// ctx.accounts.admin_settings.admin_key = admin_key; +/// Ok(()) +/// } +/// +/// pub fn set_admin(...){...} +/// +/// pub fn set_settings(...){...} +/// } +/// +/// #[account] +/// #[derive(Default, Debug)] +/// pub struct AdminSettings { +/// admin_key: Pubkey +/// } +/// +/// #[derive(Accounts)] +/// pub struct SetInitialAdmin<'info> { +/// #[account(init, payer = authority, seeds = [b"admin"], bump)] +/// pub admin_settings: Account<'info, AdminSettings>, +/// #[account(mut)] +/// pub authority: Signer<'info>, +/// #[account(constraint = program.programdata_address() == Some(program_data.key()))] +/// pub program: Program<'info, MyProgram>, +/// #[account(constraint = program_data.upgrade_authority_address == Some(authority.key()))] +/// pub program_data: Account<'info, ProgramData>, +/// pub system_program: Program<'info, System>, +/// } +/// ``` +/// +/// This example solves a problem you may face if your program has admin settings: How do you set the +/// admin key for restricted functionality after deployment? Setting the admin key itself should +/// be a restricted action but how do you restrict it without having set an admin key? +/// You're stuck in a loop. +/// One solution is to use the upgrade authority of the program as the initial +/// (or permanent) admin key. +/// +/// ### SPL Types +/// +/// Anchor provides wrapper types to access accounts owned by the token program. Use +/// ```ignore +/// use anchor_spl::token::TokenAccount; +/// +/// #[derive(Accounts)] +/// pub struct Example { +/// pub my_acc: Account<'info, TokenAccount> +/// } +/// ``` +/// to access token accounts and +/// ```ignore +/// use anchor_spl::token::Mint; +/// +/// #[derive(Accounts)] +/// pub struct Example { +/// pub my_acc: Account<'info, Mint> +/// } +/// ``` +/// to access mint accounts. #[derive(Clone)] pub struct Account<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> { account: T, @@ -186,6 +283,22 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> Account<'a, T self.account } + /// Sets the inner account. + /// + /// Instead of this: + /// ```ignore + /// pub fn new_user(ctx: Context, new_user:User) -> ProgramResult { + /// (*ctx.accounts.user_to_create).name = new_user.name; + /// (*ctx.accounts.user_to_create).age = new_user.age; + /// (*ctx.accounts.user_to_create).address = new_user.address; + /// } + /// ``` + /// You can do this: + /// ```ignore + /// pub fn new_user(ctx: Context, new_user:User) -> ProgramResult { + /// ctx.accounts.user_to_create.set_inner(new_user); + /// } + /// ``` pub fn set_inner(&mut self, inner: T) { self.account = inner; } diff --git a/lang/src/accounts/account_info.rs b/lang/src/accounts/account_info.rs index b33ebd3b13..d28179f8ea 100644 --- a/lang/src/accounts/account_info.rs +++ b/lang/src/accounts/account_info.rs @@ -1,3 +1,7 @@ +//! AccountInfo can be used as a type but +//! [Unchecked Account](crate::accounts::unchecked_account::UncheckedAccount) +//! should be used instead. + use crate::error::ErrorCode; use crate::{Accounts, AccountsExit, Key, ToAccountInfo, ToAccountInfos, ToAccountMetas}; use solana_program::account_info::AccountInfo; diff --git a/lang/src/accounts/boxed.rs b/lang/src/accounts/boxed.rs index 9a43812e4b..7bfb5738be 100644 --- a/lang/src/accounts/boxed.rs +++ b/lang/src/accounts/boxed.rs @@ -1,3 +1,18 @@ +//! Box type to save stack space. +//! +//! Sometimes accounts are too large for the stack, +//! leading to stack violations. +//! +//! Boxing the account can help. +//! +//! # Example +//! ```ignore +//! #[derive(Accounts)] +//! pub struct Example { +//! pub my_acc: Box> +//! } +//! ``` + use crate::{Accounts, AccountsClose, AccountsExit, ToAccountInfos, ToAccountMetas}; use solana_program::account_info::AccountInfo; use solana_program::entrypoint::ProgramResult; diff --git a/lang/src/accounts/loader_account.rs b/lang/src/accounts/loader_account.rs index 2d8edbe84c..ceb51bf6ca 100644 --- a/lang/src/accounts/loader_account.rs +++ b/lang/src/accounts/loader_account.rs @@ -1,3 +1,5 @@ +//! Type facilitating on demand zero copy deserialization. + use crate::error::ErrorCode; use crate::{ Accounts, AccountsClose, AccountsExit, Key, Owner, ToAccountInfo, ToAccountInfos, @@ -15,17 +17,81 @@ use std::io::Write; use std::marker::PhantomData; use std::ops::DerefMut; -/// Account AccountLoader facilitating on demand zero copy deserialization. +/// Type facilitating on demand zero copy deserialization. +/// /// Note that using accounts in this way is distinctly different from using, /// for example, the [`Account`](./struct.Account.html). Namely, -/// one must call `load`, `load_mut`, or `load_init`, before reading or writing -/// to the account. For more details on zero-copy-deserialization, see the +/// one must call +/// - `load_init` after initializing an account (this will ignore the missing +/// account discriminator that gets added only after the user's instruction code) +/// - `load` when the account is not mutable +/// - `load_mut` when the account is mutable +/// +/// For more details on zero-copy-deserialization, see the /// [`account`](./attr.account.html) attribute. +///

+/// ⚠️ When using this type it's important to be mindful +/// of any calls to the load functions so as not to +/// induce a RefCell panic, especially when sharing accounts across CPI +/// boundaries. When in doubt, one should make sure all refs resulting from +/// a call to a load function are dropped before CPI. +/// This can be done explicitly by calling drop(my_var) or implicitly +/// by wrapping the code using the Ref in braces {..} or +/// moving it into its own function. +///

+/// +/// # Example +/// ```ignore +/// use anchor_lang::prelude::*; +/// +/// declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); +/// +/// #[program] +/// pub mod bar { +/// use super::*; +/// +/// pub fn create_bar(ctx: Context, data: u64) -> ProgramResult { +/// let bar = &mut ctx.accounts.bar.load_init()?; +/// bar.authority = ctx.accounts.authority.key(); +/// bar.data = data; +/// Ok(()) +/// } +/// +/// pub fn update_bar(ctx: Context, data: u64) -> ProgramResult { +/// (*ctx.accounts.bar.load_mut()?).data = data; +/// Ok(()) +/// } +/// } +/// +/// #[account(zero_copy)] +/// #[derive(Default)] +/// pub struct Bar { +/// authority: Pubkey, +/// data: u64 +/// } +/// +/// #[derive(Accounts)] +/// pub struct CreateBar<'info> { +/// #[account( +/// init, +/// payer = authority +/// )] +/// bar: AccountLoader<'info, Bar>, +/// #[account(mut)] +/// authority: Signer<'info>, +/// system_program: AccountInfo<'info>, +/// } /// -/// When using it's important to be mindful of any calls to `load` so as not to -/// induce a `RefCell` panic, especially when sharing accounts across CPI -/// boundaries. When in doubt, one should make sure all refs resulting from a -/// call to `load` are dropped before CPI. +/// #[derive(Accounts)] +/// pub struct UpdateBar<'info> { +/// #[account( +/// mut, +/// has_one = authority, +/// )] +/// pub bar: AccountLoader<'info, Bar>, +/// pub authority: Signer<'info>, +/// } +/// ``` #[derive(Clone)] pub struct AccountLoader<'info, T: ZeroCopy + Owner> { acc_info: AccountInfo<'info>, diff --git a/lang/src/accounts/program.rs b/lang/src/accounts/program.rs index 8d7f417b9e..545376aa85 100644 --- a/lang/src/accounts/program.rs +++ b/lang/src/accounts/program.rs @@ -1,3 +1,5 @@ +//! Type validating that the account is the given Program + use crate::error::ErrorCode; use crate::*; use solana_program::account_info::AccountInfo; @@ -9,7 +11,52 @@ use std::fmt; use std::marker::PhantomData; use std::ops::Deref; -/// Account container that checks ownership on deserialization. +/// Type validating that the account is the given Program +/// +/// The type has a `programdata_address` property that will be set +/// if the program is owned by the [`BPFUpgradeableLoader`](https://docs.rs/solana-program/latest/solana_program/bpf_loader_upgradeable/index.html) +/// and will contain the `programdata_address` property of the `Program` variant of the [`UpgradeableLoaderState`](https://docs.rs/solana-program/latest/solana_program/bpf_loader_upgradeable/enum.UpgradeableLoaderState.html) enum. +/// +/// Checks: +/// +/// - `Account.info.key == Program` +/// - `Account.info.executable == true` +/// +/// # Example +/// ```ignore +/// +/// #[program] +/// mod my_program { +/// fn set_admin_settings(...){...} +/// } +/// +/// #[account] +/// #[derive(Default)] +/// pub struct AdminSettings { +/// ... +/// } +/// +/// #[derive(Accounts)] +/// pub struct SetAdminSettings<'info> { +/// #[account(mut, seeds = [b"admin"], bump)] +/// pub admin_settings: Account<'info, AdminSettings>, +/// #[account(constraint = program.programdata_address() == Some(program_data.key()))] +/// pub program: Program<'info, MyProgram>, +/// #[account(constraint = program_data.upgrade_authority_address == Some(authority.key()))] +/// pub program_data: Account<'info, ProgramData>, +/// pub authority: Signer<'info>, +/// } +/// ``` +/// The given program has a function with which the upgrade authority can set admin settings. +/// +/// The required constraints are as follows: +/// +/// - `program` is the account of the program itself. +/// Its constraint checks that `program_data` is the account that contains the program's upgrade authority. +/// Implicitly, this checks that `program` is a BPFUpgradeable program (`program.programdata_address()` +/// will be `None` if it's not). +/// - `program_data`'s constraint checks that its upgrade authority is the `authority` account. +/// - Finally, `authority` needs to sign the transaction. #[derive(Clone)] pub struct Program<'info, T: Id + Clone> { info: AccountInfo<'info>, diff --git a/lang/src/accounts/signer.rs b/lang/src/accounts/signer.rs index 635fd12a26..a2e788a03e 100644 --- a/lang/src/accounts/signer.rs +++ b/lang/src/accounts/signer.rs @@ -1,3 +1,4 @@ +//! Type validating that the account signed the transaction use crate::error::ErrorCode; use crate::*; use solana_program::account_info::AccountInfo; @@ -10,6 +11,30 @@ use std::ops::Deref; /// Type validating that the account signed the transaction. No other ownership /// or type checks are done. If this is used, one should not try to access the /// underlying account data. +/// +/// Checks: +/// +/// - `Signer.info.is_signer == true` +/// +/// # Example +/// ```ignore +/// #[account] +/// #[derive(Default)] +/// pub struct MyData { +/// pub data: u64 +/// } +/// +/// #[derive(Accounts)] +/// pub struct Example<'info> { +/// #[account(init, payer = payer)] +/// pub my_acc: Account<'info, MyData>, +/// #[account(mut)] +/// pub payer: Signer<'info>, +/// pub system_program: Program<'info, System> +/// } +/// ``` +/// +/// When creating an account with `init`, the `payer` needs to sign the transaction. #[derive(Debug, Clone)] pub struct Signer<'info> { info: AccountInfo<'info>, diff --git a/lang/src/accounts/system_account.rs b/lang/src/accounts/system_account.rs index 889bde39e9..221792a20a 100644 --- a/lang/src/accounts/system_account.rs +++ b/lang/src/accounts/system_account.rs @@ -1,3 +1,5 @@ +//! Type validating that the account is owned by the system program + use crate::error::ErrorCode; use crate::*; use solana_program::account_info::AccountInfo; @@ -8,6 +10,11 @@ use solana_program::pubkey::Pubkey; use solana_program::system_program; use std::ops::Deref; +/// Type validating that the account is owned by the system program +/// +/// Checks: +/// +/// - `SystemAccount.info.owner == SystemProgram` #[derive(Debug, Clone)] pub struct SystemAccount<'info> { info: AccountInfo<'info>, diff --git a/lang/src/accounts/sysvar.rs b/lang/src/accounts/sysvar.rs index 20a9e3e272..5f367b6e40 100644 --- a/lang/src/accounts/sysvar.rs +++ b/lang/src/accounts/sysvar.rs @@ -1,3 +1,5 @@ +//! Type validating that the account is a sysvar and deserializing it + use crate::error::ErrorCode; use crate::{Accounts, AccountsExit, Key, ToAccountInfo, ToAccountInfos, ToAccountMetas}; use solana_program::account_info::AccountInfo; @@ -8,7 +10,27 @@ use solana_program::pubkey::Pubkey; use std::fmt; use std::ops::{Deref, DerefMut}; -/// Container for sysvars. +/// Type validating that the account is a sysvar and deserializing it. +/// +/// If possible, sysvars should not be used via accounts +/// but by using the [`get`](https://docs.rs/solana-program/latest/solana_program/sysvar/trait.Sysvar.html#method.get) +/// function on the desired sysvar. This is because using `get` +/// does not run the risk of Anchor having a bug in its `Sysvar` type +/// and using `get` also decreases tx size, making space for other +/// accounts that cannot be requested via syscall. +/// +/// # Example +/// ```ignore +/// // OK - via account in the account validation struct +/// #[derive(Accounts)] +/// pub struct Example<'info> { +/// pub clock: Sysvar<'info, Clock> +/// } +/// // BETTER - via syscall in the instruction function +/// fn better(ctx: Context) -> ProgramResult { +/// let clock = Clock::get()?; +/// } +/// ``` pub struct Sysvar<'info, T: solana_program::sysvar::Sysvar> { info: AccountInfo<'info>, account: T, diff --git a/lang/src/accounts/unchecked_account.rs b/lang/src/accounts/unchecked_account.rs index 633b599fe7..9abf315af6 100644 --- a/lang/src/accounts/unchecked_account.rs +++ b/lang/src/accounts/unchecked_account.rs @@ -1,3 +1,6 @@ +//! Explicit wrapper for AccountInfo types to emphasize +//! that no checks are performed + use crate::error::ErrorCode; use crate::{Accounts, AccountsExit, Key, ToAccountInfo, ToAccountInfos, ToAccountMetas}; use solana_program::account_info::AccountInfo; @@ -7,7 +10,8 @@ use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use std::ops::Deref; -/// Explicit wrapper for AccountInfo types. +/// Explicit wrapper for AccountInfo types to emphasize +/// that no checks are performed #[derive(Debug, Clone)] pub struct UncheckedAccount<'info>(AccountInfo<'info>); 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 6017db4ad3..661d6a9936 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 @@ -2,9 +2,6 @@ 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::*; @@ -48,8 +45,10 @@ pub enum CustomError { } #[derive(Accounts)] -#[instruction(admin_data: u64)] pub struct SetAdminSettings<'info> { + // In a real program, this should be a PDA, + // so the authority cannot create multiple settings accounts. + // Not done here for easier testing #[account(init, payer = authority)] pub settings: Account<'info, Settings>, #[account(mut)] @@ -62,8 +61,10 @@ pub struct SetAdminSettings<'info> { } #[derive(Accounts)] -#[instruction(admin_data: u64)] pub struct SetAdminSettingsUseProgramState<'info> { + // In a real program, this should be a PDA, + // so the authority cannot create multiple settings accounts. + // Not done here for easier testing #[account(init, payer = authority)] pub settings: Account<'info, Settings>, #[account(mut)]