1use {
2 crate::cli::CliError,
3 miraland_rpc_client::rpc_client::RpcClient,
4 miraland_rpc_client_api::client_error::{Error as ClientError, Result as ClientResult},
5 miraland_sdk::{
6 commitment_config::CommitmentConfig, message::Message, native_token::lamports_to_mln,
7 pubkey::Pubkey,
8 },
9};
10
11pub fn check_account_for_fee(
12 rpc_client: &RpcClient,
13 account_pubkey: &Pubkey,
14 message: &Message,
15) -> Result<(), CliError> {
16 check_account_for_multiple_fees(rpc_client, account_pubkey, &[message])
17}
18
19pub fn check_account_for_fee_with_commitment(
20 rpc_client: &RpcClient,
21 account_pubkey: &Pubkey,
22 message: &Message,
23 commitment: CommitmentConfig,
24) -> Result<(), CliError> {
25 check_account_for_multiple_fees_with_commitment(
26 rpc_client,
27 account_pubkey,
28 &[message],
29 commitment,
30 )
31}
32
33pub fn check_account_for_multiple_fees(
34 rpc_client: &RpcClient,
35 account_pubkey: &Pubkey,
36 messages: &[&Message],
37) -> Result<(), CliError> {
38 check_account_for_multiple_fees_with_commitment(
39 rpc_client,
40 account_pubkey,
41 messages,
42 CommitmentConfig::default(),
43 )
44}
45
46pub fn check_account_for_multiple_fees_with_commitment(
47 rpc_client: &RpcClient,
48 account_pubkey: &Pubkey,
49 messages: &[&Message],
50 commitment: CommitmentConfig,
51) -> Result<(), CliError> {
52 check_account_for_spend_multiple_fees_with_commitment(
53 rpc_client,
54 account_pubkey,
55 0,
56 messages,
57 commitment,
58 )
59}
60
61pub fn check_account_for_spend_multiple_fees_with_commitment(
62 rpc_client: &RpcClient,
63 account_pubkey: &Pubkey,
64 balance: u64,
65 messages: &[&Message],
66 commitment: CommitmentConfig,
67) -> Result<(), CliError> {
68 let fee = get_fee_for_messages(rpc_client, messages)?;
69 check_account_for_spend_and_fee_with_commitment(
70 rpc_client,
71 account_pubkey,
72 balance,
73 fee,
74 commitment,
75 )
76}
77
78pub fn check_account_for_spend_and_fee_with_commitment(
79 rpc_client: &RpcClient,
80 account_pubkey: &Pubkey,
81 balance: u64,
82 fee: u64,
83 commitment: CommitmentConfig,
84) -> Result<(), CliError> {
85 if !check_account_for_balance_with_commitment(
86 rpc_client,
87 account_pubkey,
88 balance + fee,
89 commitment,
90 )
91 .map_err(Into::<ClientError>::into)?
92 {
93 if balance > 0 {
94 return Err(CliError::InsufficientFundsForSpendAndFee(
95 lamports_to_mln(balance),
96 lamports_to_mln(fee),
97 *account_pubkey,
98 ));
99 } else {
100 return Err(CliError::InsufficientFundsForFee(
101 lamports_to_mln(fee),
102 *account_pubkey,
103 ));
104 }
105 }
106 Ok(())
107}
108
109pub fn get_fee_for_messages(
110 rpc_client: &RpcClient,
111 messages: &[&Message],
112) -> Result<u64, CliError> {
113 Ok(messages
114 .iter()
115 .map(|message| rpc_client.get_fee_for_message(*message))
116 .collect::<Result<Vec<_>, _>>()?
117 .iter()
118 .sum())
119}
120
121pub fn check_account_for_balance(
122 rpc_client: &RpcClient,
123 account_pubkey: &Pubkey,
124 balance: u64,
125) -> ClientResult<bool> {
126 check_account_for_balance_with_commitment(
127 rpc_client,
128 account_pubkey,
129 balance,
130 CommitmentConfig::default(),
131 )
132}
133
134pub fn check_account_for_balance_with_commitment(
135 rpc_client: &RpcClient,
136 account_pubkey: &Pubkey,
137 balance: u64,
138 commitment: CommitmentConfig,
139) -> ClientResult<bool> {
140 let lamports = rpc_client
141 .get_balance_with_commitment(account_pubkey, commitment)?
142 .value;
143 if lamports != 0 && lamports >= balance {
144 return Ok(true);
145 }
146 Ok(false)
147}
148
149pub fn check_unique_pubkeys(
150 pubkey0: (&Pubkey, String),
151 pubkey1: (&Pubkey, String),
152) -> Result<(), CliError> {
153 if pubkey0.0 == pubkey1.0 {
154 Err(CliError::BadParameter(format!(
155 "Identical pubkeys found: `{}` and `{}` must be unique",
156 pubkey0.1, pubkey1.1
157 )))
158 } else {
159 Ok(())
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use {
166 super::*,
167 miraland_rpc_client_api::{
168 request::RpcRequest,
169 response::{Response, RpcResponseContext},
170 },
171 serde_json::json,
172 miraland_sdk::system_instruction,
173 std::collections::HashMap,
174 };
175
176 #[test]
177 fn test_check_account_for_fees() {
178 let account_balance = 1;
179 let account_balance_response = json!(Response {
180 context: RpcResponseContext {
181 slot: 1,
182 api_version: None
183 },
184 value: json!(account_balance),
185 });
186 let pubkey = miraland_sdk::pubkey::new_rand();
187
188 let pubkey0 = Pubkey::from([0; 32]);
189 let pubkey1 = Pubkey::from([1; 32]);
190 let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
191 let message0 = Message::new(&[ix0], Some(&pubkey0));
192
193 let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
194 let ix1 = system_instruction::transfer(&pubkey1, &pubkey0, 1);
195 let message1 = Message::new(&[ix0, ix1], Some(&pubkey0));
196
197 let mut mocks = HashMap::new();
198 mocks.insert(RpcRequest::GetBalance, account_balance_response.clone());
199 let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
200 check_account_for_fee(&rpc_client, &pubkey, &message0).expect("unexpected result");
201
202 let check_fee_response = json!(Response {
203 context: RpcResponseContext {
204 slot: 1,
205 api_version: None
206 },
207 value: json!(2),
208 });
209 let mut mocks = HashMap::new();
210 mocks.insert(RpcRequest::GetFeeForMessage, check_fee_response);
211 mocks.insert(RpcRequest::GetBalance, account_balance_response.clone());
212 let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
213 assert!(check_account_for_fee(&rpc_client, &pubkey, &message1).is_err());
214
215 let check_fee_response = json!(Response {
216 context: RpcResponseContext {
217 slot: 1,
218 api_version: None
219 },
220 value: json!(2),
221 });
222 let mut mocks = HashMap::new();
223 mocks.insert(RpcRequest::GetFeeForMessage, check_fee_response);
224 mocks.insert(RpcRequest::GetBalance, account_balance_response);
225 let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
226 assert!(
227 check_account_for_multiple_fees(&rpc_client, &pubkey, &[&message0, &message0]).is_err()
228 );
229
230 let account_balance = 2;
231 let account_balance_response = json!(Response {
232 context: RpcResponseContext {
233 slot: 1,
234 api_version: None
235 },
236 value: json!(account_balance),
237 });
238 let check_fee_response = json!(Response {
239 context: RpcResponseContext {
240 slot: 1,
241 api_version: None
242 },
243 value: json!(1),
244 });
245
246 let mut mocks = HashMap::new();
247 mocks.insert(RpcRequest::GetFeeForMessage, check_fee_response);
248 mocks.insert(RpcRequest::GetBalance, account_balance_response);
249 let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
250
251 check_account_for_multiple_fees(&rpc_client, &pubkey, &[&message0, &message0])
252 .expect("unexpected result");
253 }
254
255 #[test]
256 fn test_check_account_for_balance() {
257 let account_balance = 50;
258 let account_balance_response = json!(Response {
259 context: RpcResponseContext {
260 slot: 1,
261 api_version: None
262 },
263 value: json!(account_balance),
264 });
265 let pubkey = miraland_sdk::pubkey::new_rand();
266
267 let mut mocks = HashMap::new();
268 mocks.insert(RpcRequest::GetBalance, account_balance_response);
269 let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
270
271 assert!(check_account_for_balance(&rpc_client, &pubkey, 1).unwrap());
272 assert!(check_account_for_balance(&rpc_client, &pubkey, account_balance).unwrap());
273 assert!(!check_account_for_balance(&rpc_client, &pubkey, account_balance + 1).unwrap());
274 }
275
276 #[test]
277 fn test_get_fee_for_messages() {
278 let check_fee_response = json!(Response {
279 context: RpcResponseContext {
280 slot: 1,
281 api_version: None
282 },
283 value: json!(1),
284 });
285 let mut mocks = HashMap::new();
286 mocks.insert(RpcRequest::GetFeeForMessage, check_fee_response);
287 let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
288
289 assert_eq!(get_fee_for_messages(&rpc_client, &[]).unwrap(), 0);
291
292 let pubkey0 = Pubkey::from([0; 32]);
294 let pubkey1 = Pubkey::from([1; 32]);
295 let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
296 let message0 = Message::new(&[ix0], Some(&pubkey0));
297 assert_eq!(get_fee_for_messages(&rpc_client, &[&message0]).unwrap(), 1);
298
299 let check_fee_response = json!(Response {
301 context: RpcResponseContext {
302 slot: 1,
303 api_version: None
304 },
305 value: json!(0),
306 });
307 let mut mocks = HashMap::new();
308 mocks.insert(RpcRequest::GetFeeForMessage, check_fee_response);
309 let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
310 let message = Message::default();
311 assert_eq!(
312 get_fee_for_messages(&rpc_client, &[&message, &message]).unwrap(),
313 0
314 );
315 }
316
317 #[test]
318 fn test_check_unique_pubkeys() {
319 let pubkey0 = miraland_sdk::pubkey::new_rand();
320 let pubkey_clone = pubkey0;
321 let pubkey1 = miraland_sdk::pubkey::new_rand();
322
323 check_unique_pubkeys((&pubkey0, "foo".to_string()), (&pubkey1, "bar".to_string()))
324 .expect("unexpected result");
325 check_unique_pubkeys((&pubkey0, "foo".to_string()), (&pubkey1, "foo".to_string()))
326 .expect("unexpected result");
327
328 assert!(check_unique_pubkeys(
329 (&pubkey0, "foo".to_string()),
330 (&pubkey_clone, "bar".to_string())
331 )
332 .is_err());
333 }
334}