diff --git a/Cargo.lock b/Cargo.lock index b388736985ffad..e3b2fe76ef648d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5676,6 +5676,7 @@ name = "solana-sdk" version = "1.8.0" dependencies = [ "assert_matches", + "base64 0.13.0", "bincode", "borsh", "borsh-derive", diff --git a/program-runtime/src/instruction_processor.rs b/program-runtime/src/instruction_processor.rs index 7b5ed32b57242c..fa970430b112db 100644 --- a/program-runtime/src/instruction_processor.rs +++ b/program-runtime/src/instruction_processor.rs @@ -655,6 +655,9 @@ impl InstructionProcessor { let keyed_accounts = Self::create_keyed_accounts(message, instruction, executable_accounts, accounts); + // clear the return data + invoke_context.set_return_data(&[]); + // Invoke callee invoke_context.push(program_id, &keyed_accounts)?; diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 353b831ebaac40..8adc0b5a63fe0d 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -3232,6 +3232,7 @@ name = "solana-sdk" version = "1.8.0" dependencies = [ "assert_matches", + "base64 0.13.0", "bincode", "borsh", "borsh-derive", diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index b85d6026c22323..3e22e7613b0bd8 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -914,6 +914,11 @@ impl Executor for BpfExecutor { let trace_string = String::from_utf8(trace_buffer).unwrap(); trace!("BPF Program Instruction Trace:\n{}", trace_string); } + drop(vm); + let return_data = invoke_context.get_return_data(); + if !return_data.is_empty() { + stable_log::program_return_data(&logger, return_data); + } match result { Ok(status) => { if status != SUCCESS { diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 847fe820ca1447..27969d1a279841 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -52,6 +52,9 @@ use thiserror::Error as ThisError; /// Maximum signers pub const MAX_SIGNERS: usize = 16; +/// Maximum size return data +pub const MAX_RETURN_DATA: u64 = 1024; + /// Error definitions #[derive(Debug, ThisError, PartialEq)] pub enum SyscallError { @@ -172,6 +175,12 @@ pub fn register_syscalls( // Memory allocator syscall_registry.register_syscall_by_name(b"sol_alloc_free_", SyscallAllocFree::call)?; + // Return data + syscall_registry + .register_syscall_by_name(b"sol_set_return_data", SyscallSetReturnData::call)?; + syscall_registry + .register_syscall_by_name(b"sol_get_return_data", SyscallGetReturnData::call)?; + Ok(syscall_registry) } @@ -380,6 +389,22 @@ pub fn bind_syscall_context_objects<'a>( None, )?; + // Return data + vm.bind_syscall_context_object( + Box::new(SyscallSetReturnData { + invoke_context: invoke_context.clone(), + loader_id, + }), + None, + )?; + vm.bind_syscall_context_object( + Box::new(SyscallGetReturnData { + invoke_context: invoke_context.clone(), + loader_id, + }), + None, + )?; + // Cross-program invocation syscalls vm.bind_syscall_context_object( Box::new(SyscallInvokeSignedC { @@ -2463,6 +2488,82 @@ fn call<'a>( Ok(SUCCESS) } +// Return data hanlding +pub struct SyscallSetReturnData<'a> { + invoke_context: Rc>, + loader_id: &'a Pubkey, +} +impl<'a> SyscallObject for SyscallSetReturnData<'a> { + fn call( + &mut self, + addr: u64, + len: u64, + _arg3: u64, + _arg4: u64, + _arg5: u64, + memory_mapping: &MemoryMapping, + result: &mut Result>, + ) { + if len > MAX_RETURN_DATA { + *result = Err(SyscallError::InstructionError(InstructionError::InvalidArgument).into()); + return; + } + + let mut invoke_context = question_mark!( + self.invoke_context + .try_borrow_mut() + .map_err(|_| SyscallError::InvokeContextBorrowFailed), + result + ); + + let return_data = question_mark!( + translate_slice::(memory_mapping, addr, len, self.loader_id, true,), + result + ); + + invoke_context.set_return_data(return_data); + + *result = Ok(0); + } +} + +pub struct SyscallGetReturnData<'a> { + invoke_context: Rc>, + loader_id: &'a Pubkey, +} +impl<'a> SyscallObject for SyscallGetReturnData<'a> { + fn call( + &mut self, + addr: u64, + len: u64, + _arg3: u64, + _arg4: u64, + _arg5: u64, + memory_mapping: &MemoryMapping, + result: &mut Result>, + ) { + let invoke_context = question_mark!( + self.invoke_context + .try_borrow() + .map_err(|_| SyscallError::InvokeContextBorrowFailed), + result + ); + + let return_data = invoke_context.get_return_data(); + + let length = std::cmp::min(return_data.len() as u64, len); + + let return_data_result = question_mark!( + translate_slice_mut::(memory_mapping, addr, length, self.loader_id, true,), + result + ); + + return_data_result.copy_from_slice(&return_data[..length as usize]); + + *result = Ok(return_data.len() as u64); + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index 06d1828cc75355..38cdb385d019ab 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -66,6 +66,7 @@ pub struct ThisInvokeContext<'a> { sysvars: RefCell>>)>>, blockhash: &'a Hash, fee_calculator: &'a FeeCalculator, + return_data: Vec, } impl<'a> ThisInvokeContext<'a> { #[allow(clippy::too_many_arguments)] @@ -121,6 +122,7 @@ impl<'a> ThisInvokeContext<'a> { sysvars: RefCell::new(vec![]), blockhash, fee_calculator, + return_data: Vec::new(), }; invoke_context .invoke_stack @@ -312,6 +314,12 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> { fn get_fee_calculator(&self) -> &FeeCalculator { self.fee_calculator } + fn set_return_data(&mut self, data: &[u8]) { + self.return_data = data.to_vec(); + } + fn get_return_data(&self) -> &[u8] { + &self.return_data + } } pub struct ThisLogger { log_collector: Option>, diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 2ebb2c9bc65e10..a3e0d5a848162c 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -41,6 +41,7 @@ full = [ assert_matches = { version = "1.5.0", optional = true } bincode = "1.3.3" borsh = "0.9.0" +base64 = "0.13" borsh-derive = "0.9.0" bs58 = "0.4.0" bv = { version = "0.11.1", features = ["serde"] } diff --git a/sdk/bpf/c/inc/sol/return_data.h b/sdk/bpf/c/inc/sol/return_data.h new file mode 100644 index 00000000000000..5efa897925afbd --- /dev/null +++ b/sdk/bpf/c/inc/sol/return_data.h @@ -0,0 +1,39 @@ +#pragma once +/** + * @brief Solana keccak system call +**/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * Maximum size of return data + */ +#define MAX_RETURN_DATA 1024 + +/** + * Set the return data + * + * @param bytes byte array to set + * @param bytes_len length of byte array. This may not exceed MAX_RETURN_DATA. + */ +void sol_set_return_data(const uint8_t *bytes, uint64_t bytes_len); + +/** + * Get the return data + * + * @param bytes byte buffer + * @param bytes_len maximum length of buffer + * @param result length of return data (may exceed bytes_len if the return data is longer) + */ +uint64_t sol_get_return_data(const uint8_t *bytes, uint64_t bytes_len); + +#ifdef __cplusplus +} +#endif + +/**@}*/ diff --git a/sdk/bpf/c/inc/solana_sdk.h b/sdk/bpf/c/inc/solana_sdk.h index 1f30c6a9a8b6b3..829b66486c541d 100644 --- a/sdk/bpf/c/inc/solana_sdk.h +++ b/sdk/bpf/c/inc/solana_sdk.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include diff --git a/sdk/src/process_instruction.rs b/sdk/src/process_instruction.rs index ec92892873498f..2dca05e828787e 100644 --- a/sdk/src/process_instruction.rs +++ b/sdk/src/process_instruction.rs @@ -113,6 +113,10 @@ pub trait InvokeContext { fn get_blockhash(&self) -> &Hash; /// Get this invocation's `FeeCalculator` fn get_fee_calculator(&self) -> &FeeCalculator; + /// Set the return data + fn set_return_data(&mut self, data: &[u8]); + /// Get the return data + fn get_return_data(&self) -> &[u8]; } /// Convenience macro to log a message with an `Rc>` @@ -309,6 +313,19 @@ pub mod stable_log { ic_logger_msg!(logger, "Program log: {}", message); } + /// Log return data as from the program itself. + /// + /// The general form is: + /// + /// ```notrust + /// "Program return data: " + /// ``` + /// + /// That is, any program-generated output is guaranteed to be prefixed by "Program return data: " + pub fn program_return_data(logger: &Rc>, data: &[u8]) { + ic_logger_msg!(logger, "Program return data: {}", base64::encode(data)); + } + /// Log successful program execution. /// /// The general form is: @@ -393,7 +410,9 @@ pub struct MockInvokeContext<'a> { pub disabled_features: HashSet, pub blockhash: Hash, pub fee_calculator: FeeCalculator, + pub return_data: Vec, } + impl<'a> MockInvokeContext<'a> { pub fn new(keyed_accounts: Vec>) -> Self { let compute_budget = ComputeBudget::default(); @@ -411,6 +430,7 @@ impl<'a> MockInvokeContext<'a> { disabled_features: HashSet::default(), blockhash: Hash::default(), fee_calculator: FeeCalculator::default(), + return_data: Vec::new(), }; invoke_context .invoke_stack @@ -539,4 +559,10 @@ impl<'a> InvokeContext for MockInvokeContext<'a> { fn get_fee_calculator(&self) -> &FeeCalculator { &self.fee_calculator } + fn set_return_data(&mut self, data: &[u8]) { + self.return_data = data.to_vec(); + } + fn get_return_data(&self) -> &[u8] { + &self.return_data + } }