diff --git a/programs/bpf/rust/build.sh b/programs/bpf/rust/build.sh index cc29ee33ed..80cb70e151 100755 --- a/programs/bpf/rust/build.sh +++ b/programs/bpf/rust/build.sh @@ -1,4 +1,8 @@ #!/usr/bin/env bash -bpf_sdk=../../../sdk/bpf -./"$bpf_sdk"/rust-utils/build.sh "$PWD"/"$1" +if [ "$#" -ne 1 ]; then + echo "Error: Must provide name of the project to build" + exit 1 +fi + +./../../../sdk/bpf/rust-utils/build.sh "$PWD"/"$1" diff --git a/programs/bpf/rust/clean.sh b/programs/bpf/rust/clean.sh index 1ac6a7ee42..ef845d480f 100755 --- a/programs/bpf/rust/clean.sh +++ b/programs/bpf/rust/clean.sh @@ -1,4 +1,8 @@ #!/usr/bin/env bash -bpf_sdk=../../../sdk/bpf -./"$bpf_sdk"/rust-utils/clean.sh "$PWD"/"$1" +if [ "$#" -ne 1 ]; then + echo "Error: Must provide the full path to the project to clean" + exit 1 +fi + +./../../../sdk/bpf/rust-utils/clean.sh "$PWD"/"$1" diff --git a/programs/bpf/rust/dump.sh b/programs/bpf/rust/dump.sh index 031ca256a6..e874835b4a 100755 --- a/programs/bpf/rust/dump.sh +++ b/programs/bpf/rust/dump.sh @@ -1,5 +1,10 @@ #!/usr/bin/env bash +if [ "$#" -ne 1 ]; then + echo "Error: Must provide the full path to the project to dump" + exit 1 +fi + ./clean.sh "$1" ./build.sh "$1" diff --git a/programs/bpf/rust/iter/src/lib.rs b/programs/bpf/rust/iter/src/lib.rs index 53a1ee936f..86aa918911 100644 --- a/programs/bpf/rust/iter/src/lib.rs +++ b/programs/bpf/rust/iter/src/lib.rs @@ -4,7 +4,7 @@ extern crate solana_sdk_bpf_utils; -use solana_sdk_bpf_utils::*; +use solana_sdk_bpf_utils::log::*; #[no_mangle] pub extern "C" fn entrypoint(_input: *mut u8) -> bool { diff --git a/programs/bpf/rust/noop/src/lib.rs b/programs/bpf/rust/noop/src/lib.rs index 354ae4db3d..eb2ba83023 100644 --- a/programs/bpf/rust/noop/src/lib.rs +++ b/programs/bpf/rust/noop/src/lib.rs @@ -4,7 +4,9 @@ extern crate solana_sdk_bpf_utils; -use solana_sdk_bpf_utils::*; +use solana_sdk_bpf_utils::entrypoint; +use solana_sdk_bpf_utils::entrypoint::*; +use solana_sdk_bpf_utils::log::*; struct SStruct { x: u64, @@ -17,7 +19,7 @@ fn return_sstruct() -> SStruct { SStruct { x: 1, y: 2, z: 3 } } -solana_entrypoint!(process_instruction); +entrypoint!(process_instruction); fn process_instruction( ka: &mut [Option; MAX_ACCOUNTS], info: &SolClusterInfo, diff --git a/sdk/bpf/rust-utils/src/entrypoint.rs b/sdk/bpf/rust-utils/src/entrypoint.rs new file mode 100644 index 0000000000..0b29b348cc --- /dev/null +++ b/sdk/bpf/rust-utils/src/entrypoint.rs @@ -0,0 +1,242 @@ +//! @brief Solana Rust-based BPF program entrypoint and its parameter types + +use crate::log::*; +use core::mem::size_of; +use core::slice::{from_raw_parts, from_raw_parts_mut}; + +/// Max number of accounts supported +pub const MAX_ACCOUNTS: usize = 10; + +/// Size in bytes of a public key +pub const SIZE_PUBKEY: usize = 32; + +/// Public key +pub struct SolPubkey<'a> { + pub key: &'a [u8; SIZE_PUBKEY], +} + +/// Keyed Account +pub struct SolKeyedAccount<'a> { + /// Public key of the account + pub key: SolPubkey<'a>, + /// Public key of the account + pub is_signer: bool, + /// Number of lamports owned by this account + pub lamports: u64, + /// On-chain data within this account + pub data: &'a mut [u8], + /// Program that owns this account + pub owner: SolPubkey<'a>, +} + +/// Information about the state of the cluster immediately before the program +/// started executing the current instruction +pub struct SolClusterInfo<'a> { + /// Current ledger tick + pub tick_height: u64, + ///program_id of the currently executing program + pub program_id: SolPubkey<'a>, +} + +/// Declare entrypoint of the program. +/// +/// Deserialize the program input parameters and call +/// a user defined entrypoint. Users must call +/// this function otherwise an entrypoint for +/// their program will not be created. +#[macro_export] +macro_rules! entrypoint { + ($process_instruction:ident) => { + #[no_mangle] + pub extern "C" fn entrypoint(input: *mut u8) -> bool { + unsafe { + if let Ok((mut ka, info, data)) = $crate::entrypoint::deserialize(input) { + // Call use function + $process_instruction(&mut ka, &info, &data) + } else { + false + } + } + } + }; +} + +/// Deserialize the input parameters +#[allow(clippy::type_complexity)] +pub unsafe fn deserialize<'a>( + input: *mut u8, +) -> Result< + ( + [Option>; MAX_ACCOUNTS], + SolClusterInfo<'a>, + &'a [u8], + ), + (), +> { + let mut offset: usize = 0; + + // Number of KeyedAccounts present + + #[allow(clippy::cast_ptr_alignment)] + let num_ka = *(input.add(offset) as *const u64) as usize; + offset += 8; + + // KeyedAccounts + + if num_ka > MAX_ACCOUNTS { + sol_log("Error: Too many accounts"); + return Err(()); + } + + let mut ka: [Option; MAX_ACCOUNTS] = + [None, None, None, None, None, None, None, None, None, None]; + let iter = 0..num_ka; // This weirdness due to #issue $#4271 + for (i, _) in iter.enumerate() { + let is_signer = { + #[allow(clippy::cast_ptr_alignment)] + let is_signer_val = *(input.add(offset) as *const u64); + (is_signer_val != 0) + }; + offset += size_of::(); + + let key = { + SolPubkey { + key: &*(input.add(offset) as *mut [u8; SIZE_PUBKEY]), + } + }; + offset += SIZE_PUBKEY; + + #[allow(clippy::cast_ptr_alignment)] + let lamports = *(input.add(offset) as *const u64); + offset += size_of::(); + + #[allow(clippy::cast_ptr_alignment)] + let data_length = *(input.add(offset) as *const u64) as usize; + offset += size_of::(); + + let data = { from_raw_parts_mut(input.add(offset), data_length) }; + offset += data_length; + + let owner = { + SolPubkey { + key: &*(input.add(offset) as *mut [u8; SIZE_PUBKEY]), + } + }; + offset += SIZE_PUBKEY; + + ka[i] = Some(SolKeyedAccount { + key, + is_signer, + lamports, + data, + owner, + }); + } + + // Instruction data + + #[allow(clippy::cast_ptr_alignment)] + let data_length = *(input.add(offset) as *const u64) as usize; + offset += size_of::(); + + let data = { from_raw_parts(input.add(offset), data_length) }; + offset += data_length; + + // Tick height + + #[allow(clippy::cast_ptr_alignment)] + let tick_height = *(input.add(offset) as *const u64); + offset += size_of::(); + + // Id + + let program_id = { + SolPubkey { + key: &*(input.add(offset) as *mut [u8; SIZE_PUBKEY]), + } + }; + + let info = SolClusterInfo { + tick_height, + program_id, + }; + + Ok((ka, info, data)) +} + +#[cfg(test)] +mod tests { + extern crate std; + + use self::std::ffi::CStr; + use self::std::println; + use self::std::string::String; + use super::*; + use core::mem; + + #[no_mangle] + fn sol_log_(message: *const u8) { + let scenario = get_log_scenario(); + let c_str = unsafe { CStr::from_ptr(message as *const i8) }; + let string = c_str.to_str().unwrap(); + println!("{:?}", string); + } + + #[no_mangle] + fn sol_log_64_(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) { + println!("{:?} {:?} {:?} {:?} {:?}", arg1, arg2, arg3, arg4, arg5); + } + + #[test] + fn test_entrypoint() { + set_log_scenario(4); + set_log_64_scenario(4); + let mut input: [u8; 154] = [ + 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 151, 116, 3, 85, 181, 39, 151, 99, 155, + 29, 208, 191, 255, 191, 11, 161, 4, 43, 104, 189, 202, 240, 231, 111, 146, 255, 199, + 71, 67, 34, 254, 68, 48, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 190, 103, 191, + 69, 193, 202, 38, 193, 95, 62, 131, 135, 105, 13, 142, 240, 155, 120, 177, 90, 212, 54, + 10, 118, 40, 33, 192, 8, 54, 141, 187, 63, + ]; + + if let Ok((mut ka, info, data)) = deserialize(&mut input[0] as *mut u8) { + let account0 = match mem::replace(&mut ka[0], None) { + Some(mut account0) => account0, + None => { + panic!("Error: account not found"); + } + }; + + for k in ka[1..].iter() { + if let Some(_) = k { + panic!("Too many keyed accounts found"); + } + } + assert_eq!(true, account0.is_signer); + let key: &[u8; SIZE_PUBKEY] = &[ + 151, 116, 3, 85, 181, 39, 151, 99, 155, 29, 208, 191, 255, 191, 11, 161, 4, 43, + 104, 189, 202, 240, 231, 111, 146, 255, 199, 71, 67, 34, 254, 68, + ]; + assert_eq!(SIZE_PUBKEY, account0.key.key.len()); + assert_eq!(key, account0.key.key); + assert_eq!(48, account0.lamports); + assert_eq!(1, account0.data.len()); + let owner = &[0; SIZE_PUBKEY]; + assert_eq!(SIZE_PUBKEY, account0.owner.key.len()); + assert_eq!(owner, account0.owner.key); + let d = [1, 0, 0, 0, 0, 0, 0, 0, 1]; + assert_eq!(9, data.len()); + assert_eq!(d, data); + assert_eq!(1, info.tick_height); + let program_id: &[u8; SIZE_PUBKEY] = &[ + 190, 103, 191, 69, 193, 202, 38, 193, 95, 62, 131, 135, 105, 13, 142, 240, 155, + 120, 177, 90, 212, 54, 10, 118, 40, 33, 192, 8, 54, 141, 187, 63, + ]; + assert_eq!(program_id, info.program_id.key); + } else { + panic!("Failed to deserialize"); + } + } +} diff --git a/sdk/bpf/rust-utils/src/lib.rs b/sdk/bpf/rust-utils/src/lib.rs index 21afd6357b..48f7c654be 100644 --- a/sdk/bpf/rust-utils/src/lib.rs +++ b/sdk/bpf/rust-utils/src/lib.rs @@ -2,471 +2,6 @@ #![no_std] -use core::mem::size_of; -#[cfg(not(test))] -use core::panic::PanicInfo; -use core::slice::{from_raw_parts, from_raw_parts_mut}; - -/// Max number of accounts supported -pub const MAX_ACCOUNTS: usize = 10; - -/// Size in bytes of a public key -pub const SIZE_PUBKEY: usize = 32; - -/// Public key -pub struct SolPubkey<'a> { - pub key: &'a [u8; SIZE_PUBKEY], -} - -/// Keyed Account -pub struct SolKeyedAccount<'a> { - /// Public key of the account - pub key: SolPubkey<'a>, - /// Public key of the account - pub is_signer: bool, - /// Number of lamports owned by this account - pub lamports: u64, - /// On-chain data within this account - pub data: &'a mut [u8], - /// Program that owns this account - pub owner: SolPubkey<'a>, -} - -/// Information about the state of the cluster immediately before the program -/// started executing the current instruction -pub struct SolClusterInfo<'a> { - /// Current ledger tick - pub tick_height: u64, - ///program_id of the currently executing program - pub program_id: SolPubkey<'a>, -} - -/// Declare entrypoint of the program. -/// -/// Deserialize the program input parameters and call -/// a user defined entrypoint. Users must call -/// this function otherwise an entrypoint for -/// their program will not be created. -#[macro_export] -macro_rules! solana_entrypoint { - ($process_instruction:ident) => { - #[no_mangle] - pub extern "C" fn entrypoint(input: *mut u8) -> bool { - if let Ok((mut ka, info, data)) = $crate::deserialize(input) { - // Call use function - $process_instruction(&mut ka, &info, &data) - } else { - false - } - } - }; -} - -/// Checks if a public key is default, default implies not set and is -/// represented as all zeros -/// -/// @param key - Public key to check -pub fn sol_key_default(key_bytes: &[u8]) -> bool { - for (_, k) in key_bytes.iter().enumerate() { - if *k != 0 { - return false; - } - } - true -} - -/// Prints a string to stdout -#[inline(never)] // stack intensive, prevent inline so everyone does not incur cost -pub fn sol_log(message: &str) { - // TODO This is extremely slow, do something better - let mut buf: [u8; 128] = [0; 128]; - for (i, b) in message.as_bytes().iter().enumerate() { - if i >= 126 { - break; - } - buf[i] = *b; - } - unsafe { - sol_log_(buf.as_ptr()); - } -} -extern "C" { - fn sol_log_(message: *const u8); -} - -/// Prints 64 bit values represented as hexadecimal to stdout -pub fn sol_log_64(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) { - unsafe { - sol_log_64_(arg1, arg2, arg3, arg4, arg5); - } -} -extern "C" { - fn sol_log_64_(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64); -} - -/// Prints the hexadecimal representation of a public key -/// -/// @param - key The public key to print -#[allow(dead_code)] -pub fn sol_log_key(key: &SolPubkey) { - for (i, k) in key.key.iter().enumerate() { - sol_log_64(0, 0, 0, i as u64, u64::from(*k)); - } -} - -/// Prints the hexadecimal representation of a slice -/// -/// @param slice - The array to print -#[allow(dead_code)] -pub fn sol_log_slice(slice: &[u8]) { - for (i, s) in slice.iter().enumerate() { - sol_log_64(0, 0, 0, i as u64, u64::from(*s)); - } -} - -/// Prints the hexadecimal representation of the program's input parameters -/// -/// @param ka - A pointer to an array of SolKeyedAccounts to print -/// @param data - A pointer to the instruction data to print -#[allow(dead_code)] -pub fn sol_log_params(ka: &[Option], data: &[u8]) { - for (i, k) in ka.iter().enumerate() { - if let Some(k) = k { - sol_log("SolKeyedAccount"); - sol_log_64(0, 0, 0, 0, i as u64); - sol_log("- Is signer"); - sol_log_64(0, 0, 0, 0, k.is_signer as u64); - sol_log("- Key"); - sol_log_key(&k.key); - sol_log("- Lamports"); - sol_log_64(0, 0, 0, 0, k.lamports); - sol_log("- AccountData"); - sol_log_slice(k.data); - sol_log("- Owner"); - sol_log_key(&k.owner); - } - } - sol_log("Instruction data"); - sol_log_slice(data); -} - -/// Deserialize the input parameters -pub fn deserialize<'a>( - input: *mut u8, -) -> Result< - ( - [Option>; MAX_ACCOUNTS], - SolClusterInfo<'a>, - &'a [u8], - ), - (), -> { - let mut offset: usize = 0; - - // Number of KeyedAccounts present - - let num_ka = unsafe { - #[allow(clippy::cast_ptr_alignment)] - let num_ka_ptr: *const u64 = input.add(offset) as *const u64; - *num_ka_ptr as usize - }; - offset += 8; - - // KeyedAccounts - - if num_ka > MAX_ACCOUNTS { - sol_log("Error: Too many accounts"); - return Err(()); - } - - let mut ka: [Option; MAX_ACCOUNTS] = - [None, None, None, None, None, None, None, None, None, None]; - let iter = 0..num_ka; // This weirdness due to #issue $#4271 - for (i, _) in iter.enumerate() { - let is_signer = unsafe { - #[allow(clippy::cast_ptr_alignment)] - let is_signer_ptr: *const u64 = input.add(offset) as *const u64; - if *is_signer_ptr == 0 { - false - } else { - true - } - }; - offset += size_of::(); - - let key = unsafe { - SolPubkey { - key: &*(input.add(offset) as *mut [u8; SIZE_PUBKEY]), - } - }; - offset += SIZE_PUBKEY; - - let lamports = unsafe { - #[allow(clippy::cast_ptr_alignment)] - let lamports_ptr: *const u64 = input.add(offset) as *const u64; - *lamports_ptr - }; - offset += size_of::(); - - let data_length = unsafe { - #[allow(clippy::cast_ptr_alignment)] - let data_length_ptr: *const u64 = input.add(offset) as *const u64; - *data_length_ptr - } as usize; - offset += size_of::(); - - let data = unsafe { from_raw_parts_mut(input.add(offset), data_length) }; - offset += data_length; - - // let owner_slice = unsafe { input.add(offset) as *mut [u8; SIZE_PUBKEY] }; - let owner = unsafe { - SolPubkey { - key: &*(input.add(offset) as *mut [u8; SIZE_PUBKEY]), - } - }; - offset += SIZE_PUBKEY; - - ka[i] = Some(SolKeyedAccount { - key, - is_signer, - lamports, - data, - owner, - }); - } - - // Instruction data - - let data_length = unsafe { - #[allow(clippy::cast_ptr_alignment)] - let data_length_ptr: *const u64 = input.add(offset) as *const u64; - *data_length_ptr - } as usize; - offset += size_of::(); - - let data = unsafe { from_raw_parts(input.add(offset), data_length) }; - offset += data_length; - - // Tick height - - let tick_height = unsafe { - #[allow(clippy::cast_ptr_alignment)] - let tick_height_ptr: *const u64 = input.add(offset) as *const u64; - *tick_height_ptr - }; - offset += size_of::(); - - // Id - - let program_id = unsafe { - SolPubkey { - key: &*(input.add(offset) as *mut [u8; SIZE_PUBKEY]), - } - }; - - let info = SolClusterInfo { - tick_height, - program_id, - }; - - Ok((ka, info, data)) -} - -// Panic handling -extern "C" { - pub fn sol_panic_() -> !; -} -#[cfg(not(test))] -#[panic_handler] -fn panic(_info: &PanicInfo) -> ! { - sol_log("Panic!"); - // TODO crashes! sol_log(_info.payload().downcast_ref::<&str>().unwrap()); - if let Some(location) = _info.location() { - if !location.file().is_empty() { - // TODO location.file() returns empty str, if we get here its been fixed - sol_log(location.file()); - sol_log("location.file() is fixed!!"); - unsafe { - sol_panic_(); - } - } - sol_log_64(0, 0, 0, location.line() as u64, location.column() as u64); - } else { - sol_log("Panic! but could not get location information"); - } - unsafe { - sol_panic_(); - } -} - -#[cfg(test)] -mod tests { - extern crate std; - - use self::std::ffi::CStr; - use self::std::println; - use self::std::string::String; - use super::*; - use core::mem; - - static mut _LOG_SCENARIO: u64 = 4; - fn get_log_scenario() -> u64 { - unsafe { _LOG_SCENARIO } - } - fn set_log_scenario(test: u64) { - unsafe { _LOG_SCENARIO = test }; - } - - #[no_mangle] - fn sol_log_(message: *const u8) { - let scenario = get_log_scenario(); - let c_str = unsafe { CStr::from_ptr(message as *const i8) }; - let string = c_str.to_str().unwrap(); - match scenario { - 1 => assert_eq!(string, "This is a test message"), - 2 => assert_eq!(string, "Attempted to log a string that is too long"), - 3 => { - let s: String = ['a'; 126].iter().collect(); - assert_eq!(string, s); - } - 4 => println!("{:?}", string), - _ => panic!("Unknown sol_log test"), - } - } - - static mut _LOG_64_SCENARIO: u64 = 4; - fn get_log_64_scenario() -> u64 { - unsafe { _LOG_64_SCENARIO } - } - fn set_log_64_scenario(test: u64) { - unsafe { _LOG_64_SCENARIO = test }; - } - - #[no_mangle] - fn sol_log_64_(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) { - let scenario = get_log_64_scenario(); - match scenario { - 1 => { - assert_eq!(1, arg1); - assert_eq!(2, arg2); - assert_eq!(3, arg3); - assert_eq!(4, arg4); - assert_eq!(5, arg5); - } - 2 => { - assert_eq!(0, arg1); - assert_eq!(0, arg2); - assert_eq!(0, arg3); - assert_eq!(arg4 + 1, arg5); - } - 3 => { - assert_eq!(0, arg1); - assert_eq!(0, arg2); - assert_eq!(0, arg3); - assert_eq!(arg4 + 1, arg5); - } - 4 => println!("{:?} {:?} {:?} {:?} {:?}", arg1, arg2, arg3, arg4, arg5), - _ => panic!("Unknown sol_log_64 test"), - } - } - - #[test] - fn test_sol_log() { - set_log_scenario(1); - sol_log("This is a test message"); - } - - #[test] - fn test_sol_log_long() { - set_log_scenario(3); - let s: String = ['a'; 256].iter().collect(); - sol_log(&s); - } - - #[test] - fn test_sol_log_max_length() { - set_log_scenario(3); - let s: String = ['a'; 126].iter().collect(); - sol_log(&s); - } - - #[test] - fn test_sol_log_64() { - set_log_64_scenario(1); - sol_log_64(1, 2, 3, 4, 5); - } - - #[test] - fn test_sol_log_key() { - set_log_64_scenario(2); - let key_array = [ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, - 25, 26, 27, 28, 29, 30, 31, 32, - ]; - let key = SolPubkey { key: &key_array }; - sol_log_key(&key); - } - - #[test] - fn test_sol_log_slice() { - set_log_64_scenario(3); - let array = [ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, - 25, 26, 27, 28, 29, 30, 31, 32, - ]; - sol_log_slice(&array); - } - - #[test] - fn test_entrypoint() { - set_log_scenario(4); - set_log_64_scenario(4); - let mut input: [u8; 154] = [ - 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 151, 116, 3, 85, 181, 39, 151, 99, 155, - 29, 208, 191, 255, 191, 11, 161, 4, 43, 104, 189, 202, 240, 231, 111, 146, 255, 199, - 71, 67, 34, 254, 68, 48, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, - 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 190, 103, 191, - 69, 193, 202, 38, 193, 95, 62, 131, 135, 105, 13, 142, 240, 155, 120, 177, 90, 212, 54, - 10, 118, 40, 33, 192, 8, 54, 141, 187, 63, - ]; - - if let Ok((mut ka, info, data)) = deserialize(&mut input[0] as *mut u8) { - let account0 = match mem::replace(&mut ka[0], None) { - Some(mut account0) => account0, - None => { - panic!("Error: account not found"); - } - }; - - for k in ka[1..].iter() { - if let Some(_) = k { - panic!("Too many keyed accounts found"); - } - } - assert_eq!(true, account0.is_signer); - let key: &[u8; SIZE_PUBKEY] = &[ - 151, 116, 3, 85, 181, 39, 151, 99, 155, 29, 208, 191, 255, 191, 11, 161, 4, 43, - 104, 189, 202, 240, 231, 111, 146, 255, 199, 71, 67, 34, 254, 68, - ]; - assert_eq!(SIZE_PUBKEY, account0.key.key.len()); - assert_eq!(key, account0.key.key); - assert_eq!(48, account0.lamports); - assert_eq!(1, account0.data.len()); - let owner = &[0; SIZE_PUBKEY]; - assert_eq!(SIZE_PUBKEY, account0.owner.key.len()); - assert_eq!(owner, account0.owner.key); - let d = [1, 0, 0, 0, 0, 0, 0, 0, 1]; - assert_eq!(9, data.len()); - assert_eq!(d, data); - assert_eq!(1, info.tick_height); - let program_id: &[u8; SIZE_PUBKEY] = &[ - 190, 103, 191, 69, 193, 202, 38, 193, 95, 62, 131, 135, 105, 13, 142, 240, 155, - 120, 177, 90, 212, 54, 10, 118, 40, 33, 192, 8, 54, 141, 187, 63, - ]; - assert_eq!(program_id, info.program_id.key); - } else { - panic!("Failed to deserialize"); - } - } -} +pub mod entrypoint; +pub mod log; +pub mod panic; diff --git a/sdk/bpf/rust-utils/src/log.rs b/sdk/bpf/rust-utils/src/log.rs new file mode 100644 index 0000000000..bb3d3a4c89 --- /dev/null +++ b/sdk/bpf/rust-utils/src/log.rs @@ -0,0 +1,197 @@ +//! @brief Solana Rust-based BPF program logging + +use crate::entrypoint::{SolKeyedAccount, SolPubkey}; + +/// Prints a string to stdout +#[inline(never)] // stack intensive, prevent inline so everyone does not incur cost +pub fn sol_log(message: &str) { + // TODO This is extremely slow, do something better + let mut buf: [u8; 128] = [0; 128]; + for (i, b) in message.as_bytes().iter().enumerate() { + if i >= 126 { + break; + } + buf[i] = *b; + } + unsafe { + sol_log_(buf.as_ptr()); + } +} +extern "C" { + fn sol_log_(message: *const u8); +} + +/// Prints 64 bit values represented as hexadecimal to stdout +pub fn sol_log_64(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) { + unsafe { + sol_log_64_(arg1, arg2, arg3, arg4, arg5); + } +} +extern "C" { + fn sol_log_64_(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64); +} + +/// Prints the hexadecimal representation of a public key +/// +/// @param - key The public key to print +#[allow(dead_code)] +pub fn sol_log_key(key: &SolPubkey) { + for (i, k) in key.key.iter().enumerate() { + sol_log_64(0, 0, 0, i as u64, u64::from(*k)); + } +} + +/// Prints the hexadecimal representation of a slice +/// +/// @param slice - The array to print +#[allow(dead_code)] +pub fn sol_log_slice(slice: &[u8]) { + for (i, s) in slice.iter().enumerate() { + sol_log_64(0, 0, 0, i as u64, u64::from(*s)); + } +} + +/// Prints the hexadecimal representation of the program's input parameters +/// +/// @param ka - A pointer to an array of `SolKeyedAccounts` to print +/// @param data - A pointer to the instruction data to print +#[allow(dead_code)] +pub fn sol_log_params(ka: &[Option], data: &[u8]) { + for (i, k) in ka.iter().enumerate() { + if let Some(k) = k { + sol_log("SolKeyedAccount"); + sol_log_64(0, 0, 0, 0, i as u64); + sol_log("- Is signer"); + sol_log_64(0, 0, 0, 0, k.is_signer as u64); + sol_log("- Key"); + sol_log_key(&k.key); + sol_log("- Lamports"); + sol_log_64(0, 0, 0, 0, k.lamports); + sol_log("- AccountData"); + sol_log_slice(k.data); + sol_log("- Owner"); + sol_log_key(&k.owner); + } + } + sol_log("Instruction data"); + sol_log_slice(data); +} + +#[cfg(test)] +mod tests { + extern crate std; + + use self::std::ffi::CStr; + use self::std::println; + use self::std::string::String; + use super::*; + use core::mem; + + static mut _LOG_SCENARIO: u64 = 4; + fn get_log_scenario() -> u64 { + unsafe { _LOG_SCENARIO } + } + fn set_log_scenario(test: u64) { + unsafe { _LOG_SCENARIO = test }; + } + + #[no_mangle] + fn sol_log_(message: *const u8) { + let scenario = get_log_scenario(); + let c_str = unsafe { CStr::from_ptr(message as *const i8) }; + let string = c_str.to_str().unwrap(); + match scenario { + 1 => assert_eq!(string, "This is a test message"), + 2 => assert_eq!(string, "Attempted to log a string that is too long"), + 3 => { + let s: String = ['a'; 126].iter().collect(); + assert_eq!(string, s); + } + 4 => println!("{:?}", string), + _ => panic!("Unknown sol_log test"), + } + } + + static mut _LOG_64_SCENARIO: u64 = 4; + fn get_log_64_scenario() -> u64 { + unsafe { _LOG_64_SCENARIO } + } + fn set_log_64_scenario(test: u64) { + unsafe { _LOG_64_SCENARIO = test }; + } + + #[no_mangle] + fn sol_log_64_(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) { + let scenario = get_log_64_scenario(); + match scenario { + 1 => { + assert_eq!(1, arg1); + assert_eq!(2, arg2); + assert_eq!(3, arg3); + assert_eq!(4, arg4); + assert_eq!(5, arg5); + } + 2 => { + assert_eq!(0, arg1); + assert_eq!(0, arg2); + assert_eq!(0, arg3); + assert_eq!(arg4 + 1, arg5); + } + 3 => { + assert_eq!(0, arg1); + assert_eq!(0, arg2); + assert_eq!(0, arg3); + assert_eq!(arg4 + 1, arg5); + } + 4 => println!("{:?} {:?} {:?} {:?} {:?}", arg1, arg2, arg3, arg4, arg5), + _ => panic!("Unknown sol_log_64 test"), + } + } + + #[test] + fn test_sol_log() { + set_log_scenario(1); + sol_log("This is a test message"); + } + + #[test] + fn test_sol_log_long() { + set_log_scenario(3); + let s: String = ['a'; 256].iter().collect(); + sol_log(&s); + } + + #[test] + fn test_sol_log_max_length() { + set_log_scenario(3); + let s: String = ['a'; 126].iter().collect(); + sol_log(&s); + } + + #[test] + fn test_sol_log_64() { + set_log_64_scenario(1); + sol_log_64(1, 2, 3, 4, 5); + } + + #[test] + fn test_sol_log_key() { + set_log_64_scenario(2); + let key_array = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + ]; + let key = SolPubkey { key: &key_array }; + sol_log_key(&key); + } + + #[test] + fn test_sol_log_slice() { + set_log_64_scenario(3); + let array = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + ]; + sol_log_slice(&array); + } +} diff --git a/sdk/bpf/rust-utils/src/panic.rs b/sdk/bpf/rust-utils/src/panic.rs new file mode 100644 index 0000000000..330a540ea7 --- /dev/null +++ b/sdk/bpf/rust-utils/src/panic.rs @@ -0,0 +1,31 @@ +//! @brief Solana Rust-based BPF program panic handling + +// #[cfg(not(test))] +use crate::log::*; +use core::panic::PanicInfo; + +#[cfg(not(test))] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + sol_log("Panic!"); + // TODO crashes! sol_log(_info.payload().downcast_ref::<&str>().unwrap()); + if let Some(location) = info.location() { + if !location.file().is_empty() { + // TODO location.file() returns empty str, if we get here its been fixed + sol_log(location.file()); + sol_log("location.file() is fixed!!"); + unsafe { + sol_panic_(); + } + } + sol_log_64(0, 0, 0, u64::from(location.line()), u64::from(location.column())); + } else { + sol_log("Panic! but could not get location information"); + } + unsafe { + sol_panic_(); + } +} +extern "C" { + pub fn sol_panic_() -> !; +}