From 63967b58102226de8854a97a897db456441cf9f6 Mon Sep 17 00:00:00 2001 From: Babur Makhmudov Date: Fri, 15 May 2026 21:58:32 +0400 Subject: [PATCH] feat: add svm crate --- Cargo.toml | 49 +- solana/README.md | 177 ++ solana/program-runtime/Cargo.toml | 4 +- solana/svm/Cargo.toml | 95 ++ solana/svm/README.md | 18 + solana/svm/src/access_permissions.rs | 262 +++ solana/svm/src/account_loader.rs | 1421 +++++++++++++++++ solana/svm/src/lib.rs | 17 + solana/svm/src/message_processor.rs | 614 +++++++ solana/svm/src/program_loader.rs | 26 + solana/svm/src/rent_calculator.rs | 107 ++ .../svm/src/transaction_account_state_info.rs | 222 +++ solana/svm/src/transaction_balances.rs | 60 + .../svm/src/transaction_execution_result.rs | 56 + .../src/transaction_processing_callback.rs | 1 + .../svm/src/transaction_processing_result.rs | 46 + solana/svm/src/transaction_processor.rs | 924 +++++++++++ .../example-programs/clock-sysvar/Cargo.toml | 11 + .../clock-sysvar/clock_sysvar_program.so | Bin 0 -> 44168 bytes .../example-programs/clock-sysvar/src/lib.rs | 21 + .../example-programs/hello-solana/Cargo.toml | 11 + .../hello-solana/hello_solana_program.so | Bin 0 -> 35408 bytes .../example-programs/hello-solana/src/lib.rs | 16 + .../simple-transfer/Cargo.toml | 11 + .../simple_transfer_program.so | Bin 0 -> 67320 bytes .../simple-transfer/src/lib.rs | 29 + .../transfer-from-account/Cargo.toml | 11 + .../transfer-from-account/src/lib.rs | 31 + .../transfer_from_account_program.so | Bin 0 -> 67888 bytes .../write-to-account/Cargo.toml | 11 + .../write-to-account/src/lib.rs | 62 + .../write_to_account_program.so | Bin 0 -> 21992 bytes solana/transaction-context/Cargo.toml | 2 +- 33 files changed, 4290 insertions(+), 25 deletions(-) create mode 100644 solana/README.md create mode 100644 solana/svm/Cargo.toml create mode 100644 solana/svm/README.md create mode 100644 solana/svm/src/access_permissions.rs create mode 100644 solana/svm/src/account_loader.rs create mode 100644 solana/svm/src/lib.rs create mode 100644 solana/svm/src/message_processor.rs create mode 100644 solana/svm/src/program_loader.rs create mode 100644 solana/svm/src/rent_calculator.rs create mode 100644 solana/svm/src/transaction_account_state_info.rs create mode 100644 solana/svm/src/transaction_balances.rs create mode 100644 solana/svm/src/transaction_execution_result.rs create mode 100644 solana/svm/src/transaction_processing_callback.rs create mode 100644 solana/svm/src/transaction_processing_result.rs create mode 100644 solana/svm/src/transaction_processor.rs create mode 100644 solana/svm/tests/example-programs/clock-sysvar/Cargo.toml create mode 100755 solana/svm/tests/example-programs/clock-sysvar/clock_sysvar_program.so create mode 100644 solana/svm/tests/example-programs/clock-sysvar/src/lib.rs create mode 100644 solana/svm/tests/example-programs/hello-solana/Cargo.toml create mode 100755 solana/svm/tests/example-programs/hello-solana/hello_solana_program.so create mode 100644 solana/svm/tests/example-programs/hello-solana/src/lib.rs create mode 100644 solana/svm/tests/example-programs/simple-transfer/Cargo.toml create mode 100755 solana/svm/tests/example-programs/simple-transfer/simple_transfer_program.so create mode 100644 solana/svm/tests/example-programs/simple-transfer/src/lib.rs create mode 100644 solana/svm/tests/example-programs/transfer-from-account/Cargo.toml create mode 100644 solana/svm/tests/example-programs/transfer-from-account/src/lib.rs create mode 100755 solana/svm/tests/example-programs/transfer-from-account/transfer_from_account_program.so create mode 100644 solana/svm/tests/example-programs/write-to-account/Cargo.toml create mode 100644 solana/svm/tests/example-programs/write-to-account/src/lib.rs create mode 100755 solana/svm/tests/example-programs/write-to-account/write_to_account_program.so diff --git a/Cargo.toml b/Cargo.toml index e9374a9..ad90967 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,5 @@ [workspace] -members = [ - "solana/account", - "solana/program-runtime", - "solana/transaction-context", -] +members = ["solana/account", "solana/program-runtime", "solana/svm", "solana/transaction-context"] resolver = "3" [workspace.package] @@ -20,6 +16,7 @@ magic-root-interface = { path = "programs/magic-root-interface" } magic-root-program = { path = "programs/magic-root-program" } solana-account = { path = "solana/account" } solana-program-runtime = { path = "solana/program-runtime" } +solana-svm = { path = "solana/svm" } solana-transaction-context = { path = "solana/transaction-context" } ahash = "0.8.12" @@ -32,6 +29,7 @@ blake3 = "1.8.5" cfg-if = "1.0.4" criterion = "0.7.0" derive_more = "2.1.1" +env_logger = "0.11.8" itertools = "0.14.0" log = "0.4.29" qualifier_attr = "0.2.2" @@ -54,9 +52,11 @@ solana-account-info = "3.1.1" solana-clock = "3.1.0" solana-compute-budget-instruction = "4.1.1" solana-cpi = "3.1.0" +solana-ed25519-program = "3.0.0" solana-epoch-rewards = "3.0.1" solana-epoch-schedule = "3.1.0" solana-feature-gate-interface = { version = "4.0.0", features = ["bincode"] } +solana-fee-calculator = "3.2.0" solana-fee-structure = "3.0.0" solana-frozen-abi = "3.3.0" solana-frozen-abi-macro = "3.3.0" @@ -66,7 +66,12 @@ solana-instruction-error = "2.3.0" solana-instructions-sysvar = "4.0.0" solana-keypair = "3.1.2" solana-last-restart-slot = "3.0.0" -solana-loader-v3-interface = "6.1.0" +solana-loader-v3-interface = "7.0.0" +solana-message = "4.1.1" +solana-msg = "3.1.0" +solana-native-token = "3.0.0" +solana-packet = "4.1.0" +solana-precompile-error = "3.0.0" solana-program-entrypoint = "3.1.1" solana-program-error = "3.0.1" solana-pubkey = "4.2.0" @@ -78,25 +83,27 @@ solana-short-vec = "3.2.1" solana-signature = "3.4.0" solana-signer = "3.0.1" solana-slot-hashes = "3.0.1" -solana-stable-layout = "3.0.0" -solana-sysvar = "3.1.1" -solana-sysvar-id = "3.1.0" -solana-system-interface = ">=3.0.0, <3.2.0" -solana-svm-callback = "4.0.0-rc.1" -solana-svm-feature-set = "4.0.0-rc.1" -solana-svm-log-collector = "4.0.0-rc.1" -solana-svm-measure = "4.0.0-rc.1" -solana-svm-timings = "4.0.0-rc.1" -solana-svm-transaction = "4.0.0-rc.1" -solana-svm-type-overrides = "4.0.0-rc.1" -solana-system-interface = ">=3.0.0, <3.2.0" -solana-system-program = "4.0.0-rc.0" -solana-system-transaction = "3.0.0" -solana-sysvar = "3.1.1" +solana-stable-layout = "3.0.1" +solana-svm-callback = "4.1.1" +solana-svm-feature-set = "4.1.1" +solana-svm-log-collector = "4.1.1" +solana-svm-measure = "4.1.1" +solana-svm-timings = "4.1.1" +solana-svm-transaction = "4.1.1" +solana-svm-type-overrides = "4.1.1" +solana-system-interface = { version = "3.2", features = ["alloc", "bincode", "serde", "wincode"] } +solana-system-program = "4.1.0" +solana-sysvar = "4.0.0" solana-sysvar-id = "3.1.0" solana-transaction = "4.1.1" solana-transaction-error = "3.2.0" +[patch.crates-io] +solana-account = { path = "solana/account" } +solana-program-runtime = { path = "solana/program-runtime" } +solana-svm = { path = "solana/svm" } +solana-transaction-context = { path = "solana/transaction-context" } + [workspace.lints.rust] missing_docs = "deny" diff --git a/solana/README.md b/solana/README.md new file mode 100644 index 0000000..b62f543 --- /dev/null +++ b/solana/README.md @@ -0,0 +1,177 @@ +# Engine Runtime Differences from Agave + +This directory holds the engine's forks of the Agave runtime crates. They are +forked rather than used unmodified because the engine is an execution engine, not +a validator runtime: the code here executes already-loaded transactions and +returns the resulting account changes to the caller. It does not handle consensus, +fork choice, confirmations, validator fee policy, or bank commit ownership — those +concerns belong to the layers above. + +This document is the maintainer reference for the fork. It records which +divergences from upstream are intentional and must be preserved, as opposed to +accidental drift that should be fixed. Before changing SVM execution, account +representation, or VM account mapping, read the relevant section: several of these +behaviors are load-bearing in non-obvious ways, and some must be kept in sync +across more than one crate. The per-crate READMEs point back here rather than +repeating it. + +## Runtime Scope + +The SVM path is caller-owned at the transaction boundary. Concretely: + +- `solana-svm` loads accounts through a caller-provided callback and returns the + mutated account set in the executed transaction. +- Persistence, commit policy, deployment-slot checks, and validator-style batch + decisions all live *outside* these crates. +- Program loading is limited to the programs a transaction actually needs, and it + checks native-loader or `PROGRAM_OWNERS` ownership before execution. +- Rent-state and lamport-balance checks still run around execution — but this + runtime does not own validator state, so that's as far as it goes. + +## Account Model + +`solana-account` replaces the standard shared-data shape with an engine account +representation built around copy-on-write storage. + +`AccountSharedData` is one of: + +- `CoWAccount::Owned`, backed by an `Arc>`. +- `CoWAccount::Borrowed`, a zero-copy view into an aligned external buffer. + +It also carries `DirtyMarkers`, which record field-level changes to data, owner, +lamports, slot, mode, and state flags — these are the writeback signal the caller +reads after execution. + +The borrowed layout is fixed, and documented in full in +`solana/account/README.md`. The invariants that matter here: + +- Borrowed buffers must be 8-byte aligned and stay live for the whole borrow. +- The buffer holds an `AccountHeader`, a shared pubkey prefix, and two account + images; `AccountHeader::sequence` selects the active one. +- `translate()` copies the active image into the shadow image before mutation. +- `commit()` publishes the shadow image by advancing the sequence counter. +- `reset()` discards shadow writes by repointing back to the active image. +- `rollback()` is only valid after a `commit()`. + +Writes try to stay borrowed as long as they fit the borrowed image capacity. A +write that doesn't fit promotes the account to owned heap storage, and a shared +owned buffer goes through `Arc::make_mut` before being mutated. + +The account core also carries engine state that Agave's account code doesn't model +the same way: + +- `AccountMode` distinguishes `ReadOnly`, `Placeholder`, `Authority`, `System`, + `Delegated`, `Ephemeral`, `Transient`, and `Closed`. +- `AccountSharedData::mutable()` is true only for `Delegated`, `Ephemeral`, and + `Transient` — this is the predicate higher layers route persisted-vs-volatile on. +- `StateFlags` include `EXECUTABLE` and `COMPRESSED`. +- `rent_epoch` is *not* stored in `AccountSharedData`; compatibility constructors + ignore it, and runtime serialization masks rent-epoch values for program inputs. + +## Transaction Context + +`solana-transaction-context` keeps transaction accounts in `UnsafeCell` +containers guarded by explicit borrow counters. This keeps account borrows local +to the transaction context while still letting VM access handlers remap account +data when a program writes through a mapped region — something ordinary borrows +can't express. + +Maintainer-visible differences: + +- `TransactionAccounts` tracks touched accounts, the total account-data resize + delta, and the per-instruction lamports delta. +- `AccountRef` and `AccountRefMut` release their custom borrow counters on drop. +- `TransactionAccountViewMut::reserve()` can reserve CoW capacity ahead of a VM + write path. +- `ExecutionRecord` returns the keyed accounts, return data, touched-account + count, and account resize delta after execution. +- `TransactionContext::push()` updates the instructions-sysvar current index when + that sysvar is present. + +All outstanding references must be gone before a transaction context is +deconstructed. If `Rc::try_unwrap` fails during deconstruction, that's a real +lifetime bug, not a tolerable edge case — treat it as one. + +## VM Account Mapping + +Account data is always directly mapped into the SBF VM. The old local-runtime +threading for `virtual_address_space_adjustments` and +`account_data_direct_mapping` is intentionally absent. **Do not** reintroduce +branches that copy account data through the serialized input buffer. + +Serialization still preserves the loader-selected ABI formats: + +- Deprecated-loader accounts use ABI-v0 metadata. +- Loader-v2 and loader-v3 accounts use ABI-v1 metadata. +- ABI-v1 may append the direct account-pointer array when + `direct_account_pointers_in_program_input` is enabled. + +The serialized input buffer carries account metadata, lamports, lengths, owner, +instruction data, and program id — but **not** account data bytes. The data lives +in separate `MemoryRegion`s: + +- Loader-v1 account regions reserve exactly the current data length. +- Loader-v2 and loader-v3 account regions reserve the current length plus + `MAX_PERMITTED_DATA_INCREASE`. +- Non-loader-v1 serialization still adds alignment padding so VM and host + addresses stay aligned across account-region gaps. + +Deserialization reads lamports, owners, and lengths back from the serialized +metadata. It does *not* copy account bytes out of the serialized buffer, because +data mutations already happened through the mapped account regions. ABI-v1 still +enforces realloc limits and the maximum account-data length. + +## Access-Violation Growth + +Writable account data may be mapped read-only at first. That's deliberate for +borrowed accounts and shared owned buffers: the first VM store has to pass through +the transaction-context access-violation handler, so the account can be touched, +translated or made unique, resized if needed, and remapped. + +The handler: + +- Only handles stores. Loads beyond mapped data stay load violations. +- Requires a `MemoryRegion` payload carrying the transaction account index. +- Ignores accesses beyond the address space reserved for that account. +- Updates touched flags and the account resize delta before resizing. +- Grows only to the requested access length — not eagerly to the full permitted + growth range. +- Replaces the region's host pointer, length, and writability after obtaining the + current account-data slice. + +VM execution maps account-region access violations back into account-specific +errors — readonly data modification, account data too small, invalid realloc. +Keep `serialization`, `transaction-context::access_violation_handler`, and +`program-runtime::vm` error remapping in sync; they're three views of the same +contract. + +## CPI Account Synchronization + +CPI helpers follow the same direct-mapped account-data rule: + +- `CallerAccount::serialized_data` is always empty for account data. +- CPI entry syncs caller lamports, owner, and data length into the callee account. + It does not copy data bytes out of caller serialized buffers. +- CPI exit syncs lamports, owner, and length back into the caller's `AccountInfo` + fields. +- If a length or owner change may have moved account storage, CPI replaces the + caller account-data `MemoryRegion` with a fresh region from + `create_memory_region_of_account`. +- Inner-instruction realloc limits are based on the caller's original data length + plus the permitted growth range — except deprecated-loader callers under strict + address checks, which reserve only the original length. + +Any change to account-region layout has to update CPI region replacement and the +VM access-violation path *together*; they can't drift apart. + +## Maintainer Rules + +- Preserve direct account-region mapping as the single runtime behavior. +- Preserve ABI-v0 and ABI-v1 metadata compatibility — but do not restore the old + "copy account data into the serialized input buffer" mode. +- Keep borrowed-account layout changes synchronized across `solana-account`, + `solana-transaction-context`, and the runtime mapping tests. +- Treat dirty markers and touched flags as the writeback signal for the caller. +- Don't add validator flow to these crates. If a behavior needs persistence, batch + commit, consensus, or validator fee decisions, it belongs *outside* this runtime + layer. diff --git a/solana/program-runtime/Cargo.toml b/solana/program-runtime/Cargo.toml index ab69205..92c36d2 100644 --- a/solana/program-runtime/Cargo.toml +++ b/solana/program-runtime/Cargo.toml @@ -4,9 +4,7 @@ name = "solana-program-runtime" authors = { workspace = true } description = "Solana program runtime" documentation = "https://docs.rs/solana-program-runtime" -version = { workspace = true } -authors = { workspace = true } -repository = { workspace = true } +edition = { workspace = true } homepage = { workspace = true } license = { workspace = true } repository = { workspace = true } diff --git a/solana/svm/Cargo.toml b/solana/svm/Cargo.toml new file mode 100644 index 0000000..04bd44a --- /dev/null +++ b/solana/svm/Cargo.toml @@ -0,0 +1,95 @@ +[package] +name = "solana-svm" + +authors = { workspace = true } +description = "Solana SVM" +documentation = "https://docs.rs/solana-svm" +edition = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +version = "4.1.1" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[lib] +crate-type = ["lib"] +name = "solana_svm" + +[features] +# No-op stub retained only so external (patched-in) crates that reference +# `solana-svm/agave-unstable-api` still resolve; the lib is no longer gated on +# it, and the upstream svm-* deps enable their unstable API unconditionally below. +agave-unstable-api = [] +dev-context-only-utils = ["dep:qualifier_attr", "solana-program-runtime/dev-context-only-utils"] +frozen-abi = [ + "dep:solana-frozen-abi", + "dep:solana-frozen-abi-macro", + "solana-program-runtime/frozen-abi" +] +shuttle-test = ["solana-program-runtime/shuttle-test", "solana-svm-type-overrides/shuttle-test"] +svm-internal = ["dep:qualifier_attr"] + +[dependencies] +magic-root-interface = { workspace = true } + +ahash = { workspace = true } +log = { workspace = true } +qualifier_attr = { workspace = true, optional = true } +scc = { workspace = true } +serde = { workspace = true, features = ["rc"] } +thiserror = { workspace = true } + +solana-account = { workspace = true } +solana-clock = { workspace = true } +solana-fee-structure = { workspace = true } +solana-frozen-abi = { workspace = true, optional = true, features = ["frozen-abi"] } +solana-frozen-abi-macro = { workspace = true, optional = true, features = ["frozen-abi"] } +solana-hash = { workspace = true } +solana-instruction = { workspace = true, features = ["std"] } +solana-instructions-sysvar = { workspace = true } +solana-loader-v3-interface = { workspace = true, features = ["bincode"] } +solana-message = { workspace = true } +solana-program-entrypoint = { workspace = true } +solana-program-runtime = { workspace = true } +solana-pubkey = { workspace = true } +solana-rent = { workspace = true } +solana-sdk-ids = { workspace = true } +solana-svm-callback = { workspace = true, features = ["agave-unstable-api"] } +solana-svm-feature-set = { workspace = true, features = ["agave-unstable-api"] } +solana-svm-log-collector = { workspace = true, features = ["agave-unstable-api"] } +solana-svm-measure = { workspace = true, features = ["agave-unstable-api"] } +solana-svm-timings = { workspace = true, features = ["agave-unstable-api"] } +solana-svm-transaction = { workspace = true, features = ["agave-unstable-api"] } +solana-svm-type-overrides = { workspace = true, features = ["agave-unstable-api"] } +solana-system-interface = { workspace = true } +solana-sysvar-id = { workspace = true } +solana-transaction-context = { workspace = true } +solana-transaction-error = { workspace = true } + +[dev-dependencies] +bincode = { workspace = true } +env_logger = { workspace = true } +rand = { workspace = true } +solana-clock = { workspace = true } +solana-ed25519-program = { workspace = true } +solana-epoch-schedule = { workspace = true } +solana-fee-calculator = { workspace = true } +solana-keypair = { workspace = true } +solana-native-token = { workspace = true } +solana-precompile-error = { workspace = true } +solana-program-runtime = { workspace = true, features = ["dev-context-only-utils"] } +solana-pubkey = { workspace = true, features = ["rand"] } +solana-rent = { workspace = true } +solana-sbpf = { workspace = true, features = ["jit"] } +solana-signature = { workspace = true, features = ["rand"] } +solana-signer = { workspace = true } +# See order-crates-for-publishing.py for using this unusual `path = "."` +solana-svm = { path = ".", features = ["dev-context-only-utils", "svm-internal"] } +solana-sysvar = { workspace = true } +solana-transaction = { workspace = true, features = ["dev-context-only-utils"] } +solana-transaction-context = { workspace = true, features = ["dev-context-only-utils"] } + +[lints.rust] +unexpected_cfgs = "allow" diff --git a/solana/svm/README.md b/solana/svm/README.md new file mode 100644 index 0000000..5393362 --- /dev/null +++ b/solana/svm/README.md @@ -0,0 +1,18 @@ +# `solana-svm` + +A fork of Agave's `solana-svm` (`anza-xyz/agave`), reshaped for the engine and +patched in workspace-wide through `[patch.crates-io]`. + +This is the transaction-level entry point into execution. It loads the accounts a +transaction needs — through a caller-provided callback, +`transaction_processing_callback`, rather than owning any storage itself — +executes through `solana-program-runtime`, and hands back the mutated account set. +The callback is the seam: it's how the engine plugs `keeper`/`accountsdb` in as +the account source without this crate knowing anything about them. + +Everything a validator would do *around* execution — committing results, batch +decisions, deployment-slot checks — stays above this crate. It loads, runs, and +returns; nothing more. + +The engine's intentional divergences from upstream are documented for the whole +fork in [`../README.md`](../README.md). diff --git a/solana/svm/src/access_permissions.rs b/solana/svm/src/access_permissions.rs new file mode 100644 index 0000000..2ec0f2f --- /dev/null +++ b/solana/svm/src/access_permissions.rs @@ -0,0 +1,262 @@ +use solana_svm_transaction::svm_message::SVMMessage; +use solana_transaction_error::TransactionError; +use std::sync::Arc; + +use crate::transaction_execution_result::ExecutedTransaction; + +impl ExecutedTransaction { + /// Enforces engine account mutability after successful execution. + pub(crate) fn validate_access(&mut self, tx: &impl SVMMessage) -> bool { + if !self.was_successful() { + return true; + } + let privileged = is_privileged(tx); + let mut accounts = self.loaded_transaction.accounts.iter().enumerate(); + let Some((_, payer)) = accounts.next() else { + // Sanitized transactions always carry a fee payer. + return false; + }; + for (i, (pk, acc)) in accounts { + if !tx.is_writable(i) || acc.mutable() || privileged { + continue; + } + let logs = Arc::make_mut(self.execution_details.log_messages.get_or_insert_default()); + let error = format!("Program log: Immutable account {pk} has been modified"); + logs.push(error); + self.execution_details.status = Err(TransactionError::InvalidWritableAccount); + return false; + } + // The payer is writable in Solana messages even when fees are disabled + // here; reject it only if execution actually changed immutable state. + if payer.1.dirty() && !payer.1.mutable() && !privileged { + let logs = Arc::make_mut(self.execution_details.log_messages.get_or_insert_default()); + logs.push(format!( + "Program log: ({}) Feepayer account's balance has been modified illegally", + payer.0 + )); + self.execution_details.status = Err(TransactionError::InvalidAccountForFee); + return false; + } + true + } +} + +/// Returns true when every instruction is handled by the MagicRoot authority path. +fn is_privileged(tx: &impl SVMMessage) -> bool { + tx.program_instructions_iter() + .all(|(program, _)| *program == magic_root_interface::ID) +} + +#[cfg(test)] +mod tests { + use { + super::*, + crate::{ + account_loader::LoadedTransaction, + transaction_execution_result::{ExecutedTransaction, TransactionExecutionDetails}, + }, + solana_account::{AccountBuilder, AccountMode, AccountSharedData}, + solana_hash::Hash, + solana_message::{ + LegacyMessage, Message, MessageHeader, SanitizedMessage, + compiled_instruction::CompiledInstruction, + }, + solana_pubkey::Pubkey, + solana_signature::Signature, + solana_transaction::sanitized::SanitizedTransaction, + solana_transaction_error::TransactionResult, + std::collections::HashSet, + }; + + /// A dirtied account in the engine-exclusive `Delegated` (mutable) mode. + fn account(mode: AccountMode) -> AccountSharedData { + let mut acc: AccountSharedData = AccountBuilder::default().mode(mode).build(); + acc.set_data_from_slice(&[1]); + acc + } + + /// Builds a sanitized transaction over `account_keys` whose only writable + /// non-signer accounts are the first `writable_non_signers` after the payer, + /// invoking one instruction per entry in `program_indices`. + /// + /// Layout is `[payer, non-signers.., program..]`: the payer signs and is + /// writable, and `is_writable(i)` follows directly from the header math the + /// engine guard relies on. + fn sanitized_tx( + account_keys: Vec, + writable_non_signers: u8, + program_indices: &[u8], + ) -> SanitizedTransaction { + let non_signers = account_keys.len() as u8 - 1; + let header = MessageHeader { + num_required_signatures: 1, + num_readonly_signed_accounts: 0, + num_readonly_unsigned_accounts: non_signers - writable_non_signers, + }; + let instructions = program_indices + .iter() + .map(|&program_id_index| CompiledInstruction { + program_id_index, + accounts: vec![], + data: vec![], + }) + .collect(); + let message = Message { + account_keys, + header, + instructions, + recent_blockhash: Hash::default(), + }; + let sanitized = SanitizedMessage::Legacy(LegacyMessage::new(message, &HashSet::new())); + SanitizedTransaction::new_for_tests(sanitized, vec![Signature::new_unique()], false) + } + + /// Wraps executed account state and a status into an `ExecutedTransaction`. + fn executed( + accounts: Vec<(Pubkey, AccountSharedData)>, + status: TransactionResult<()>, + ) -> ExecutedTransaction { + ExecutedTransaction { + loaded_transaction: LoadedTransaction { accounts, ..Default::default() }, + execution_details: TransactionExecutionDetails { + status, + log_messages: None, + inner_instructions: None, + return_data: None, + executed_units: 0, + accounts_data_len_delta: 0, + }, + } + } + + /// Runs the guard over a `[payer, target, program]` transaction, returning + /// its verdict and the (possibly rewritten) execution details. + /// + /// `program` is MagicRoot when `privileged`, so the whole tx takes the + /// authority path; only `target` (index 1) is ever writable in the message. + fn run( + payer: AccountSharedData, + target: AccountSharedData, + writable_target: bool, + privileged: bool, + status: TransactionResult<()>, + ) -> (bool, ExecutedTransaction) { + let payer_key = Pubkey::new_unique(); + let target_key = Pubkey::new_unique(); + let program = if privileged { magic_root_interface::ID } else { Pubkey::new_unique() }; + let tx = sanitized_tx( + vec![payer_key, target_key, program], + writable_target as u8, + &[2], + ); + let mut executed = executed( + vec![ + (payer_key, payer), + (target_key, target), + (program, AccountSharedData::default()), + ], + status, + ); + let verdict = executed.validate_access(&tx); + (verdict, executed) + } + + /// Asserts a log line containing `needle` was recorded. + fn assert_logged(tx: &ExecutedTransaction, needle: &str) { + let logs: &[String] = tx + .execution_details + .log_messages + .as_deref() + .map(Vec::as_slice) + .unwrap_or_default(); + assert!( + logs.iter().any(|l| l.contains(needle)), + "expected {needle:?} in {logs:?}" + ); + } + + #[test] + fn writable_account_guard() { + // A dirty, writable, immutable operand is rejected — unless the tx is + // privileged; mutable or message-read-only operands are always fine. + let cases = [ + // (target, writable, privileged, accepted) + (account(AccountMode::ReadOnly), true, false, false), + (account(AccountMode::Delegated), true, false, true), + (account(AccountMode::ReadOnly), false, false, true), // read-only in the message + (account(AccountMode::ReadOnly), true, true, true), // MagicRoot bypass + ]; + for (i, (target, writable, privileged, accepted)) in cases.into_iter().enumerate() { + let (verdict, executed) = run( + AccountSharedData::default(), + target, + writable, + privileged, + Ok(()), + ); + assert_eq!(verdict, accepted, "case {i}"); + let expected = + if accepted { Ok(()) } else { Err(TransactionError::InvalidWritableAccount) }; + assert_eq!(executed.execution_details.status, expected, "case {i}"); + if !accepted { + assert_logged(&executed, "Immutable account"); + } + } + } + + #[test] + fn fee_payer_guard() { + // A dirty immutable fee payer is rejected unless privileged; a mutable + // payer is always accepted. + let cases = [ + // (payer, privileged, accepted) + (account(AccountMode::ReadOnly), false, false), + (account(AccountMode::Delegated), false, true), + (account(AccountMode::ReadOnly), true, true), // MagicRoot bypass + ]; + for (i, (payer, privileged, accepted)) in cases.into_iter().enumerate() { + let (verdict, executed) = run( + payer, + AccountSharedData::default(), + false, + privileged, + Ok(()), + ); + assert_eq!(verdict, accepted, "case {i}"); + let expected = + if accepted { Ok(()) } else { Err(TransactionError::InvalidAccountForFee) }; + assert_eq!(executed.execution_details.status, expected, "case {i}"); + if !accepted { + assert_logged(&executed, "Feepayer account's balance"); + } + } + } + + #[test] + fn guard_edge_cases() { + // A failed run committed nothing, so a dirty immutable account must not + // rewrite the original error into an access error. + let (verdict, failed) = run( + AccountSharedData::default(), + account(AccountMode::ReadOnly), + true, + false, + Err(TransactionError::AccountInUse), + ); + assert!(verdict); + assert_eq!( + failed.execution_details.status, + Err(TransactionError::AccountInUse) + ); + + // A transaction without a fee payer account cannot be validated. + let tx = sanitized_tx(vec![Pubkey::new_unique(), Pubkey::new_unique()], 0, &[1]); + let mut payerless = executed(vec![], Ok(())); + assert!(!payerless.validate_access(&tx)); + + // Privilege requires *every* instruction to invoke MagicRoot. + let keys = vec![Pubkey::new_unique(), magic_root_interface::ID, Pubkey::new_unique()]; + assert!(is_privileged(&sanitized_tx(keys.clone(), 0, &[1, 1]))); + assert!(!is_privileged(&sanitized_tx(keys, 0, &[1, 2]))); + } +} diff --git a/solana/svm/src/account_loader.rs b/solana/svm/src/account_loader.rs new file mode 100644 index 0000000..5cfea00 --- /dev/null +++ b/solana/svm/src/account_loader.rs @@ -0,0 +1,1421 @@ +#[cfg(feature = "dev-context-only-utils")] +use qualifier_attr::{field_qualifiers, qualifiers}; +use { + solana_account::{Account, AccountSharedData, PROGRAM_OWNERS, ReadableAccount}, + solana_fee_structure::FeeDetails, + solana_instruction::{BorrowedAccountMeta, BorrowedInstruction}, + solana_instructions_sysvar::construct_instructions_data, + solana_program_runtime::execution_budget::{ + SVMTransactionExecutionAndFeeBudgetLimits, SVMTransactionExecutionBudget, + }, + solana_pubkey::Pubkey, + solana_sdk_ids::sysvar, + solana_svm_callback::TransactionProcessingCallback, + solana_svm_transaction::svm_message::SVMMessage, + solana_transaction_context::{IndexOfAccount, transaction_accounts::KeyedAccountSharedData}, + solana_transaction_error::{TransactionError, TransactionResult as Result}, +}; + +// Per SIMD-0186, all accounts are assigned a base size of 64 bytes to cover +// the storage cost of metadata. +#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))] +pub(crate) const TRANSACTION_ACCOUNT_BASE_SIZE: usize = 64; + +// Per SIMD-0186, resolved address lookup tables are assigned a base size of 8248 +// bytes: 8192 bytes for the maximum table size plus 56 bytes for metadata. +const ADDRESS_LOOKUP_TABLE_BASE_SIZE: usize = 8248; + +/// Result of transaction prechecking before account loading. +pub type TransactionCheckResult = Result; + +#[derive(PartialEq, Eq, Debug)] +pub(crate) enum TransactionLoadResult { + /// All transaction accounts and executable program accounts were resolved. + Loaded(LoadedTransaction), + /// Loading failed before execution could start. + NotLoaded(TransactionError), +} + +/// Transaction limits and metadata computed before account loading. +#[derive(PartialEq, Eq, Debug, Clone)] +#[cfg_attr(feature = "svm-internal", qualifier_attr::field_qualifiers(nonce_address(pub)))] +pub struct CheckedTransactionDetails { + pub(crate) nonce_address: Option, + pub(crate) compute_budget_and_limits: SVMTransactionExecutionAndFeeBudgetLimits, +} + +impl Default for CheckedTransactionDetails { + fn default() -> Self { + Self { + nonce_address: None, + compute_budget_and_limits: SVMTransactionExecutionAndFeeBudgetLimits { + budget: SVMTransactionExecutionBudget::default(), + loaded_accounts_data_size_limit: 32, + fee_details: FeeDetails::default(), + }, + } + } +} + +impl CheckedTransactionDetails { + /// Creates checked transaction details from caller-provided validation. + pub fn new( + nonce_address: Option, + compute_budget_and_limits: SVMTransactionExecutionAndFeeBudgetLimits, + ) -> Self { + Self { + nonce_address, + compute_budget_and_limits, + } + } +} + +#[derive(PartialEq, Eq, Debug, Clone)] +pub(crate) struct ValidatedTransactionDetails { + pub(crate) compute_budget: SVMTransactionExecutionBudget, + pub(crate) loaded_accounts_bytes_limit: u32, + pub(crate) fee_details: FeeDetails, +} + +#[cfg(feature = "dev-context-only-utils")] +impl Default for ValidatedTransactionDetails { + fn default() -> Self { + Self { + compute_budget: SVMTransactionExecutionBudget::default(), + loaded_accounts_bytes_limit: + solana_program_runtime::execution_budget::MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES.get(), + fee_details: FeeDetails::default(), + } + } +} + +#[derive(PartialEq, Eq, Debug, Clone)] +#[cfg_attr(feature = "dev-context-only-utils", derive(Default))] +pub(crate) struct LoadedTransactionAccount { + pub(crate) account: AccountSharedData, + pub(crate) loaded_size: usize, +} + +impl LoadedTransactionAccount { + fn new(account: AccountSharedData) -> Self { + Self { + loaded_size: TRANSACTION_ACCOUNT_BASE_SIZE.saturating_add(account.data().len()), + account, + } + } +} + +/// Accounts and execution metadata needed to run one transaction. +#[derive(PartialEq, Eq, Debug, Clone, Default)] +#[cfg_attr( + feature = "dev-context-only-utils", + field_qualifiers(program_indices(pub), compute_budget(pub)) +)] +pub struct LoadedTransaction { + /// Transaction accounts in message account-key order. + pub accounts: Vec, + pub(crate) program_indices: Vec, + /// Fee metadata carried through for callers that still consume it. + pub fee_details: FeeDetails, + pub(crate) compute_budget: SVMTransactionExecutionBudget, + /// Total loaded account data size charged against the transaction limit. + pub loaded_accounts_data_size: u32, +} + +pub(crate) fn load_transaction( + account_loader: &CB, + message: &impl SVMMessage, + validation_details: ValidatedTransactionDetails, +) -> TransactionLoadResult { + let load_result = load_transaction_accounts( + account_loader, + message, + validation_details.loaded_accounts_bytes_limit, + ); + + match load_result { + Ok(loaded_tx_accounts) => TransactionLoadResult::Loaded(LoadedTransaction { + accounts: loaded_tx_accounts.accounts, + program_indices: loaded_tx_accounts.program_indices, + fee_details: validation_details.fee_details, + compute_budget: validation_details.compute_budget, + loaded_accounts_data_size: loaded_tx_accounts.loaded_accounts_data_size, + }), + Err(err) => TransactionLoadResult::NotLoaded(err), + } +} + +#[derive(PartialEq, Eq, Debug, Clone)] +struct LoadedTransactionAccounts { + pub(crate) accounts: Vec, + pub(crate) program_indices: Vec, + pub(crate) loaded_accounts_data_size: u32, +} + +impl LoadedTransactionAccounts { + fn increase_calculated_data_size( + &mut self, + data_size_delta: usize, + requested_loaded_accounts_data_size_limit: u32, + ) -> Result<()> { + let Ok(data_size_delta) = u32::try_from(data_size_delta) else { + return Err(TransactionError::MaxLoadedAccountsDataSizeExceeded); + }; + + self.loaded_accounts_data_size = + self.loaded_accounts_data_size.saturating_add(data_size_delta); + + if self.loaded_accounts_data_size > requested_loaded_accounts_data_size_limit { + Err(TransactionError::MaxLoadedAccountsDataSizeExceeded) + } else { + Ok(()) + } + } +} + +fn load_transaction_accounts( + account_loader: &CB, + message: &impl SVMMessage, + loaded_accounts_bytes_limit: u32, +) -> Result { + let account_keys = message.account_keys(); + + let mut loaded_transaction_accounts = LoadedTransactionAccounts { + accounts: Vec::with_capacity(account_keys.len()), + program_indices: Vec::with_capacity(message.num_instructions()), + loaded_accounts_data_size: 0, + }; + + // Transactions pay a base fee per address lookup table. + loaded_transaction_accounts.increase_calculated_data_size( + message.num_lookup_tables().saturating_mul(ADDRESS_LOOKUP_TABLE_BASE_SIZE), + loaded_accounts_bytes_limit, + )?; + + let mut collect_loaded_account = |key: &Pubkey, loaded_account| -> Result<()> { + let LoadedTransactionAccount { account, loaded_size } = loaded_account; + + loaded_transaction_accounts + .increase_calculated_data_size(loaded_size, loaded_accounts_bytes_limit)?; + + loaded_transaction_accounts.accounts.push((*key, account)); + + Ok(()) + }; + + // Attempt to load all of the transaction accounts + for account_key in account_keys.iter() { + let loaded_account = load_transaction_account(account_loader, message, account_key); + collect_loaded_account(account_key, loaded_account)?; + } + + for (program_id, instruction) in message.program_instructions_iter() { + let Some(program_account) = account_loader.get_account_shared_data(program_id) else { + return Err(TransactionError::ProgramAccountNotFound); + }; + + let owner_id = program_account.0.owner(); + if !PROGRAM_OWNERS.contains(owner_id) { + return Err(TransactionError::InvalidProgramForExecution); + } + + loaded_transaction_accounts + .program_indices + .push(instruction.program_id_index as IndexOfAccount); + } + + Ok(loaded_transaction_accounts) +} + +fn load_transaction_account( + account_loader: &CB, + message: &impl SVMMessage, + account_key: &Pubkey, +) -> LoadedTransactionAccount { + if solana_sdk_ids::sysvar::instructions::check_id(account_key) { + // Since the instructions sysvar is constructed by the SVM and modified + // for each transaction instruction, it cannot be loaded. + return LoadedTransactionAccount { + loaded_size: 0, + account: construct_instructions_account(message), + }; + } + account_loader + .get_account_shared_data(account_key) + .map(|a| LoadedTransactionAccount::new(a.0)) + .unwrap_or_else(|| LoadedTransactionAccount::new(Default::default())) +} + +fn construct_instructions_account(message: &impl SVMMessage) -> AccountSharedData { + let account_keys = message.account_keys(); + let mut decompiled_instructions = Vec::with_capacity(message.num_instructions()); + for (program_id, instruction) in message.program_instructions_iter() { + let accounts = instruction + .accounts + .iter() + .map(|account_index| { + let account_index = usize::from(*account_index); + BorrowedAccountMeta { + is_signer: message.is_signer(account_index), + is_writable: message.is_writable(account_index), + pubkey: account_keys.get(account_index).unwrap(), + } + }) + .collect(); + + decompiled_instructions.push(BorrowedInstruction { + accounts, + data: instruction.data, + program_id, + }); + } + + AccountSharedData::from(Account { + data: construct_instructions_data(&decompiled_instructions).unwrap_or_default(), + owner: sysvar::id(), + ..Account::default() + }) +} + +#[cfg(test)] +mod tests { + use { + super::*, + crate::{ + rent_calculator::RENT_EXEMPT_RENT_EPOCH, + transaction_account_state_info::TransactionAccountStateInfo, + }, + ahash::AHashMap, + rand::prelude::*, + solana_account::{ + Account, AccountSharedData, ReadableAccount, WritableAccount, state_traits::StateMut, + }, + solana_clock::Slot, + solana_hash::Hash, + solana_instruction::{AccountMeta, Instruction}, + solana_keypair::Keypair, + solana_loader_v3_interface::state::UpgradeableLoaderState, + solana_message::{ + LegacyMessage, Message, MessageHeader, SanitizedMessage, + compiled_instruction::CompiledInstruction, + v0::{LoadedAddresses, LoadedMessage}, + }, + solana_native_token::LAMPORTS_PER_SOL, + solana_program_runtime::execution_budget::{ + DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES, + }, + solana_pubkey::Pubkey, + solana_rent::Rent, + solana_sdk_ids::{ + bpf_loader, bpf_loader_upgradeable, native_loader, system_program, sysvar, + }, + solana_signature::Signature, + solana_signer::Signer, + solana_svm_callback::{AccountState, InvokeContextCallback, TransactionProcessingCallback}, + solana_system_interface::instruction as system_instruction, + solana_transaction::{Transaction, sanitized::SanitizedTransaction}, + solana_transaction_context::{ + transaction::TransactionContext, transaction_accounts::KeyedAccountSharedData, + }, + solana_transaction_error::TransactionError, + std::{ + borrow::Cow, + cell::RefCell, + collections::{HashMap, HashSet}, + sync::Arc, + }, + }; + + fn setup_test_logger() { + let _ = env_logger::Builder::from_env(env_logger::Env::new().default_filter_or("error")) + .format_timestamp_nanos() + .is_test(true) + .try_init(); + } + + #[derive(Clone, Default)] + struct TestCallbacks { + accounts_map: HashMap, + #[allow(clippy::type_complexity)] + inspected_accounts: + RefCell, /* is_writable */ bool)>>>, + } + + impl InvokeContextCallback for TestCallbacks {} + + impl TransactionProcessingCallback for TestCallbacks { + fn get_account_shared_data(&self, pubkey: &Pubkey) -> Option<(AccountSharedData, Slot)> { + self.accounts_map.get(pubkey).map(|(account, slot)| (account.clone(), *slot)) + } + + fn inspect_account( + &self, + address: &Pubkey, + account_state: AccountState, + is_writable: bool, + ) { + let account = match account_state { + AccountState::Dead => None, + AccountState::Alive(account) => Some(account.clone()), + }; + self.inspected_accounts + .borrow_mut() + .entry(*address) + .or_default() + .push((account, is_writable)); + } + } + + fn load_accounts_with_features_and_rent( + tx: Transaction, + accounts: &[KeyedAccountSharedData], + ) -> TransactionLoadResult { + let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(tx); + let mut accounts_map = HashMap::new(); + for (pubkey, account) in accounts { + accounts_map.insert(*pubkey, (account.clone(), 1)); + } + let callbacks = TestCallbacks { + accounts_map, + ..Default::default() + }; + load_transaction( + &callbacks, + &sanitized_tx, + ValidatedTransactionDetails::default(), + ) + } + + fn new_unchecked_sanitized_message(message: Message) -> SanitizedMessage { + SanitizedMessage::Legacy(LegacyMessage::new(message, &HashSet::new())) + } + + #[test] + fn test_load_accounts_unknown_program_id() { + let mut accounts: Vec = Vec::new(); + + let keypair = Keypair::new(); + let key0 = keypair.pubkey(); + let key1 = Pubkey::from([5u8; 32]); + + let account = AccountSharedData::new(1, 0, &Pubkey::default()); + accounts.push((key0, account)); + + let account = AccountSharedData::new(2, 1, &Pubkey::default()); + accounts.push((key1, account)); + + let instructions = vec![CompiledInstruction::new(1, &(), vec![0])]; + let tx = Transaction::new_with_compiled_instructions( + &[&keypair], + &[], + Hash::default(), + vec![Pubkey::default()], + instructions, + ); + + let load_results = load_accounts_with_features_and_rent(tx, &accounts); + + assert!(matches!( + load_results, + TransactionLoadResult::NotLoaded(TransactionError::ProgramAccountNotFound), + )); + } + + #[test] + fn test_load_accounts_no_loaders() { + let mut accounts: Vec = Vec::new(); + + let keypair = Keypair::new(); + let key0 = keypair.pubkey(); + let key1 = Pubkey::from([5u8; 32]); + + let mut account = AccountSharedData::new(1, 0, &Pubkey::default()); + account.set_rent_epoch(1); + accounts.push((key0, account)); + + let mut account = AccountSharedData::new(2, 1, &Pubkey::default()); + account.set_rent_epoch(1); + accounts.push((key1, account)); + + let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])]; + let tx = Transaction::new_with_compiled_instructions( + &[&keypair], + &[key1], + Hash::default(), + vec![native_loader::id()], + instructions, + ); + + let loaded_accounts = load_accounts_with_features_and_rent(tx, &accounts); + + match &loaded_accounts { + TransactionLoadResult::NotLoaded(err) => { + assert_eq!(*err, TransactionError::ProgramAccountNotFound); + } + result => panic!("unexpected result: {result:?}"), + } + } + + #[test] + fn test_load_accounts_bad_owner() { + let mut accounts: Vec = Vec::new(); + + let keypair = Keypair::new(); + let key0 = keypair.pubkey(); + let key1 = Pubkey::from([5u8; 32]); + + let account = AccountSharedData::new(1, 0, &Pubkey::default()); + accounts.push((key0, account)); + + let mut account = AccountSharedData::new(40, 1, &Pubkey::default()); + account.set_executable(true); + accounts.push((key1, account)); + + let instructions = vec![CompiledInstruction::new(1, &(), vec![0])]; + let tx = Transaction::new_with_compiled_instructions( + &[&keypair], + &[], + Hash::default(), + vec![key1], + instructions, + ); + + let load_results = load_accounts_with_features_and_rent(tx, &accounts); + + assert!(matches!( + load_results, + TransactionLoadResult::NotLoaded(TransactionError::InvalidProgramForExecution), + )); + } + + #[test] + fn test_load_accounts_not_executable() { + let mut accounts: Vec = Vec::new(); + + let keypair = Keypair::new(); + let key0 = keypair.pubkey(); + let key1 = Pubkey::from([5u8; 32]); + + let account = AccountSharedData::new(1, 0, &Pubkey::default()); + accounts.push((key0, account)); + + let account = AccountSharedData::new(40, 0, &native_loader::id()); + accounts.push((key1, account)); + + let instructions = vec![CompiledInstruction::new(1, &(), vec![0])]; + let tx = Transaction::new_with_compiled_instructions( + &[&keypair], + &[], + Hash::default(), + vec![key1], + instructions, + ); + + let load_results = load_accounts_with_features_and_rent(tx, &accounts); + + match &load_results { + TransactionLoadResult::Loaded(loaded_transaction) => { + assert_eq!(loaded_transaction.accounts.len(), 2); + assert_eq!(loaded_transaction.accounts[0].1, accounts[0].1); + assert_eq!(loaded_transaction.accounts[1].1, accounts[1].1); + assert_eq!(loaded_transaction.program_indices.len(), 1); + assert_eq!(loaded_transaction.program_indices[0], 1); + } + TransactionLoadResult::NotLoaded(e) => panic!("{e}"), + } + } + + #[test] + fn test_load_accounts_multiple_loaders() { + let mut accounts: Vec = Vec::new(); + + let keypair = Keypair::new(); + let key0 = keypair.pubkey(); + let key1 = bpf_loader_upgradeable::id(); + let key2 = Pubkey::from([6u8; 32]); + + let mut account = AccountSharedData::new(1, 0, &Pubkey::default()); + account.set_rent_epoch(1); + accounts.push((key0, account)); + + let mut account = AccountSharedData::new(40, 1, &Pubkey::default()); + account.set_executable(true); + account.set_rent_epoch(1); + account.set_owner(native_loader::id()); + accounts.push((key1, account)); + + let mut account = AccountSharedData::new(41, 1, &Pubkey::default()); + account.set_executable(true); + account.set_rent_epoch(1); + account.set_owner(key1); + accounts.push((key2, account)); + + let instructions = vec![ + CompiledInstruction::new(1, &(), vec![0]), + CompiledInstruction::new(2, &(), vec![0]), + ]; + let tx = Transaction::new_with_compiled_instructions( + &[&keypair], + &[], + Hash::default(), + vec![key1, key2], + instructions, + ); + + let loaded_accounts = load_accounts_with_features_and_rent(tx, &accounts); + + match &loaded_accounts { + TransactionLoadResult::Loaded(loaded_transaction) => { + assert_eq!(loaded_transaction.accounts.len(), 3); + assert_eq!(loaded_transaction.accounts[0].1, accounts[0].1); + assert_eq!(loaded_transaction.program_indices.len(), 2); + assert_eq!(loaded_transaction.program_indices[0], 1); + assert_eq!(loaded_transaction.program_indices[1], 2); + } + TransactionLoadResult::NotLoaded(e) => panic!("{e}"), + } + } + + fn load_accounts_no_store( + accounts: &[KeyedAccountSharedData], + tx: Transaction, + ) -> TransactionLoadResult { + let tx = SanitizedTransaction::from_transaction_for_tests(tx); + + let mut accounts_map = HashMap::new(); + for (pubkey, account) in accounts { + accounts_map.insert(*pubkey, (account.clone(), 1)); + } + let callbacks = TestCallbacks { + accounts_map, + ..Default::default() + }; + load_transaction(&callbacks, &tx, ValidatedTransactionDetails::default()) + } + + #[test] + fn test_instructions() { + setup_test_logger(); + let instructions_key = solana_sdk_ids::sysvar::instructions::id(); + let keypair = Keypair::new(); + let instructions = vec![CompiledInstruction::new(1, &(), vec![0, 1])]; + let tx = Transaction::new_with_compiled_instructions( + &[&keypair], + &[solana_pubkey::new_rand(), instructions_key], + Hash::default(), + vec![native_loader::id()], + instructions, + ); + + let load_results = load_accounts_no_store(&[], tx); + assert!(matches!( + load_results, + TransactionLoadResult::NotLoaded(TransactionError::ProgramAccountNotFound), + )); + } + + #[test] + fn test_increase_calculated_data_size() { + let mut acc = LoadedTransactionAccounts { + accounts: vec![], + program_indices: vec![], + loaded_accounts_data_size: 0, + }; + + let data_size: usize = 123; + let requested_data_size_limit = data_size as u32; + + // OK - loaded data size is up to limit + assert!(acc.increase_calculated_data_size(data_size, requested_data_size_limit).is_ok()); + assert_eq!(data_size as u32, acc.loaded_accounts_data_size); + + // fail - loading more data that would exceed limit + let another_byte: usize = 1; + assert_eq!( + acc.increase_calculated_data_size(another_byte, requested_data_size_limit), + Err(TransactionError::MaxLoadedAccountsDataSizeExceeded) + ); + } + + #[test] + fn test_construct_instructions_account() { + let loaded_message = LoadedMessage { + message: Cow::Owned(solana_message::v0::Message::default()), + loaded_addresses: Cow::Owned(LoadedAddresses::default()), + is_writable_account_cache: vec![false], + }; + let message = SanitizedMessage::V0(loaded_message); + let shared_data = construct_instructions_account(&message); + let expected = AccountSharedData::from(Account { + data: construct_instructions_data(&message.decompile_instructions()).unwrap(), + owner: sysvar::id(), + ..Account::default() + }); + assert_eq!(shared_data, expected); + } + + #[test] + fn test_load_transaction_accounts_fee_payer() { + let fee_payer_address = Pubkey::new_unique(); + let message = Message { + account_keys: vec![fee_payer_address], + header: MessageHeader::default(), + instructions: vec![], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = new_unchecked_sanitized_message(message); + let mut mock_bank = TestCallbacks::default(); + + let fee_payer_balance = 200; + let mut fee_payer_account = AccountSharedData::default(); + fee_payer_account.set_lamports(fee_payer_balance); + mock_bank.accounts_map.insert(fee_payer_address, (fee_payer_account.clone(), 1)); + let sanitized_transaction = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + let result = load_transaction_accounts( + &mock_bank, + sanitized_transaction.message(), + MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES.get(), + ); + assert_eq!( + result.unwrap(), + LoadedTransactionAccounts { + accounts: vec![(fee_payer_address, fee_payer_account)], + program_indices: vec![], + loaded_accounts_data_size: TRANSACTION_ACCOUNT_BASE_SIZE as u32, + } + ); + } + + #[test] + fn test_load_transaction_accounts_native_loader() { + let key1 = Keypair::new(); + let message = Message { + account_keys: vec![key1.pubkey(), native_loader::id()], + header: MessageHeader::default(), + instructions: vec![CompiledInstruction { + program_id_index: 1, + accounts: vec![0], + data: vec![], + }], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = new_unchecked_sanitized_message(message); + let mut mock_bank = TestCallbacks::default(); + mock_bank + .accounts_map + .insert(native_loader::id(), (AccountSharedData::default(), 0)); + let mut fee_payer_account = AccountSharedData::default(); + fee_payer_account.set_lamports(200); + mock_bank.accounts_map.insert(key1.pubkey(), (fee_payer_account.clone(), 1)); + let sanitized_transaction = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + + let result = load_transaction_accounts( + &mock_bank, + sanitized_transaction.message(), + MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES.get(), + ); + + assert_eq!( + result.unwrap_err(), + TransactionError::InvalidProgramForExecution + ); + } + + #[test] + fn test_load_transaction_accounts_program_account_no_data() { + let key1 = Keypair::new(); + let key2 = Keypair::new(); + + let message = Message { + account_keys: vec![key1.pubkey(), key2.pubkey()], + header: MessageHeader::default(), + instructions: vec![CompiledInstruction { + program_id_index: 1, + accounts: vec![0, 1], + data: vec![], + }], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = new_unchecked_sanitized_message(message); + let mut mock_bank = TestCallbacks::default(); + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(200); + mock_bank.accounts_map.insert(key1.pubkey(), (account_data, 1)); + let sanitized_transaction = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + let result = load_transaction_accounts( + &mock_bank, + sanitized_transaction.message(), + MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES.get(), + ); + + assert_eq!(result.err(), Some(TransactionError::ProgramAccountNotFound)); + } + + #[test] + fn test_load_transaction_accounts_invalid_program_for_execution() { + let key1 = Keypair::new(); + let key2 = Keypair::new(); + + let message = Message { + account_keys: vec![key1.pubkey(), key2.pubkey()], + header: MessageHeader::default(), + instructions: vec![CompiledInstruction { + program_id_index: 0, + accounts: vec![0, 1], + data: vec![], + }], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = new_unchecked_sanitized_message(message); + let mut mock_bank = TestCallbacks::default(); + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(200); + mock_bank.accounts_map.insert(key1.pubkey(), (account_data, 1)); + let sanitized_transaction = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + let result = load_transaction_accounts( + &mock_bank, + sanitized_transaction.message(), + MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES.get(), + ); + + assert_eq!( + result.err(), + Some(TransactionError::InvalidProgramForExecution) + ); + } + + #[test] + fn test_load_transaction_accounts_native_loader_owner() { + let key1 = Keypair::new(); + let key2 = Keypair::new(); + + let message = Message { + account_keys: vec![key2.pubkey(), key1.pubkey()], + header: MessageHeader::default(), + instructions: vec![CompiledInstruction { + program_id_index: 1, + accounts: vec![0], + data: vec![], + }], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = new_unchecked_sanitized_message(message); + let mut mock_bank = TestCallbacks::default(); + let mut account_data = AccountSharedData::default(); + account_data.set_owner(native_loader::id()); + account_data.set_lamports(1); + account_data.set_executable(true); + mock_bank.accounts_map.insert(key1.pubkey(), (account_data, 1)); + + let mut fee_payer_account = AccountSharedData::default(); + fee_payer_account.set_lamports(200); + mock_bank.accounts_map.insert(key2.pubkey(), (fee_payer_account.clone(), 1)); + let sanitized_transaction = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + + let result = load_transaction_accounts( + &mock_bank, + sanitized_transaction.message(), + MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES.get(), + ); + + let loaded_accounts_data_size = TRANSACTION_ACCOUNT_BASE_SIZE as u32 * 2; + + assert_eq!( + result.unwrap(), + LoadedTransactionAccounts { + accounts: vec![ + (key2.pubkey(), fee_payer_account), + ( + key1.pubkey(), + mock_bank.accounts_map[&key1.pubkey()].0.clone() + ), + ], + program_indices: vec![1], + loaded_accounts_data_size, + } + ); + } + + #[test] + fn test_load_transaction_accounts_program_account_not_found_after_all_checks() { + let key1 = Keypair::new(); + let key2 = Keypair::new(); + + let message = Message { + account_keys: vec![key2.pubkey(), key1.pubkey()], + header: MessageHeader::default(), + instructions: vec![CompiledInstruction { + program_id_index: 1, + accounts: vec![0], + data: vec![], + }], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = new_unchecked_sanitized_message(message); + let mut mock_bank = TestCallbacks::default(); + let mut account_data = AccountSharedData::default(); + account_data.set_executable(true); + mock_bank.accounts_map.insert(key1.pubkey(), (account_data, 1)); + + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(200); + mock_bank.accounts_map.insert(key2.pubkey(), (account_data, 1)); + let sanitized_transaction = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + let result = load_transaction_accounts( + &mock_bank, + sanitized_transaction.message(), + MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES.get(), + ); + + assert_eq!( + result.err(), + Some(TransactionError::InvalidProgramForExecution) + ); + } + + #[test] + fn test_load_transaction_accounts_program_account_invalid_program_for_execution_last_check() { + let key1 = Keypair::new(); + let key2 = Keypair::new(); + let key3 = Keypair::new(); + + let message = Message { + account_keys: vec![key2.pubkey(), key1.pubkey()], + header: MessageHeader::default(), + instructions: vec![CompiledInstruction { + program_id_index: 1, + accounts: vec![0], + data: vec![], + }], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = new_unchecked_sanitized_message(message); + let mut mock_bank = TestCallbacks::default(); + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(1); + account_data.set_executable(true); + account_data.set_owner(key3.pubkey()); + mock_bank.accounts_map.insert(key1.pubkey(), (account_data, 1)); + + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(200); + mock_bank.accounts_map.insert(key2.pubkey(), (account_data, 1)); + mock_bank.accounts_map.insert(key3.pubkey(), (AccountSharedData::default(), 0)); + let sanitized_transaction = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + let result = load_transaction_accounts( + &mock_bank, + sanitized_transaction.message(), + MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES.get(), + ); + + assert_eq!( + result.err(), + Some(TransactionError::InvalidProgramForExecution) + ); + } + + #[test] + fn test_load_transaction_accounts_program_success_complete() { + let key1 = Keypair::new(); + let key2 = Keypair::new(); + + let message = Message { + account_keys: vec![key2.pubkey(), key1.pubkey()], + header: MessageHeader::default(), + instructions: vec![CompiledInstruction { + program_id_index: 1, + accounts: vec![0], + data: vec![], + }], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = new_unchecked_sanitized_message(message); + let mut mock_bank = TestCallbacks::default(); + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(1); + account_data.set_executable(true); + account_data.set_owner(bpf_loader::id()); + mock_bank.accounts_map.insert(key1.pubkey(), (account_data, 1)); + + let mut fee_payer_account = AccountSharedData::default(); + fee_payer_account.set_lamports(200); + mock_bank.accounts_map.insert(key2.pubkey(), (fee_payer_account.clone(), 1)); + + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(1); + account_data.set_executable(true); + account_data.set_owner(native_loader::id()); + mock_bank.accounts_map.insert(bpf_loader::id(), (account_data, 0)); + let sanitized_transaction = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + + let result = load_transaction_accounts( + &mock_bank, + sanitized_transaction.message(), + MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES.get(), + ); + + let loaded_accounts_data_size = TRANSACTION_ACCOUNT_BASE_SIZE as u32 * 2; + + assert_eq!( + result.unwrap(), + LoadedTransactionAccounts { + accounts: vec![ + (key2.pubkey(), fee_payer_account), + ( + key1.pubkey(), + mock_bank.accounts_map[&key1.pubkey()].0.clone() + ), + ], + program_indices: vec![1], + loaded_accounts_data_size, + } + ); + } + + #[test] + fn test_load_transaction_accounts_program_builtin_saturating_add() { + let key1 = Keypair::new(); + let key2 = Keypair::new(); + let key3 = Keypair::new(); + + let message = Message { + account_keys: vec![key2.pubkey(), key1.pubkey(), key3.pubkey()], + header: MessageHeader::default(), + instructions: vec![ + CompiledInstruction { + program_id_index: 1, + accounts: vec![0], + data: vec![], + }, + CompiledInstruction { + program_id_index: 1, + accounts: vec![2], + data: vec![], + }, + ], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = new_unchecked_sanitized_message(message); + let mut mock_bank = TestCallbacks::default(); + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(1); + account_data.set_executable(true); + account_data.set_owner(bpf_loader::id()); + mock_bank.accounts_map.insert(key1.pubkey(), (account_data, 0)); + + let mut fee_payer_account = AccountSharedData::default(); + fee_payer_account.set_lamports(200); + mock_bank.accounts_map.insert(key2.pubkey(), (fee_payer_account.clone(), 1)); + + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(1); + account_data.set_executable(true); + account_data.set_owner(native_loader::id()); + mock_bank.accounts_map.insert(bpf_loader::id(), (account_data, 0)); + let sanitized_transaction = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + + let result = load_transaction_accounts( + &mock_bank, + sanitized_transaction.message(), + MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES.get(), + ); + + let loaded_accounts_data_size = TRANSACTION_ACCOUNT_BASE_SIZE as u32 * 3; + + let mut account_data = AccountSharedData::default(); + account_data.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH); + assert_eq!( + result.unwrap(), + LoadedTransactionAccounts { + accounts: vec![ + (key2.pubkey(), fee_payer_account), + ( + key1.pubkey(), + mock_bank.accounts_map[&key1.pubkey()].0.clone() + ), + (key3.pubkey(), account_data), + ], + program_indices: vec![1, 1], + loaded_accounts_data_size, + } + ); + } + + #[test] + fn test_rent_state_list_len() { + let mint_keypair = Keypair::new(); + let mut bank = TestCallbacks::default(); + let recipient = Pubkey::new_unique(); + let last_block_hash = Hash::new_unique(); + + let mut system_data = AccountSharedData::default(); + system_data.set_lamports(1); + system_data.set_executable(true); + system_data.set_owner(native_loader::id()); + bank.accounts_map.insert(Pubkey::new_from_array([0u8; 32]), (system_data, 0)); + + let mut mint_data = AccountSharedData::default(); + mint_data.set_lamports(2); + bank.accounts_map.insert(mint_keypair.pubkey(), (mint_data, 0)); + bank.accounts_map.insert(recipient, (AccountSharedData::default(), 1)); + let mut tx = Transaction::new_with_payer( + &[system_instruction::transfer( + &mint_keypair.pubkey(), + &recipient, + LAMPORTS_PER_SOL, + )], + Some(&mint_keypair.pubkey()), + ); + tx.sign(&[&mint_keypair], last_block_hash); + let num_accounts = tx.message().account_keys.len(); + let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(tx); + let load_result = + load_transaction(&bank, &sanitized_tx, ValidatedTransactionDetails::default()); + + let TransactionLoadResult::Loaded(loaded_transaction) = load_result else { + panic!("transaction loading failed"); + }; + + let compute_budget = SVMTransactionExecutionBudget { + compute_unit_limit: u64::from(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT), + ..SVMTransactionExecutionBudget::default() + }; + let rent = Rent::default(); + let transaction_context = TransactionContext::new( + loaded_transaction.accounts, + rent.clone(), + compute_budget.max_instruction_stack_depth, + compute_budget.max_instruction_trace_length, + 1, + ); + + assert_eq!( + TransactionAccountStateInfo::new(&transaction_context, sanitized_tx.message(), &rent,) + .len(), + num_accounts, + ); + } + + #[test] + fn test_load_accounts_success() { + let key1 = Keypair::new(); + let key2 = Keypair::new(); + let key3 = Keypair::new(); + + let message = Message { + account_keys: vec![key2.pubkey(), key1.pubkey(), key3.pubkey()], + header: MessageHeader::default(), + instructions: vec![ + CompiledInstruction { + program_id_index: 1, + accounts: vec![0], + data: vec![], + }, + CompiledInstruction { + program_id_index: 1, + accounts: vec![2], + data: vec![], + }, + ], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = new_unchecked_sanitized_message(message); + let mut mock_bank = TestCallbacks::default(); + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(1); + account_data.set_executable(true); + account_data.set_owner(bpf_loader::id()); + mock_bank.accounts_map.insert(key1.pubkey(), (account_data, 0)); + + let mut fee_payer_account = AccountSharedData::default(); + fee_payer_account.set_lamports(200); + mock_bank.accounts_map.insert(key2.pubkey(), (fee_payer_account.clone(), 1)); + + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(1); + account_data.set_executable(true); + account_data.set_owner(native_loader::id()); + mock_bank.accounts_map.insert(bpf_loader::id(), (account_data, 0)); + let sanitized_transaction = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + + let load_result = load_transaction( + &mock_bank, + &sanitized_transaction, + ValidatedTransactionDetails::default(), + ); + + let loaded_accounts_data_size = TRANSACTION_ACCOUNT_BASE_SIZE as u32 * 3; + + let mut account_data = AccountSharedData::default(); + account_data.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH); + + let TransactionLoadResult::Loaded(loaded_transaction) = load_result else { + panic!("transaction loading failed"); + }; + assert_eq!( + loaded_transaction, + LoadedTransaction { + accounts: vec![ + ( + key2.pubkey(), + mock_bank.accounts_map[&key2.pubkey()].0.clone() + ), + ( + key1.pubkey(), + mock_bank.accounts_map[&key1.pubkey()].0.clone() + ), + (key3.pubkey(), account_data), + ], + program_indices: vec![1, 1], + fee_details: FeeDetails::default(), + compute_budget: SVMTransactionExecutionBudget::default(), + loaded_accounts_data_size, + } + ); + } + + #[test] + fn test_load_accounts_error() { + let mock_bank = TestCallbacks::default(); + let message = Message { + account_keys: vec![Pubkey::new_from_array([0; 32])], + header: MessageHeader::default(), + instructions: vec![CompiledInstruction { + program_id_index: 0, + accounts: vec![], + data: vec![], + }], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = new_unchecked_sanitized_message(message); + let sanitized_transaction = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + + let load_result = load_transaction( + &mock_bank, + &sanitized_transaction, + ValidatedTransactionDetails::default(), + ); + + assert!(matches!( + load_result, + TransactionLoadResult::NotLoaded(TransactionError::ProgramAccountNotFound), + )); + } + + // note all magic numbers (how many accounts, how many instructions, how big to size buffers) are arbitrary + // other than trying not to swamp programs with blank accounts and keep transaction size below the 64mb limit + #[test] + fn test_load_transaction_accounts_data_sizes() { + let mut rng = rand::rng(); + let mut mock_bank = TestCallbacks::default(); + + // arbitrary accounts + for _ in 0..128 { + let account = AccountSharedData::create_from_existing_shared_data( + 1, + Arc::new(vec![0; rng.random_range(0..128)]), + Pubkey::new_unique(), + rng.random(), + u64::MAX, + ); + mock_bank.accounts_map.insert(Pubkey::new_unique(), (account, 1)); + } + + // fee-payers + let mut fee_payers = vec![]; + for _ in 0..8 { + let fee_payer = Pubkey::new_unique(); + let account = AccountSharedData::create_from_existing_shared_data( + LAMPORTS_PER_SOL, + Arc::new(vec![0; rng.random_range(0..32)]), + system_program::id(), + rng.random(), + u64::MAX, + ); + mock_bank.accounts_map.insert(fee_payer, (account, 1)); + fee_payers.push(fee_payer); + } + + // programs + let mut loader_owned_accounts = vec![]; + let mut programdata_tracker = AHashMap::new(); + for loader in PROGRAM_OWNERS { + for _ in 0..16 { + let program_id = Pubkey::new_unique(); + let mut account = AccountSharedData::create_from_existing_shared_data( + 1, + Arc::new(vec![0; rng.random_range(0..512)]), + *loader, + rng.random(), + u64::MAX, + ); + + // give half loaderv3 accounts (if they're long enough) a valid programdata + // a quarter a dead pointer and a quarter nothing + // we set executable like a program because after the flag is disabled... + // ...programdata and buffer accounts can be used as program ids without aborting loading + // this will always fail at execution but we are merely testing the data size accounting here + if *loader == bpf_loader_upgradeable::id() && account.data().len() >= 64 { + let programdata_address = Pubkey::new_unique(); + let has_programdata = rng.random(); + + if has_programdata { + let programdata_account = + AccountSharedData::create_from_existing_shared_data( + 1, + Arc::new(vec![0; rng.random_range(0..512)]), + *loader, + rng.random(), + u64::MAX, + ); + programdata_tracker.insert( + program_id, + (programdata_address, programdata_account.data().len()), + ); + mock_bank + .accounts_map + .insert(programdata_address, (programdata_account, 1)); + loader_owned_accounts.push(programdata_address); + } + + if has_programdata || rng.random() { + account + .set_state(&UpgradeableLoaderState::Program { programdata_address }) + .unwrap(); + } + } + + mock_bank.accounts_map.insert(program_id, (account, 1)); + loader_owned_accounts.push(program_id); + } + } + + let mut all_accounts = mock_bank.accounts_map.keys().copied().collect::>(); + + // Append some missing accounts. The current loader materializes them as + // default accounts, so they still contribute the base account size. + for _ in 0..32 { + all_accounts.push(Pubkey::new_unique()); + } + + // now generate arbitrary transactions using this accounts + // we ensure valid fee-payers and that all program ids are loader-owned + // otherwise any account can appear anywhere + // some edge cases we hope to hit (not necessarily all in every run): + // * programs used multiple times as program ids and/or normal accounts are counted once + // * loaderv3 programdata used explicitly zero one or multiple times is counted once + // * loaderv3 programs with missing programdata are allowed through + // * loaderv3 programdata used as program id does nothing weird + // * loaderv3 programdata used as a regular account does nothing weird + // * the programdata conditions hold regardless of ordering + for _ in 0..1024 { + let mut instructions = vec![]; + for _ in 0..rng.random_range(1..8) { + let mut accounts = vec![]; + for _ in 0..rng.random_range(1..16) { + all_accounts.shuffle(&mut rng); + let pubkey = all_accounts[0]; + + accounts.push(AccountMeta { + pubkey, + is_writable: rng.random(), + is_signer: rng.random() && rng.random(), + }); + } + + loader_owned_accounts.shuffle(&mut rng); + let program_id = loader_owned_accounts[0]; + instructions.push(Instruction { + accounts, + program_id, + data: vec![], + }); + } + + fee_payers.shuffle(&mut rng); + let fee_payer = fee_payers[0]; + let transaction = SanitizedTransaction::from_transaction_for_tests( + Transaction::new_with_payer(&instructions, Some(&fee_payer)), + ); + + let mut expected_size = 0; + for pubkey in transaction.account_keys().iter() { + let account_data_len = mock_bank + .accounts_map + .get(pubkey) + .map(|(account, _last_modification_slot)| account.data().len()) + .unwrap_or_default(); + expected_size += TRANSACTION_ACCOUNT_BASE_SIZE + account_data_len; + } + + assert!(expected_size <= MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES.get() as usize); + + let loaded_transaction_accounts = load_transaction_accounts( + &mock_bank, + &transaction, + MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES.get(), + ) + .unwrap(); + + assert_eq!( + loaded_transaction_accounts.loaded_accounts_data_size, + expected_size as u32, + ); + } + } +} diff --git a/solana/svm/src/lib.rs b/solana/svm/src/lib.rs new file mode 100644 index 0000000..3993798 --- /dev/null +++ b/solana/svm/src/lib.rs @@ -0,0 +1,17 @@ +#![cfg_attr(feature = "frozen-abi", feature(min_specialization))] +#![allow(clippy::arithmetic_side_effects)] +#![allow(clippy::arc_with_non_send_sync)] +#![allow(clippy::disallowed_methods)] +#![doc = include_str!("../README.md")] + +mod access_permissions; +pub mod account_loader; +pub mod message_processor; +pub mod program_loader; +pub mod rent_calculator; +pub mod transaction_account_state_info; +pub mod transaction_balances; +pub mod transaction_execution_result; +pub mod transaction_processing_callback; +pub mod transaction_processing_result; +pub mod transaction_processor; diff --git a/solana/svm/src/message_processor.rs b/solana/svm/src/message_processor.rs new file mode 100644 index 0000000..301963e --- /dev/null +++ b/solana/svm/src/message_processor.rs @@ -0,0 +1,614 @@ +use { + solana_program_runtime::invoke_context::InvokeContext, + solana_svm_transaction::svm_message::SVMMessage, solana_transaction_context::IndexOfAccount, + solana_transaction_error::TransactionError, +}; + +/// Process each top-level instruction in a message. +/// +/// The caller provides a transaction context containing the loaded accounts. +/// This function advances instruction state, dispatches either a precompile or +/// program entrypoint, and maps instruction failures to transaction errors. +pub(crate) fn process_message<'ix_data>( + message: &'ix_data impl SVMMessage, + program_indices: &[IndexOfAccount], + invoke_context: &mut InvokeContext<'_, 'ix_data>, + accumulated_consumed_units: &mut u64, +) -> Result<(), TransactionError> { + debug_assert_eq!(program_indices.len(), message.num_instructions()); + for (top_level_instruction_index, ((program_id, instruction), program_account_index)) in + message.program_instructions_iter().zip(program_indices.iter()).enumerate() + { + invoke_context + .prepare_next_top_level_instruction( + message, + &instruction, + *program_account_index, + instruction.data, + ) + .map_err(|err| { + TransactionError::InstructionError(top_level_instruction_index as u8, err) + })?; + + let mut compute_units_consumed = 0; + let result = if invoke_context.is_precompile(program_id) { + invoke_context.process_precompile( + program_id, + instruction.data, + message.instructions_iter().map(|ix| ix.data), + ) + } else { + invoke_context.process_instruction(&mut compute_units_consumed) + }; + + *accumulated_consumed_units = + accumulated_consumed_units.saturating_add(compute_units_consumed); + + result.map_err(|err| { + TransactionError::InstructionError(top_level_instruction_index as u8, err) + })?; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use { + super::*, + solana_account::{ + Account, AccountSharedData, DUMMY_INHERITABLE_ACCOUNT_FIELDS, ReadableAccount, + }, + solana_ed25519_program::new_ed25519_instruction_with_signature, + solana_hash::Hash, + solana_instruction::{AccountMeta, Instruction, error::InstructionError}, + solana_keypair::Keypair, + solana_message::{AccountKeys, Message, SanitizedMessage}, + solana_precompile_error::PrecompileError, + solana_program_runtime::{ + declare_process_instruction, + execution_budget::{SVMTransactionExecutionBudget, SVMTransactionExecutionCost}, + invoke_context::EnvironmentConfig, + loaded_programs::{ + ProgramCacheEntry, ProgramCacheForTxBatch, ProgramRuntimeEnvironments, + }, + solana_sbpf::program::BuiltinFunctionDefinition, + sysvar_cache::SysvarCache, + }, + solana_pubkey::Pubkey, + solana_rent::Rent, + solana_sdk_ids::{ed25519_program, native_loader}, + solana_signer::Signer, + solana_svm_callback::InvokeContextCallback, + solana_svm_feature_set::SVMFeatureSet, + solana_transaction_context::transaction::TransactionContext, + std::{collections::HashSet, sync::Arc}, + }; + + struct MockCallback {} + impl InvokeContextCallback for MockCallback {} + + fn create_loadable_account_for_test(name: &str) -> AccountSharedData { + let (lamports, rent_epoch) = DUMMY_INHERITABLE_ACCOUNT_FIELDS; + AccountSharedData::from(Account { + lamports, + owner: native_loader::id(), + data: name.as_bytes().to_vec(), + executable: true, + rent_epoch, + }) + } + + fn new_sanitized_message(message: Message) -> SanitizedMessage { + SanitizedMessage::try_from_legacy_message(message, &HashSet::new()).unwrap() + } + + fn ed25519_instruction_for_test() -> Instruction { + let keypair = Keypair::new(); + let signature = keypair.sign_message(b"hello"); + let pubkey = keypair.pubkey().to_bytes(); + new_ed25519_instruction_with_signature(b"hello", signature.as_array(), &pubkey) + } + + #[test] + fn test_process_message_readonly_handling() { + #[derive(serde::Serialize, serde::Deserialize)] + enum MockSystemInstruction { + Correct, + TransferLamports { lamports: u64 }, + ChangeData { data: u8 }, + } + + declare_process_instruction!(MockBuiltin, 1, |invoke_context| { + let transaction_context = &invoke_context.transaction_context; + let instruction_context = transaction_context.get_current_instruction_context()?; + let instruction_data = instruction_context.get_instruction_data(); + if let Ok(instruction) = bincode::deserialize(instruction_data) { + match instruction { + MockSystemInstruction::Correct => Ok(()), + MockSystemInstruction::TransferLamports { lamports } => { + instruction_context + .try_borrow_instruction_account(0)? + .checked_sub_lamports(lamports)?; + instruction_context + .try_borrow_instruction_account(1)? + .checked_add_lamports(lamports)?; + Ok(()) + } + MockSystemInstruction::ChangeData { data } => { + instruction_context + .try_borrow_instruction_account(1)? + .set_data_from_slice(&[data])?; + Ok(()) + } + } + } else { + Err(InstructionError::InvalidInstructionData) + } + }); + + let writable_pubkey = Pubkey::new_unique(); + let readonly_pubkey = Pubkey::new_unique(); + let mock_system_program_id = Pubkey::new_unique(); + + let accounts = vec![ + ( + writable_pubkey, + AccountSharedData::new(100, 1, &mock_system_program_id), + ), + ( + readonly_pubkey, + AccountSharedData::new(0, 1, &mock_system_program_id), + ), + ( + mock_system_program_id, + create_loadable_account_for_test("mock_system_program"), + ), + ]; + let mut transaction_context = + TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1); + let program_indices = vec![2]; + let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default(); + program_cache_for_tx_batch.replenish( + mock_system_program_id, + Arc::new(ProgramCacheEntry::new_builtin(( + MockBuiltin::vm, + MockBuiltin::codegen, + ))), + ); + let account_keys = (0..transaction_context.get_number_of_accounts()) + .map(|index| *transaction_context.get_key_of_account_at_index(index).unwrap()) + .collect::>(); + let account_metas = vec![ + AccountMeta::new(writable_pubkey, true), + AccountMeta::new_readonly(readonly_pubkey, false), + ]; + + let message = new_sanitized_message(Message::new_with_compiled_instructions( + 1, + 0, + 2, + account_keys.clone(), + Hash::default(), + AccountKeys::new(&account_keys, None).compile_instructions(&[ + Instruction::new_with_bincode( + mock_system_program_id, + &MockSystemInstruction::Correct, + account_metas.clone(), + ), + ]), + )); + let sysvar_cache = SysvarCache::default(); + let feature_set = SVMFeatureSet::all_enabled(); + let program_runtime_environments = ProgramRuntimeEnvironments::default(); + let environment_config = EnvironmentConfig::new( + Hash::default(), + 0, + &MockCallback {}, + &feature_set, + &program_runtime_environments, + &sysvar_cache, + ); + let mut invoke_context = InvokeContext::new( + &mut transaction_context, + &mut program_cache_for_tx_batch, + environment_config, + None, + SVMTransactionExecutionBudget::default(), + SVMTransactionExecutionCost::default(), + ); + let result = process_message(&message, &program_indices, &mut invoke_context, &mut 0); + assert!(result.is_ok()); + assert_eq!( + transaction_context.accounts().try_borrow(0).unwrap().lamports(), + 100 + ); + assert_eq!( + transaction_context.accounts().try_borrow(1).unwrap().lamports(), + 0 + ); + + let message = new_sanitized_message(Message::new_with_compiled_instructions( + 1, + 0, + 2, + account_keys.clone(), + Hash::default(), + AccountKeys::new(&account_keys, None).compile_instructions(&[ + Instruction::new_with_bincode( + mock_system_program_id, + &MockSystemInstruction::TransferLamports { lamports: 50 }, + account_metas.clone(), + ), + ]), + )); + let program_runtime_environments = ProgramRuntimeEnvironments::default(); + let environment_config = EnvironmentConfig::new( + Hash::default(), + 0, + &MockCallback {}, + &feature_set, + &program_runtime_environments, + &sysvar_cache, + ); + let mut transaction_context = + TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1); + let mut invoke_context = InvokeContext::new( + &mut transaction_context, + &mut program_cache_for_tx_batch, + environment_config, + None, + SVMTransactionExecutionBudget::default(), + SVMTransactionExecutionCost::default(), + ); + let result = process_message(&message, &program_indices, &mut invoke_context, &mut 0); + assert_eq!( + result, + Err(TransactionError::InstructionError( + 0, + InstructionError::ReadonlyLamportChange + )) + ); + + let message = new_sanitized_message(Message::new_with_compiled_instructions( + 1, + 0, + 2, + account_keys.clone(), + Hash::default(), + AccountKeys::new(&account_keys, None).compile_instructions(&[ + Instruction::new_with_bincode( + mock_system_program_id, + &MockSystemInstruction::ChangeData { data: 50 }, + account_metas, + ), + ]), + )); + let program_runtime_environments = ProgramRuntimeEnvironments::default(); + let environment_config = EnvironmentConfig::new( + Hash::default(), + 0, + &MockCallback {}, + &feature_set, + &program_runtime_environments, + &sysvar_cache, + ); + let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 3, 1); + let mut invoke_context = InvokeContext::new( + &mut transaction_context, + &mut program_cache_for_tx_batch, + environment_config, + None, + SVMTransactionExecutionBudget::default(), + SVMTransactionExecutionCost::default(), + ); + let result = process_message(&message, &program_indices, &mut invoke_context, &mut 0); + assert_eq!( + result, + Err(TransactionError::InstructionError( + 0, + InstructionError::ReadonlyDataModified + )) + ); + } + + #[test] + fn test_process_message_duplicate_accounts() { + #[derive(serde::Serialize, serde::Deserialize)] + enum MockSystemInstruction { + BorrowFail, + MultiBorrowMut, + DoWork { lamports: u64, data: u8 }, + } + + declare_process_instruction!(MockBuiltin, 1, |invoke_context| { + let transaction_context = &invoke_context.transaction_context; + let instruction_context = transaction_context.get_current_instruction_context()?; + let instruction_data = instruction_context.get_instruction_data(); + let mut to_account = instruction_context.try_borrow_instruction_account(1)?; + if let Ok(instruction) = bincode::deserialize(instruction_data) { + match instruction { + MockSystemInstruction::BorrowFail => { + let from_account = instruction_context.try_borrow_instruction_account(0)?; + let dup_account = instruction_context.try_borrow_instruction_account(2)?; + if from_account.get_lamports() != dup_account.get_lamports() { + return Err(InstructionError::InvalidArgument); + } + Ok(()) + } + MockSystemInstruction::MultiBorrowMut => { + let lamports_a = + instruction_context.try_borrow_instruction_account(0)?.get_lamports(); + let lamports_b = + instruction_context.try_borrow_instruction_account(2)?.get_lamports(); + if lamports_a != lamports_b { + return Err(InstructionError::InvalidArgument); + } + Ok(()) + } + MockSystemInstruction::DoWork { lamports, data } => { + let mut dup_account = + instruction_context.try_borrow_instruction_account(2)?; + dup_account.checked_sub_lamports(lamports)?; + to_account.checked_add_lamports(lamports)?; + dup_account.set_data_from_slice(&[data])?; + drop(dup_account); + let mut from_account = + instruction_context.try_borrow_instruction_account(0)?; + from_account.checked_sub_lamports(lamports)?; + to_account.checked_add_lamports(lamports)?; + Ok(()) + } + } + } else { + Err(InstructionError::InvalidInstructionData) + } + }); + let mock_program_id = Pubkey::from([2u8; 32]); + let accounts = vec![ + ( + solana_pubkey::new_rand(), + AccountSharedData::new(100, 1, &mock_program_id), + ), + ( + solana_pubkey::new_rand(), + AccountSharedData::new(0, 1, &mock_program_id), + ), + ( + mock_program_id, + create_loadable_account_for_test("mock_system_program"), + ), + ]; + let mut transaction_context = + TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1); + let program_indices = vec![2]; + let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default(); + program_cache_for_tx_batch.replenish( + mock_program_id, + Arc::new(ProgramCacheEntry::new_builtin(( + MockBuiltin::vm, + MockBuiltin::codegen, + ))), + ); + let account_metas = vec![ + AccountMeta::new( + *transaction_context.get_key_of_account_at_index(0).unwrap(), + true, + ), + AccountMeta::new( + *transaction_context.get_key_of_account_at_index(1).unwrap(), + false, + ), + AccountMeta::new( + *transaction_context.get_key_of_account_at_index(0).unwrap(), + false, + ), + ]; + + // Try to borrow mut the same account + let message = new_sanitized_message(Message::new( + &[Instruction::new_with_bincode( + mock_program_id, + &MockSystemInstruction::BorrowFail, + account_metas.clone(), + )], + Some(transaction_context.get_key_of_account_at_index(0).unwrap()), + )); + let sysvar_cache = SysvarCache::default(); + let feature_set = SVMFeatureSet::all_enabled(); + let program_runtime_environments = ProgramRuntimeEnvironments::default(); + let environment_config = EnvironmentConfig::new( + Hash::default(), + 0, + &MockCallback {}, + &feature_set, + &program_runtime_environments, + &sysvar_cache, + ); + let mut invoke_context = InvokeContext::new( + &mut transaction_context, + &mut program_cache_for_tx_batch, + environment_config, + None, + SVMTransactionExecutionBudget::default(), + SVMTransactionExecutionCost::default(), + ); + let result = process_message(&message, &program_indices, &mut invoke_context, &mut 0); + assert_eq!( + result, + Err(TransactionError::InstructionError( + 0, + InstructionError::AccountBorrowFailed + )) + ); + + // Try to borrow mut the same account in a safe way + let message = new_sanitized_message(Message::new( + &[Instruction::new_with_bincode( + mock_program_id, + &MockSystemInstruction::MultiBorrowMut, + account_metas.clone(), + )], + Some(transaction_context.get_key_of_account_at_index(0).unwrap()), + )); + let program_runtime_environments = ProgramRuntimeEnvironments::default(); + let environment_config = EnvironmentConfig::new( + Hash::default(), + 0, + &MockCallback {}, + &feature_set, + &program_runtime_environments, + &sysvar_cache, + ); + let mut transaction_context = + TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1); + let mut invoke_context = InvokeContext::new( + &mut transaction_context, + &mut program_cache_for_tx_batch, + environment_config, + None, + SVMTransactionExecutionBudget::default(), + SVMTransactionExecutionCost::default(), + ); + let result = process_message(&message, &program_indices, &mut invoke_context, &mut 0); + assert!(result.is_ok()); + + // Do work on the same transaction account but at different instruction accounts + let message = new_sanitized_message(Message::new( + &[Instruction::new_with_bincode( + mock_program_id, + &MockSystemInstruction::DoWork { lamports: 10, data: 42 }, + account_metas, + )], + Some(transaction_context.get_key_of_account_at_index(0).unwrap()), + )); + let program_runtime_environments = ProgramRuntimeEnvironments::default(); + let environment_config = EnvironmentConfig::new( + Hash::default(), + 0, + &MockCallback {}, + &feature_set, + &program_runtime_environments, + &sysvar_cache, + ); + let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 3, 1); + let mut invoke_context = InvokeContext::new( + &mut transaction_context, + &mut program_cache_for_tx_batch, + environment_config, + None, + SVMTransactionExecutionBudget::default(), + SVMTransactionExecutionCost::default(), + ); + let result = process_message(&message, &program_indices, &mut invoke_context, &mut 0); + assert!(result.is_ok()); + assert_eq!( + transaction_context.accounts().try_borrow(0).unwrap().lamports(), + 80 + ); + assert_eq!( + transaction_context.accounts().try_borrow(1).unwrap().lamports(), + 20 + ); + assert_eq!( + transaction_context.accounts().try_borrow(0).unwrap().data(), + &vec![42] + ); + } + + #[test] + fn test_precompile() { + let mock_program_id = Pubkey::new_unique(); + declare_process_instruction!(MockBuiltin, 1, |_invoke_context| { + Err(InstructionError::Custom(0xbabb1e)) + }); + + let payer = Pubkey::new_unique(); + let accounts = vec![ + (payer, AccountSharedData::new(1, 0, &native_loader::id())), + ( + ed25519_program::id(), + create_loadable_account_for_test("ed25519_program"), + ), + ( + mock_program_id, + create_loadable_account_for_test("mock_program"), + ), + ]; + let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 2, 2); + + let account_keys = (0..transaction_context.get_number_of_accounts()) + .map(|index| *transaction_context.get_key_of_account_at_index(index).unwrap()) + .collect::>(); + let instructions = vec![ + ed25519_instruction_for_test(), + Instruction::new_with_bytes(mock_program_id, &[], vec![]), + ]; + let message = new_sanitized_message(Message::new_with_compiled_instructions( + 1, + 0, + account_keys.len() as u8 - 1, + account_keys.clone(), + Hash::default(), + AccountKeys::new(&account_keys, None).compile_instructions(&instructions), + )); + + let sysvar_cache = SysvarCache::default(); + let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default(); + program_cache_for_tx_batch.replenish( + mock_program_id, + Arc::new(ProgramCacheEntry::new_builtin(( + MockBuiltin::vm, + MockBuiltin::codegen, + ))), + ); + + struct MockCallback {} + impl InvokeContextCallback for MockCallback { + fn is_precompile(&self, program_id: &Pubkey) -> bool { + program_id == &ed25519_program::id() + } + + fn process_precompile( + &self, + program_id: &Pubkey, + _data: &[u8], + _instruction_datas: Vec<&[u8]>, + ) -> std::result::Result<(), PrecompileError> { + if self.is_precompile(program_id) { + Ok(()) + } else { + Err(PrecompileError::InvalidPublicKey) + } + } + } + + let feature_set = SVMFeatureSet::all_enabled(); + let program_runtime_environments = ProgramRuntimeEnvironments::default(); + let environment_config = EnvironmentConfig::new( + Hash::default(), + 0, + &MockCallback {}, + &feature_set, + &program_runtime_environments, + &sysvar_cache, + ); + let mut invoke_context = InvokeContext::new( + &mut transaction_context, + &mut program_cache_for_tx_batch, + environment_config, + None, + SVMTransactionExecutionBudget::default(), + SVMTransactionExecutionCost::default(), + ); + let result = process_message(&message, &[1, 2], &mut invoke_context, &mut 0); + + assert_eq!( + result, + Err(TransactionError::InstructionError( + 1, + InstructionError::Custom(0xbabb1e) + )) + ); + assert_eq!(transaction_context.get_instruction_trace_length(), 2); + } +} diff --git a/solana/svm/src/program_loader.rs b/solana/svm/src/program_loader.rs new file mode 100644 index 0000000..78bebe9 --- /dev/null +++ b/solana/svm/src/program_loader.rs @@ -0,0 +1,26 @@ +use { + solana_account::{AccountSharedData, ReadableAccount}, + solana_program_runtime::loaded_programs::{ + ProgramCacheEntry, ProgramCacheEntryType, ProgramRuntimeEnvironments, + }, + solana_svm_type_overrides::sync::Arc, +}; + +/// Builds a program-cache entry from an executable account. +/// +/// Invalid program data is kept as a failed-verification entry so execution +/// can report the normal program failure path. +pub fn load_program( + environments: &ProgramRuntimeEnvironments, + program: &AccountSharedData, +) -> Arc { + let environment = environments.get_env_for_execution().clone(); + ProgramCacheEntry::new(environment.clone(), program.data()) + .map(Arc::new) + .unwrap_or_else(|_| { + ProgramCacheEntry { + program: ProgramCacheEntryType::FailedVerification(environment), + } + .into() + }) +} diff --git a/solana/svm/src/rent_calculator.rs b/solana/svm/src/rent_calculator.rs new file mode 100644 index 0000000..b235630 --- /dev/null +++ b/solana/svm/src/rent_calculator.rs @@ -0,0 +1,107 @@ +//! Solana SVM Rent Calculator. +//! +//! Rent management for SVM. + +use { + solana_account::{AccountMode, AccountSharedData, ReadableAccount}, + solana_clock::Epoch, + solana_pubkey::Pubkey, + solana_rent::Rent, + solana_transaction_context::{IndexOfAccount, transaction::TransactionContext}, + solana_transaction_error::{TransactionError, TransactionResult}, +}; + +/// When rent is collected from an exempt account, rent_epoch is set to this +/// value. The idea is to have a fixed, consistent value for rent_epoch for all accounts that do not collect rent. +/// This enables us to get rid of the field completely. +pub const RENT_EXEMPT_RENT_EPOCH: Epoch = Epoch::MAX; + +/// Rent state of a Solana account. +#[derive(Debug, PartialEq, Eq)] +pub enum RentState { + /// account.lamports == 0 + Uninitialized, + /// 0 < account.lamports < rent-exempt-minimum + RentPaying { + lamports: u64, // account.lamports() + data_size: usize, // account.data().len() + }, + /// account.lamports >= rent-exempt-minimum + RentExempt, +} + +/// Checks a writable account rent-state transition inside a transaction. +pub fn check_rent_state( + pre_rent_state: Option<&RentState>, + post_rent_state: Option<&RentState>, + transaction_context: &TransactionContext, + index: IndexOfAccount, +) -> TransactionResult<()> { + if let Some((pre_rent_state, post_rent_state)) = pre_rent_state.zip(post_rent_state) { + let expect_msg = "account must exist at TransactionContext index if rent-states are Some"; + check_rent_state_with_account( + pre_rent_state, + post_rent_state, + transaction_context.get_key_of_account_at_index(index).expect(expect_msg), + index, + )?; + } + Ok(()) +} + +/// Checks a rent-state transition for a known account address. +/// +/// The incinerator is exempt from this check. +pub fn check_rent_state_with_account( + pre_rent_state: &RentState, + post_rent_state: &RentState, + address: &Pubkey, + account_index: IndexOfAccount, +) -> TransactionResult<()> { + if !solana_sdk_ids::incinerator::check_id(address) + && !transition_allowed(pre_rent_state, post_rent_state) + { + let account_index = account_index as u8; + Err(TransactionError::InsufficientFundsForRent { account_index }) + } else { + Ok(()) + } +} + +/// Determines the rent state of an account from lamports and data size. +pub fn get_account_rent_state(rent: &Rent, acc: &AccountSharedData) -> RentState { + let (lamports, len) = (acc.lamports(), acc.data().len()); + if lamports == 0 { + RentState::Uninitialized + } else if rent.is_exempt(lamports, len) || acc.is(AccountMode::Ephemeral) { + RentState::RentExempt + } else { + RentState::RentPaying { data_size: len, lamports } + } +} + +/// Returns whether a pre/post rent-state transition is valid. +/// +/// Any state may become uninitialized or rent-exempt. A rent-paying account +/// may remain rent-paying only if it keeps the same data size and is not +/// credited. +pub fn transition_allowed(pre_rent_state: &RentState, post_rent_state: &RentState) -> bool { + match post_rent_state { + RentState::Uninitialized | RentState::RentExempt => true, + RentState::RentPaying { + data_size: post_data_size, + lamports: post_lamports, + } => { + match pre_rent_state { + RentState::Uninitialized | RentState::RentExempt => false, + RentState::RentPaying { + data_size: pre_data_size, + lamports: pre_lamports, + } => { + // Cannot remain RentPaying if resized or credited. + post_data_size == pre_data_size && post_lamports <= pre_lamports + } + } + } + } +} diff --git a/solana/svm/src/transaction_account_state_info.rs b/solana/svm/src/transaction_account_state_info.rs new file mode 100644 index 0000000..06e20ed --- /dev/null +++ b/solana/svm/src/transaction_account_state_info.rs @@ -0,0 +1,222 @@ +use { + crate::rent_calculator::{RentState, check_rent_state, get_account_rent_state}, + solana_rent::Rent, + solana_svm_transaction::svm_message::SVMMessage, + solana_transaction_context::{IndexOfAccount, transaction::TransactionContext}, + solana_transaction_error::TransactionResult as Result, +}; + +#[derive(PartialEq, Debug)] +pub(crate) struct TransactionAccountStateInfo { + rent_state: Option, // None: readonly account +} + +impl TransactionAccountStateInfo { + pub(crate) fn new( + transaction_context: &TransactionContext, + message: &impl SVMMessage, + rent: &Rent, + ) -> Vec { + (0..message.account_keys().len()) + .map(|i| { + let rent_state = if message.is_writable(i) { + let state = transaction_context + .accounts() + .try_borrow(i as IndexOfAccount) + .map(|acc| get_account_rent_state(rent, &acc)) + .ok(); + debug_assert!( + state.is_some(), + "message and transaction context out of sync, fatal" + ); + state + } else { + None + }; + Self { rent_state } + }) + .collect() + } + + pub(crate) fn verify_changes( + pre_state_infos: &[Self], + post_state_infos: &[Self], + transaction_context: &TransactionContext, + ) -> Result<()> { + for (i, (pre_state_info, post_state_info)) in + pre_state_infos.iter().zip(post_state_infos).enumerate() + { + check_rent_state( + pre_state_info.rent_state.as_ref(), + post_state_info.rent_state.as_ref(), + transaction_context, + i as IndexOfAccount, + )?; + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use { + super::*, + solana_account::AccountSharedData, + solana_hash::Hash, + solana_keypair::Keypair, + solana_message::{ + LegacyMessage, Message, MessageHeader, SanitizedMessage, + compiled_instruction::CompiledInstruction, + }, + solana_rent::Rent, + solana_signer::Signer, + solana_transaction_context::transaction::TransactionContext, + solana_transaction_error::TransactionError, + std::collections::HashSet, + }; + + #[test] + fn test_new() { + let rent = Rent::default(); + let key1 = Keypair::new(); + let key2 = Keypair::new(); + let key3 = Keypair::new(); + let key4 = Keypair::new(); + + let message = Message { + account_keys: vec![key2.pubkey(), key1.pubkey(), key4.pubkey()], + header: MessageHeader::default(), + instructions: vec![ + CompiledInstruction { + program_id_index: 1, + accounts: vec![0], + data: vec![], + }, + CompiledInstruction { + program_id_index: 1, + accounts: vec![2], + data: vec![], + }, + ], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = + SanitizedMessage::Legacy(LegacyMessage::new(message, &HashSet::new())); + + let transaction_accounts = vec![ + (key1.pubkey(), AccountSharedData::default()), + (key2.pubkey(), AccountSharedData::default()), + (key3.pubkey(), AccountSharedData::default()), + ]; + + let context = TransactionContext::new(transaction_accounts, rent.clone(), 20, 20, 1); + let result = TransactionAccountStateInfo::new(&context, &sanitized_message, &rent); + assert_eq!( + result, + vec![ + TransactionAccountStateInfo { + rent_state: Some(RentState::Uninitialized) + }, + TransactionAccountStateInfo { rent_state: None }, + TransactionAccountStateInfo { + rent_state: Some(RentState::Uninitialized) + } + ] + ); + } + + #[test] + #[should_panic(expected = "message and transaction context out of sync, fatal")] + fn test_new_panic() { + let rent = Rent::default(); + let key1 = Keypair::new(); + let key2 = Keypair::new(); + let key3 = Keypair::new(); + let key4 = Keypair::new(); + + let message = Message { + account_keys: vec![key2.pubkey(), key1.pubkey(), key4.pubkey(), key3.pubkey()], + header: MessageHeader::default(), + instructions: vec![ + CompiledInstruction { + program_id_index: 1, + accounts: vec![0], + data: vec![], + }, + CompiledInstruction { + program_id_index: 1, + accounts: vec![2], + data: vec![], + }, + ], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = + SanitizedMessage::Legacy(LegacyMessage::new(message, &HashSet::new())); + + let transaction_accounts = vec![ + (key1.pubkey(), AccountSharedData::default()), + (key2.pubkey(), AccountSharedData::default()), + (key3.pubkey(), AccountSharedData::default()), + ]; + + let context = TransactionContext::new(transaction_accounts, rent.clone(), 20, 20, 1); + let _result = TransactionAccountStateInfo::new(&context, &sanitized_message, &rent); + } + + #[test] + fn test_verify_changes() { + let key1 = Keypair::new(); + let key2 = Keypair::new(); + let pre_rent_state = vec![ + TransactionAccountStateInfo { + rent_state: Some(RentState::Uninitialized), + }, + TransactionAccountStateInfo { + rent_state: Some(RentState::Uninitialized), + }, + ]; + let post_rent_state = vec![TransactionAccountStateInfo { + rent_state: Some(RentState::Uninitialized), + }]; + + let transaction_accounts = vec![ + (key1.pubkey(), AccountSharedData::default()), + (key2.pubkey(), AccountSharedData::default()), + ]; + + let context = TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 1); + + let result = TransactionAccountStateInfo::verify_changes( + &pre_rent_state, + &post_rent_state, + &context, + ); + assert!(result.is_ok()); + + let pre_rent_state = vec![TransactionAccountStateInfo { + rent_state: Some(RentState::Uninitialized), + }]; + let post_rent_state = vec![TransactionAccountStateInfo { + rent_state: Some(RentState::RentPaying { data_size: 2, lamports: 5 }), + }]; + + let transaction_accounts = vec![ + (key1.pubkey(), AccountSharedData::default()), + (key2.pubkey(), AccountSharedData::default()), + ]; + + let context = TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 1); + let result = TransactionAccountStateInfo::verify_changes( + &pre_rent_state, + &post_rent_state, + &context, + ); + assert_eq!( + result.err(), + Some(TransactionError::InsufficientFundsForRent { account_index: 0 }) + ); + } +} diff --git a/solana/svm/src/transaction_balances.rs b/solana/svm/src/transaction_balances.rs new file mode 100644 index 0000000..28a817c --- /dev/null +++ b/solana/svm/src/transaction_balances.rs @@ -0,0 +1,60 @@ +#[cfg(feature = "dev-context-only-utils")] +use qualifier_attr::field_qualifiers; +use solana_account::ReadableAccount; +use solana_transaction_context::transaction_accounts::KeyedAccountSharedData; + +// Use an internal alias so this stays tied to native lamport balances. +type TxNativeBalances = Vec; + +// Implemented for Option to keep call sites branch-free. +pub(crate) trait BalanceCollectionRoutines { + fn collect_pre_balances(&mut self, accounts: &[KeyedAccountSharedData]); + + fn collect_post_balances(&mut self, accounts: &[KeyedAccountSharedData]); +} + +/// Native account balances recorded before and after execution. +#[derive(Debug, Default, Clone)] +#[cfg_attr( + feature = "dev-context-only-utils", + field_qualifiers(native_pre(pub), native_post(pub)) +)] +pub struct BalanceCollector { + native_pre: TxNativeBalances, + native_post: TxNativeBalances, +} + +impl BalanceCollector { + /// Returns recorded pre- and post-execution lamport balances. + pub fn into_vecs(self) -> (TxNativeBalances, TxNativeBalances) { + (self.native_pre, self.native_post) + } + + fn collect_balances(&mut self, accounts: &[KeyedAccountSharedData]) -> TxNativeBalances { + accounts.iter().map(|a| a.1.lamports()).collect() + } +} + +impl BalanceCollectionRoutines for BalanceCollector { + fn collect_pre_balances(&mut self, accounts: &[KeyedAccountSharedData]) { + self.native_pre = self.collect_balances(accounts); + } + + fn collect_post_balances(&mut self, accounts: &[KeyedAccountSharedData]) { + self.native_post = self.collect_balances(accounts); + } +} + +impl BalanceCollectionRoutines for Option { + fn collect_pre_balances(&mut self, accounts: &[KeyedAccountSharedData]) { + if let Some(inner) = self { + inner.collect_pre_balances(accounts) + } + } + + fn collect_post_balances(&mut self, accounts: &[KeyedAccountSharedData]) { + if let Some(inner) = self { + inner.collect_post_balances(accounts) + } + } +} diff --git a/solana/svm/src/transaction_execution_result.rs b/solana/svm/src/transaction_execution_result.rs new file mode 100644 index 0000000..954c1f0 --- /dev/null +++ b/solana/svm/src/transaction_execution_result.rs @@ -0,0 +1,56 @@ +use { + crate::account_loader::LoadedTransaction, + solana_message::inner_instruction::InnerInstructionsList, + solana_transaction_context::transaction::TransactionReturnData, + solana_transaction_error::TransactionResult, std::sync::Arc, +}; + +/// Loaded-account statistics for one transaction. +#[derive(Debug, Default, Clone, PartialEq)] +pub struct TransactionLoadedAccountsStats { + /// Total loaded account data size charged to the transaction. + pub loaded_accounts_data_size: u32, + /// Number of loaded transaction accounts. + pub loaded_accounts_count: usize, +} + +/// A transaction that reached execution, including mutated accounts. +#[derive(Debug, Clone)] +pub struct ExecutedTransaction { + /// Loaded transaction state after execution. + pub loaded_transaction: LoadedTransaction, + /// Execution status and optional recording data. + pub execution_details: TransactionExecutionDetails, +} + +impl ExecutedTransaction { + /// Returns true when execution completed with `Ok(())`. + pub fn was_successful(&self) -> bool { + self.execution_details.was_successful() + } +} + +/// Status and recordings produced by transaction execution. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TransactionExecutionDetails { + /// Final execution status. + pub status: TransactionResult<()>, + /// Program log messages when log recording is enabled. + pub log_messages: Option>>, + /// CPI inner instructions when CPI recording is enabled. + pub inner_instructions: Option, + /// Non-empty return data when return-data recording is enabled. + pub return_data: Option, + /// Compute units consumed by top-level instruction execution. + pub executed_units: u64, + /// The change in accounts data len for this transaction. + /// NOTE: This value is valid IFF `status` is `Ok`. + pub accounts_data_len_delta: i64, +} + +impl TransactionExecutionDetails { + /// Returns true when `status` is `Ok(())`. + pub fn was_successful(&self) -> bool { + self.status.is_ok() + } +} diff --git a/solana/svm/src/transaction_processing_callback.rs b/solana/svm/src/transaction_processing_callback.rs new file mode 100644 index 0000000..cf53bea --- /dev/null +++ b/solana/svm/src/transaction_processing_callback.rs @@ -0,0 +1 @@ +pub use solana_svm_callback::{AccountState, InvokeContextCallback, TransactionProcessingCallback}; diff --git a/solana/svm/src/transaction_processing_result.rs b/solana/svm/src/transaction_processing_result.rs new file mode 100644 index 0000000..6e1316c --- /dev/null +++ b/solana/svm/src/transaction_processing_result.rs @@ -0,0 +1,46 @@ +use { + crate::transaction_execution_result::ExecutedTransaction, + solana_transaction_error::TransactionResult, +}; + +/// Result of loading and executing a transaction. +/// +/// `Err` means execution did not produce an `ExecutedTransaction`. `Ok` means +/// execution was attempted; inspect the contained execution status for program +/// success or failure. +pub type TransactionProcessingResult = TransactionResult>; + +/// Convenience methods for nested transaction processing results. +pub trait TransactionProcessingResultExtensions { + /// Returns true when the transaction reached execution. + fn was_processed(&self) -> bool; + /// Returns true when the transaction reached execution and succeeded. + fn was_processed_with_successful_result(&self) -> bool; + /// Returns the executed transaction when execution was attempted. + fn processed_transaction(&self) -> Option<&ExecutedTransaction>; + /// Collapses load and execution status into a single transaction result. + fn flattened_result(&self) -> TransactionResult<()>; +} + +impl TransactionProcessingResultExtensions for TransactionProcessingResult { + fn was_processed(&self) -> bool { + self.is_ok() + } + + fn was_processed_with_successful_result(&self) -> bool { + match self { + Ok(processed_tx) => processed_tx.was_successful(), + Err(_) => false, + } + } + + fn processed_transaction(&self) -> Option<&ExecutedTransaction> { + self.as_deref().ok() + } + + fn flattened_result(&self) -> TransactionResult<()> { + self.as_ref() + .map_err(|err| err.clone()) + .and_then(|processed_tx| processed_tx.execution_details.status.clone()) + } +} diff --git a/solana/svm/src/transaction_processor.rs b/solana/svm/src/transaction_processor.rs new file mode 100644 index 0000000..ebbe25c --- /dev/null +++ b/solana/svm/src/transaction_processor.rs @@ -0,0 +1,924 @@ +#[cfg(test)] +use solana_svm_type_overrides::sync::RwLock; +use solana_transaction_context::transaction_accounts::KeyedAccountSharedData; + +#[cfg(feature = "dev-context-only-utils")] +use qualifier_attr::{field_qualifiers, qualifiers}; +use { + crate::{ + account_loader::{ + CheckedTransactionDetails, LoadedTransaction, TransactionLoadResult, + ValidatedTransactionDetails, load_transaction, + }, + message_processor::process_message, + program_loader::load_program, + transaction_account_state_info::TransactionAccountStateInfo, + transaction_balances::{BalanceCollectionRoutines, BalanceCollector}, + transaction_execution_result::{ExecutedTransaction, TransactionExecutionDetails}, + transaction_processing_result::TransactionProcessingResult, + }, + solana_account::{AccountSharedData, PROGRAM_OWNERS, ReadableAccount}, + solana_clock::Slot, + solana_hash::Hash, + solana_instruction::TRANSACTION_LEVEL_STACK_HEIGHT, + solana_message::{ + compiled_instruction::CompiledInstruction, + inner_instruction::{InnerInstruction, InnerInstructionsList}, + }, + solana_program_runtime::{ + execution_budget::SVMTransactionExecutionCost, + invoke_context::{EnvironmentConfig, InvokeContext}, + loaded_programs::{ProgramCache, ProgramCacheForTxBatch, ProgramRuntimeEnvironments}, + sysvar_cache::SysvarCache, + }, + solana_pubkey::Pubkey, + solana_rent::Rent, + solana_svm_callback::TransactionProcessingCallback, + solana_svm_feature_set::SVMFeatureSet, + solana_svm_log_collector::LogCollector, + solana_svm_transaction::svm_transaction::SVMTransaction, + solana_svm_type_overrides::sync::Arc, + solana_transaction_context::transaction::{ExecutionRecord, TransactionContext}, + solana_transaction_error::TransactionError, + std::{ + fmt::{Debug, Formatter}, + rc::Rc, + }, +}; + +/// Log messages emitted during a transaction. +pub type TransactionLogMessages = Vec; + +/// Result of loading and executing one sanitized transaction. +pub struct LoadAndExecuteSanitizedTransactionOutput { + /// Load or execution result. + /// + /// `Ok` means execution was attempted. The contained transaction can still + /// have a failed execution status. + pub processing_result: TransactionProcessingResult, + /// Native pre/post balances when balance recording is enabled. + pub balance_collector: Option, +} + +/// Controls which execution artifacts are retained in the result. +#[derive(Copy, Clone, Default)] +pub struct ExecutionRecordingConfig { + /// Record inner instructions produced by CPI. + pub enable_cpi_recording: bool, + /// Record program log messages. + pub enable_log_recording: bool, + /// Record non-empty return data. + pub enable_return_data_recording: bool, + /// Record native account balances before and after execution. + pub enable_transaction_balance_recording: bool, +} + +impl ExecutionRecordingConfig { + /// Creates a recording config with every flag set to the same value. + pub fn new_single_setting(option: bool) -> Self { + ExecutionRecordingConfig { + enable_return_data_recording: option, + enable_log_recording: option, + enable_cpi_recording: option, + enable_transaction_balance_recording: option, + } + } +} + +/// Transaction execution options. +#[derive(Default)] +pub struct TransactionProcessingConfig { + /// The maximum number of bytes that log messages can consume. + pub log_messages_bytes_limit: Option, + /// Recording capabilities for transaction execution. + pub recording_config: ExecutionRecordingConfig, +} + +/// Runtime inputs that are shared across a transaction execution. +#[derive(Default)] +pub struct TransactionProcessingEnvironment { + /// Blockhash exposed to programs through the invocation environment. + pub blockhash: Hash, + /// Lamports per signature associated with `blockhash`. + pub blockhash_lamports_per_signature: u64, + /// Retained for API compatibility; the current execution path does not use + /// stake weighting. + pub epoch_total_stake: u64, + /// Runtime feature set used during execution. + pub feature_set: SVMFeatureSet, + /// Runtime environments used for executing already deployed programs. + pub program_runtime_environments_for_execution: ProgramRuntimeEnvironments, + /// Rent calculator used for transaction-context construction and rent-state checks. + pub rent: Rent, +} + +#[cfg_attr( + feature = "dev-context-only-utils", + field_qualifiers(slot(pub), sysvar_cache(pub)) +)] +pub struct TransactionBatchProcessor { + /// Slot associated with this processor. + pub slot: Slot, + + /// Sysvars exposed to programs during execution. + sysvar_cache: SysvarCache, + + /// Shared cache of loaded programs. + pub program_cache: Arc, + + execution_cost: SVMTransactionExecutionCost, +} + +impl Debug for TransactionBatchProcessor { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TransactionBatchProcessor") + .field("slot", &self.slot) + .field("sysvar_cache", &self.sysvar_cache) + .field("program_cache", &self.program_cache) + .finish() + } +} + +impl Default for TransactionBatchProcessor { + fn default() -> Self { + Self { + slot: Slot::default(), + sysvar_cache: Default::default(), + program_cache: Arc::new(ProgramCache::default()), + execution_cost: SVMTransactionExecutionCost::default(), + } + } +} + +impl TransactionBatchProcessor { + /// Create a new, uninitialized `TransactionBatchProcessor`. + /// + /// In this context, uninitialized means that the `TransactionBatchProcessor` + /// has been initialized with an empty program cache. The cache contains no + /// programs, including builtins. + pub fn new_uninitialized(slot: Slot, cache: Arc) -> Self { + Self { + slot, + program_cache: cache, + ..Self::default() + } + } + + /// Create a new `TransactionBatchProcessor`. + /// + /// Missing runtime environments are replaced with empty loaders. The + /// program cache is used as provided and does not receive builtins here. + pub fn new(slot: Slot, cache: Arc) -> Self { + Self::new_uninitialized(slot, cache) + } + + /// Sets the base execution cost charged by this processor. + pub fn set_execution_cost(&mut self, cost: SVMTransactionExecutionCost) { + self.execution_cost = cost; + } + + /// Returns mutable access to cached sysvars for the current processor slot. + pub fn sysvar_cache_mut(&mut self) -> &mut SysvarCache { + &mut self.sysvar_cache + } + + /// Loads accounts, prepares programs, and executes one sanitized transaction. + pub fn load_and_execute_sanitized_transaction( + &self, + callbacks: &CB, + tx: &impl SVMTransaction, + details: CheckedTransactionDetails, + environment: &TransactionProcessingEnvironment, + config: &TransactionProcessingConfig, + ) -> LoadAndExecuteSanitizedTransactionOutput { + // Determine a capacity for the internal account cache. This + // over-allocates but avoids ever reallocating, and spares us from + // deduplicating the account keys lists. + + // Create the transaction balance collector if recording is enabled. + let mut balance_collector = config + .recording_config + .enable_transaction_balance_recording + .then(BalanceCollector::default); + + // Create the batch-local program cache. + let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new(self.slot); + + let details = ValidatedTransactionDetails { + compute_budget: details.compute_budget_and_limits.budget, + loaded_accounts_bytes_limit: details + .compute_budget_and_limits + .loaded_accounts_data_size_limit, + fee_details: details.compute_budget_and_limits.fee_details, + }; + let load_result = load_transaction(callbacks, tx, details); + + let processing_result = match load_result { + TransactionLoadResult::NotLoaded(err) => Err(err), + TransactionLoadResult::Loaded(loaded_transaction) => { + balance_collector.collect_pre_balances(&loaded_transaction.accounts); + self.replenish_program_cache( + &environment.program_runtime_environments_for_execution, + &mut program_cache_for_tx_batch, + &loaded_transaction.accounts, + ); + + let mut executed_tx = self.execute_loaded_transaction( + callbacks, + tx, + loaded_transaction, + &mut program_cache_for_tx_batch, + environment, + config, + ); + if executed_tx.validate_access(tx) { + balance_collector + .collect_post_balances(&executed_tx.loaded_transaction.accounts); + } + if executed_tx.was_successful() { + let cache = program_cache_for_tx_batch.drain_modified_entries(); + self.program_cache.merge(&cache); + } + Ok(Box::new(executed_tx)) + } + }; + + LoadAndExecuteSanitizedTransactionOutput { + processing_result, + balance_collector, + } + } + + #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))] + fn replenish_program_cache( + &self, + environments: &ProgramRuntimeEnvironments, + program_cache_for_tx_batch: &mut ProgramCacheForTxBatch, + accounts: &[KeyedAccountSharedData], + ) { + for (pubkey, acc) in accounts { + if !(acc.executable() && PROGRAM_OWNERS.iter().any(|o| o == acc.owner())) { + continue; + } + let entry = if let Some(entry) = self.program_cache.get(pubkey) { + entry + } else { + let entry = load_program(environments, acc); + self.program_cache.assign_program(*pubkey, entry.clone()); + entry + }; + program_cache_for_tx_batch.replenish(*pubkey, entry); + } + } + + /// Executes a transaction using already loaded accounts. + #[allow(clippy::too_many_arguments)] + fn execute_loaded_transaction( + &self, + callback: &CB, + tx: &impl SVMTransaction, + mut loaded_transaction: LoadedTransaction, + program_cache_for_tx_batch: &mut ProgramCacheForTxBatch, + environment: &TransactionProcessingEnvironment, + config: &TransactionProcessingConfig, + ) -> ExecutedTransaction { + let transaction_accounts = std::mem::take(&mut loaded_transaction.accounts); + + // Ensure the length of accounts matches the expected length from tx.account_keys(). + // This is a sanity check in case that someone starts adding some additional accounts + // since this has been done before. See discussion in PR #4497 for details + debug_assert!(transaction_accounts.len() == tx.account_keys().len()); + + fn transaction_accounts_lamports_sum( + accounts: &[(Pubkey, AccountSharedData)], + ) -> Option { + accounts.iter().try_fold(0u128, |sum, (_, account)| { + sum.checked_add(u128::from(account.lamports())) + }) + } + + let lamports_before_tx = + transaction_accounts_lamports_sum(&transaction_accounts).unwrap_or(0); + + let compute_budget = loaded_transaction.compute_budget; + + let mut transaction_context = TransactionContext::new( + transaction_accounts, + environment.rent.clone(), + compute_budget.max_instruction_stack_depth, + compute_budget.max_instruction_trace_length, + tx.num_instructions(), + ); + + let pre_account_state_info = + TransactionAccountStateInfo::new(&transaction_context, tx, &environment.rent); + + let log_collector = if config.recording_config.enable_log_recording { + match config.log_messages_bytes_limit { + None => Some(LogCollector::new_ref()), + Some(log_messages_bytes_limit) => Some(LogCollector::new_ref_with_limit(Some( + log_messages_bytes_limit, + ))), + } + } else { + None + }; + + let mut executed_units = 0u64; + + let mut invoke_context = InvokeContext::new( + &mut transaction_context, + program_cache_for_tx_batch, + EnvironmentConfig::new( + environment.blockhash, + environment.blockhash_lamports_per_signature, + callback, + &environment.feature_set, + &environment.program_runtime_environments_for_execution, + &self.sysvar_cache, + ), + log_collector.clone(), + compute_budget, + self.execution_cost, + ); + + let process_result = process_message( + tx, + &loaded_transaction.program_indices, + &mut invoke_context, + &mut executed_units, + ); + + drop(invoke_context); + + let mut status = process_result.and_then(|info| { + let post_account_state_info = + TransactionAccountStateInfo::new(&transaction_context, tx, &environment.rent); + TransactionAccountStateInfo::verify_changes( + &pre_account_state_info, + &post_account_state_info, + &transaction_context, + ) + .map(|_| info) + }); + + let log_messages: Option = + log_collector.and_then(|log_collector| { + Rc::try_unwrap(log_collector) + .map(|log_collector| log_collector.into_inner().into_messages()) + .ok() + }); + + let (execution_record, inner_instructions) = Self::deconstruct_transaction( + transaction_context, + config.recording_config.enable_cpi_recording, + ); + + let ExecutionRecord { + accounts, + return_data, + accounts_resize_delta: accounts_data_len_delta, + .. + } = execution_record; + + if status.is_ok() + && transaction_accounts_lamports_sum(&accounts) + .filter(|lamports_after_tx| lamports_before_tx == *lamports_after_tx) + .is_none() + { + status = Err(TransactionError::UnbalancedTransaction); + } + let status = status.map(|_| ()); + + loaded_transaction.accounts = accounts; + + let return_data = if config.recording_config.enable_return_data_recording + && !return_data.data.is_empty() + { + Some(return_data) + } else { + None + }; + + ExecutedTransaction { + execution_details: TransactionExecutionDetails { + status, + log_messages: log_messages.map(Arc::new), + inner_instructions, + return_data, + executed_units, + accounts_data_len_delta, + }, + loaded_transaction, + } + } + + /// Extract an ExecutionRecord and an InnerInstructionsList from a TransactionContext + fn deconstruct_transaction( + mut transaction_context: TransactionContext, + record_inner_instructions: bool, + ) -> (ExecutionRecord, Option) { + let inner_ix = if record_inner_instructions { + debug_assert!( + transaction_context + .get_instruction_context_at_index_in_trace(0) + .map(|instruction_context| instruction_context.get_stack_height() + == TRANSACTION_LEVEL_STACK_HEIGHT) + .unwrap_or(true) + ); + + let (ix_trace, accounts, ix_data_trace) = transaction_context.take_instruction_trace(); + let mut outer_instructions = Vec::new(); + for ((ix_in_trace, ix_data), ix_accounts) in + ix_trace.into_iter().zip(ix_data_trace.into_iter()).zip(accounts) + { + let stack_height = ix_in_trace.nesting_level.saturating_add(1) as usize; + if stack_height == TRANSACTION_LEVEL_STACK_HEIGHT { + outer_instructions.push(Vec::new()); + } else if let Some(inner_instructions) = outer_instructions.last_mut() { + let stack_height = u8::try_from(stack_height).unwrap_or(u8::MAX); + inner_instructions.push(InnerInstruction { + instruction: CompiledInstruction::new_from_raw_parts( + ix_in_trace.program_account_index_in_tx as u8, + ix_data.into_owned(), + ix_accounts.iter().map(|acc| acc.index_in_transaction as u8).collect(), + ), + stack_height, + }); + } else { + debug_assert!(false); + } + } + + Some(outer_instructions) + } else { + None + }; + + let record: ExecutionRecord = transaction_context.into(); + + (record, inner_ix) + } + + pub fn fill_missing_sysvar_cache_entries( + &mut self, + callbacks: &CB, + ) { + self.sysvar_cache.fill_missing_entries(|pubkey, set_sysvar| { + if let Some((account, _slot)) = callbacks.get_account_shared_data(pubkey) { + set_sysvar(account.data()); + } + }); + } + + pub fn reset_sysvar_cache(&mut self) { + self.sysvar_cache.reset(); + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + #[allow(deprecated)] + use solana_sysvar::fees::Fees; + use { + super::*, + solana_account::{WritableAccount, create_account_shared_data_for_test}, + solana_clock::Clock, + solana_epoch_schedule::EpochSchedule, + solana_fee_calculator::FeeCalculator, + solana_fee_structure::FeeDetails, + solana_hash::Hash, + solana_message::{LegacyMessage, Message, MessageHeader, SanitizedMessage}, + solana_program_runtime::{ + execution_budget::SVMTransactionExecutionBudget, loaded_programs::ProgramCacheEntryType, + }, + solana_rent::Rent, + solana_sdk_ids::{bpf_loader, sysvar}, + solana_signature::Signature, + solana_svm_callback::{AccountState, InvokeContextCallback}, + solana_transaction::sanitized::SanitizedTransaction, + solana_transaction_context::transaction::TransactionContext, + std::collections::HashMap, + }; + + fn new_unchecked_sanitized_message(message: Message) -> SanitizedMessage { + SanitizedMessage::Legacy(LegacyMessage::new(message, &HashSet::new())) + } + + #[derive(Clone, Default)] + struct MockBankCallback { + account_shared_data: Arc>>, + #[allow(clippy::type_complexity)] + inspected_accounts: + Arc, /* is_writable */ bool)>>>>, + } + + impl InvokeContextCallback for MockBankCallback {} + + impl TransactionProcessingCallback for MockBankCallback { + fn get_account_shared_data(&self, pubkey: &Pubkey) -> Option<(AccountSharedData, Slot)> { + self.account_shared_data + .read() + .unwrap() + .get(pubkey) + .map(|account| (account.clone(), 0)) + } + + fn inspect_account( + &self, + address: &Pubkey, + account_state: AccountState, + is_writable: bool, + ) { + let account = match account_state { + AccountState::Dead => None, + AccountState::Alive(account) => Some(account.clone()), + }; + self.inspected_accounts + .write() + .unwrap() + .entry(*address) + .or_default() + .push((account, is_writable)); + } + } + + #[test] + fn test_inner_instructions_list_from_instruction_trace() { + let mut transaction_context = TransactionContext::new( + vec![( + Pubkey::new_unique(), + AccountSharedData::new(1, 1, &bpf_loader::ID), + )], + Rent::default(), + 4, + 11, + 4, + ); + + // To be uncommented when we reorder the instruction trace + // Four top level instructions + // for i in 0..4 { + // transaction_context + // .configure_instruction_at_index( + // i, + // 0, + // vec![], + // vec![u16::MAX; 256], + // Cow::Owned(vec![i as u8]), + // None, + // ) + // .unwrap(); + // } + + // Execute ix #0 + transaction_context + .configure_top_level_instruction_for_tests(0, vec![], vec![0]) + .unwrap(); + transaction_context.push().unwrap(); + // ix #0 does a CPI + transaction_context.configure_next_cpi_for_tests(0, vec![], vec![0, 0]).unwrap(); + transaction_context.push().unwrap(); + // Returning from everything + transaction_context.pop().unwrap(); + transaction_context.pop().unwrap(); + // Execute ix #1 + transaction_context + .configure_top_level_instruction_for_tests(0, vec![], vec![1]) + .unwrap(); + transaction_context.push().unwrap(); + transaction_context.pop().unwrap(); + // Execute ix #2 + transaction_context + .configure_top_level_instruction_for_tests(0, vec![], vec![2]) + .unwrap(); + transaction_context.push().unwrap(); + // ix #2 does a CPI + transaction_context.configure_next_cpi_for_tests(0, vec![], vec![2, 0]).unwrap(); + transaction_context.push().unwrap(); + // A nested CPI + transaction_context.configure_next_cpi_for_tests(0, vec![], vec![2, 1]).unwrap(); + transaction_context.push().unwrap(); + // Return from nested CPI + transaction_context.pop().unwrap(); + // Return from CPI + transaction_context.pop().unwrap(); + // ix #2 does another CPI + transaction_context.configure_next_cpi_for_tests(0, vec![], vec![2, 2]).unwrap(); + transaction_context.push().unwrap(); + // Return from everything related to ix #2 + transaction_context.pop().unwrap(); + transaction_context.pop().unwrap(); + // Execute ix #3 + transaction_context + .configure_top_level_instruction_for_tests(0, vec![], vec![3]) + .unwrap(); + transaction_context.push().unwrap(); + // ix #3 does a CPI + transaction_context.configure_next_cpi_for_tests(0, vec![], vec![3, 0]).unwrap(); + transaction_context.push().unwrap(); + // ix #3 does a nested CPI + transaction_context.configure_next_cpi_for_tests(0, vec![], vec![3, 1]).unwrap(); + transaction_context.push().unwrap(); + // ix #3 does a second nested CPI + transaction_context.configure_next_cpi_for_tests(0, vec![], vec![3, 2]).unwrap(); + transaction_context.push().unwrap(); + // Return from everything related to ix #3 + transaction_context.pop().unwrap(); + transaction_context.pop().unwrap(); + transaction_context.pop().unwrap(); + transaction_context.pop().unwrap(); + + let inner_instructions = + TransactionBatchProcessor::deconstruct_transaction(transaction_context, true) + .1 + .unwrap(); + + assert_eq!( + inner_instructions, + vec![ + vec![InnerInstruction { + instruction: CompiledInstruction::new_from_raw_parts(0, vec![0, 0], vec![]), + stack_height: 2, + }], + vec![], + vec![ + InnerInstruction { + instruction: CompiledInstruction::new_from_raw_parts(0, vec![2, 0], vec![]), + stack_height: 2, + }, + InnerInstruction { + instruction: CompiledInstruction::new_from_raw_parts(0, vec![2, 1], vec![]), + stack_height: 3, + }, + InnerInstruction { + instruction: CompiledInstruction::new_from_raw_parts(0, vec![2, 2], vec![]), + stack_height: 2, + }, + ], + vec![ + InnerInstruction { + instruction: CompiledInstruction::new_from_raw_parts(0, vec![3, 0], vec![]), + stack_height: 2, + }, + InnerInstruction { + instruction: CompiledInstruction::new_from_raw_parts(0, vec![3, 1], vec![]), + stack_height: 3, + }, + InnerInstruction { + instruction: CompiledInstruction::new_from_raw_parts(0, vec![3, 2], vec![]), + stack_height: 4, + }, + ] + ] + ); + } + + #[test] + fn test_execute_loaded_transaction_recordings() { + // Setting all the arguments correctly is too burdensome for testing + // execute_loaded_transaction separately.This function will be tested in an integration + // test with load_and_execute_sanitized_transactions + let message = Message { + account_keys: vec![Pubkey::new_from_array([0; 32])], + header: MessageHeader::default(), + instructions: vec![CompiledInstruction { + program_id_index: 0, + accounts: vec![], + data: vec![], + }], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = new_unchecked_sanitized_message(message); + let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default(); + let batch_processor = TransactionBatchProcessor::default(); + + let sanitized_transaction = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + + let loaded_transaction = LoadedTransaction { + accounts: vec![(Pubkey::new_unique(), AccountSharedData::default())], + program_indices: vec![0], + fee_details: FeeDetails::default(), + compute_budget: SVMTransactionExecutionBudget::default(), + loaded_accounts_data_size: 32, + }; + + let processing_environment = TransactionProcessingEnvironment::default(); + + let mut processing_config = TransactionProcessingConfig::default(); + processing_config.recording_config.enable_log_recording = true; + + let mock_bank = MockBankCallback::default(); + + let executed_tx = batch_processor.execute_loaded_transaction( + &mock_bank, + &sanitized_transaction, + loaded_transaction.clone(), + &mut program_cache_for_tx_batch, + &processing_environment, + &processing_config, + ); + assert!(executed_tx.execution_details.log_messages.is_some()); + + processing_config.log_messages_bytes_limit = Some(2); + + let executed_tx = batch_processor.execute_loaded_transaction( + &mock_bank, + &sanitized_transaction, + loaded_transaction.clone(), + &mut program_cache_for_tx_batch, + &processing_environment, + &processing_config, + ); + assert!(executed_tx.execution_details.log_messages.is_some()); + assert!(executed_tx.execution_details.inner_instructions.is_none()); + + processing_config.recording_config.enable_log_recording = false; + processing_config.recording_config.enable_cpi_recording = true; + processing_config.log_messages_bytes_limit = None; + + let executed_tx = batch_processor.execute_loaded_transaction( + &mock_bank, + &sanitized_transaction, + loaded_transaction, + &mut program_cache_for_tx_batch, + &processing_environment, + &processing_config, + ); + + assert!(executed_tx.execution_details.log_messages.is_none()); + assert!(executed_tx.execution_details.inner_instructions.is_some()); + } + + #[test] + fn test_replenish_program_cache() { + let batch_processor = TransactionBatchProcessor::default(); + let key = Pubkey::new_unique(); + + let mut account_data = AccountSharedData::default(); + account_data.set_owner(bpf_loader::id()); + account_data.set_executable(true); + let accounts = vec![(key, account_data)]; + + let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new(batch_processor.slot); + + batch_processor.replenish_program_cache( + &Default::default(), + &mut program_cache_for_tx_batch, + &accounts, + ); + + let program = program_cache_for_tx_batch.find(&key).unwrap(); + assert!(matches!( + program.program, + ProgramCacheEntryType::FailedVerification(_) + )); + assert!(batch_processor.program_cache.get(&key).is_some()); + } + + #[test] + #[allow(deprecated)] + fn test_sysvar_cache_initialization1() { + let mock_bank = MockBankCallback::default(); + + let clock = Clock { + slot: 1, + epoch_start_timestamp: 2, + epoch: 3, + leader_schedule_epoch: 4, + unix_timestamp: 5, + }; + let clock_account = create_account_shared_data_for_test(&clock); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(sysvar::clock::id(), clock_account); + + let epoch_schedule = EpochSchedule::custom(64, 2, true); + let epoch_schedule_account = create_account_shared_data_for_test(&epoch_schedule); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(sysvar::epoch_schedule::id(), epoch_schedule_account); + + let fees = Fees { + fee_calculator: FeeCalculator { lamports_per_signature: 123 }, + }; + let fees_account = create_account_shared_data_for_test(&fees); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(sysvar::fees::id(), fees_account); + + let rent = Rent::default(); + let rent_account = create_account_shared_data_for_test(&rent); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(sysvar::rent::id(), rent_account); + + let mut transaction_processor = TransactionBatchProcessor::default(); + transaction_processor.fill_missing_sysvar_cache_entries(&mock_bank); + + let sysvar_cache = &transaction_processor.sysvar_cache; + let cached_clock = sysvar_cache.get_clock(); + let cached_rent = sysvar_cache.get_rent(); + + assert_eq!( + cached_clock.expect("clock sysvar missing in cache"), + clock.into() + ); + assert_eq!( + cached_rent.expect("rent sysvar missing in cache"), + rent.into() + ); + assert!(sysvar_cache.get_slot_hashes().is_err()); + } + + #[test] + #[allow(deprecated)] + fn test_reset_and_fill_sysvar_cache() { + let mock_bank = MockBankCallback::default(); + + let clock = Clock { + slot: 1, + epoch_start_timestamp: 2, + epoch: 3, + leader_schedule_epoch: 4, + unix_timestamp: 5, + }; + let clock_account = create_account_shared_data_for_test(&clock); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(sysvar::clock::id(), clock_account); + + let epoch_schedule = EpochSchedule::custom(64, 2, true); + let epoch_schedule_account = create_account_shared_data_for_test(&epoch_schedule); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(sysvar::epoch_schedule::id(), epoch_schedule_account); + + let fees = Fees { + fee_calculator: FeeCalculator { lamports_per_signature: 123 }, + }; + let fees_account = create_account_shared_data_for_test(&fees); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(sysvar::fees::id(), fees_account); + + let rent = Rent::default(); + let rent_account = create_account_shared_data_for_test(&rent); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(sysvar::rent::id(), rent_account); + + let mut transaction_processor = TransactionBatchProcessor::default(); + // Fill the sysvar cache + transaction_processor.fill_missing_sysvar_cache_entries(&mock_bank); + // Reset the sysvar cache + transaction_processor.reset_sysvar_cache(); + + { + let sysvar_cache = &transaction_processor.sysvar_cache; + // Test that sysvar cache is empty and none of the values are found + assert!(sysvar_cache.get_clock().is_err()); + assert!(sysvar_cache.get_rent().is_err()); + } + + // Refill the cache and test the values are available. + transaction_processor.fill_missing_sysvar_cache_entries(&mock_bank); + + let sysvar_cache = &transaction_processor.sysvar_cache; + let cached_clock = sysvar_cache.get_clock(); + let cached_rent = sysvar_cache.get_rent(); + + assert_eq!( + cached_clock.expect("clock sysvar missing in cache"), + clock.into() + ); + assert_eq!( + cached_rent.expect("rent sysvar missing in cache"), + rent.into() + ); + assert!(sysvar_cache.get_slot_hashes().is_err()); + } +} diff --git a/solana/svm/tests/example-programs/clock-sysvar/Cargo.toml b/solana/svm/tests/example-programs/clock-sysvar/Cargo.toml new file mode 100644 index 0000000..3dacc69 --- /dev/null +++ b/solana/svm/tests/example-programs/clock-sysvar/Cargo.toml @@ -0,0 +1,11 @@ +[package] +edition = "2021" +name = "clock-sysvar-program" +version = "4.0.0-rc.1" + +[dependencies] + +[lib] +crate-type = ["cdylib", "rlib"] + +[workspace] diff --git a/solana/svm/tests/example-programs/clock-sysvar/clock_sysvar_program.so b/solana/svm/tests/example-programs/clock-sysvar/clock_sysvar_program.so new file mode 100755 index 0000000000000000000000000000000000000000..dee43a1e38b28684312760f0b81203605ae79c6c GIT binary patch literal 44168 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 ProgramResult { + let time_now = Clock::get().unwrap().unix_timestamp; + let return_data = time_now.to_be_bytes(); + set_return_data(&return_data); + Ok(()) +} diff --git a/solana/svm/tests/example-programs/hello-solana/Cargo.toml b/solana/svm/tests/example-programs/hello-solana/Cargo.toml new file mode 100644 index 0000000..4f08a1b --- /dev/null +++ b/solana/svm/tests/example-programs/hello-solana/Cargo.toml @@ -0,0 +1,11 @@ +[package] +edition = "2021" +name = "hello-solana-program" +version = "4.0.0-rc.1" + +[dependencies] + +[lib] +crate-type = ["cdylib", "rlib"] + +[workspace] diff --git a/solana/svm/tests/example-programs/hello-solana/hello_solana_program.so b/solana/svm/tests/example-programs/hello-solana/hello_solana_program.so new file mode 100755 index 0000000000000000000000000000000000000000..a9da4ff47e5d84903c9ade357361930953992ec3 GIT binary patch literal 35408 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 literal 0 HcmV?d00001 diff --git a/solana/svm/tests/example-programs/hello-solana/src/lib.rs b/solana/svm/tests/example-programs/hello-solana/src/lib.rs new file mode 100644 index 0000000..3f6799c --- /dev/null +++ b/solana/svm/tests/example-programs/hello-solana/src/lib.rs @@ -0,0 +1,16 @@ +use { + solana_account_info::AccountInfo, solana_msg::msg, solana_program_entrypoint::entrypoint, + solana_program_error::ProgramResult, solana_pubkey::Pubkey, +}; + +entrypoint!(process_instruction); + +fn process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + msg!("Hello, Solana!"); + + Ok(()) +} diff --git a/solana/svm/tests/example-programs/simple-transfer/Cargo.toml b/solana/svm/tests/example-programs/simple-transfer/Cargo.toml new file mode 100644 index 0000000..8d93d9d --- /dev/null +++ b/solana/svm/tests/example-programs/simple-transfer/Cargo.toml @@ -0,0 +1,11 @@ +[package] +edition = "2021" +name = "simple-transfer-program" +version = "4.0.0-rc.1" + +[dependencies] + +[lib] +crate-type = ["cdylib", "rlib"] + +[workspace] diff --git a/solana/svm/tests/example-programs/simple-transfer/simple_transfer_program.so b/solana/svm/tests/example-programs/simple-transfer/simple_transfer_program.so new file mode 100755 index 0000000000000000000000000000000000000000..5132b38cdc1b3dcfab8714c9aca50be5a4f08ff6 GIT binary patch literal 67320 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 literal 0 HcmV?d00001 diff --git a/solana/svm/tests/example-programs/simple-transfer/src/lib.rs b/solana/svm/tests/example-programs/simple-transfer/src/lib.rs new file mode 100644 index 0000000..1e922c2 --- /dev/null +++ b/solana/svm/tests/example-programs/simple-transfer/src/lib.rs @@ -0,0 +1,29 @@ +use { + solana_account_info::{next_account_info, AccountInfo}, + solana_program::program::invoke, + solana_program_entrypoint::entrypoint, + solana_program_error::ProgramResult, + solana_pubkey::Pubkey, + solana_system_interface::instruction as system_instruction, +}; + +entrypoint!(process_instruction); + +fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let amount = u64::from_be_bytes(data[0..8].try_into().unwrap()); + let accounts_iter = &mut accounts.iter(); + let payer = next_account_info(accounts_iter)?; + let recipient = next_account_info(accounts_iter)?; + let system_program = next_account_info(accounts_iter)?; + + invoke( + &system_instruction::transfer(payer.key, recipient.key, amount), + &[payer.clone(), recipient.clone(), system_program.clone()], + )?; + + Ok(()) +} diff --git a/solana/svm/tests/example-programs/transfer-from-account/Cargo.toml b/solana/svm/tests/example-programs/transfer-from-account/Cargo.toml new file mode 100644 index 0000000..c431ebe --- /dev/null +++ b/solana/svm/tests/example-programs/transfer-from-account/Cargo.toml @@ -0,0 +1,11 @@ +[package] +edition = "2021" +name = "transfer-from-account" +version = "4.0.0-rc.1" + +[dependencies] + +[lib] +crate-type = ["cdylib", "rlib"] + +[workspace] diff --git a/solana/svm/tests/example-programs/transfer-from-account/src/lib.rs b/solana/svm/tests/example-programs/transfer-from-account/src/lib.rs new file mode 100644 index 0000000..4460873 --- /dev/null +++ b/solana/svm/tests/example-programs/transfer-from-account/src/lib.rs @@ -0,0 +1,31 @@ +use { + solana_account_info::{next_account_info, AccountInfo}, + solana_program::program::invoke, + solana_program_entrypoint::entrypoint, + solana_program_error::ProgramResult, + solana_pubkey::Pubkey, + solana_system_interface::instruction as system_instruction, +}; + +entrypoint!(process_instruction); + +fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + _data: &[u8], +) -> ProgramResult { + let accounts_iter = &mut accounts.iter(); + let payer = next_account_info(accounts_iter)?; + let recipient = next_account_info(accounts_iter)?; + let data_account = next_account_info(accounts_iter)?; + let system_program = next_account_info(accounts_iter)?; + + let amount = u64::from_le_bytes(data_account.data.borrow()[0..8].try_into().unwrap()); + + invoke( + &system_instruction::transfer(payer.key, recipient.key, amount), + &[payer.clone(), recipient.clone(), system_program.clone()], + )?; + + Ok(()) +} diff --git a/solana/svm/tests/example-programs/transfer-from-account/transfer_from_account_program.so b/solana/svm/tests/example-programs/transfer-from-account/transfer_from_account_program.so new file mode 100755 index 0000000000000000000000000000000000000000..a3ef926d3747ef4a499aa4dd4127fcadbead6948 GIT binary patch literal 67888 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 literal 0 HcmV?d00001 diff --git a/solana/svm/tests/example-programs/write-to-account/Cargo.toml b/solana/svm/tests/example-programs/write-to-account/Cargo.toml new file mode 100644 index 0000000..122f321 --- /dev/null +++ b/solana/svm/tests/example-programs/write-to-account/Cargo.toml @@ -0,0 +1,11 @@ +[package] +edition = "2021" +name = "write-to-account" +version = "4.0.0-rc.1" + +[dependencies] + +[lib] +crate-type = ["cdylib", "rlib"] + +[workspace] diff --git a/solana/svm/tests/example-programs/write-to-account/src/lib.rs b/solana/svm/tests/example-programs/write-to-account/src/lib.rs new file mode 100644 index 0000000..0fde9fa --- /dev/null +++ b/solana/svm/tests/example-programs/write-to-account/src/lib.rs @@ -0,0 +1,62 @@ +use { + solana_account_info::{next_account_info, AccountInfo}, + solana_msg::msg, + solana_program_entrypoint::entrypoint, + solana_program_error::{ProgramError, ProgramResult}, + solana_pubkey::Pubkey, + solana_sdk_ids::incinerator, +}; + +entrypoint!(process_instruction); + +fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let accounts_iter = &mut accounts.iter(); + let target_account_info = next_account_info(accounts_iter)?; + match data[0] { + // print account size + 0 => { + msg!( + "account size {}", + target_account_info.try_borrow_data()?.len() + ); + } + // set account data + 1 => { + let mut account_data = target_account_info.try_borrow_mut_data()?; + account_data[0] = 100; + } + // deallocate account + 2 => { + let incinerator_info = next_account_info(accounts_iter)?; + if !incinerator::check_id(incinerator_info.key) { + return Err(ProgramError::InvalidAccountData); + } + + let mut target_lamports = target_account_info.try_borrow_mut_lamports()?; + let mut incinerator_lamports = incinerator_info.try_borrow_mut_lamports()?; + + **incinerator_lamports = incinerator_lamports + .checked_add(**target_lamports) + .ok_or(ProgramError::ArithmeticOverflow)?; + + **target_lamports = target_lamports + .checked_sub(**target_lamports) + .ok_or(ProgramError::InsufficientFunds)?; + } + // reallocate account + 3 => { + let new_size = usize::from_le_bytes(data[1..9].try_into().unwrap()); + target_account_info.realloc(new_size, true)?; + } + // bad ixn + _ => { + return Err(ProgramError::InvalidArgument); + } + } + + Ok(()) +} diff --git a/solana/svm/tests/example-programs/write-to-account/write_to_account_program.so b/solana/svm/tests/example-programs/write-to-account/write_to_account_program.so new file mode 100755 index 0000000000000000000000000000000000000000..e43642631bc6880b32bbd7d9facef6977f69eb84 GIT binary patch literal 21992 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 literal 0 HcmV?d00001 diff --git a/solana/transaction-context/Cargo.toml b/solana/transaction-context/Cargo.toml index d0c0a3d..4056bbb 100644 --- a/solana/transaction-context/Cargo.toml +++ b/solana/transaction-context/Cargo.toml @@ -8,7 +8,7 @@ edition = { workspace = true } homepage = { workspace = true } license = { workspace = true } repository = { workspace = true } -version = { workspace = true } +version = "4.1.1" [package.metadata.docs.rs] all-features = true