|
@@ -1,3 +1,5 @@
|
|
|
+//! Account container that checks ownership on deserialization.
|
|
|
+
|
|
|
use crate::error::ErrorCode;
|
|
|
use crate::*;
|
|
|
use solana_program::account_info::AccountInfo;
|
|
@@ -8,7 +10,115 @@ use solana_program::pubkey::Pubkey;
|
|
|
use std::fmt;
|
|
|
use std::ops::{Deref, DerefMut};
|
|
|
|
|
|
-/// Account container that checks ownership on deserialization.
|
|
|
+/// Wrapper around [`AccountInfo`](crate::solana_program::account_info::AccountInfo)
|
|
|
+/// that verifies program ownership and deserializes underlying data into a Rust type.
|
|
|
+///
|
|
|
+/// 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).
|
|
|
+/// The `#[account]` attribute implements the Owner trait for
|
|
|
+/// a struct using the `crate::ID` declared by [`declareId`](crate::declare_id)
|
|
|
+/// in the same program. It follows that Account can also be used
|
|
|
+/// with a `T` that comes from a different program.
|
|
|
+///
|
|
|
+/// Checks:
|
|
|
+///
|
|
|
+/// - `Account.info.owner == T::owner()`
|
|
|
+/// - `!(Account.info.owner == SystemProgram && Account.info.lamports() == 0)`
|
|
|
+///
|
|
|
+/// Example
|
|
|
+/// ```ignore
|
|
|
+/// use anchor_lang::prelude::*;
|
|
|
+/// use other_program::Auth;
|
|
|
+///
|
|
|
+/// declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
|
|
|
+///
|
|
|
+/// #[program]
|
|
|
+/// mod hello_anchor {
|
|
|
+/// use super::*;
|
|
|
+/// pub fn set_data(ctx: Context<SetData>, data: u64) -> ProgramResult {
|
|
|
+/// if (*ctx.accounts.auth_account).authorized {
|
|
|
+/// (*ctx.accounts.my_account).data = data;
|
|
|
+/// }
|
|
|
+/// Ok(())
|
|
|
+/// }
|
|
|
+/// }
|
|
|
+///
|
|
|
+/// #[account]
|
|
|
+/// #[derive(Default)]
|
|
|
+/// pub struct MyData {
|
|
|
+/// pub data: u64
|
|
|
+/// }
|
|
|
+///
|
|
|
+/// #[derive(Accounts)]
|
|
|
+/// pub struct SetData<'info> {
|
|
|
+/// #[account(mut)]
|
|
|
+/// pub my_account: Account<'info, MyData> // checks that my_account.info.owner == Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS
|
|
|
+/// pub auth_account: Account<'info, Auth> // checks that auth_account.info.owner == FEZGUxNhZWpYPj9MJCrZJvUo1iF9ys34UHx52y4SzVW9
|
|
|
+/// }
|
|
|
+///
|
|
|
+/// // In a different program
|
|
|
+///
|
|
|
+/// ...
|
|
|
+/// declare_id!("FEZGUxNhZWpYPj9MJCrZJvUo1iF9ys34UHx52y4SzVW9");
|
|
|
+/// #[account]
|
|
|
+/// #[derive(Default)]
|
|
|
+/// pub struct Auth {
|
|
|
+/// pub authorized: bool
|
|
|
+/// }
|
|
|
+/// ...
|
|
|
+/// ```
|
|
|
+///
|
|
|
+/// Account can also be used with non-anchor programs. The data types from
|
|
|
+/// those programs are not annotated with `#[account]` so you have to
|
|
|
+/// - create a wrapper type around the structs you want to wrap with Account
|
|
|
+/// - implement the functions required by Account yourself
|
|
|
+/// instead of using `#[account]`. You only have to implement a fraction of the
|
|
|
+/// 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))
|
|
|
+/// ```ignore
|
|
|
+/// #[derive(Clone)]
|
|
|
+/// pub struct Mint(spl_token::state::Mint);
|
|
|
+///
|
|
|
+/// // This is necessary so we can use "anchor_spl::token::Mint::LEN"
|
|
|
+/// // because rust does not resolve "anchor_spl::token::Mint::LEN" to
|
|
|
+/// // "spl_token::state::Mint::LEN" automatically
|
|
|
+/// impl Mint {
|
|
|
+/// pub const LEN: usize = spl_token::state::Mint::LEN;
|
|
|
+/// }
|
|
|
+///
|
|
|
+/// // You don't have to implement the "try_deserialize" function
|
|
|
+/// // from this trait. It delegates to
|
|
|
+/// // "try_deserialize_unchecked" by default which is what we want here
|
|
|
+/// // because non-anchor accounts don't have a discriminator to check
|
|
|
+/// impl anchor_lang::AccountDeserialize for Mint {
|
|
|
+/// fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError> {
|
|
|
+/// spl_token::state::Mint::unpack(buf).map(Mint)
|
|
|
+/// }
|
|
|
+/// }
|
|
|
+/// // AccountSerialize defaults to a no-op which is what we want here
|
|
|
+/// // because it's a foreign program, so our program does not
|
|
|
+/// // have permission to write to the foreign program's accounts anyway
|
|
|
+/// impl anchor_lang::AccountSerialize for Mint {}
|
|
|
+///
|
|
|
+/// impl anchor_lang::Owner for Mint {
|
|
|
+/// fn owner() -> Pubkey {
|
|
|
+/// // pub use spl_token::ID is used at the top of the file
|
|
|
+/// ID
|
|
|
+/// }
|
|
|
+/// }
|
|
|
+///
|
|
|
+/// // Implement the "std::ops::Deref" trait for better user experience
|
|
|
+/// impl Deref for Mint {
|
|
|
+/// type Target = spl_token::state::Mint;
|
|
|
+///
|
|
|
+/// fn deref(&self) -> &Self::Target {
|
|
|
+/// &self.0
|
|
|
+/// }
|
|
|
+/// }
|
|
|
+/// ```
|
|
|
#[derive(Clone)]
|
|
|
pub struct Account<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> {
|
|
|
account: T,
|