Anchor Program Structure
The Anchor framework uses Rust macros to reduce boilerplate code and simplify the implementation of common security checks required for writing Solana programs.
The main macros found in an Anchor program include:
declare_id
: Specifies the program's on-chain address#[program]
: Specifies the module containing the program’s instruction logic#[derive(Accounts)]
: Applied to structs to indicate a list of accounts required by an instruction#[account]
: Applied to structs to create custom account types for the program
Example Program
Let's examine a simple program that demonstrates the usage of the macros mentioned above to understand the basic structure of an Anchor program.
The example program below creates a new account (NewAccount
) that stores a
u64
value passed to the initialize
instruction.
declare_id! macro
The
declare_id
macro specifies the on-chain address of the program, known as the program ID.
By default, the program ID is the public key of the keypair generated at
/target/deploy/your_program_name.json
.
To update the value of the program ID in the declare_id
macro with the public
key of the keypair in the /target/deploy/your_program_name.json
file, run the
following command:
The anchor keys sync
command is useful to run when cloning a repository where
the value of the program ID in a cloned repo's declare_id
macro won't match
the one generated when you run anchor build
locally.
#[program] macro
The
#[program]
macro defines the module that contains all the instruction handlers for your
program. Each public function within this module corresponds to an instruction
that can be invoked.
Instruction Context
Instruction handlers are functions that define the logic executed when an
instruction is invoked. The first parameter of each handler is a Context<T>
type, where T
is a struct implementing the Accounts
trait and specifies the
accounts the instruction requires.
The
Context
type provides the instruction with access to the following non-argument inputs:
The Context
fields can be accessed in an instruction using dot notation:
ctx.accounts
: The accounts required for the instructionctx.program_id
: The program's public key (address)ctx.remaining_accounts
: Additional accounts not specified in theAccounts
struct.ctx.bumps
: Bump seeds for any Program Derived Address (PDA) accounts specified in theAccounts
struct
Additional parameters are optional and can be included to specify arguments that must be provided when the instruction is invoked.
In this example, the Initialize
struct implements the Accounts
trait where
each field in the struct represents an account required by the initialize
instruction.
#[derive(Accounts)] macro
The
#[derive(Accounts)]
macro is applied to a struct to specify the accounts that must be provided when
an instruction is invoked. This macro implements the
Accounts
trait, which simplifies account validation and serialization and deserialization
of account data.
Each field in the struct represents an account required by an instruction. The naming of each field is arbitrary, but it is recommended to use a descriptive name that indicates the purpose of the account.
Account Validation
To prevent security vulnerabilities, it's important to verify that accounts provided to an instruction are the expected accounts. Accounts are validated in Anchor programs in two ways that are generally used together:
-
Account Constraints: Constraints define additional conditions that an account must satisfy to be considered valid for the instruction. Constraints are applied using the
#[account(..)]
attribute, which is placed above a field in a struct that implements theAccounts
trait.You can find the implementation of the constraints here.
-
Account Types: Anchor provides various account types to help ensure that the account provided by the client matches what the program expects.
You can find the implementation of the account types here.
When an instruction in an Anchor program is invoked, the program first validates
the accounts provided before executing the instruction's logic. After
validation, these accounts can be accessed within the instruction using the
ctx.accounts
syntax.
#[account] macro
The
#[account]
macro is applied to structs that define the data stored in custom accounts
created by your program.
This macro implements various traits
detailed here.
The key functionalities of the #[account]
macro include:
- Assign Program Owner:
When creating an account, the program owner of the account is automatically
set to the program specified in
declare_id
. - Set Discriminator: A unique 8 byte discriminator, specific to the account type, is added as the first 8 bytes of account data during its initialization. This helps in differentiating account types and is used for account validation.
- Data Serialization and Deserialization: Account data is automatically serialized and deserialized as the account type.
Account Discriminator
An account discriminator in an Anchor program refers to an 8 byte identifier
unique to each account type. It's derived from the first 8 bytes of the SHA256
hash of the string account:<AccountName>
. This discriminator is stored as the
first 8 bytes of account data when an account is created.
When creating an account in an Anchor program, 8 bytes must be allocated for the discriminator.
The discriminator is used during the following two scenarios:
- Initialization: When an account is created, the discriminator is set as the first 8 bytes of the account's data.
- Deserialization: When account data is deserialized, the first 8 bytes of account data is checked against the discriminator of the expected account type.
If there's a mismatch, it indicates that the client has provided an unexpected account. This mechanism serves as an account validation check in Anchor programs.
Last updated on