Hello! Welcome back to our course on Solana development.
In the last lesson, we focused on the instruction_data that gets passed into our program's entrypoint. You learned how to use the borsh library to deserialize that raw byte slice into a structured Rust enum, allowing your program to understand which command to execute.
Today, we turn our attention to the second crucial parameter of the entrypoint: accounts: &[AccountInfo]. While instructions tell our program what to do, accounts tell it what to do it to. Accounts are where all data and state are stored on the Solana blockchain.
Our learning outcome for this lesson is to read and interpret AccountInfo data passed to a program. You will learn what the AccountInfo struct contains, what each of its fields represents, and how to access and deserialize the custom data stored within an account. This is the fundamental skill for managing state in any Solana program.
Understanding Solana Accounts
Before we look at the Rust code, it's essential to understand the central role of accounts in Solana's architecture. On Solana, everything is an account: your wallet is an account, a deployed program is an account, and the data your program uses is stored in one or more separate accounts. Programs themselves are stateless; they only contain logic. All the state they operate on (like a user's score or a counter's value) lives in data accounts.
Every account on Solana, regardless of its purpose, has a standard set of properties or metadata.
A simple introduction to Solana accounts, rent, and PDAs
To get a clear overview of these properties, watch the following segment from the video "A simple introduction to Solana accounts, rent, and PDAs" by Abdullah Raza.
Watch the clip titled "Inside a Solana Account" (02:12 - 08:34). As you watch, focus on the explanations for these five core fields common to all accounts: Owner: Which program has permission to modify the account. Executable: A boolean flag indicating if the account contains code or data. Rent Epoch / Lamports: How accounts pay to exist on the blockchain. Data: The raw byte array where custom information is stored.
This video gives you the conceptual model. Now, let's see how these concepts are represented in the Rust code your program will interact with.
The AccountInfo Struct
When a client sends a transaction to your program, the Solana runtime passes a slice of AccountInfo structs to your entrypoint. AccountInfo is the program's view of an on-chain account during a transaction.
The solana-program crate provides the official definition. Let's examine its key fields, which directly map to the concepts from the video.
AccountInfo in solana_program::account_info - Rust
The official documentation provides the definitive structure of AccountInfo. We'll focus on the fields and their descriptions.
Review the sections defining the struct and its fields. You don't need to read about the 'Implementations' or methods for now. Just focus on understanding the purpose of each field listed.
Here is a summary of the most important fields you'll use constantly:
key: &'a Pubkey: The account's unique address on the blockchain.owner: &'a Pubkey: The public key of the program that owns this account. This is a critical security feature. Only the owner program can modify the account's data. Your program must always check that it is the owner of any data accounts it intends to write to.data: Rc<RefCell<&'a mut [u8]>>: The raw data stored in the account, represented as a mutable slice of bytes. This is where your program's state lives. The complex typeRc<RefCell<...>>is a Rust pattern that allows for safe, mutable borrowing of the data across different parts of the code.lamports: Rc<RefCell<&'a mut u64>>: The amount of SOL (in lamports; 1 SOL = 1,000,000,000 lamports) held by this account.is_signer: bool: A boolean indicating whether this account's private key was used to sign the transaction. This is how you verify authority (e.g., "is the person trying to withdraw funds actually the owner of the source account?").is_writable: bool: A boolean indicating if the transaction declared its intention to modify this account. The Solana runtime enforces that a program can only write to accounts marked as writable.executable: bool: A flag that istrueif the account contains program code andfalseif it's a data account. You will almost always be interacting with accounts where this isfalse.
Accessing Accounts in Your Program
Your program receives the accounts as a slice, &[AccountInfo]. The order of accounts in this slice is determined by the client that constructed the transaction. Therefore, your program must know the expected order.
The recommended way to process this slice is to create an iterator and pull accounts off one by one using the next_account_info helper function. This is safer than indexing the slice directly (e.g., accounts[0]), as it returns a proper Rust Result, helping you handle cases where an account might be missing.
Here's the common pattern you'll see in Solana programs:
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
pubkey::Pubkey,
// ... other imports
};
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
// Create an iterator over the accounts array
let accounts_iter = &mut accounts.iter();
// Get the first account, which we expect to be our data account
let data_account = next_account_info(accounts_iter)?;
// Get the second account, which we might expect to be the user who signed
let signer_account = next_account_info(accounts_iter)?;
// ... and so on for all expected accounts
Ok(())
}
This pattern is shown and explained well in the following video.
Rust Solana Tutorial #3 - Persisting Data
The video "Rust Solana Tutorial #3 - Persisting Data" by Coding & Crypto walks through this exact process.
Watch the short segment from 06:12 to 07:25. It explains why iterating is safer than indexing and highlights the crucial check for account ownership (account.owner == program_id).
Interpreting the data Field
The most important field for state management is data, but it's just a collection of bytes (&[u8]). How do we get a meaningful structure from it?
The answer is the same as for instruction_data: deserialization with borsh.
The process is nearly identical to what you learned in the previous lesson:
- Define a Rust
structthat represents the data schema for your account. - Add
#[derive(BorshSerialize, BorshDeserialize)]to this struct. - In your program, use the
try_from_slice()method to deserialize the account'sdatabytes into an instance of your struct.
Let's see this in action with a counter program example. First, we define the structure of our account's data:
use borsh::{BorshDeserialize, BorshSerialize};
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct CounterAccount {
pub count: u64,
}
Now, within process_instruction, after getting the AccountInfo for our counter, we can read its data.
The official Solana documentation provides a clear example of reading, modifying, and writing back account data.
Read the code block in the section titled "Implement increment handler". Notice these key steps: Get the counter_account using next_account_info. Check the owner: if counter_account.owner != program_id. Borrow the data: let mut data = counter_account.data.borrow_mut(); Deserialize: let mut counter_data: CounterAccount = CounterAccount::try_from_slice(&data)?; Modify the data: counter_data.count = counter_data.count.checked_add(1)... Serialize back: counter_data.serialize(&mut &mut data[..])?;
The core of reading the data is this line:let mut counter_data = CounterAccount::try_from_slice(&counter_account.data.borrow())?;
This borrows the raw byte data from the AccountInfo and attempts to fit it into the CounterAccount struct. If the data is empty or has an incorrect format, try_from_slice will return an error, which your program can handle gracefully.
Test your understanding!
You are writing a program to store a user's profile, which consists of a username (String) and an age (u8).
The state struct is defined as:
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct UserProfile {
pub username: String,
pub age: u8,
}
In your process_instruction function, you have already retrieved the AccountInfo for the profile into a variable named profile_account.
Write the Rust code snippet that would:
- Check if
profile_accountis owned by your program (program_id). If not, it should return anIncorrectProgramIderror. - If the ownership check passes, deserialize the data from
profile_accountinto aUserProfilestruct.
You can assume program_id and profile_account are already defined.
Show answer
// 1. Check the account's owner.
if profile_account.owner != program_id {
msg!("Profile account is not owned by this program.");
return Err(ProgramError::IncorrectProgramId);
}
// 2. Borrow the account's data.
let profile_data_bytes = profile_account.data.borrow();
// Deserialize the byte slice into the UserProfile struct.
// The '?' operator will propagate any deserialization errors.
let user_profile = UserProfile::try_from_slice(&profile_data_bytes)?;
msg!("Successfully deserialized user profile for: {}", user_profile.username);
This snippet first performs the essential security check and then uses borsh's try_from_slice to safely interpret the account's on-chain data.
Conclusion
Congratulations! You've just learned one of the most fundamental patterns in Solana development. By combining the AccountInfo struct with borsh deserialization, you can now read and interpret any state stored on the blockchain.
Let's recap the key takeaways:
- All state on Solana is stored in accounts. Programs are stateless.
- The
AccountInfostruct is your program's view of an account during a transaction. - Crucial
AccountInfofields includekey,owner,is_signer,is_writable, and thedatabuffer. - You should always use
next_account_infoto safely iterate through the accounts passed to your program. - The
datafield is a raw byte slice (&[u8]) that you can interpret by deserializing it withborsh, just like you did with instruction data.
In this lesson and the last, we've covered the two main inputs to a program: instructions and accounts. You now know how to read and understand both.
In the next lesson, we will focus more deeply on security. We will learn how to manually validate account ownership and signer privileges in program logic. While we've touched on checking the owner, the next lesson will solidify these and other essential security checks that prevent common vulnerabilities and ensure your program behaves as expected.
Can't find a good explanation? Sign up and we'll make it for you
Sign up