Summary
Runtime checks after each instruction: only the owner can debit lamports or modify data, data can grow max 10 KiB per instruction, owner changes require zero-initialized data, and the executable flag is irreversible.
The Solana runtime enforces these rules after each instruction executes via
BorrowedInstructionAccount
methods. Each rule is checked at the point of modification, and the transaction
is rolled back if any check fails.
Lamports rules
| Rule | Enforcement | Error |
|---|---|---|
| Only the owner can debit lamports | set_lamports(): checks is_owned_by_current_program() when lamports < current | ExternalAccountLamportSpend |
| Read-only accounts cannot have lamports changed | set_lamports(): checks is_writable() | ReadonlyLamportChange |
| Any program can credit lamports to a writable account | set_lamports(): the ownership check only applies when the new balance is less than the current balance; the writable check still applies | ReadonlyLamportChange |
| Lamports must balance across an instruction | TransactionContext::pop(): verifies get_lamports_delta() == 0 | UnbalancedInstruction |
Data rules
| Rule | Enforcement | Error |
|---|---|---|
| Only the owner can modify data | can_data_be_changed(): checks is_owned_by_current_program() | ExternalAccountDataModified |
| Read-only accounts cannot have data modified | can_data_be_changed(): checks is_writable() | ReadonlyDataModified |
| Only the owner can resize data | can_data_be_resized(): checks is_owned_by_current_program() when new_len != old_len | AccountDataSizeChanged |
| Max data size: 10 MiB | TransactionAccounts::can_data_be_resized(): checks new_len <= MAX_ACCOUNT_DATA_LEN | InvalidRealloc |
| Max growth per instruction: 10 KiB | Deserialization in deserialize_parameters_aligned(): checks post_len - pre_len <= MAX_PERMITTED_DATA_INCREASE | InvalidRealloc |
| Max growth per transaction: 20 MiB | TransactionAccounts::can_data_be_resized(): checks cumulative resize_delta <= MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION | MaxAccountsDataAllocationsExceeded |
Owner rules
| Rule | Enforcement | Error |
|---|---|---|
| Only the current owner can reassign the owner | set_owner(): checks is_owned_by_current_program() | ModifiedProgramId |
| Account must be writable | set_owner(): checks is_writable() | ModifiedProgramId |
| Data must be zero-initialized | set_owner(): checks is_zeroed(data) | ModifiedProgramId |
Executable flag rules
| Rule | Enforcement | Error |
|---|---|---|
| Account must be rent-exempt | set_executable(): checks rent.is_exempt(lamports, data_len) | ExecutableAccountNotRentExempt |
| Only the owner can set the flag | set_executable(): checks is_owned_by_current_program() | ExecutableModified |
| Account must be writable | set_executable(): checks is_writable() | ExecutableModified |
Rent state transitions
Accounts exist in one of three
RentState
values: Uninitialized (0 lamports), RentPaying (above 0 but below the
rent-exempt minimum), and RentExempt (at or above the minimum). Disallowed
transitions produce TransactionError::InsufficientFundsForRent.
The runtime enforces these rules via
transition_allowed():
- Any account can transition to
Uninitialized(close) orRentExempt. - No account can enter
RentPayingfromUninitializedorRentExempt. All new accounts must be rent-exempt.
Account borrow rules
During instruction execution, the runtime enforces single-writer borrow
semantics on accounts. A program can obtain either one mutable reference or
multiple immutable references to an account, but not both simultaneously. If a
program attempts to borrow an account that is already mutably borrowed (or
mutably borrow an account that is already immutably borrowed), the instruction
fails with AccountBorrowFailed via
try_borrow()
and
try_borrow_mut().
If an instruction completes while a borrow is still outstanding, the runtime
returns AccountBorrowOutstanding in
TransactionContext::pop().
Is this page helpful?