diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 8f211ed3c25f80..09c592345d7a44 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -3032,6 +3032,13 @@ dependencies = [ "solana-program 1.11.0", ] +[[package]] +name = "solana-bpf-rust-get-minimum-delegation" +version = "1.11.0" +dependencies = [ + "solana-program 1.11.0", +] + [[package]] name = "solana-bpf-rust-instruction-introspection" version = "1.11.0" diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index c8769088b828e6..a8522840235542 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -57,6 +57,7 @@ members = [ "rust/error_handling", "rust/log_data", "rust/external_spend", + "rust/get_minimum_delegation", "rust/finalize", "rust/instruction_introspection", "rust/invoke", diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index 30bef9df1ee189..d456ff4c2f972b 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -70,6 +70,7 @@ fn main() { "log_data", "external_spend", "finalize", + "get_minimum_delegation", "instruction_introspection", "invoke", "invoke_and_error", diff --git a/programs/bpf/rust/get_minimum_delegation/Cargo.toml b/programs/bpf/rust/get_minimum_delegation/Cargo.toml new file mode 100644 index 00000000000000..671417394b2f98 --- /dev/null +++ b/programs/bpf/rust/get_minimum_delegation/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "solana-bpf-rust-get-minimum-delegation" +version = "1.11.0" +description = "Solana BPF test program written in Rust" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +documentation = "https://docs.rs/solana-bpf-rust-get-minimum-delegation" +edition = "2021" + +[dependencies] +solana-program = { path = "../../../../sdk/program", version = "=1.11.0" } + +[lib] +crate-type = ["cdylib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/bpf/rust/get_minimum_delegation/src/lib.rs b/programs/bpf/rust/get_minimum_delegation/src/lib.rs new file mode 100644 index 00000000000000..6a922433568ff7 --- /dev/null +++ b/programs/bpf/rust/get_minimum_delegation/src/lib.rs @@ -0,0 +1,23 @@ +//! Example/test program to get the minimum stake delegation via the helper function + +#![allow(unreachable_code)] + +extern crate solana_program; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey, stake, +}; + +solana_program::entrypoint!(process_instruction); +#[allow(clippy::unnecessary_wraps)] +fn process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + let minimum_delegation = stake::tools::get_minimum_delegation()?; + msg!( + "The minimum stake delegation is {} lamports", + minimum_delegation + ); + Ok(()) +} diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 0df6803177ab46..ab97d8cafdc428 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -56,6 +56,7 @@ use { pubkey::Pubkey, rent::Rent, signature::{keypair_from_seed, Keypair, Signer}, + stake, system_instruction::{self, MAX_PERMITTED_DATA_LENGTH}, system_program, sysvar::{self, clock, rent}, @@ -3524,3 +3525,32 @@ fn test_program_fees() { let post_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap(); assert_eq!(pre_balance - post_balance, expected_min_fee); } + +#[test] +#[cfg(feature = "bpf_rust")] +fn test_get_minimum_delegation() { + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(100_123_456_789); + let mut bank = Bank::new_for_tests(&genesis_config); + bank.feature_set = Arc::new(FeatureSet::all_enabled()); + + let (name, id, entrypoint) = solana_bpf_loader_program!(); + bank.add_builtin(&name, &id, entrypoint); + let bank = Arc::new(bank); + let bank_client = BankClient::new_shared(&bank); + + let program_id = load_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + "solana_bpf_rust_get_minimum_delegation", + ); + + let account_metas = vec![AccountMeta::new_readonly(stake::program::id(), false)]; + let instruction = Instruction::new_with_bytes(program_id, &[], account_metas); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert!(result.is_ok()); +} diff --git a/sdk/program/src/stake/instruction.rs b/sdk/program/src/stake/instruction.rs index 087407e226f3b1..12715a3e79ee6e 100644 --- a/sdk/program/src/stake/instruction.rs +++ b/sdk/program/src/stake/instruction.rs @@ -228,8 +228,11 @@ pub enum StakeInstruction { /// # Account references /// None /// - /// The minimum delegation will be returned via the transaction context's returndata. - /// Use `get_return_data()` to retrieve the result. + /// Returns the minimum delegation as a little-endian encoded u64 value. + /// Programs can use the [`get_minimum_delegation()`] helper function to invoke and + /// retrieve the return value for this instruction. + /// + /// [`get_minimum_delegation()`]: super::tools::get_minimum_delegation GetMinimumDelegation, } diff --git a/sdk/program/src/stake/mod.rs b/sdk/program/src/stake/mod.rs index 87fc3f572f45d7..5366b112b8b2cc 100644 --- a/sdk/program/src/stake/mod.rs +++ b/sdk/program/src/stake/mod.rs @@ -1,6 +1,7 @@ pub mod config; pub mod instruction; pub mod state; +pub mod tools; pub mod program { crate::declare_id!("Stake11111111111111111111111111111111111111"); diff --git a/sdk/program/src/stake/tools.rs b/sdk/program/src/stake/tools.rs new file mode 100644 index 00000000000000..24d5e2d8a90963 --- /dev/null +++ b/sdk/program/src/stake/tools.rs @@ -0,0 +1,38 @@ +//! Utility functions +use crate::program_error::ProgramError; + +/// Helper function for programs to call [`GetMinimumDelegation`] and then fetch the return data +/// +/// This fn handles performing the CPI to call the [`GetMinimumDelegation`] function, and then +/// calls [`get_return_data()`] to fetch the return data. +/// +/// [`GetMinimumDelegation`]: super::instruction::StakeInstruction::GetMinimumDelegation +/// [`get_return_data()`]: crate::program::get_return_data +pub fn get_minimum_delegation() -> Result { + let instruction = super::instruction::get_minimum_delegation(); + crate::program::invoke_unchecked(&instruction, &[])?; + get_minimum_delegation_return_data() +} + +/// Helper function for programs to get the return data after calling [`GetMinimumDelegation`] +/// +/// This fn handles calling [`get_return_data()`], ensures the result is from the correct +/// program, and returns the correct type. +/// +/// [`GetMinimumDelegation`]: super::instruction::StakeInstruction::GetMinimumDelegation +/// [`get_return_data()`]: crate::program::get_return_data +fn get_minimum_delegation_return_data() -> Result { + crate::program::get_return_data() + .ok_or(ProgramError::InvalidInstructionData) + .and_then(|(program_id, return_data)| { + (program_id == super::program::id()) + .then(|| return_data) + .ok_or(ProgramError::IncorrectProgramId) + }) + .and_then(|return_data| { + return_data + .try_into() + .or(Err(ProgramError::InvalidInstructionData)) + }) + .map(u64::from_le_bytes) +}