From 5edaf7af841296079906e16bf8b9fb8795249403 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 3 Jan 2022 20:16:52 +0100 Subject: [PATCH] lang, docs: improved constraints reference (#1210) --- lang/derive/accounts/src/lib.rs | 515 +++++++++++++++++++++++++++++--- 1 file changed, 477 insertions(+), 38 deletions(-) diff --git a/lang/derive/accounts/src/lib.rs b/lang/derive/accounts/src/lib.rs index b56844961..14830feb2 100644 --- a/lang/derive/accounts/src/lib.rs +++ b/lang/derive/accounts/src/lib.rs @@ -5,55 +5,494 @@ use quote::ToTokens; use syn::parse_macro_input; /// Implements an [`Accounts`](./trait.Accounts.html) deserializer on the given -/// struct, applying any constraints specified via inert `#[account(..)]` -/// attributes upon deserialization. +/// struct. Can provide further functionality through the use of attributes. +/// +/// # Table of Contents +/// - [Instruction Attribute](#instruction-attribute) +/// - [Constraints](#constraints) +/// +/// # Instruction Attribute +/// +/// You can access the instruction's arguments with the +/// `#[instruction(..)]` attribute. You have to list them +/// in the same order as in the instruction but you can +/// omit all arguments after the last one you need. /// /// # Example /// /// ```ignore -/// #[derive(Accounts)] -/// pub struct Auth<'info> { -/// #[account(mut, has_one = authority)] -/// pub data: Account<'info, MyData>, -/// #[account(signer)] -/// pub authority: AccountInfo<'info>, +/// ... +/// pub fn initialize(ctx: Context, bump: u8, authority: Pubkey, data: u64) -> ProgramResult { +/// ... +/// Ok(()) /// } -/// -/// #[account] -/// pub struct MyData { -/// authority: Pubkey, -/// protected_data: u64, +/// ... +/// #[derive(Accounts)] +/// #[instruction(bump: u8)] +/// pub struct Initialize<'info> { +/// ... /// } /// ``` /// -/// Here, any instance of the `Auth` struct created via -/// [`try_accounts`](trait.Accounts.html#tymethod.try_accounts) is guaranteed -/// to have been both +/// # Constraints /// -/// * Signed by `authority`. -/// * Checked that `&data.authority == authority.key`. +/// There are different types of constraints that can be applied with the `#[account(..)]` attribute. /// -/// The full list of available attributes is as follows. +/// Attributes may reference other data structures. When `` is used in the tables below, an arbitrary expression +/// may be passed in as long as it evaluates to a value of the expected type, e.g. `owner = token_program.key()`. If `target_account` +/// used, the `target_account` must exist in the struct and the `.key()` is implicit, e.g. `payer = authority`. /// -/// | Attribute | Location | Description | -/// |:--|:--|:--| -/// | `#[account(signer)]`

`#[account(signer @ )]` | On raw `AccountInfo` structs. | Checks the given account signed the transaction. Custom errors are supported via `@`. | -/// | `#[account(mut)]`

`#[account(mut @ )]` | On `AccountInfo`, `Account` or `CpiAccount` structs. | Marks the account as mutable and persists the state transition. Custom errors are supported via `@`. | -/// | `#[account(init)]` | On `Account` structs. | Marks the account as being initialized, creating the account via the system program. | -/// | `#[account(init_if_needed)]` | On `Account` structs. | Same as `init` but skip if already initialized. | -/// | `#[account(zero)]` | On `Account` structs. | Asserts the account discriminator is zero. | -/// | `#[account(close = )]` | On `Account` and `AccountLoader` structs. | Marks the account as being closed at the end of the instruction's execution, sending the rent exemption lamports to the specified . | -/// | `#[account(has_one = )]`

`#[account(has_one = @ )]` | On `Account` or `CpiAccount` structs | Checks the `target` field on the account matches the `target` field in the struct deriving `Accounts`. Custom errors are supported via `@`. | -/// | `#[account(seeds = [], bump? = , payer? = , space? = , owner? = )]` | On `AccountInfo` structs | Seeds for the program derived address an `AccountInfo` struct represents. If bump is provided, then appends it to the seeds. On initialization, validates the given bump is the bump provided by `Pubkey::find_program_address`.| -/// | `#[account(constraint = )]`

`#[account(constraint = @ )]` | On any type deriving `Accounts` | Executes the given code as a constraint. The expression should evaluate to a boolean. Custom errors are supported via `@`. | -/// | `#[account("")]` | Deprecated | Executes the given code literal as a constraint. The literal should evaluate to a boolean. | -/// | `#[account(rent_exempt = )]` | On `AccountInfo` or `Account` structs | Optional attribute to skip the rent exemption check. By default, all accounts marked with `#[account(init)]` will be rent exempt, and so this should rarely (if ever) be used. Similarly, omitting `= skip` will mark the account rent exempt. | -/// | `#[account(executable)]` | On `AccountInfo` structs | Checks the given account is an executable program. | -/// | `#[account(state = )]` | On `CpiState` structs | Checks the given state is the canonical state account for the target program. | -/// | `#[account(owner = )]`

`#[account(owner = @ )]` | On `CpiState`, `CpiAccount`, and `AccountInfo` | Checks the account owner matches the target. Custom errors are supported via `@`. | -/// | `#[account(address = )]`

`#[account(address = @ )]` | On `AccountInfo` and `Account` | Checks the account key matches the pubkey. Custom errors are supported via `@`. | -// TODO: How do we make the markdown render correctly without putting everything -// on absurdly long lines? +/// - [Normal Constraints](#normal-constraints) +/// - [SPL Constraints](#spl-constraints) +/// # Normal Constraints +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +///
AttributeDescription
+/// #[account(signer)]

#[account(signer @ <custom_error>)] +///
+/// Checks the given account signed the transaction.
+/// Custom errors are supported via @.
+/// Consider using the Signer type if you would only have this constraint on the account.

+/// Example: +///

+/// #[account(signer)]
+/// pub authority: AccountInfo<'info>,
+/// #[account(signer @ MyError::MyErrorCode)]
+/// pub payer: AccountInfo<'info>
+///                 
+///
+/// #[account(mut)]

#[account(mut @ <custom_error>)] +///
+/// Checks the given account is mutable.
+/// Makes anchor persist any state changes.
+/// Custom errors are supported via @.

+/// Example: +///

+/// #[account(mut)]
+/// pub data_account: Account<'info, MyData>,
+/// #[account(mut @ MyError::MyErrorCode)]
+/// pub data_account_two: Account<'info, MyData>
+///                 
+///
+/// #[account(init, payer = <target_account>)]

+/// #[account(init, payer = <target_account>, space = <num_bytes>)] +///
+/// Creates the account via a CPI to the system program and +/// initializes it (sets its account discriminator).
+/// Marks the account as mutable and is mutually exclusive with mut.
+/// Makes the account rent exempt unless skipped with `rent_exempt = skip`.
+///
    +///
  • +/// Requires the payer constraint to also be on the account. +/// The payer account pays for the +/// account creation. +///
  • +///
  • +/// Requires the system program to exist on the struct +/// and be called system_program. +///
  • +///
  • +/// Requires that the space constraint is specified +/// or, if creating an Account type, the T of Account +/// to implement the rust std Default trait.
    +/// When using the space constraint, one must remember to add 8 to it +/// which is the size of the account discriminator.
    +/// The given number is the size of the account in bytes, so accounts that hold +/// a variable number of items such as a Vec should use the space +/// constraint instead of using the Default trait and allocate sufficient space for all items that may +/// be added to the data structure because account size is fixed. Check out the borsh library +/// (which anchor uses under the hood for serialization) specification to learn how much +/// space different data structures require. +///
  • +///
    +/// Example: +///
    +/// #[account]
    +/// #[derive(Default)]
    +/// pub struct MyData {
    +///     pub data: u64
    +/// }
    
    +/// #[account]
    +/// pub struct OtherData {
    +///     pub data: u64
    +/// }
    
    +/// #[derive(Accounts)]
    +/// pub struct Initialize<'info> {
    +///     #[account(init, payer = payer)]
    +///     pub data_account: Account<'info, MyData>,
    +///     #[account(init, payer = payer, space = 8 + 8)]
    +///     pub data_account_two: Account<'info, OtherData>,
    +///     #[account(mut)]
    +///     pub payer: Signer<'info>,
    +///     pub system_program: Program<'info, System>,
    +/// }
    +///                 
    +///
+/// init can be combined with other constraints (at the same time): +///
    +///
  • +/// By default init sets the owner field of the created account to the +/// currently executing program. Add the owner constraint to specify a +/// different program owner. +///
  • +///
  • +/// Use the seeds constraint together with bumpto create PDAs.
    +/// init uses find_program_address to calculate the pda so the +/// bump value can be left empty.
    +/// However, if you want to use the bump in your instruction, +/// you can pass it in as instruction data and set the bump value like shown in the example, +/// using the instruction_data attribute. +/// Anchor will then check that the bump returned by find_program_address equals +/// the bump in the instruction data. +///
  • +///
+/// Example: +///
+/// #[derive(Accounts)]
+/// #[instruction(bump: u8)]
+/// pub struct Initialize<'info> {
+///     #[account(
+///         init, payer = payer,
+///         seeds = [b"example_seed".as_ref()], bump = bump
+///     )]
+///     pub pda_data_account: Account<'info, MyData>,
+///     #[account(
+///         init, payer = payer,
+///         space = 8 + 8, owner = other_program.key()
+///     )]
+///     pub account_for_other_program: AccountInfo<'info>,
+///     #[account(
+///         init,payer = payer, space = 8 + 8,
+///         owner = other_program.key(),
+///         seeds = [b"other_seed".as_ref()], bump
+///     )]
+///     pub pda_for_other_program: AccountInfo<'info>,
+///     #[account(mut)]
+///     pub payer: Signer<'info>,
+///     pub system_program: Program<'info, System>,
+///     pub other_program: Program<'info, OtherProgram>
+/// }
+///                 
+///
+/// #[account(init_if_needed, payer = <target_account>)]

+/// #[account(init_if_needed, payer = <target_account>, space = <num_bytes>)] +///
+/// Exact same functionality as the init constraint but only runs if the account does not exist yet.
+/// If it does exist, it still checks whether the given init constraints are correct, +/// e.g. that the account has the expected amount of space and, if it's a PDA, the correct seeds etc. +///

+/// Example: +///
+/// #[account]
+/// #[derive(Default)]
+/// pub struct MyData {
+///     pub data: u64
+/// }

+/// #[account]
+/// pub struct OtherData {
+///     pub data: u64
+/// }

+/// #[derive(Accounts)]
+/// pub struct Initialize<'info> {
+///     #[account(init_if_needed, payer = payer)]
+///     pub data_account: Account<'info, MyData>,
+///     #[account(init_if_needed, payer = payer, space = 8 + 8)]
+///     pub data_account_two: Account<'info, OtherData>,
+///     #[account(mut)]
+///     pub payer: Signer<'info>,
+///     pub system_program: Program<'info, System>
+/// }
+///                 
+///
+/// #[account(seeds = <seeds>, bump)]

+/// #[account(seeds = <seeds>, bump = <expr>)] +///
+/// Checks that given account is a PDA derived from the currently executing program, +/// the seeds, and if provided, the bump. If not provided, anchor uses the canonical +/// bump. Will be adjusted in the future to allow PDA to be derived from other programs.
+/// This constraint behaves slightly differently when used with init. +/// See its description. +///

+/// Example: +///

+/// #[account(seeds = [b"example_seed], bump)]
+/// pub canonical_pda: AccountInfo<'info>,
+/// #[account(seeds = [b"other_seed], bump = 142)]
+/// pub arbitrary_pda: AccountInfo<'info>
+///                 
+///
+/// #[account(has_one = <target_account>)]

+/// #[account(has_one = <target_account> @ <custom_error>)] +///
+/// Checks the target_account field on the account matches the +/// key of the target_account field in the Accounts struct.
+/// Custom errors are supported via @.

+/// Example: +///

+/// #[account(mut, has_one = authority)]
+/// pub data: Account<'info, MyData>,
+/// pub authority: Signer<'info>
+///                 
+/// In this example has_one checks that data.authority = authority.key() +///
+/// #[account(address = <expr>)]

+/// #[account(address = <expr> @ <custom_error>)] +///
+/// Checks the account key matches the pubkey.
+/// Custom errors are supported via @.

+/// Example: +///

+/// #[account(address = crate::ID)]
+/// pub data: Account<'info, MyData>,
+/// #[account(address = crate::ID @ MyError::MyErrorCode)]
+/// pub data_two: Account<'info, MyData>
+///                 
+///
+/// #[account(owner = <expr>)]

+/// #[account(owner = <expr> @ <custom_error>)] +///
+/// Checks the account owner matches expr.
+/// Custom errors are supported via @.

+/// Example: +///

+/// #[account(owner = Token::ID @ MyError::MyErrorCode)]
+/// pub data: Account<'info, MyData>,
+/// #[account(owner = token_program.key())]
+/// pub data_two: Account<'info, MyData>,
+/// pub token_program: Program<'info, Token>
+///                 
+///
+/// #[account(executable)] +/// +/// Checks the account is executable (i.e. the account is a program).
+/// You may want to use the Program type instead.

+/// Example: +///

+/// #[account(executable)]
+/// pub my_program: AccountInfo<'info>
+///                 
+///
+/// #[account(rent_exempt = skip)]

+/// #[account(rent_exempt = enforce)] +///
+/// Enforces rent exemption with = enforce.
+/// Skips rent exemption check that would normally be done +/// through other constraints with = skip, +/// e.g. when used with the zero constraint

+/// Example: +///

+/// #[account(zero, rent_exempt = skip)]
+/// pub skipped_account: Account<'info, MyData>,
+/// #[account(rent_exempt = enforce)]
+/// pub enforced_account: AccountInfo<'info>
+///                 
+///
+/// #[account(zero)] +/// +/// Checks the account discriminator is zero.
+/// Enforces rent exemption unless skipped with rent_exempt = skip

+/// Example: +///

+/// #[account(zero)]
+/// pub my_account: Account<'info, MyData>
+///                 
+///
+/// #[account(close = <target_account>)] +/// +/// Marks the account as closed at the end of the instruction’s execution +/// (sets its discriminator to the CLOSED_ACCOUNT_DISCRIMINATOR) +/// and sends its lamports to the specified account.
+/// Setting the discriminator to a special variant +/// makes account revival attacks (where a subsequent instruction +/// adds the rent exemption lamports again) impossible.
+/// Requires mut to exist on the account. +///

+/// Example: +///

+/// #[account(mut, close = receiver)]
+/// pub data_account: Account<'info, MyData>,
+/// #[account(mut)]
+/// pub receiver: SystemAccount<'info>
+///                 
+///
+/// #[account(constraint = <expr>)]

#[account(constraint = <expr> @ <custom_error>)] +///
+/// Constraint that checks whether the given expression evaluates to true.
+/// Use this when no other constraint fits your use case. +///

+/// Example: +///

+/// #[account(constraint = one.keys[0].age == two.apple.age)]
+/// pub one: Account<'info, MyData>,
+/// pub two: Account<'info, OtherData>
+///                 
+///
+/// +/// # SPL Constraints +/// +/// Anchor provides constraints that make verifying SPL accounts easier. +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +///
AttributeDescription
+/// #[account(token::mint = <target_account>, token::authority = <target_account>)] +/// +/// Can currently only be used with init to create a token +/// account with the given mint address and authority. +///

+/// Example: +///
+/// use anchor_spl::{mint, token::{TokenAccount, Mint}};
+/// ...

+/// #[account(
+///     init,
+///     payer = payer,
+///     token::mint = mint,
+///     token::authority = payer,
+/// )]
+/// pub token: Account<'info, TokenAccount>,
+/// #[account(address = mint::USDC)]
+/// pub mint: Account<'info, Mint>,
+/// #[account(mut)]
+/// pub payer: Signer<'info>,
+///                 
+///
+/// #[account(mint::authority = <target_account>, mint::decimals = <expr>)] +///

+/// #[account(mint::authority = <target_account>, mint::decimals = <expr>, mint::freeze_authority = <target_account>)] +///
+/// Can currently only be used with init to create a mint +/// account with the given mint decimals and mint authority.
+/// The freeze authority is optional. +///

+/// Example: +///
+/// use anchor_spl::token::Mint;
+/// ...

+/// #[account(
+///     init,
+///     payer = payer,
+///     mint::decimals = 9,
+///     mint::authority = payer,
+/// )]
+/// pub mint_one: Account<'info, Mint>,
+/// #[account(
+///     init,
+///     payer = payer,
+///     mint::decimals = 9,
+///     mint::authority = payer,
+///     mint::freeze_authority = payer
+/// )]
+/// pub mint_two: Account<'info, Mint>,
+/// #[account(mut)]
+/// pub payer: Signer<'info>,
+///                 
+///
+/// #[account(associated_token::mint = <target_account>, associated_token::authority = <target_account>)] +/// +/// Can be used as a standalone as a check or with init to create an associated token +/// account with the given mint address and authority. +///

+/// Example: +///
+/// use anchor_spl::{mint, token::{TokenAccount, Mint}};
+/// ...

+/// #[account(
+///     init,
+///     payer = payer,
+///     associated_token::mint = mint,
+///     associated_token::authority = payer,
+/// )]
+/// pub token: Account<'info, TokenAccount>,
+/// #[account(
+///     associated_token::mint = mint,
+///     associated_token::authority = payer,
+/// )]
+/// pub second_token: Account<'info, TokenAccount>,
+/// #[account(address = mint::USDC)]
+/// pub mint: Account<'info, Mint>,
+/// #[account(mut)]
+/// pub payer: Signer<'info>,
+///                 
+///
#[proc_macro_derive(Accounts, attributes(account, instruction))] pub fn derive_anchor_deserialize(item: TokenStream) -> TokenStream { parse_macro_input!(item as anchor_syn::AccountsStruct)