From 990d7e05903b338d6baf583a8fb3945801efba66 Mon Sep 17 00:00:00 2001 From: Brooks Prumo Date: Fri, 13 May 2022 12:54:03 -0500 Subject: [PATCH 1/3] Add get_stake_minimum_delegation() to rpc_client --- client/src/nonblocking/rpc_client.rs | 39 ++++++++++++++++++++++++- client/src/rpc_client.rs | 43 +++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/client/src/nonblocking/rpc_client.rs b/client/src/nonblocking/rpc_client.rs index 5980beb1f0bb4d..32b458b09e9cbe 100644 --- a/client/src/nonblocking/rpc_client.rs +++ b/client/src/nonblocking/rpc_client.rs @@ -39,10 +39,11 @@ use { epoch_schedule::EpochSchedule, fee_calculator::{FeeCalculator, FeeRateGovernor}, hash::Hash, + instruction::InstructionError, message::Message, pubkey::Pubkey, signature::Signature, - transaction::{self, uses_durable_nonce, Transaction}, + transaction::{self, uses_durable_nonce, Transaction, TransactionError}, }, solana_transaction_status::{ EncodedConfirmedBlock, EncodedConfirmedTransactionWithStatusMeta, TransactionStatus, @@ -4507,6 +4508,42 @@ impl RpcClient { parse_keyed_accounts(accounts, RpcRequest::GetProgramAccounts) } + /// Returns the stake minimum delegation, in lamports. + pub async fn get_stake_minimum_delegation(&self) -> ClientResult { + let instruction = solana_sdk::stake::instruction::get_minimum_delegation(); + let payer = None; + let transaction = Transaction::new_with_payer(&[instruction], payer); + let response = self.simulate_transaction(&transaction).await?; + let return_data = response + .value + .return_data + .ok_or_else(|| ClientErrorKind::Custom("return data was empty".to_string()))?; + let (program_id, data) = (return_data.program_id, return_data.data); + if Pubkey::from_str(&program_id) != Ok(solana_sdk::stake::program::id()) { + return Err(TransactionError::InstructionError( + 0, + InstructionError::IncorrectProgramId, + ) + .into()); + } + if data.1 != ReturnDataEncoding::Base64 { + return Err( + ClientErrorKind::Custom("return data encoding is invalid".to_string()).into(), + ); + } + let data = base64::decode(data.0).map_err(|err| { + ClientErrorKind::Custom(format!("failed to decode return data: {}", err)) + })?; + let minimum_delegation = u64::from_le_bytes(data.try_into().map_err(|data: Vec| { + ClientErrorKind::Custom(format!( + "return data cannot be represented as a u64: expected size: {}, actual size: {}", + std::mem::size_of::(), + data.len() + )) + })?); + Ok(minimum_delegation) + } + /// Request the transaction count. pub async fn get_transaction_count(&self) -> ClientResult { self.get_transaction_count_with_commitment(self.commitment()) diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index 2985c0ca757aea..c0e53b3b3f9cf8 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -3711,6 +3711,11 @@ impl RpcClient { ) } + /// Returns the stake minimum delegation, in lamports. + pub fn get_stake_minimum_delegation(&self) -> ClientResult { + self.invoke(self.rpc_client.get_stake_minimum_delegation()) + } + /// Request the transaction count. pub fn get_transaction_count(&self) -> ClientResult { self.invoke(self.rpc_client.get_transaction_count()) @@ -4097,7 +4102,7 @@ mod tests { system_transaction, transaction::TransactionError, }, - std::{io, thread}, + std::{collections::HashMap, io, thread}, }; #[test] @@ -4313,4 +4318,40 @@ mod tests { let is_err = rpc_client.get_latest_blockhash().is_err(); assert!(is_err); } + + #[test] + fn test_get_stake_minimum_delegation() { + let expected_minimum_delegation: u64 = 123_456_789; + let rpc_client = { + let mocks = { + let rpc_response = { + let program_id = solana_sdk::stake::program::id().to_string(); + let data = ( + base64::encode(expected_minimum_delegation.to_le_bytes()), + ReturnDataEncoding::Base64, + ); + serde_json::to_value(Response { + context: RpcResponseContext { slot: 1 }, + value: RpcSimulateTransactionResult { + err: None, + logs: None, + accounts: None, + units_consumed: None, + return_data: Some(RpcTransactionReturnData { program_id, data }), + }, + }) + .unwrap() + }; + let mut mocks = HashMap::new(); + mocks.insert(RpcRequest::SimulateTransaction, rpc_response); + mocks + }; + RpcClient::new_mock_with_mocks("succeeds".to_string(), mocks) + }; + + let client_result = rpc_client.get_stake_minimum_delegation(); + assert!(client_result.is_ok()); + let actual_minimum_delegation = client_result.unwrap(); + assert_eq!(actual_minimum_delegation, expected_minimum_delegation); + } } From c3b58081fbe7266302821276c38d6ff8ed78acd1 Mon Sep 17 00:00:00 2001 From: Brooks Prumo Date: Tue, 17 May 2022 09:16:37 -0500 Subject: [PATCH 2/3] pr: remove line --- client/src/nonblocking/rpc_client.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/src/nonblocking/rpc_client.rs b/client/src/nonblocking/rpc_client.rs index 32b458b09e9cbe..e0f96d8de0692e 100644 --- a/client/src/nonblocking/rpc_client.rs +++ b/client/src/nonblocking/rpc_client.rs @@ -4511,8 +4511,7 @@ impl RpcClient { /// Returns the stake minimum delegation, in lamports. pub async fn get_stake_minimum_delegation(&self) -> ClientResult { let instruction = solana_sdk::stake::instruction::get_minimum_delegation(); - let payer = None; - let transaction = Transaction::new_with_payer(&[instruction], payer); + let transaction = Transaction::new_with_payer(&[instruction], None); let response = self.simulate_transaction(&transaction).await?; let return_data = response .value From e8252e9914739d252bcc004b0752d275f8bfd8b7 Mon Sep 17 00:00:00 2001 From: Brooks Prumo Date: Tue, 17 May 2022 09:25:18 -0500 Subject: [PATCH 3/3] pr: destructure --- client/src/nonblocking/rpc_client.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/client/src/nonblocking/rpc_client.rs b/client/src/nonblocking/rpc_client.rs index e0f96d8de0692e..c9cce93a3461d3 100644 --- a/client/src/nonblocking/rpc_client.rs +++ b/client/src/nonblocking/rpc_client.rs @@ -4513,11 +4513,13 @@ impl RpcClient { let instruction = solana_sdk::stake::instruction::get_minimum_delegation(); let transaction = Transaction::new_with_payer(&[instruction], None); let response = self.simulate_transaction(&transaction).await?; - let return_data = response + let RpcTransactionReturnData { + program_id, + data: (data, encoding), + } = response .value .return_data .ok_or_else(|| ClientErrorKind::Custom("return data was empty".to_string()))?; - let (program_id, data) = (return_data.program_id, return_data.data); if Pubkey::from_str(&program_id) != Ok(solana_sdk::stake::program::id()) { return Err(TransactionError::InstructionError( 0, @@ -4525,12 +4527,12 @@ impl RpcClient { ) .into()); } - if data.1 != ReturnDataEncoding::Base64 { + if encoding != ReturnDataEncoding::Base64 { return Err( ClientErrorKind::Custom("return data encoding is invalid".to_string()).into(), ); } - let data = base64::decode(data.0).map_err(|err| { + let data = base64::decode(data).map_err(|err| { ClientErrorKind::Custom(format!("failed to decode return data: {}", err)) })?; let minimum_delegation = u64::from_le_bytes(data.try_into().map_err(|data: Vec| {