diff --git a/svm/tests/example-programs/simple-transfer/Cargo.toml b/svm/tests/example-programs/simple-transfer/Cargo.toml new file mode 100644 index 00000000000000..9ccbf60aa8b8f7 --- /dev/null +++ b/svm/tests/example-programs/simple-transfer/Cargo.toml @@ -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] \ No newline at end of file diff --git a/svm/tests/example-programs/simple-transfer/simple_transfer_program.so b/svm/tests/example-programs/simple-transfer/simple_transfer_program.so new file mode 100755 index 00000000000000..5132b38cdc1b3d Binary files /dev/null and b/svm/tests/example-programs/simple-transfer/simple_transfer_program.so differ diff --git a/svm/tests/example-programs/simple-transfer/src/lib.rs b/svm/tests/example-programs/simple-transfer/src/lib.rs new file mode 100644 index 00000000000000..dc82f8d83330e5 --- /dev/null +++ b/svm/tests/example-programs/simple-transfer/src/lib.rs @@ -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(()) +} \ No newline at end of file diff --git a/svm/tests/integration_test.rs b/svm/tests/integration_test.rs index 1b8d1c08ccdf9e..b414281267b6e1 100644 --- a/svm/tests/integration_test.rs +++ b/svm/tests/integration_test.rs @@ -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, @@ -17,7 +19,7 @@ use { timings::ExecuteTimings, }, solana_sdk::{ - account::{AccountSharedData, WritableAccount}, + account::{AccountSharedData, ReadableAccount, WritableAccount}, bpf_loader, clock::{Epoch, Slot}, epoch_schedule::EpochSchedule, @@ -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; @@ -108,6 +111,10 @@ fn create_custom_environment<'a>() -> BuiltinProgram> { .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) } @@ -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 @@ -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 { + // 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, Vec) { @@ -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(); @@ -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 @@ -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() @@ -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); }