Skip to content

Commit

Permalink
Include simple transfer example in SVM (anza-xyz#388)
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasSte authored Mar 22, 2024
1 parent 977b1b8 commit fe16e84
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 14 deletions.
12 changes: 12 additions & 0 deletions svm/tests/example-programs/simple-transfer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "simple-transfer-program"
version = "2.0.0"
edition = "2021"

[dependencies]
solana-program = { path = "../../../../sdk/program", version = "=2.0.0" }

[lib]
crate-type = ["cdylib", "rlib"]

[workspace]
Binary file not shown.
26 changes: 26 additions & 0 deletions svm/tests/example-programs/simple-transfer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use solana_program::{
account_info::{AccountInfo, next_account_info}, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,
program::invoke, 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(())
}
153 changes: 139 additions & 14 deletions svm/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

use {
crate::mock_bank::MockBankCallback,
solana_bpf_loader_program::syscalls::{SyscallAbort, SyscallLog, SyscallMemcpy, SyscallMemset},
solana_bpf_loader_program::syscalls::{
SyscallAbort, SyscallInvokeSignedRust, SyscallLog, SyscallMemcpy, SyscallMemset,
},
solana_program_runtime::{
compute_budget::ComputeBudget,
invoke_context::InvokeContext,
Expand All @@ -17,7 +19,7 @@ use {
timings::ExecuteTimings,
},
solana_sdk::{
account::{AccountSharedData, WritableAccount},
account::{AccountSharedData, ReadableAccount, WritableAccount},
bpf_loader,
clock::{Epoch, Slot},
epoch_schedule::EpochSchedule,
Expand Down Expand Up @@ -48,6 +50,7 @@ use {
mod mock_bank;

const BPF_LOADER_NAME: &str = "solana_bpf_loader_program";
const SYSTEM_PROGRAM_NAME: &str = "system_program";
const DEPLOYMENT_SLOT: u64 = 0;
const EXECUTION_SLOT: u64 = 5; // The execution slot must be greater than the deployment slot
const DEPLOYMENT_EPOCH: u64 = 0;
Expand Down Expand Up @@ -108,6 +111,10 @@ fn create_custom_environment<'a>() -> BuiltinProgram<InvokeContext<'a>> {
.register_function_hashed(*b"sol_memset_", SyscallMemset::vm)
.expect("Registration failed");

function_registry
.register_function_hashed(*b"sol_invoke_signed_rust", SyscallInvokeSignedRust::vm)
.expect("Registration failed");

BuiltinProgram::new_loader(vm_config, function_registry)
}

Expand Down Expand Up @@ -136,6 +143,24 @@ fn create_executable_environment(
)),
);

// In order to perform a transference of native tokens using the system instruction,
// the system program builtin must be registered.
let account_data = native_loader::create_loadable_account_with_fields(
SYSTEM_PROGRAM_NAME,
(5000, DEPLOYMENT_EPOCH),
);
mock_bank
.account_shared_data
.insert(solana_system_program::id(), account_data);
program_cache.assign_program(
solana_system_program::id(),
Arc::new(LoadedProgram::new_builtin(
DEPLOYMENT_SLOT,
SYSTEM_PROGRAM_NAME.len(),
solana_system_program::system_processor::Entrypoint::vm,
)),
);

program_cache.environments = ProgramRuntimeEnvironments {
program_runtime_v1: Arc::new(create_custom_environment()),
// We are not using program runtime v2
Expand All @@ -148,10 +173,25 @@ fn create_executable_environment(
program_cache.fork_graph = Some(Arc::new(RwLock::new(MockForkGraph {})));

// Inform SVM of the registered builins
let registered_built_ins = vec![bpf_loader::id()];
let registered_built_ins = vec![bpf_loader::id(), solana_system_program::id()];
(program_cache, registered_built_ins)
}

fn load_program(name: String) -> Vec<u8> {
// Loading the program file
let mut dir = env::current_dir().unwrap();
dir.push("tests");
dir.push("example-programs");
dir.push(name.as_str());
let name = name.replace('-', "_");
dir.push(name + "_program.so");
let mut file = File::open(dir.clone()).expect("file not found");
let metadata = fs::metadata(dir).expect("Unable to read metadata");
let mut buffer = vec![0; metadata.len() as usize];
file.read_exact(&mut buffer).expect("Buffer overflow");
buffer
}

fn prepare_transactions(
mock_bank: &mut MockBankCallback,
) -> (Vec<SanitizedTransaction>, Vec<TransactionCheckResult>) {
Expand Down Expand Up @@ -186,15 +226,7 @@ fn prepare_transactions(
transaction_checks.push((Ok(()), None, Some(20)));

// Loading the program file
let mut dir = env::current_dir().unwrap();
dir.push("tests");
dir.push("example-programs");
dir.push("hello-solana");
dir.push("hello_solana_program.so");
let mut file = File::open(dir.clone()).expect("file not found");
let metadata = fs::metadata(dir).expect("Unable to read metadata");
let mut buffer = vec![0; metadata.len() as usize];
file.read_exact(&mut buffer).expect("Buffer overflow");
let buffer = load_program("hello-solana".to_string());

// The program account must have funds and hold the executable binary
let mut account_data = AccountSharedData::default();
Expand All @@ -212,8 +244,83 @@ fn prepare_transactions(
.account_shared_data
.insert(fee_payer, account_data);

// TODO: Include these examples as well:
// A simple funds transfer between accounts
let program_account = Pubkey::new_unique();
let sender = Pubkey::new_unique();
let recipient = Pubkey::new_unique();
let fee_payer = Pubkey::new_unique();
let system_account = Pubkey::from([0u8; 32]);
let message = Message {
account_keys: vec![
fee_payer,
sender,
program_account,
recipient,
system_account,
],
header: MessageHeader {
// The signers must appear in the `account_keys` vector in positions whose index is
// less than `num_required_signatures`
num_required_signatures: 2,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 0,
},
instructions: vec![CompiledInstruction {
program_id_index: 2,
accounts: vec![1, 3, 4],
data: vec![0, 0, 0, 0, 0, 0, 0, 10],
}],
recent_blockhash: Hash::default(),
};

let transaction = Transaction {
signatures: vec![Signature::new_unique(), Signature::new_unique()],
message,
};

let sanitized_transaction =
SanitizedTransaction::try_from_legacy_transaction(transaction).unwrap();
all_transactions.push(sanitized_transaction);
transaction_checks.push((Ok(()), None, Some(20)));

// Setting up the accounts for the transfer

// fee payer
let mut account_data = AccountSharedData::default();
account_data.set_lamports(80000);
mock_bank
.account_shared_data
.insert(fee_payer, account_data);

let buffer = load_program("simple-transfer".to_string());

// The program account must have funds and hold the executable binary
let mut account_data = AccountSharedData::default();
// The executable account owner must be one of the loaders.
account_data.set_owner(bpf_loader::id());
account_data.set_data(buffer);
account_data.set_executable(true);
account_data.set_lamports(25);
mock_bank
.account_shared_data
.insert(program_account, account_data);

// sender
let mut account_data = AccountSharedData::default();
account_data.set_lamports(900000);
mock_bank.account_shared_data.insert(sender, account_data);

// recipient
let mut account_data = AccountSharedData::default();
account_data.set_lamports(900000);
mock_bank
.account_shared_data
.insert(recipient, account_data);

// The program account is set in `create_executable_environment`

// TODO: Include these examples as well:
// An example with a sysvar
// A transaction that fails
// A transaction whose verification has already failed

Expand Down Expand Up @@ -256,7 +363,7 @@ fn svm_integration() {
false,
);

assert_eq!(result.execution_results.len(), 1);
assert_eq!(result.execution_results.len(), 2);
assert!(result.execution_results[0]
.details()
.unwrap()
Expand All @@ -269,4 +376,22 @@ fn svm_integration() {
.as_ref()
.unwrap();
assert!(logs.contains(&"Program log: Hello, Solana!".to_string()));

assert!(result.execution_results[1]
.details()
.unwrap()
.status
.is_ok());

// The SVM does not commit the account changes in MockBank
let recipient_key = transactions[1].message().account_keys()[3];
let recipient_data = result.loaded_transactions[1]
.0
.as_ref()
.unwrap()
.accounts
.iter()
.find(|key| key.0 == recipient_key)
.unwrap();
assert_eq!(recipient_data.1.lamports(), 900010);
}

0 comments on commit fe16e84

Please sign in to comment.