Accounts and Transaction Context
Use this page when you need to read or write account data, or read transaction and block context, from a Rust program.
AccountManager
Section titled “AccountManager”AccountManager<NUM_ACCOUNTS> is the safe entry point for account access. It tracks borrows at runtime so two live references to the same account cannot alias, and it enforces the transaction’s own mutability rules.
NUM_ACCOUNTS is the maximum number of distinct accounts you can have borrowed at the same time, not the number of accounts in the transaction. A power of two performs best; next_pow2 helps:
use thru_core::{next_pow2, AccountManager};
const NUM_ACCOUNTS: usize = next_pow2(10); /* 16 */Declare it in your entry signature and the #[entry] macro constructs it for you:
#[entry(stack_size = 8192)]fn main(instr_data: &[u8], mgr: AccountManager<4>) -> Result<u64, u64> { /* ... */ }To build it yourself:
use thru_core::{get_txn, AccountManager};
let mgr = AccountManager::<4>::from_txn(get_txn())?;from_txn fails with AccountError::UnsupportedTxnVersion for any transaction version other than 1.
Account roles
Section titled “Account roles”| Index | Role | Mutable |
|---|---|---|
0 | AccountType::FeePayer | yes |
1 | AccountType::Program | no |
2 .. 2 + readwrite_cnt | AccountType::ReadWrite | yes |
| remaining | AccountType::ReadOnly | no |
mgr.accounts_count() returns the total, mgr.account_role(idx) the role, and mgr.is_mutable(idx) whether the role allows mutation.
Borrowing accounts
Section titled “Borrowing accounts”let mut account = mgr.get(2)?; /* role-appropriate borrow */let bytes = account.data(); /* &[u8] */if let Some(bytes) = account.data_mut() { /* Some only for mutable roles */ bytes[0] = 1;}
let readonly = mgr.get_readonly(2)?; /* force an immutable borrow */| Method | Returns |
|---|---|
get(idx) | Mutable borrow for fee-payer and read-write accounts, immutable otherwise. |
get_readonly(idx) | Always an immutable borrow, whatever the role. |
accounts_iter() | (index, AccountRef) pairs in wire order, skipping accounts that cannot currently be borrowed. |
is_borrowed(idx) | Whether that account has a live borrow. |
has_active_borrows() | Whether any account has a live borrow. |
get and get_readonly return AccountError when the index is out of bounds, the account is already borrowed incompatibly, more than NUM_ACCOUNTS distinct accounts have been touched, or the account info cannot be read.
Reading an AccountRef
Section titled “Reading an AccountRef”| Accessor | Value |
|---|---|
data() | Account data as &[u8]. |
data_mut() | Some(&mut [u8]) for mutable borrows, None otherwise. |
owner() / owner_bytes() | Owner as &Pubkey or &[u8; 32]. |
is_owned_by_current_program() | Whether the owner is the executing program. |
balance() | Native balance. |
data_size() | Data length recorded in metadata. |
nonce() | Account nonce. |
is_mutable() | Whether this borrow can write. |
account_type() | The AccountType of the borrow. |
Data is reachable only through these accessors: the borrow guard lives in the AccountRef, so references cannot outlive it.
Writing account data
Section titled “Writing account data”Account data segments start read-only. Mark the segment writable before writing, and drop every borrow of the account first, because the mutating operations panic while a borrow is live:
use thru_core::syscall::SyscallCode;
if mgr.set_account_data_writable(2) != SyscallCode::Success { return Err(1);}
let mut account = mgr.get(2)?;if let Some(data) = account.data_mut() { data[0] = 42;}Mutating helpers on AccountManager (set_account_data_writable, account_transfer, account_resize, account_create, create_and_init, account_delete, account_compress, account_decompress, account_set_flags, account_create_eoa, invoke) are listed in Syscalls.
Transaction context
Section titled “Transaction context”mgr.txn is the parsed transaction; thru_core::get_txn() returns it without a manager.
| Method | Value |
|---|---|
account_pubkeys() / account_pubkey(idx) | Account addresses referenced by the transaction. |
program_pubkey() | Address of the invoked program. |
instr_data() | Instruction data (the same bytes passed to your entry function). |
accounts_cnt() / readwrite_accounts_cnt() / readonly_accounts_cnt() | Account counts. |
is_account_idx_writable(idx) | Whether the transaction marked that index writable. |
fee(), nonce(), chain_id() | Fee, nonce, and chain id. |
start_slot(), expiry_slot() | Validity window. |
requested_compute_units(), requested_mem_units() | Requested resource limits. |
fee_payer_proof(), fee_payer_meta() | Fee-payer state proof and metadata, when present. |
Block context
Section titled “Block context”use thru_core::{mem::get_block_ctx, types::block_ctx::BlockCtx};
let block: &BlockCtx = get_block_ctx();let now_ns = block.block_time;BlockCtx exposes slot, block_time (Unix nanoseconds), block_price, state_root, cur_block_hash, block_producer, and weight_slot. get_past_block_ctx(blocks_ago) returns an older context, or None when it is older than the chain’s current slot.
Shadow stack
Section titled “Shadow stack”use thru_core::get_shadow_stack;
let stack = get_shadow_stack();let depth = stack.call_depth();let program_idx = stack.current_program_acc_idx();ShadowStack also exposes max_call_depth(), current_total_stack_pages(), current_total_heap_pages(), get_frame(idx), get_parent_frame(), and get_current_frame(). Use it to reason about invocation depth and callers; see Cross-Program Invocation.
Authorization helpers
Section titled “Authorization helpers”use thru_core::program_utils;
program_utils::is_account_authorized_by_idx(idx);program_utils::is_account_authorized_by_pubkey(&pubkey);program_utils::is_account_idx_owned_by_current_program(idx);program_utils::is_program_reentrant();An account counts as authorized when it is the fee payer, the current program, part of the invocation chain, or explicitly delegated by a caller frame that owns it.
Packed structs
Section titled “Packed structs”Account layouts are usually #[repr(C, packed)]. Read and write potentially misaligned fields through the macros instead of taking references:
use thru_core::{read_packed_field, write_packed_field};
let amount = read_packed_field!(token_account, amount);write_packed_field!(token_account, amount, amount + 1);