Source: https://github.com/coral-xyz/anchor Guide length: 53,636 chars Extraction method: Concept-driven LangGraph
================================================================================
1. Rust eDSL (Embedded Domain-Specific Language) - A specialized syntax and set of constructs in Rust for writing Solana programs, allowing developers to define program logic and data structures in a more intuitive way.
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
mod counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>, start: u64) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.authority = *ctx.accounts.authority.key;
counter.count = start;
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = authority, space = 48)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, has_one = authority)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
#[account]
pub struct Counter {
pub authority: Pubkey,
pub count: u64,
}- Account Struct: Defines the data structure stored on-chain.
#[account]
pub struct Counter {
pub authority: Pubkey,
pub count: u64,
}- Context Structs: Define the accounts required for each instruction.
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = authority, space = 48)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}- Define Program ID: Use
declare_id!to set the program's unique identifier. - Create Program Module: Define a module with the
#[program]attribute. - Implement Instructions: Write functions for each instruction, using
Contextto access accounts. - Define Account Structures: Use
#[account]to define the data structures that will be stored on-chain. - Handle Results: Use
Result<()>for function return types to handle success and errors.
- Initialization: Call
initializeto set up the counter. - Increment: Call
incrementto increase the counter value.
- Account Size: Ensure the space allocated for accounts is sufficient (e.g.,
space = 48). - Signer Requirement: Use
Signer<'info>for accounts that must authorize actions.
- Data Structure Size: Keep account structures minimal to reduce storage costs.
- Batch Operations: Consider batching multiple operations to minimize transaction fees.
- Authority Checks: Always verify the authority of accounts before modifying state (e.g.,
has_one = authority). - Error Handling: Use
Resulttypes to propagate errors effectively.
- Account Initialization Errors: Ensure accounts are initialized correctly with the right space.
- Authority Mismatches: Verify that the authority account matches the expected signer.
- Initialization Fix: Ensure the
#[account(init)]attribute is used correctly.
#[account(init, payer = authority, space = 48)]
pub counter: Account<'info, Counter>,- Authority Check Fix: Use
#[account(mut, has_one = authority)]to enforce authority checks.
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, has_one = authority)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}- Use
Resultto handle errors gracefully and provide meaningful feedback.
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
Ok(())
}- Modular Design: Keep program logic modular by separating concerns into different functions and modules.
- Use Contexts: Leverage
Contextstructs to encapsulate account requirements for each instruction.
- Group Related Functions: Organize functions that operate on the same data structure together.
- Consistent Naming: Use clear and consistent naming conventions for functions and structs.
- Minimize State Changes: Reduce the number of state changes in a single transaction to save on fees.
- Efficient Data Structures: Use efficient data structures to minimize on-chain storage costs.
By following these guidelines, developers can effectively utilize the Rust eDSL for writing Solana programs, ensuring robust, efficient, and secure applications.
2. IDL (Interface Description Language) - A specification that describes the interface of Solana programs, enabling automatic client generation in TypeScript and facilitating interaction with the program.
When working with IDL in the Solana Anchor framework, you typically define your program's interface using Rust. Below is an example of a simple counter program that demonstrates function calls and method signatures.
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
mod counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>, start: u64) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.authority = *ctx.accounts.authority.key;
counter.count = start;
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = authority, space = 48)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, has_one = authority)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
#[account]
pub struct Counter {
pub authority: Pubkey,
pub count: u64,
}The IDL allows you to define types and interfaces that can be used in TypeScript clients. Below is an example of how you might define an interface for the counter program.
export interface Counter {
authority: string;
count: number;
}
export interface Initialize {
accounts: {
counter: string;
authority: string;
};
args: {
start: number;
};
}
export interface Increment {
accounts: {
counter: string;
authority: string;
};
}- Define Your Program: Create your program in Rust using the Anchor framework.
- Generate IDL: Use the Anchor CLI to generate the IDL for your program.
- Use IDL in TypeScript: Import the generated IDL in your TypeScript client to interact with the program.
-
Initialize the Counter:
const initializeCounter = async (provider: Provider, start: number) => { const program = new Program(idl, programId, provider); const tx = await program.rpc.initialize(new anchor.BN(start), { accounts: { counter: counterPublicKey, authority: provider.wallet.publicKey, systemProgram: SystemProgram.programId, }, }); console.log("Transaction signature", tx); };
-
Increment the Counter:
const incrementCounter = async (provider: Provider, counterPublicKey: PublicKey) => { const program = new Program(idl, programId, provider); const tx = await program.rpc.increment({ accounts: { counter: counterPublicKey, authority: provider.wallet.publicKey, }, }); console.log("Transaction signature", tx); };
- Ensure that the account structures defined in Rust match the TypeScript interfaces.
- The IDL must be generated after any changes to the program to keep the TypeScript client in sync.
- Minimize the number of state changes in a single transaction to reduce costs.
- Use batch processing where applicable to optimize performance.
- Validate all inputs in your program to prevent unexpected behavior.
- Use appropriate access controls to restrict who can call certain functions.
- Mismatched Types: Ensure that the types defined in Rust match those in TypeScript.
- Account Initialization Errors: Ensure that accounts are initialized correctly before use.
- If you encounter a type mismatch, double-check the IDL and TypeScript interfaces.
- For account initialization errors, verify that the payer and space parameters are correctly set.
Use Rust's Result type to handle errors gracefully:
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
if counter.count >= MAX_COUNT {
return Err(ErrorCode::CountExceeded.into());
}
counter.count += 1;
Ok(())
}- Use the IDL to generate TypeScript clients to ensure type safety.
- Keep your Rust program logic modular to facilitate testing and maintenance.
- Organize your Rust code into modules based on functionality.
- Keep TypeScript clients in a separate directory to maintain a clean project structure.
- Use efficient data structures and algorithms in your Rust code.
- Profile your program to identify bottlenecks and optimize accordingly.
By following these guidelines, you can effectively utilize IDL in the Solana Anchor framework to create robust and efficient programs.
3. Accounts - A core concept in Anchor that represents stateful data stored on the Solana blockchain. Developers define accounts using the #[account] attribute, which specifies the structure and constraints of the account data.
To define and use accounts in Anchor, you typically create a struct that represents the account's data and use the #[account] attribute to specify its structure.
use anchor_lang::prelude::*;
#[account]
pub struct Counter {
pub authority: Pubkey,
pub count: u64,
}Here’s a complete example of a simple counter program that uses accounts:
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
mod counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>, start: u64) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.authority = *ctx.accounts.authority.key;
counter.count = start;
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = authority, space = 48)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, has_one = authority)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}- Account Type: Use
Account<'info, T>whereTis the struct representing the account data. - Signer Type: Use
Signer<'info>for accounts that need to sign transactions.
- Define the Account Structure: Create a struct with the required fields and annotate it with
#[account]. - Implement Context Structs: Define context structs for each instruction that includes the accounts needed.
- Initialize Accounts: Use the
#[account(init)]attribute to create new accounts. - Access and Modify Account Data: Use mutable references to access and modify account data within instruction functions.
- Call
initializeto create a newCounteraccount. - Call
incrementto update thecountfield in theCounteraccount.
- Account Size: Specify the space required for the account using the
spaceparameter in the#[account(init)]attribute. - Ownership: Use constraints like
has_oneto enforce ownership rules.
- Account Initialization: Ensure accounts are initialized properly to avoid runtime errors.
- Data Access: Minimize the number of mutable references to accounts to reduce the risk of data races.
- Signer Verification: Always verify that the account calling the instruction is a valid signer.
- Data Validation: Implement checks to validate data before modifying account states.
- Uninitialized Accounts: Attempting to access uninitialized accounts will result in runtime errors.
- Ownership Violations: Failing to meet ownership constraints can lead to transaction failures.
- Initialization Check: Always check if an account is initialized before accessing its data.
if !ctx.accounts.counter.is_initialized {
return Err(ErrorCode::AccountNotInitialized.into());
}- Ownership Enforcement: Use the
has_oneconstraint to enforce ownership rules.
- Use
Result<()>for functions to handle errors gracefully. - Define custom error types using the
#[error]attribute.
- Use Context Structs: Always define context structs for clarity and to enforce account constraints.
- Keep Account Logic Encapsulated: Encapsulate account logic within the program to maintain separation of concerns.
- Group Related Accounts: Organize accounts that are used together in the same context struct.
- Modularize Code: Break down large programs into smaller modules for better maintainability.
- Batch Updates: If multiple accounts need to be updated, consider batching updates to minimize transaction costs.
- Use Efficient Data Structures: Choose data structures that minimize space and access time for account data.
By following these guidelines, you can effectively utilize accounts in the Solana Anchor framework to build robust and efficient programs.
4. Context - A structure that provides access to the accounts and other contextual information during the execution of a program instruction. It is passed as an argument to instruction handlers.
The Context structure is used to access accounts and contextual information during the execution of program instructions. It is passed as an argument to instruction handlers.
use anchor_lang::prelude::*;
#[program]
mod my_program {
use super::*;
pub fn my_instruction(ctx: Context<MyInstruction>, param: u64) -> Result<()> {
// Accessing accounts from context
let my_account = &mut ctx.accounts.my_account;
my_account.value += param;
Ok(())
}
}
#[derive(Accounts)]
pub struct MyInstruction<'info> {
#[account(mut)]
pub my_account: Account<'info, MyAccount>,
}
#[account]
pub struct MyAccount {
pub value: u64,
}Here’s a complete example demonstrating the use of Context:
use anchor_lang::prelude::*;
declare_id!("YourProgramIdHere");
#[program]
mod counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>, start: u64) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.authority = *ctx.accounts.authority.key;
counter.count = start;
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = authority, space = 48)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, has_one = authority)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
#[account]
pub struct Counter {
pub authority: Pubkey,
pub count: u64,
}-
Define the Context Structure: Create a struct that derives from
Accountsto define the accounts needed for your instruction. -
Access Accounts in Instruction Handlers: Use the
ctxparameter in your instruction handler to access the accounts. -
Modify Account Data: Perform operations on the accounts as needed.
- Call the instruction handler with the
Contextas an argument. - Access the accounts through
ctx.accounts. - Modify the account data and return a
Result.
- Ensure that the accounts passed in the
Contextare initialized and have the correct permissions. - Use the
#[account]attribute to define the structure of your accounts.
- Minimize the number of mutable accounts to reduce the risk of state conflicts.
- Batch updates to accounts when possible to optimize transaction costs.
- Validate all inputs to prevent unauthorized access or manipulation of account data.
- Use the
has_oneconstraint to enforce ownership and permissions on accounts.
- Uninitialized Accounts: Attempting to access an uninitialized account will result in a runtime error.
- Ensure accounts are initialized before use:
#[account(init, payer = authority, space = 8)]
pub my_account: Account<'info, MyAccount>,- Use
Resultto handle errors gracefully:
pub fn my_instruction(ctx: Context<MyInstruction>, param: u64) -> Result<()> {
if param == 0 {
return Err(ErrorCode::InvalidParameter.into());
}
// Proceed with logic
Ok(())
}- Use
Contextto encapsulate all account-related logic in a single struct. - Keep your instruction handlers focused on a single responsibility.
- Group related accounts in a single
Contextstruct to improve readability. - Use clear naming conventions for accounts and contexts to enhance maintainability.
- Avoid unnecessary mutable references to accounts.
- Use
#[account(mut)]only when modifications are required.
By following these guidelines, you can effectively utilize the Context structure in the Solana Anchor framework to manage accounts and contextual information in your programs.
5. Program - The main entry point for a Solana program, defined using the #[program] attribute. It contains instruction handlers that define the logic for various operations.
In a Solana program, the main entry point is defined using the #[program] attribute. Below is an example of a simple counter program:
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
mod counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>, start: u64) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.authority = *ctx.accounts.authority.key;
counter.count = start;
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
Ok(())
}
}Define the account structures and contexts as follows:
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = authority, space = 48)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, has_one = authority)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
#[account]
pub struct Counter {
pub authority: Pubkey,
pub count: u64,
}- Define the Program: Use the
#[program]attribute to define the main program module. - Create Instruction Handlers: Implement functions for each operation (e.g.,
initialize,increment). - Define Account Structures: Use
#[derive(Accounts)]to define the context for each instruction. - Implement Logic: Write the logic for each instruction, ensuring to handle the accounts correctly.
- Call
initializeto set up the counter with an initial value. - Call
incrementto increase the counter value.
- Ensure that the
#[account]attributes are correctly defined to manage account state. - Use the
#[program]attribute to encapsulate all instruction handlers.
- Minimize state changes to reduce transaction costs.
- Use efficient data structures for account storage.
- Validate all inputs to prevent unauthorized access or manipulation.
- Use the
has_oneconstraint to enforce ownership rules on accounts.
- Account Initialization Errors: Ensure that accounts are initialized correctly with the right space and payer.
- Ownership Violations: Ensure that the
authoritymatches the expected signer in the context.
-
Check for the correct initialization of accounts:
#[account(init, payer = authority, space = 48)] pub counter: Account<'info, Counter>,
-
Handle errors gracefully:
pub fn increment(ctx: Context<Increment>) -> Result<()> { let counter = &mut ctx.accounts.counter; if counter.authority != *ctx.accounts.authority.key { return Err(ErrorCode::Unauthorized.into()); } counter.count += 1; Ok(()) }
- Use
Result<()>for functions to indicate success or failure. - Define custom error types using the
#[error]attribute.
- Group related functions within the same program module.
- Use clear and descriptive names for functions and account structures.
- Keep the program logic separate from account definitions.
- Use modules to organize related functionality.
- Batch state changes when possible to reduce transaction costs.
- Use efficient data types and minimize unnecessary computations.
By following these guidelines, you can effectively utilize the #[program] attribute in your Solana programs, ensuring robust and efficient smart contract development.
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
mod my_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
let state = &mut ctx.accounts.state;
state.data = data;
Ok(())
}
pub fn update(ctx: Context<Update>, new_data: u64) -> Result<()> {
let state = &mut ctx.accounts.state;
state.data = new_data;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 8)]
pub state: Account<'info, State>,
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Update<'info> {
#[account(mut)]
pub state: Account<'info, State>,
}
#[account]
pub struct State {
pub data: u64,
}// Initialize the program
let tx = my_program::initialize(ctx, 42).await?;
// Update the program state
let tx = my_program::update(ctx, 100).await?;Create a new Rust file and define your program structure as shown in the code example above.
Define the accounts that your program will use. Use the #[derive(Accounts)] attribute to create context structs for each instruction.
Implement the logic for each instruction in your program. Use the Result type to handle success and failure.
After writing your program, compile and deploy it to the Solana blockchain.
- Account Size: Ensure that the space allocated for accounts is sufficient. Use
space = 8 + 8for a simpleu64data type. - Error Handling: Use the
Resulttype to manage errors effectively. Define custom error types using the#[error]attribute. - Cross-Program Invocation (CPI): If your program interacts with other programs, ensure you manage accounts correctly to avoid runtime errors.
Error: Account not initialized
Solution: Ensure that the account is initialized before accessing it. Use the #[account(init)] attribute in the context struct.
Error: Insufficient funds for transaction
Solution: Ensure that the payer account has enough SOL to cover the transaction fees and account creation.
Error: Account type mismatch
Solution: Verify that the account types in the context match the expected types in the program logic.
- Use Descriptive Names: Name your accounts and instructions clearly to improve code readability.
- Modular Design: Break down complex logic into smaller functions or modules to enhance maintainability.
- Testing: Write unit tests for your program logic to ensure correctness. Use the
#[cfg(test)]attribute to include test cases. - Documentation: Comment your code and use doc comments (
///) to generate documentation for your program.
#[cfg(test)]
mod tests {
use super::*;
use anchor_lang::prelude::*;
#[test]
fn test_initialize() {
let mut ctx = ...; // Setup context
let result = my_program::initialize(ctx, 42);
assert!(result.is_ok());
}
}By following these guidelines, you can effectively get started with the Solana Anchor Framework and build robust programs on the Solana blockchain.
use anchor_lang::prelude::*;
declare_id!("YourProgramIDHere");
#[program]
mod my_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
let state = &mut ctx.accounts.state;
state.data = data;
Ok(())
}
pub fn update(ctx: Context<Update>, new_data: u64) -> Result<()> {
let state = &mut ctx.accounts.state;
state.data = new_data;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 8)]
pub state: Account<'info, State>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Update<'info> {
#[account(mut)]
pub state: Account<'info, State>,
}
#[account]
pub struct State {
pub data: u64,
}import { Program, AnchorProvider, web3 } from '@coral-xyz/anchor';
import { MyProgram } from './my_program';
const provider = AnchorProvider.local();
const program = new Program<MyProgram>(idl, programId, provider);
async function initializeProgram(data: number) {
const tx = await program.methods.initialize(new anchor.BN(data))
.accounts({
state: statePublicKey,
user: provider.wallet.publicKey,
systemProgram: web3.SystemProgram.programId,
})
.rpc();
console.log("Transaction signature", tx);
}
async function updateProgram(newData: number) {
const tx = await program.methods.update(new anchor.BN(newData))
.accounts({
state: statePublicKey,
})
.rpc();
console.log("Transaction signature", tx);
}- Create a new Rust file for your program.
- Define the program structure using
#[program]and#[derive(Accounts)].
- Implement the logic for each instruction (e.g.,
initialize,update). - Use
Contextto access accounts and manage state.
- Use
#[account]to define the structure of your stateful data. - Specify the fields and their types.
- Generate the IDL using
anchor idl init. - Use the IDL to create a TypeScript client for interacting with the program.
- Deploy your program using the Anchor CLI.
- Write tests in Rust to ensure your program behaves as expected.
- Account Size: Ensure that the space allocated for accounts is sufficient for the data structure.
- Error Handling: Use the
Resulttype to manage errors effectively. Define custom error types with#[error]. - Cross-Program Invocations (CPI): Be mindful of the accounts required for CPI calls and ensure they are correctly specified in the context.
#[account(mut)]
pub state: Account<'info, State>, // Ensure the account is initialized before accessingSolution: Ensure the account is initialized and the correct public key is used.
#[account(init, payer = user, space = 8 + 8)] // Adjust space as neededSolution: Increase the space allocated for the account if you add more fields.
pub fn update(ctx: Context<Update>, new_data: u64) -> Result<()> {
let state = &mut ctx.accounts.state;
state.data = new_data; // Ensure types match
Ok(())
}Solution: Verify that the types in the function signature match the account structure.
- Use Descriptive Names: Name your accounts and functions clearly to reflect their purpose.
- Modular Design: Break down complex logic into smaller functions to improve readability and maintainability.
- Testing: Write comprehensive tests for each instruction to ensure correctness and handle edge cases.
- Documentation: Comment your code and maintain an updated README to help others understand your program's functionality.
- Version Control: Use semantic versioning for your program to track changes and maintain backward compatibility.
#[cfg(test)]
mod tests {
use super::*;
use anchor_lang::prelude::*;
use anchor_lang::solana_program::program::invoke_signed;
use anchor_lang::solana_program::pubkey::Pubkey;
use anchor_lang::prelude::Account;
#[tokio::test]
async fn test_initialize() {
let program_id = Pubkey::from_str("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS").unwrap();
let (mut context, _accounts) = setup_test_context(program_id).await;
let start_count = 0;
let result = counter::initialize(context, start_count).await;
assert!(result.is_ok());
let counter_account = context.accounts.counter.load().await.unwrap();
assert_eq!(counter_account.count, start_count);
}
#[tokio::test]
async fn test_increment() {
let program_id = Pubkey::from_str("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS").unwrap();
let (mut context, _accounts) = setup_test_context(program_id).await;
// Initialize the counter first
counter::initialize(context.clone(), 0).await.unwrap();
// Increment the counter
let result = counter::increment(context).await;
assert!(result.is_ok());
let counter_account = context.accounts.counter.load().await.unwrap();
assert_eq!(counter_account.count, 1);
}
}async fn setup_test_context(program_id: Pubkey) -> (Context<Initialize>, Accounts) {
let authority = Keypair::new();
let counter_account = Account::new(&authority.pubkey(), 48, &program_id);
let context = Context::new(
program_id,
Initialize {
counter: counter_account,
authority: authority.clone(),
system_program: System::id(),
},
);
(context, Accounts { authority })
}#[cfg(test)]
mod tests {
use super::*;
// Import necessary modules
}#[tokio::test]
async fn test_function_name() {
// Setup context and accounts
// Call the program instruction
// Assert the expected outcomes
}async fn setup_context() -> (Context<YourInstruction>, YourAccounts) {
// Create keypairs and accounts
// Initialize context
// Return context and accounts
}- Environment: Ensure tests are run in a local or test environment to avoid affecting mainnet.
- Account Initialization: Always initialize accounts with the correct space and payer.
- Error Handling: Use
Resulttypes to handle errors gracefully in tests. - Asynchronous Calls: Use
#[tokio::test]for asynchronous test functions.
let result = counter::increment(context).await;
assert!(result.is_err()); // Expecting an error if account is not initializedlet result = counter::increment(context_with_different_authority).await;
assert!(result.is_err()); // Expecting an error due to authority mismatch- Use Descriptive Test Names: Clearly describe what each test is verifying.
- Isolate Tests: Ensure tests do not depend on each other to avoid cascading failures.
- Mock External Calls: Use mocks for external dependencies to keep tests focused.
- Clean Up After Tests: Reset state or clean up accounts after tests to maintain isolation.
- Use Assertions Wisely: Check for both expected outcomes and error conditions.
#[tokio::test]
async fn test_external_call() {
// Mock external call
let mock_response = ...; // Define mock response
let result = your_function_call(mock_response).await;
assert_eq!(result, expected_value);
}This guide provides a structured approach to testing with the Solana Anchor Framework, focusing on practical code examples and best practices.
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
mod counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>, start: u64) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.authority = *ctx.accounts.authority.key;
counter.count = start;
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = authority, space = 48)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, has_one = authority)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
#[account]
pub struct Counter {
pub authority: Pubkey,
pub count: u64,
}#[error]
pub enum CounterError {
#[msg("Unauthorized access.")]
Unauthorized,
#[msg("Counter overflow.")]
Overflow,
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
if counter.count == u64::MAX {
return Err(CounterError::Overflow.into());
}
counter.count += 1;
Ok(())
}#[error]
pub enum MyError {
#[msg("An error occurred.")]
GeneralError,
}#[derive(Accounts)]
pub struct MyInstructionContext<'info> {
#[account(mut)]
pub my_account: Account<'info, MyAccount>,
pub signer: Signer<'info>,
}pub fn my_instruction(ctx: Context<MyInstructionContext>) -> Result<()> {
// Your logic here
Ok(())
}- Account Initialization: Ensure accounts are initialized correctly with the right space and payer.
- Authority Checks: Use
has_oneto enforce authority checks on mutable accounts. - Error Handling: Always return a
Resulttype to handle errors gracefully. - Data Size: Be mindful of the space allocated for accounts; use
spaceparameter wisely.
#[account(init, payer = authority, space = 8 + 8)]
pub counter: Account<'info, Counter>,Solution: Ensure the account is initialized with the correct parameters.
#[account(mut, has_one = authority)]
pub counter: Account<'info, Counter>,Solution: Verify that the authority is correctly set and matches the signer.
if counter.count == u64::MAX {
return Err(CounterError::Overflow.into());
}Solution: Implement checks to prevent overflow before incrementing.
- Use Descriptive Error Messages: Define clear error messages to aid debugging.
- Modularize Code: Break down complex logic into smaller functions for easier testing.
- Test Thoroughly: Write unit tests for each instruction to ensure expected behavior.
- Leverage Context: Use the
Contextstruct to pass necessary accounts and data efficiently. - Document Code: Comment on complex logic to improve maintainability and understanding.
By following these guidelines, you can effectively debug and maintain your Solana Anchor programs.
use anchor_lang::prelude::*;
declare_id!("YourProgramID");
#[program]
mod my_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: String) -> Result<()> {
let my_account = &mut ctx.accounts.my_account;
my_account.data = data;
Ok(())
}
}
#[account]
pub struct MyAccount {
pub data: String,
}#[error]
pub enum MyError {
#[msg("Invalid input provided.")]
InvalidInput,
}
pub fn increment(ctx: Context<Increment>, value: u64) -> Result<()> {
if value == 0 {
return Err(MyError::InvalidInput.into());
}
// Increment logic here
Ok(())
}pub fn call_other_program(ctx: Context<CallOtherProgram>, amount: u64) -> Result<()> {
let cpi_accounts = Transfer {
from: ctx.accounts.from.to_account_info(),
to: ctx.accounts.to.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.clone();
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
token::transfer(cpi_ctx, amount)?;
Ok(())
}- Define the account structure.
- Use the
#[account]attribute. - Initialize the account in the program.
#[account]
pub struct MyAccount {
pub data: String,
}
pub fn create_account(ctx: Context<CreateAccount>, data: String) -> Result<()> {
let my_account = &mut ctx.accounts.my_account;
my_account.data = data;
Ok(())
}let tx = provider.send_and_confirm(&transaction, &[&signer]).await?;program.add_event_listener("MyEvent", |event| {
// Handle event
});- Account Size: Ensure that the account size is sufficient for the data being stored.
- Discriminators: Use unique discriminators for accounts to avoid conflicts.
- Error Handling: Always handle errors gracefully and provide meaningful messages.
- CPI Constraints: Ensure that the accounts passed to CPI calls are correctly initialized and owned.
if !ctx.accounts.my_account.is_initialized {
return Err(MyError::AccountNotInitialized.into());
}#[account(discriminator = "MY_ACCOUNT_DISCRIMINATOR")]
pub struct MyAccount {
pub data: String,
}let cpi_ctx = CpiContext::new(ctx.accounts.token_program.clone(), cpi_accounts);
token::transfer(cpi_ctx, amount).map_err(|_| MyError::CpiFailed)?;- Use Contexts: Always pass
Contextto instruction handlers to access accounts and program state. - Define Clear Interfaces: Use IDL to define clear interfaces for your program, enabling easier client generation.
- Modular Design: Break down complex logic into smaller, reusable functions and modules.
- Testing: Write comprehensive tests for all program logic, including edge cases and error scenarios.
- Documentation: Use comments and documentation attributes to describe the purpose and usage of accounts and instructions.
/// Initializes a new account with the provided data.
///
/// # Arguments
/// * `ctx` - The context containing the accounts involved in the transaction.
/// * `data` - The string data to initialize the account with.
///
/// # Returns
/// * `Result<()>` - Returns Ok if successful, or an error if initialization fails.
pub fn initialize(ctx: Context<Initialize>, data: String) -> Result<()> {
let my_account = &mut ctx.accounts.my_account;
my_account.data = data;
Ok(())
}By following these best practices, developers can create robust, efficient, and maintainable programs using the Solana Anchor framework.
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + std::mem::size_of::<MyAccount>())]
pub my_account: Account<'info, MyAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[program]
pub mod my_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {
let my_account = &mut ctx.accounts.my_account;
my_account.data = 0; // Initialize account data
Ok(())
}
}#[program]
pub mod my_program {
use super::*;
pub fn increment(ctx: Context<Increment>) -> ProgramResult {
let my_account = &mut ctx.accounts.my_account;
my_account.data += 1; // Ensure context is passed correctly
Ok(())
}
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut)]
pub my_account: Account<'info, MyAccount>,
}#[account]
pub struct MyAccount {
pub data: u64,
}#[program]
pub mod my_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {
let my_account = &mut ctx.accounts.my_account;
my_account.data = 0; // Initialize data
Ok(())
}
}pub fn call_other_program(ctx: Context<CallOtherProgram>, amount: u64) -> ProgramResult {
let cpi_accounts = Transfer {
from: ctx.accounts.from.to_account_info(),
to: ctx.accounts.to.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.clone();
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
token::transfer(cpi_ctx, amount)?;
Ok(())
}- Account Size: Always ensure the space allocated for accounts is sufficient.
- Discriminators: Use unique discriminators for accounts to avoid conflicts.
- Error Handling: Define custom error types using the
#[error]attribute to manage errors effectively. - CPI Management: Ensure all accounts required for CPI are included in the context.
// Solution: Ensure account is initialized before use
if my_account.data.is_none() {
return Err(ErrorCode::AccountNotInitialized.into());
}// Solution: Use unique discriminators
#[account(discriminator = 1234567890)]
pub struct MyAccount {
pub data: u64,
}- Use Context Structs: Always define context structs for instruction handlers to manage accounts cleanly.
- Avoid Hardcoding Values: Use constants or configuration files for values like account sizes and discriminators.
- Test Thoroughly: Write unit tests for each instruction handler to ensure expected behavior.
- Leverage IDL: Use the generated IDL for TypeScript clients to ensure type safety and reduce errors in client interactions.
By following these guidelines, developers can avoid common pitfalls when working with the Solana Anchor Framework and ensure robust program development.
use anchor_lang::prelude::*;
declare_id!("YourProgramId");
#[program]
mod my_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: String) -> Result<()> {
let account = &mut ctx.accounts.my_account;
account.data = data;
Ok(())
}
}
#[account]
pub struct MyAccount {
pub data: String,
pub discriminator: [u8; 8], // Custom discriminator
}pub fn call_other_program(ctx: Context<CallOtherProgram>) -> Result<()> {
let cpi_program = ctx.accounts.other_program.to_account_info();
let cpi_accounts = OtherProgramAccounts {
account: ctx.accounts.account.to_account_info(),
};
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
other_program::process_instruction(cpi_ctx)
}#[error_code]
pub enum MyError {
#[msg("Invalid input provided.")]
InvalidInput,
#[msg("Account not found.")]
AccountNotFound,
}- Define the instruction handler function.
- Use the
Contextstruct to access accounts. - Implement logic and return a
Result.
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 64)]
pub my_account: Account<'info, MyAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
pub fn initialize(ctx: Context<Initialize>, data: String) -> Result<()> {
let account = &mut ctx.accounts.my_account;
account.data = data;
Ok(())
}- Generate IDL using
anchor idl init. - Use the generated IDL to create a TypeScript client.
import { Program, Provider, web3 } from '@project-serum/anchor';
import { MyProgram } from './my_program_idl';
const provider = Provider.local();
const program = new Program(MyProgram, programId, provider);- Account Size: Ensure the account size is sufficient for the data structure.
- Discriminator Uniqueness: Custom discriminators must be unique to avoid conflicts.
- Error Handling: Always handle errors gracefully and provide meaningful messages.
- CPI Constraints: Ensure the called program is deployed and accessible.
Problem: Attempting to access an account that hasn't been initialized.
Solution: Ensure the account is initialized before accessing it.
if account.data.is_empty() {
return Err(MyError::AccountNotFound.into());
}Problem: Two accounts have the same discriminator.
Solution: Use unique discriminators for each account type.
#[account(discriminator = "MY_ACCOUNT_DISCRIMINATOR")]
pub struct MyAccount {
pub data: String,
}- Use Context Structs: Always define a context struct for instruction handlers to manage accounts cleanly.
- Modular Design: Break down complex logic into smaller functions or modules for better readability and maintainability.
- Testing: Write comprehensive tests for each instruction handler to ensure correctness.
- Documentation: Use comments and documentation to explain complex logic and account structures.
- Version Control: Keep track of changes in the program and IDL to avoid breaking changes in client applications.
By following these advanced patterns and practices, developers can create robust and efficient Solana programs using the Anchor framework.