Skip to content

Accounts and Transaction Context

View as Markdown

Use this page when you need to read or write account data, or read transaction and block context, from a Rust program.

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.

IndexRoleMutable
0AccountType::FeePayeryes
1AccountType::Programno
2 .. 2 + readwrite_cntAccountType::ReadWriteyes
remainingAccountType::ReadOnlyno

mgr.accounts_count() returns the total, mgr.account_role(idx) the role, and mgr.is_mutable(idx) whether the role allows mutation.

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 */
MethodReturns
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.

AccessorValue
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.

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.

mgr.txn is the parsed transaction; thru_core::get_txn() returns it without a manager.

MethodValue
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.
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.

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.

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.

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);