miraland_cli/
checks.rs

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        // No messages, no fee.
290        assert_eq!(get_fee_for_messages(&rpc_client, &[]).unwrap(), 0);
291
292        // One message w/ one signature, a fee.
293        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        // No signatures, no fee.
300        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}