Skip to main content

casper_client/cli/
parse.rs

1//! This module contains structs and helpers which are used by multiple subcommands related to
2//! creating deploys.
3
4use std::fs;
5#[cfg(feature = "std-fs-io")]
6use std::path::Path;
7use std::str::FromStr;
8
9use rand::Rng;
10
11#[cfg(feature = "std-fs-io")]
12use casper_types::SecretKey;
13use casper_types::{
14    account::AccountHash, bytesrepr::Bytes, crypto, AsymmetricType, BlockHash, DeployHash, Digest,
15    EntityAddr, ExecutableDeployItem, HashAddr, Key, NamedArg, PricingMode, PublicKey, RuntimeArgs,
16    TimeDiff, Timestamp, TransactionArgs, TransactionHash, TransactionV1Hash, TransferTarget,
17    UIntParseError, URef, U512,
18};
19
20use super::{simple_args, CliError, PaymentStrParams, SessionStrParams};
21#[cfg(feature = "std-fs-io")]
22use crate::OutputKind;
23use crate::{
24    rpcs::EraIdentifier, AccountIdentifier, BlockIdentifier, EntityIdentifier,
25    GlobalStateIdentifier, JsonRpcId, PurseIdentifier, Verbosity,
26};
27
28pub(super) fn rpc_id(maybe_rpc_id: &str) -> JsonRpcId {
29    if maybe_rpc_id.is_empty() {
30        JsonRpcId::from(rand::thread_rng().gen::<i64>())
31    } else if let Ok(i64_id) = maybe_rpc_id.parse::<i64>() {
32        JsonRpcId::from(i64_id)
33    } else {
34        JsonRpcId::from(maybe_rpc_id.to_string())
35    }
36}
37
38pub(super) fn verbosity(verbosity_level: u64) -> Verbosity {
39    match verbosity_level {
40        0 => Verbosity::Low,
41        1 => Verbosity::Medium,
42        _ => Verbosity::High,
43    }
44}
45
46#[cfg(feature = "std-fs-io")]
47pub(super) fn output_kind(maybe_output_path: &str, force: bool) -> OutputKind {
48    if maybe_output_path.is_empty() {
49        OutputKind::Stdout
50    } else {
51        OutputKind::file(Path::new(maybe_output_path), force)
52    }
53}
54
55#[cfg(feature = "std-fs-io")]
56pub(super) fn secret_key_from_file<P: AsRef<Path>>(
57    secret_key_path: P,
58) -> Result<SecretKey, CliError> {
59    SecretKey::from_file(secret_key_path).map_err(|error| {
60        CliError::Core(crate::Error::CryptoError {
61            context: "secret key",
62            error,
63        })
64    })
65}
66
67pub(super) fn timestamp(value: &str) -> Result<Timestamp, CliError> {
68    #[cfg(any(feature = "std-fs-io", test))]
69    let timestamp = Timestamp::now();
70    #[cfg(not(any(feature = "std-fs-io", test)))]
71    let timestamp = Timestamp::zero();
72    if value.is_empty() {
73        return Ok(timestamp);
74    }
75    Timestamp::from_str(value).map_err(|error| CliError::FailedToParseTimestamp {
76        context: "timestamp",
77        error,
78    })
79}
80
81pub(super) fn ttl(value: &str) -> Result<TimeDiff, CliError> {
82    TimeDiff::from_str(value).map_err(|error| CliError::FailedToParseTimeDiff {
83        context: "ttl",
84        error,
85    })
86}
87
88pub(super) fn session_account(value: &str) -> Result<Option<PublicKey>, CliError> {
89    if value.is_empty() {
90        return Ok(None);
91    }
92
93    let public_key = PublicKey::from_hex(value).map_err(|error| crate::Error::CryptoError {
94        context: "session account",
95        error: crypto::ErrorExt::from(error),
96    })?;
97    Ok(Some(public_key))
98}
99
100/// Handles providing the arg for and retrieval of simple session and payment args.
101pub(crate) mod arg_simple {
102    use super::*;
103
104    pub(crate) mod session {
105        use super::*;
106
107        /// Parse session arguments into runtime args.
108        pub fn parse(values: &[&str]) -> Result<Option<RuntimeArgs>, CliError> {
109            Ok(if values.is_empty() {
110                None
111            } else {
112                Some(get(values)?)
113            })
114        }
115    }
116
117    pub(crate) mod payment {
118        use super::*;
119
120        pub fn parse(values: &[&str]) -> Result<Option<RuntimeArgs>, CliError> {
121            Ok(if values.is_empty() {
122                None
123            } else {
124                Some(get(values)?)
125            })
126        }
127    }
128
129    fn get(values: &[&str]) -> Result<RuntimeArgs, CliError> {
130        let mut runtime_args = RuntimeArgs::new();
131        for arg in values {
132            simple_args::insert_arg(arg, &mut runtime_args)?;
133        }
134        Ok(runtime_args)
135    }
136}
137
138pub(crate) mod args_json {
139    use super::*;
140    use crate::cli::JsonArg;
141
142    pub mod session {
143        use super::*;
144
145        /// Parse session arguments into runtime args.
146        pub fn parse(json_str: &str) -> Result<Option<RuntimeArgs>, CliError> {
147            get(json_str)
148        }
149    }
150
151    pub mod payment {
152        use super::*;
153
154        pub fn parse(json_str: &str) -> Result<Option<RuntimeArgs>, CliError> {
155            get(json_str)
156        }
157    }
158
159    fn get(json_str: &str) -> Result<Option<RuntimeArgs>, CliError> {
160        if json_str.is_empty() {
161            return Ok(None);
162        }
163        let json_args: Vec<JsonArg> = serde_json::from_str(json_str)?;
164        let mut named_args = Vec::with_capacity(json_args.len());
165        for json_arg in json_args {
166            named_args.push(NamedArg::try_from(json_arg)?);
167        }
168        Ok(Some(RuntimeArgs::from(named_args)))
169    }
170}
171
172const STANDARD_PAYMENT_ARG_NAME: &str = "amount";
173fn standard_payment(value: &str) -> Result<RuntimeArgs, CliError> {
174    if value.is_empty() {
175        return Err(CliError::InvalidCLValue(value.to_string()));
176    }
177    let arg = U512::from_dec_str(value).map_err(|err| CliError::FailedToParseUint {
178        context: "amount",
179        error: UIntParseError::FromDecStr(err),
180    })?;
181    let mut runtime_args = RuntimeArgs::new();
182    runtime_args.insert(STANDARD_PAYMENT_ARG_NAME, arg)?;
183    Ok(runtime_args)
184}
185
186/// Checks if conflicting arguments are provided for parsing session information.
187///
188/// # Arguments
189///
190/// * `context` - A string indicating the context in which the arguments are checked.
191/// * `simple` - A vector of strings representing simple arguments.
192/// * `json` - A string representing JSON-formatted arguments.
193///
194/// # Returns
195///
196/// Returns a `Result` with an empty `Ok(())` variant if no conflicting arguments are found. If
197/// conflicting arguments are provided, an `Err` variant with a `CliError::ConflictingArguments` is
198/// returned.
199///
200/// # Errors
201///
202/// Returns an `Err` variant with a `CliError::ConflictingArguments` if conflicting arguments are
203/// provided.
204fn check_no_conflicting_arg_types(
205    context: &str,
206    simple: &[&str],
207    json: &str,
208) -> Result<(), CliError> {
209    let count = [!simple.is_empty(), !json.is_empty()]
210        .iter()
211        .filter(|&&x| x)
212        .count();
213
214    if count > 1 {
215        return Err(CliError::ConflictingArguments {
216            context: format!("{context} args conflict (simple json)",),
217            args: vec![simple.join(", "), json.to_owned()],
218        });
219    }
220    Ok(())
221}
222
223/// Constructs a `RuntimeArgs` instance from either simple or JSON representation.
224///
225/// # Arguments
226///
227/// * `simple`: An optional `RuntimeArgs` representing simple arguments.
228/// * `json`: An optional `RuntimeArgs` representing arguments in JSON format.
229///
230/// # Returns
231///
232/// A `RuntimeArgs` instance representing the merged arguments from `simple` and `json`.
233///
234/// # Examples
235///
236/// ```
237/// use casper_client::cli::parse::args_from_simple_or_json;
238/// use casper_types::RuntimeArgs;
239///
240/// let simple_args = RuntimeArgs::new(); // Simple arguments
241/// let json_args = RuntimeArgs::new();   // JSON arguments
242///
243/// let _result_args = args_from_simple_or_json(Some(simple_args), None, None);
244/// let _result_args = args_from_simple_or_json(None, Some(json_args), None);
245/// ```
246pub fn args_from_simple_or_json(
247    simple: Option<RuntimeArgs>,
248    json: Option<RuntimeArgs>,
249    chunked: Option<Vec<u8>>,
250) -> TransactionArgs {
251    // We can have exactly zero or one of the two as `Some`.
252    match chunked {
253        Some(chunked) => TransactionArgs::Bytesrepr(chunked.into()),
254        None => {
255            let named_args = match (simple, json) {
256                (Some(args), None) | (None, Some(args)) => args,
257                (None, None) => RuntimeArgs::new(),
258                _ => unreachable!("should not have more than one of simple, json args"),
259            };
260            TransactionArgs::Named(named_args)
261        }
262    }
263}
264
265/// Private macro for enforcing parameter validity.
266/// e.g. check_exactly_one_not_empty!(
267///   (field1) requires[another_field],
268///   (field2) requires[another_field, yet_another_field]
269///   (field3) requires[]
270/// )
271/// Returns an error if:
272/// - More than one parameter is non-empty.
273/// - Any parameter that is non-empty has requires[] requirements that are empty.
274macro_rules! check_exactly_one_not_empty {
275    ( context: $site:tt, $( ($x:expr) requires[$($y:expr),*] requires_empty[$($z:expr),*] ),+ $(,)? ) => {{
276
277        let field_is_empty_map = &[$(
278            (stringify!($x), $x.is_empty())
279        ),+];
280
281        let required_arguments = field_is_empty_map
282            .iter()
283            .filter(|(_, is_empty)| !*is_empty)
284            .map(|(field, _)| field.to_string())
285            .collect::<Vec<_>>();
286
287        if required_arguments.is_empty() {
288            let required_param_names = vec![$((stringify!($x))),+];
289            return Err(CliError::InvalidArgument {
290                context: $site,
291                error: format!("Missing a required arg - exactly one of the following must be provided: {:?}", required_param_names),
292            });
293        }
294        if required_arguments.len() == 1 {
295            let name = &required_arguments[0];
296            let field_requirements = &[$(
297                (
298                    stringify!($x),
299                    $x,
300                    vec![$((stringify!($y), $y)),*],
301                    vec![$((stringify!($z), $z)),*],
302                )
303            ),+];
304
305            // Check requires[] and requires_requires_empty[] fields
306            let (_, value, requirements, required_empty) = field_requirements
307                .iter()
308                .find(|(field, _, _, _)| *field == name).expect("should exist");
309            let required_arguments = requirements
310                .iter()
311                .filter(|(_, value)| !value.is_empty())
312                .collect::<Vec<_>>();
313
314            if requirements.len() != required_arguments.len() {
315                let required_param_names = requirements
316                    .iter()
317                    .map(|(requirement_name, _)| requirement_name)
318                    .collect::<Vec<_>>();
319                return Err(CliError::InvalidArgument {
320                    context: $site,
321                    error: format!("Field {} also requires following fields to be provided: {:?}", name, required_param_names),
322                });
323            }
324
325            let mut conflicting_fields = required_empty
326                .iter()
327                .filter(|(_, value)| !value.is_empty())
328                .map(|(field, value)| format!("{}={}", field, value)).collect::<Vec<_>>();
329
330            if !conflicting_fields.is_empty() {
331                conflicting_fields.push(format!("{}={}", name, value));
332                conflicting_fields.sort();
333                return Err(CliError::ConflictingArguments{
334                    context: $site.to_string(),
335                    args: conflicting_fields,
336                });
337            }
338        } else {
339            // Here we have more than one non-empty arg, so it is an error.  Collect all non-empty
340            // fields and their values into a string to populate the returned Error.
341            let mut non_empty_fields_with_values = [$((stringify!($x), $x)),+]
342                .iter()
343                .filter_map(|(field_name, field_value)| if !field_value.is_empty() {
344                    Some(format!("{}={}", field_name, field_value))
345                } else {
346                    None
347                })
348                .collect::<Vec<String>>();
349            non_empty_fields_with_values.sort();
350            return Err(CliError::ConflictingArguments {
351                context: $site.to_string(),
352                args: non_empty_fields_with_values,
353            });
354        }
355    }}
356}
357
358pub(super) fn session_executable_deploy_item(
359    params: SessionStrParams,
360) -> Result<ExecutableDeployItem, CliError> {
361    let SessionStrParams {
362        session_hash,
363        session_name,
364        session_package_hash,
365        session_package_name,
366        session_path,
367        session_bytes,
368        ref session_args_simple,
369        session_args_json,
370        session_version,
371        session_entry_point,
372        is_session_transfer: session_transfer,
373        session_chunked_args,
374    } = params;
375    // This is to make sure that we're using &str consistently in the macro call below.
376    let is_session_transfer = if session_transfer { "true" } else { "" };
377    // This is to make sure that we're using &str consistently in the macro call below.
378    let has_session_bytes = if session_bytes.is_empty() { "" } else { "true" };
379
380    check_exactly_one_not_empty!(
381        context: "parse_session_info",
382        (session_hash)
383            requires[session_entry_point] requires_empty[session_version],
384        (session_name)
385            requires[session_entry_point] requires_empty[session_version],
386        (session_package_hash)
387            requires[session_entry_point] requires_empty[],
388        (session_package_name)
389            requires[session_entry_point] requires_empty[],
390        (session_path)
391            requires[] requires_empty[session_entry_point, session_version, has_session_bytes],
392        (has_session_bytes)
393            requires[] requires_empty[session_entry_point, session_version, session_path],
394        (is_session_transfer)
395            requires[] requires_empty[session_entry_point, session_version]
396    );
397
398    check_no_conflicting_arg_types("parse_session_info", session_args_simple, session_args_json)?;
399
400    let session_args = args_from_simple_or_json(
401        arg_simple::session::parse(session_args_simple)?,
402        args_json::session::parse(session_args_json)?,
403        session_chunked_args.map(ToOwned::to_owned),
404    );
405
406    if session_transfer {
407        let session_args = session_args.as_named().unwrap().clone();
408        if session_args.is_empty() {
409            return Err(CliError::InvalidArgument {
410                context: "is_session_transfer",
411                error: "requires --session-arg to be present".to_string(),
412            });
413        }
414        return Ok(ExecutableDeployItem::Transfer { args: session_args });
415    }
416    let invalid_entry_point = || CliError::InvalidArgument {
417        context: "session_entry_point",
418        error: session_entry_point.to_string(),
419    };
420    if let Some(session_name) = name(session_name) {
421        let session_args = session_args.as_named().unwrap().clone();
422
423        return Ok(ExecutableDeployItem::StoredContractByName {
424            name: session_name,
425            entry_point: entry_point(session_entry_point).ok_or_else(invalid_entry_point)?,
426            args: session_args,
427        });
428    }
429
430    if let Some(session_hash) = contract_hash(session_hash)? {
431        let session_args = session_args.as_named().unwrap().clone();
432        return Ok(ExecutableDeployItem::StoredContractByHash {
433            hash: session_hash.into(),
434            entry_point: entry_point(session_entry_point).ok_or_else(invalid_entry_point)?,
435            args: session_args,
436        });
437    }
438
439    let version = version(session_version)?;
440    if let Some(package_name) = name(session_package_name) {
441        let session_args = session_args.as_named().unwrap().clone();
442        return Ok(ExecutableDeployItem::StoredVersionedContractByName {
443            name: package_name,
444            version, // defaults to highest enabled version
445            entry_point: entry_point(session_entry_point).ok_or_else(invalid_entry_point)?,
446            args: session_args,
447        });
448    }
449
450    if let Some(package_hash) = contract_hash(session_package_hash)? {
451        let session_args = session_args.as_named().unwrap().clone();
452        return Ok(ExecutableDeployItem::StoredVersionedContractByHash {
453            hash: package_hash.into(),
454            version, // defaults to highest enabled version
455            entry_point: entry_point(session_entry_point).ok_or_else(invalid_entry_point)?,
456            args: session_args,
457        });
458    }
459
460    let module_bytes = if !session_bytes.is_empty() {
461        session_bytes
462    } else {
463        #[cfg(feature = "std-fs-io")]
464        {
465            transaction_module_bytes(session_path)?
466        }
467        #[cfg(not(feature = "std-fs-io"))]
468        return Err(CliError::InvalidArgument {
469            context: "session_executable_deploy_item",
470            error: "missing session bytes".to_string(),
471        });
472    };
473
474    let args = session_args
475        .as_named()
476        .ok_or(CliError::UnexpectedTransactionArgsVariant)?;
477
478    Ok(ExecutableDeployItem::ModuleBytes {
479        module_bytes,
480        args: args.clone(),
481    })
482}
483
484/// Parse a transaction file into Bytes to be used in crafting a new session transaction
485pub fn transaction_module_bytes(session_path: &str) -> Result<Bytes, CliError> {
486    let module_bytes = fs::read(session_path).map_err(|error| crate::Error::IoError {
487        context: format!("unable to read session file at '{}'", session_path),
488        error,
489    })?;
490    Ok(Bytes::from(module_bytes))
491}
492
493/// Parses transfer target from a string for use with the transaction builder
494pub fn transfer_target(target_str: &str) -> Result<TransferTarget, CliError> {
495    if let Ok(public_key) = PublicKey::from_hex(target_str) {
496        return Ok(TransferTarget::PublicKey(public_key));
497    }
498    #[cfg(feature = "std-fs-io")]
499    {
500        if let Ok(public_key) = PublicKey::from_file(target_str) {
501            return Ok(TransferTarget::PublicKey(public_key));
502        }
503    }
504    if let Ok(account_hash) = AccountHash::from_formatted_str(target_str) {
505        return Ok(TransferTarget::AccountHash(account_hash));
506    }
507    if let Ok(uref) = URef::from_formatted_str(target_str) {
508        return Ok(TransferTarget::URef(uref));
509    }
510    Err(CliError::FailedToParseTransferTarget)
511}
512
513/// Parses a URef from a formatted string for the purposes of creating transactions.
514pub fn uref(uref_str: &str) -> Result<URef, CliError> {
515    match URef::from_formatted_str(uref_str) {
516        Ok(uref) => Ok(uref),
517        Err(err) => Err(CliError::FailedToParseURef {
518            context: "Failed to parse URef for transaction",
519            error: err,
520        }),
521    }
522}
523
524pub(super) fn payment_executable_deploy_item(
525    params: PaymentStrParams,
526) -> Result<ExecutableDeployItem, CliError> {
527    let PaymentStrParams {
528        payment_amount,
529        payment_hash,
530        payment_name,
531        payment_package_hash,
532        payment_package_name,
533        payment_path,
534        payment_bytes,
535        ref payment_args_simple,
536        payment_args_json,
537        payment_version,
538        payment_entry_point,
539    } = params;
540    // This is to make sure that we're using &str consistently in the macro call below.
541    let has_payment_bytes = if payment_bytes.is_empty() { "" } else { "true" };
542    check_exactly_one_not_empty!(
543        context: "parse_payment_info",
544        (payment_amount)
545            requires[] requires_empty[payment_entry_point, payment_version],
546        (payment_hash)
547            requires[payment_entry_point] requires_empty[payment_version],
548        (payment_name)
549            requires[payment_entry_point] requires_empty[payment_version],
550        (payment_package_hash)
551            requires[payment_entry_point] requires_empty[],
552        (payment_package_name)
553            requires[payment_entry_point] requires_empty[],
554        (payment_path) requires[] requires_empty[payment_entry_point, payment_version, has_payment_bytes],
555        (has_payment_bytes)
556            requires[] requires_empty[payment_entry_point, payment_version, payment_path],
557    );
558
559    check_no_conflicting_arg_types("parse_payment_info", payment_args_simple, payment_args_json)?;
560
561    let payment_args = args_from_simple_or_json(
562        arg_simple::payment::parse(payment_args_simple)?,
563        args_json::payment::parse(payment_args_json)?,
564        None,
565    );
566
567    if let Ok(payment_args) = standard_payment(payment_amount) {
568        return Ok(ExecutableDeployItem::ModuleBytes {
569            module_bytes: vec![].into(),
570            args: payment_args,
571        });
572    }
573
574    let invalid_entry_point = || CliError::InvalidArgument {
575        context: "payment_entry_point",
576        error: payment_entry_point.to_string(),
577    };
578
579    let payment_args = payment_args
580        .as_named()
581        .cloned()
582        .ok_or(CliError::UnexpectedTransactionArgsVariant)?;
583
584    if let Some(payment_name) = name(payment_name) {
585        return Ok(ExecutableDeployItem::StoredContractByName {
586            name: payment_name,
587            entry_point: entry_point(payment_entry_point).ok_or_else(invalid_entry_point)?,
588            args: payment_args,
589        });
590    }
591
592    if let Some(payment_hash) = contract_hash(payment_hash)? {
593        return Ok(ExecutableDeployItem::StoredContractByHash {
594            hash: payment_hash.into(),
595            entry_point: entry_point(payment_entry_point).ok_or_else(invalid_entry_point)?,
596            args: payment_args,
597        });
598    }
599
600    let version = version(payment_version)?;
601    if let Some(package_name) = name(payment_package_name) {
602        return Ok(ExecutableDeployItem::StoredVersionedContractByName {
603            name: package_name,
604            version, // defaults to highest enabled version
605            entry_point: entry_point(payment_entry_point).ok_or_else(invalid_entry_point)?,
606            args: payment_args,
607        });
608    }
609
610    if let Some(package_hash) = contract_hash(payment_package_hash)? {
611        return Ok(ExecutableDeployItem::StoredVersionedContractByHash {
612            hash: package_hash.into(),
613            version, // defaults to highest enabled version
614            entry_point: entry_point(payment_entry_point).ok_or_else(invalid_entry_point)?,
615            args: payment_args,
616        });
617    }
618
619    let module_bytes = fs::read(payment_path).map_err(|error| crate::Error::IoError {
620        context: format!("unable to read payment file at '{}'", payment_path),
621        error,
622    })?;
623    Ok(ExecutableDeployItem::ModuleBytes {
624        module_bytes: module_bytes.into(),
625        args: payment_args,
626    })
627}
628
629fn contract_hash(value: &str) -> Result<Option<HashAddr>, CliError> {
630    if value.is_empty() {
631        return Ok(None);
632    }
633
634    match Digest::from_hex(value) {
635        Ok(digest) => Ok(Some(digest.value())),
636        Err(error) => match Key::from_formatted_str(value) {
637            Ok(Key::Hash(hash)) | Ok(Key::SmartContract(hash)) => Ok(Some(hash)),
638            _ => Err(CliError::FailedToParseDigest {
639                context: "contract hash",
640                error,
641            }),
642        },
643    }
644}
645
646fn name(value: &str) -> Option<String> {
647    if value.is_empty() {
648        return None;
649    }
650    Some(value.to_string())
651}
652
653fn entry_point(value: &str) -> Option<String> {
654    if value.is_empty() {
655        return None;
656    }
657    Some(value.to_string())
658}
659
660fn version(value: &str) -> Result<Option<u32>, CliError> {
661    if value.is_empty() {
662        return Ok(None);
663    }
664    let parsed = value
665        .parse::<u32>()
666        .map_err(|error| CliError::FailedToParseInt {
667            context: "version",
668            error,
669        })?;
670    Ok(Some(parsed))
671}
672
673pub(super) fn transfer_id(value: &str) -> Result<u64, CliError> {
674    value.parse().map_err(|error| CliError::FailedToParseInt {
675        context: "transfer_id",
676        error,
677    })
678}
679
680pub(super) fn block_identifier(
681    maybe_block_identifier: &str,
682) -> Result<Option<BlockIdentifier>, CliError> {
683    if maybe_block_identifier.is_empty() {
684        return Ok(None);
685    }
686
687    if maybe_block_identifier.len() == (Digest::LENGTH * 2) {
688        let hash = Digest::from_hex(maybe_block_identifier).map_err(|error| {
689            CliError::FailedToParseDigest {
690                context: "block_identifier",
691                error,
692            }
693        })?;
694        Ok(Some(BlockIdentifier::Hash(BlockHash::new(hash))))
695    } else {
696        let height =
697            maybe_block_identifier
698                .parse()
699                .map_err(|error| CliError::FailedToParseInt {
700                    context: "block_identifier",
701                    error,
702                })?;
703        Ok(Some(BlockIdentifier::Height(height)))
704    }
705}
706
707pub(super) fn deploy_hash(deploy_hash: &str) -> Result<DeployHash, CliError> {
708    let hash = Digest::from_hex(deploy_hash).map_err(|error| CliError::FailedToParseDigest {
709        context: "deploy hash",
710        error,
711    })?;
712    Ok(DeployHash::new(hash))
713}
714
715pub(super) fn key_for_query(key: &str) -> Result<Key, CliError> {
716    match Key::from_formatted_str(key) {
717        Ok(key) => Ok(key),
718        Err(error) => {
719            if let Ok(public_key) = PublicKey::from_hex(key) {
720                Ok(Key::Account(public_key.to_account_hash()))
721            } else {
722                Err(CliError::FailedToParseKey {
723                    context: "key for query",
724                    error,
725                })
726            }
727        }
728    }
729}
730
731/// `maybe_block_id` can be either a block hash or a block height.
732pub(super) fn global_state_identifier(
733    maybe_block_id: &str,
734    maybe_state_root_hash: &str,
735) -> Result<Option<GlobalStateIdentifier>, CliError> {
736    if maybe_state_root_hash.is_empty() && maybe_block_id.is_empty() {
737        return Ok(None);
738    }
739
740    match block_identifier(maybe_block_id)? {
741        Some(BlockIdentifier::Hash(hash)) => {
742            return Ok(Some(GlobalStateIdentifier::BlockHash(hash)))
743        }
744        Some(BlockIdentifier::Height(height)) => {
745            return Ok(Some(GlobalStateIdentifier::BlockHeight(height)))
746        }
747        None => (),
748    }
749
750    if maybe_state_root_hash.is_empty() {
751        return Ok(None);
752    }
753
754    let state_root_hash =
755        Digest::from_hex(maybe_state_root_hash).map_err(|error| CliError::FailedToParseDigest {
756            context: "state root hash in global_state_identifier",
757            error,
758        })?;
759    Ok(Some(GlobalStateIdentifier::StateRootHash(state_root_hash)))
760}
761
762/// `purse_id` can be a formatted public key, account hash, or URef.  It may not be empty.
763pub fn purse_identifier(purse_id: &str) -> Result<PurseIdentifier, CliError> {
764    const ACCOUNT_HASH_PREFIX: &str = "account-hash-";
765    const UREF_PREFIX: &str = "uref-";
766    const ENTITY_PREFIX: &str = "entity-";
767
768    if purse_id.is_empty() {
769        return Err(CliError::InvalidArgument {
770            context: "purse_identifier",
771            error: "cannot be empty string".to_string(),
772        });
773    }
774
775    if purse_id.starts_with(ACCOUNT_HASH_PREFIX) {
776        let account_hash = AccountHash::from_formatted_str(purse_id).map_err(|error| {
777            CliError::FailedToParseAccountHash {
778                context: "purse_identifier",
779                error,
780            }
781        })?;
782        return Ok(PurseIdentifier::MainPurseUnderAccountHash(account_hash));
783    }
784
785    if purse_id.starts_with(ENTITY_PREFIX) {
786        let entity_addr = EntityAddr::from_formatted_str(purse_id).map_err(|error| {
787            CliError::FailedToParseAddressableEntityHash {
788                context: "purse_identifier",
789                error,
790            }
791        })?;
792        return Ok(PurseIdentifier::MainPurseUnderEntityAddr(entity_addr));
793    }
794
795    if purse_id.starts_with(UREF_PREFIX) {
796        let uref =
797            URef::from_formatted_str(purse_id).map_err(|error| CliError::FailedToParseURef {
798                context: "purse_identifier",
799                error,
800            })?;
801        return Ok(PurseIdentifier::PurseUref(uref));
802    }
803
804    let public_key =
805        PublicKey::from_hex(purse_id).map_err(|error| CliError::FailedToParsePublicKey {
806            context: "purse_identifier".to_string(),
807            error,
808        })?;
809    Ok(PurseIdentifier::MainPurseUnderPublicKey(public_key))
810}
811
812/// `account_identifier` can be a formatted public key, in the form of a hex-formatted string,
813/// a pem file, or a file containing a hex formatted string, or a formatted string representing
814/// an account hash.  It may not be empty.
815pub fn account_identifier(account_identifier: &str) -> Result<AccountIdentifier, CliError> {
816    const ACCOUNT_HASH_PREFIX: &str = "account-hash-";
817
818    if account_identifier.is_empty() {
819        return Err(CliError::InvalidArgument {
820            context: "account_identifier",
821            error: "cannot be empty string".to_string(),
822        });
823    }
824
825    if account_identifier.starts_with(ACCOUNT_HASH_PREFIX) {
826        let account_hash =
827            AccountHash::from_formatted_str(account_identifier).map_err(|error| {
828                CliError::FailedToParseAccountHash {
829                    context: "account_identifier",
830                    error,
831                }
832            })?;
833        return Ok(AccountIdentifier::AccountHash(account_hash));
834    }
835
836    let public_key = PublicKey::from_hex(account_identifier).map_err(|error| {
837        CliError::FailedToParsePublicKey {
838            context: "account_identifier".to_string(),
839            error,
840        }
841    })?;
842    Ok(AccountIdentifier::PublicKey(public_key))
843}
844
845/// `entity_identifier` can be a formatted public key, in the form of a hex-formatted string,
846/// a pem file, or a file containing a hex formatted string, or a formatted string representing
847/// an account hash.  It may not be empty.
848pub fn entity_identifier(entity_identifier: &str) -> Result<EntityIdentifier, CliError> {
849    const ENTITY_PREFIX: &str = "entity-";
850    const ACCOUNT_HASH_PREFIX: &str = "account-hash-";
851
852    if entity_identifier.is_empty() {
853        return Err(CliError::InvalidArgument {
854            context: "entity_identifier",
855            error: "cannot be empty string".to_string(),
856        });
857    }
858
859    if entity_identifier.starts_with(ACCOUNT_HASH_PREFIX) {
860        let account_hash = AccountHash::from_formatted_str(entity_identifier).map_err(|error| {
861            CliError::FailedToParseAccountHash {
862                context: "entity_identifier",
863                error,
864            }
865        })?;
866        return Ok(EntityIdentifier::AccountHash(account_hash));
867    }
868    if entity_identifier.starts_with(ENTITY_PREFIX) {
869        let entity_addr = EntityAddr::from_formatted_str(entity_identifier).map_err(|error| {
870            CliError::FailedToParseAddressableEntityHash {
871                context: "entity_identifier",
872                error,
873            }
874        })?;
875        return Ok(EntityIdentifier::EntityAddr(entity_addr));
876    }
877
878    let public_key = PublicKey::from_hex(entity_identifier).map_err(|error| {
879        CliError::FailedToParsePublicKey {
880            context: "entity_identifier".to_string(),
881            error,
882        }
883    })?;
884    Ok(EntityIdentifier::PublicKey(public_key))
885}
886
887/// `era_identifier` must be an integer representing the era ID.
888pub(super) fn era_identifier(era_identifier: &str) -> Result<Option<EraIdentifier>, CliError> {
889    if era_identifier.is_empty() {
890        return Ok(None);
891    }
892    let era_id = era_identifier
893        .parse()
894        .map_err(|error| CliError::FailedToParseInt {
895            context: "era_identifier",
896            error,
897        })?;
898    Ok(Some(EraIdentifier::Era(era_id)))
899}
900
901/// `public_key` must be a public key formatted as a hex-encoded string,
902pub(super) fn public_key(public_key: &str) -> Result<Option<PublicKey>, CliError> {
903    if public_key.is_empty() {
904        return Ok(None);
905    }
906    let key =
907        PublicKey::from_hex(public_key).map_err(|error| CliError::FailedToParsePublicKey {
908            context: "public_key".to_owned(),
909            error,
910        })?;
911    Ok(Some(key))
912}
913
914pub(super) fn pricing_mode(
915    pricing_mode_identifier_str: &str,
916    payment_amount_str: &str,
917    gas_price_tolerance_str: &str,
918    additional_computation_factor_str: &str,
919    standard_payment_str: &str,
920    maybe_receipt: Option<Digest>,
921) -> Result<PricingMode, CliError> {
922    match pricing_mode_identifier_str.to_lowercase().as_str() {
923        "classic" => {
924            if gas_price_tolerance_str.is_empty() {
925                return Err(CliError::InvalidArgument {
926                    context: "gas_price_tolerance",
927                    error: "Gas price tolerance is required".to_string(),
928                });
929            }
930            if payment_amount_str.is_empty() {
931                return Err(CliError::InvalidArgument {
932                    context: "payment_amount",
933                    error: "Payment amount is required".to_string(),
934                });
935            }
936            if standard_payment_str.is_empty() {
937                return Err(CliError::InvalidArgument {
938                    context: "standard_payment",
939                    error: "Standard payment flag is required".to_string(),
940                });
941            }
942            let gas_price_tolerance = gas_price_tolerance_str.parse::<u8>().map_err(|error| {
943                CliError::FailedToParseInt {
944                    context: "gas_price_tolerance",
945                    error,
946                }
947            })?;
948            let payment_amount =
949                payment_amount_str
950                    .parse::<u64>()
951                    .map_err(|error| CliError::FailedToParseInt {
952                        context: "payment_amount",
953                        error,
954                    })?;
955            let standard_payment = standard_payment_str.parse::<bool>().map_err(|error| {
956                CliError::FailedToParseBool {
957                    context: "standard_payment",
958                    error,
959                }
960            })?;
961            Ok(PricingMode::PaymentLimited {
962                payment_amount,
963                gas_price_tolerance,
964                standard_payment,
965            })
966        }
967        "fixed" => {
968            if gas_price_tolerance_str.is_empty() {
969                return Err(CliError::InvalidArgument {
970                    context: "gas_price_tolerance",
971                    error: "Gas price tolerance is required".to_string(),
972                });
973            }
974            let gas_price_tolerance = gas_price_tolerance_str.parse::<u8>().map_err(|error| {
975                CliError::FailedToParseInt {
976                    context: "gas_price_tolerance",
977                    error,
978                }
979            })?;
980
981            // Additional Computation Factor defaults to 0 if the string is empty
982            let additional_computation_factor = if additional_computation_factor_str.is_empty() {
983                u8::default()
984            } else {
985                additional_computation_factor_str
986                    .parse::<u8>()
987                    .map_err(|error| CliError::FailedToParseInt {
988                        context: "additional_computation_factor",
989                        error,
990                    })?
991            };
992            Ok(PricingMode::Fixed {
993                gas_price_tolerance,
994                additional_computation_factor,
995            })
996        }
997        "reserved" => {
998            if maybe_receipt.is_none() {
999                return Err(CliError::InvalidArgument {
1000                    context: "receipt",
1001                    error: "Receipt is required for reserved pricing mode".to_string(),
1002                });
1003            }
1004            Ok(PricingMode::Prepaid {
1005                receipt: maybe_receipt.unwrap_or_default(),
1006            })
1007        }
1008        _ => Err(CliError::InvalidArgument {
1009            context: "pricing_mode",
1010            error: "Invalid pricing mode identifier".to_string(),
1011        }),
1012    }
1013}
1014
1015pub(super) fn transaction_hash(transaction_hash: &str) -> Result<TransactionHash, CliError> {
1016    let digest =
1017        Digest::from_hex(transaction_hash).map_err(|error| CliError::FailedToParseDigest {
1018            context: "failed to parse digest from string for transaction hash",
1019            error,
1020        })?;
1021    Ok(TransactionHash::from(TransactionV1Hash::from(digest)))
1022}
1023
1024#[cfg(test)]
1025mod tests {
1026    use std::convert::TryFrom;
1027
1028    use super::*;
1029
1030    const HASH: &str = "09dcee4b212cfd53642ab323fbef07dafafc6f945a80a00147f62910a915c4e6";
1031    const NAME: &str = "name";
1032    const PACKAGE_HASH: &str = "09dcee4b212cfd53642ab323fbef07dafafc6f945a80a00147f62910a915c4e6";
1033    const PACKAGE_NAME: &str = "package_name";
1034    const PATH: &str = "./session.wasm";
1035    const ENTRY_POINT: &str = "entrypoint";
1036    const VERSION: &str = "3";
1037    const TRANSFER: bool = true;
1038
1039    impl<'a> TryFrom<SessionStrParams<'a>> for ExecutableDeployItem {
1040        type Error = CliError;
1041
1042        fn try_from(params: SessionStrParams<'a>) -> Result<ExecutableDeployItem, Self::Error> {
1043            session_executable_deploy_item(params)
1044        }
1045    }
1046
1047    impl<'a> TryFrom<PaymentStrParams<'a>> for ExecutableDeployItem {
1048        type Error = CliError;
1049
1050        fn try_from(params: PaymentStrParams<'a>) -> Result<ExecutableDeployItem, Self::Error> {
1051            payment_executable_deploy_item(params)
1052        }
1053    }
1054
1055    #[test]
1056    fn should_fail_to_parse_conflicting_arg_types() {
1057        let test_context = "parse_session_info args conflict (simple json)".to_string();
1058        let actual_error = session_executable_deploy_item(SessionStrParams {
1059            session_hash: "",
1060            session_name: "name",
1061            session_package_hash: "",
1062            session_package_name: "",
1063            session_path: "",
1064            session_bytes: Bytes::new(),
1065            session_args_simple: vec!["something:u32='0'"],
1066            session_args_json: "{\"name\":\"entry_point_name\",\"type\":\"Bool\",\"value\":false}",
1067            session_version: "",
1068            session_entry_point: "entrypoint",
1069            is_session_transfer: false,
1070            session_chunked_args: None,
1071        })
1072        .unwrap_err();
1073
1074        assert!(
1075            matches!(actual_error, CliError::ConflictingArguments { ref context, .. } if *context == test_context),
1076            "{:?}",
1077            actual_error
1078        );
1079
1080        let test_context = "parse_payment_info args conflict (simple json)";
1081        let actual_error = payment_executable_deploy_item(PaymentStrParams {
1082            payment_amount: "",
1083            payment_hash: "name",
1084            payment_name: "",
1085            payment_package_hash: "",
1086            payment_package_name: "",
1087            payment_path: "",
1088            payment_bytes: Bytes::new(),
1089            payment_args_simple: vec!["something:u32='0'"],
1090            payment_args_json: "{\"name\":\"entry_point_name\",\"type\":\"Bool\",\"value\":false}",
1091            payment_version: "",
1092            payment_entry_point: "entrypoint",
1093        })
1094        .unwrap_err();
1095        assert!(
1096            matches!(
1097                actual_error,
1098                CliError::ConflictingArguments { ref context, .. } if context == test_context
1099            ),
1100            "{:?}",
1101            actual_error
1102        );
1103    }
1104
1105    #[test]
1106    fn should_fail_to_parse_conflicting_session_parameters() {
1107        let test_context = String::from("parse_session_info");
1108        assert!(matches!(
1109            session_executable_deploy_item(SessionStrParams {
1110                session_hash: HASH,
1111                session_name: NAME,
1112                session_package_hash: PACKAGE_HASH,
1113                session_package_name: PACKAGE_NAME,
1114                session_path: PATH,
1115                session_bytes: Bytes::new(),
1116                session_args_simple: vec![],
1117                session_args_json: "",
1118                session_version: "",
1119                session_entry_point: "",
1120                is_session_transfer: false,
1121                session_chunked_args: None,
1122            }),
1123            Err(CliError::ConflictingArguments { context, .. }) if context == test_context
1124        ));
1125    }
1126
1127    #[test]
1128    fn should_fail_to_parse_conflicting_payment_parameters() {
1129        let test_context = String::from("parse_payment_info");
1130        assert!(matches!(
1131            payment_executable_deploy_item(PaymentStrParams {
1132                payment_amount: "12345",
1133                payment_hash: HASH,
1134                payment_name: NAME,
1135                payment_package_hash: PACKAGE_HASH,
1136                payment_package_name: PACKAGE_NAME,
1137                payment_path: PATH,
1138                payment_bytes: Bytes::new(),
1139                payment_args_simple: vec![],
1140                payment_args_json: "",
1141                payment_version: "",
1142                payment_entry_point: "",
1143            }),
1144            Err(CliError::ConflictingArguments { context, .. }) if context == test_context
1145        ));
1146    }
1147
1148    mod missing_args {
1149        use super::*;
1150
1151        #[test]
1152        fn session_name_should_fail_to_parse_missing_entry_point() {
1153            let result = session_executable_deploy_item(SessionStrParams {
1154                session_name: NAME,
1155                ..Default::default()
1156            });
1157
1158            assert!(matches!(
1159                result,
1160                Err(CliError::InvalidArgument {
1161                    context: "parse_session_info",
1162                    ..
1163                })
1164            ));
1165        }
1166
1167        #[test]
1168        fn session_hash_should_fail_to_parse_missing_entry_point() {
1169            let result = session_executable_deploy_item(SessionStrParams {
1170                session_hash: HASH,
1171                ..Default::default()
1172            });
1173
1174            assert!(matches!(
1175                result,
1176                Err(CliError::InvalidArgument {
1177                    context: "parse_session_info",
1178                    ..
1179                })
1180            ));
1181        }
1182
1183        #[test]
1184        fn session_package_hash_should_fail_to_parse_missing_entry_point() {
1185            let result = session_executable_deploy_item(SessionStrParams {
1186                session_package_hash: PACKAGE_HASH,
1187                ..Default::default()
1188            });
1189
1190            assert!(matches!(
1191                result,
1192                Err(CliError::InvalidArgument {
1193                    context: "parse_session_info",
1194                    ..
1195                })
1196            ));
1197        }
1198
1199        #[test]
1200        fn session_package_name_should_fail_to_parse_missing_entry_point() {
1201            let result = session_executable_deploy_item(SessionStrParams {
1202                session_package_name: PACKAGE_NAME,
1203                ..Default::default()
1204            });
1205
1206            assert!(matches!(
1207                result,
1208                Err(CliError::InvalidArgument {
1209                    context: "parse_session_info",
1210                    ..
1211                })
1212            ));
1213        }
1214
1215        #[test]
1216        fn payment_name_should_fail_to_parse_missing_entry_point() {
1217            let result = payment_executable_deploy_item(PaymentStrParams {
1218                payment_name: NAME,
1219                ..Default::default()
1220            });
1221
1222            assert!(matches!(
1223                result,
1224                Err(CliError::InvalidArgument {
1225                    context: "parse_payment_info",
1226                    ..
1227                })
1228            ));
1229        }
1230
1231        #[test]
1232        fn payment_hash_should_fail_to_parse_missing_entry_point() {
1233            let result = payment_executable_deploy_item(PaymentStrParams {
1234                payment_hash: HASH,
1235                ..Default::default()
1236            });
1237
1238            assert!(matches!(
1239                result,
1240                Err(CliError::InvalidArgument {
1241                    context: "parse_payment_info",
1242                    ..
1243                })
1244            ));
1245        }
1246
1247        #[test]
1248        fn payment_package_hash_should_fail_to_parse_missing_entry_point() {
1249            let result = payment_executable_deploy_item(PaymentStrParams {
1250                payment_package_hash: PACKAGE_HASH,
1251                ..Default::default()
1252            });
1253
1254            assert!(matches!(
1255                result,
1256                Err(CliError::InvalidArgument {
1257                    context: "parse_payment_info",
1258                    ..
1259                })
1260            ));
1261        }
1262
1263        #[test]
1264        fn payment_package_name_should_fail_to_parse_missing_entry_point() {
1265            let result = payment_executable_deploy_item(PaymentStrParams {
1266                payment_package_name: PACKAGE_NAME,
1267                ..Default::default()
1268            });
1269
1270            assert!(matches!(
1271                result,
1272                Err(CliError::InvalidArgument {
1273                    context: "parse_payment_info",
1274                    ..
1275                })
1276            ));
1277        }
1278    }
1279
1280    mod conflicting_args {
1281        use super::*;
1282
1283        /// impl_test_matrix - implements many tests for SessionStrParams or PaymentStrParams which
1284        /// ensures that an error is returned when the permutation they define is executed.
1285        ///
1286        /// For instance, it is neccesary to check that when `session_path` is set, other arguments
1287        /// are not.
1288        ///
1289        /// For example, a sample invocation with one test:
1290        /// ```
1291        /// impl_test_matrix![
1292        ///     type: SessionStrParams,
1293        ///     context: "parse_session_info",
1294        ///     session_str_params[
1295        ///         test[
1296        ///             session_path => PATH,
1297        ///             conflict: session_package_hash => PACKAGE_HASH,
1298        ///             requires[],
1299        ///             path_conflicts_with_package_hash
1300        ///         ]
1301        ///     ]
1302        /// ];
1303        /// ```
1304        /// This generates the following test module (with the fn name passed), with one test per
1305        /// line in `session_str_params[]`:
1306        /// ```
1307        /// #[cfg(test)]
1308        /// mod session_str_params {
1309        ///     use super::*;
1310        ///
1311        ///     #[test]
1312        ///     fn path_conflicts_with_package_hash() {
1313        ///         let info: StdResult<ExecutableDeployItem, _> = SessionStrParams {
1314        ///                 session_path: PATH,
1315        ///                 session_package_hash: PACKAGE_HASH,
1316        ///                 ..Default::default()
1317        ///             }
1318        ///             .try_into();
1319        ///         let mut conflicting = vec![
1320        ///             format!("{}={}", "session_path", PATH),
1321        ///             format!("{}={}", "session_package_hash", PACKAGE_HASH),
1322        ///         ];
1323        ///         conflicting.sort();
1324        ///         assert!(matches!(
1325        ///             info,
1326        ///             Err(CliError::ConflictingArguments {
1327        ///                 context: "parse_session_info".to_string(),
1328        ///                 args: conflicting
1329        ///             }
1330        ///             ))
1331        ///         );
1332        ///     }
1333        /// }
1334        /// ```
1335        macro_rules! impl_test_matrix {
1336            (
1337                /// Struct for which to define the following tests. In our case, SessionStrParams or PaymentStrParams.
1338                type: $t:ident,
1339                /// Expected `context` field to be returned in the `CliError::ConflictingArguments{ context, .. }` field.
1340                context: $context:expr,
1341
1342                /// $module will be our module name.
1343                $module:ident [$(
1344                    // many tests can be defined
1345                    test[
1346                        /// The argument's ident to be tested, followed by it's value.
1347                        $arg:tt => $arg_value:expr,
1348                        /// The conflicting argument's ident to be tested, followed by it's value.
1349                        conflict: $con:tt => $con_value:expr,
1350                        /// A list of any additional fields required by the argument, and their values.
1351                        requires[$($req:tt => $req_value:expr),*],
1352                        /// fn name for the defined test.
1353                        $test_fn_name:ident
1354                    ]
1355                )+]
1356            ) => {
1357                #[cfg(test)]
1358                mod $module {
1359                    use super::*;
1360
1361                    $(
1362                        #[test]
1363                        fn $test_fn_name() {
1364                            let info: Result<ExecutableDeployItem, _> = $t {
1365                                $arg: $arg_value,
1366                                $con: $con_value,
1367                                $($req: $req_value,),*
1368                                ..Default::default()
1369                            }
1370                            .try_into();
1371                            let mut conflicting = vec![
1372                                format!("{}={}", stringify!($arg), $arg_value),
1373                                format!("{}={}", stringify!($con), $con_value),
1374                            ];
1375                            conflicting.sort();
1376                            let _context_string = $context.to_string();
1377                            assert!(matches!(
1378                                info,
1379                                Err(CliError::ConflictingArguments {
1380                                    context: _context_string,
1381                                    ..
1382                                }
1383                                ))
1384                            );
1385                        }
1386                    )+
1387                }
1388            };
1389        }
1390
1391        // NOTE: there's no need to test a conflicting argument in both directions, since they
1392        // amount to passing two fields to a structs constructor.
1393        // Where a reverse test like this is omitted, a comment should be left.
1394        impl_test_matrix![
1395            type: SessionStrParams,
1396            context: "parse_session_info",
1397            session_str_params[
1398
1399                // path
1400                test[session_path => PATH, conflict: session_package_hash => PACKAGE_HASH, requires[], path_conflicts_with_package_hash]
1401                test[session_path => PATH, conflict: session_package_name => PACKAGE_NAME, requires[], path_conflicts_with_package_name]
1402                test[session_path => PATH, conflict: session_hash =>         HASH,         requires[], path_conflicts_with_hash]
1403                test[session_path => PATH, conflict: session_name =>         HASH,         requires[], path_conflicts_with_name]
1404                test[session_path => PATH, conflict: session_version =>      VERSION,      requires[], path_conflicts_with_version]
1405                test[session_path => PATH, conflict: session_entry_point =>  ENTRY_POINT,  requires[], path_conflicts_with_entry_point]
1406                test[session_path => PATH, conflict: is_session_transfer =>  TRANSFER,     requires[], path_conflicts_with_transfer]
1407
1408                // name
1409                test[session_name => NAME, conflict: session_package_hash => PACKAGE_HASH, requires[session_entry_point => ENTRY_POINT], name_conflicts_with_package_hash]
1410                test[session_name => NAME, conflict: session_package_name => PACKAGE_NAME, requires[session_entry_point => ENTRY_POINT], name_conflicts_with_package_name]
1411                test[session_name => NAME, conflict: session_hash =>         HASH,         requires[session_entry_point => ENTRY_POINT], name_conflicts_with_hash]
1412                test[session_name => NAME, conflict: session_version =>      VERSION,      requires[session_entry_point => ENTRY_POINT], name_conflicts_with_version]
1413                test[session_name => NAME, conflict: is_session_transfer =>  TRANSFER,     requires[session_entry_point => ENTRY_POINT], name_conflicts_with_transfer]
1414
1415                // hash
1416                test[session_hash => HASH, conflict: session_package_hash => PACKAGE_HASH, requires[session_entry_point => ENTRY_POINT], hash_conflicts_with_package_hash]
1417                test[session_hash => HASH, conflict: session_package_name => PACKAGE_NAME, requires[session_entry_point => ENTRY_POINT], hash_conflicts_with_package_name]
1418                test[session_hash => HASH, conflict: session_version =>      VERSION,      requires[session_entry_point => ENTRY_POINT], hash_conflicts_with_version]
1419                test[session_hash => HASH, conflict: is_session_transfer =>  TRANSFER,     requires[session_entry_point => ENTRY_POINT], hash_conflicts_with_transfer]
1420                // name <-> hash is already checked
1421                // name <-> path is already checked
1422
1423                // package_name
1424                // package_name + session_version is optional and allowed
1425                test[session_package_name => PACKAGE_NAME, conflict: session_package_hash => PACKAGE_HASH, requires[session_entry_point => ENTRY_POINT], package_name_conflicts_with_package_hash]
1426                test[session_package_name => VERSION, conflict: is_session_transfer => TRANSFER, requires[session_entry_point => ENTRY_POINT], package_name_conflicts_with_transfer]
1427                // package_name <-> hash is already checked
1428                // package_name <-> name is already checked
1429                // package_name <-> path is already checked
1430
1431                // package_hash
1432                // package_hash + session_version is optional and allowed
1433                test[session_package_hash => PACKAGE_HASH, conflict: is_session_transfer => TRANSFER, requires[session_entry_point => ENTRY_POINT], package_hash_conflicts_with_transfer]
1434                // package_hash <-> package_name is already checked
1435                // package_hash <-> hash is already checked
1436                // package_hash <-> name is already checked
1437                // package_hash <-> path is already checked
1438
1439            ]
1440        ];
1441
1442        impl_test_matrix![
1443            type: PaymentStrParams,
1444            context: "parse_payment_info",
1445            payment_str_params[
1446
1447                // amount
1448                test[payment_amount => PATH, conflict: payment_package_hash => PACKAGE_HASH, requires[], amount_conflicts_with_package_hash]
1449                test[payment_amount => PATH, conflict: payment_package_name => PACKAGE_NAME, requires[], amount_conflicts_with_package_name]
1450                test[payment_amount => PATH, conflict: payment_hash =>         HASH,         requires[], amount_conflicts_with_hash]
1451                test[payment_amount => PATH, conflict: payment_name =>         HASH,         requires[], amount_conflicts_with_name]
1452                test[payment_amount => PATH, conflict: payment_version =>      VERSION,      requires[], amount_conflicts_with_version]
1453                test[payment_amount => PATH, conflict: payment_entry_point =>  ENTRY_POINT,  requires[], amount_conflicts_with_entry_point]
1454
1455                // path
1456                // amount <-> path is already checked
1457                test[payment_path => PATH, conflict: payment_package_hash => PACKAGE_HASH, requires[], path_conflicts_with_package_hash]
1458                test[payment_path => PATH, conflict: payment_package_name => PACKAGE_NAME, requires[], path_conflicts_with_package_name]
1459                test[payment_path => PATH, conflict: payment_hash =>         HASH,         requires[], path_conflicts_with_hash]
1460                test[payment_path => PATH, conflict: payment_name =>         HASH,         requires[], path_conflicts_with_name]
1461                test[payment_path => PATH, conflict: payment_version =>      VERSION,      requires[], path_conflicts_with_version]
1462                test[payment_path => PATH, conflict: payment_entry_point =>  ENTRY_POINT,  requires[], path_conflicts_with_entry_point]
1463
1464                // name
1465                // amount <-> path is already checked
1466                test[payment_name => NAME, conflict: payment_package_hash => PACKAGE_HASH, requires[payment_entry_point => ENTRY_POINT], name_conflicts_with_package_hash]
1467                test[payment_name => NAME, conflict: payment_package_name => PACKAGE_NAME, requires[payment_entry_point => ENTRY_POINT], name_conflicts_with_package_name]
1468                test[payment_name => NAME, conflict: payment_hash =>         HASH,         requires[payment_entry_point => ENTRY_POINT], name_conflicts_with_hash]
1469                test[payment_name => NAME, conflict: payment_version =>      VERSION,      requires[payment_entry_point => ENTRY_POINT], name_conflicts_with_version]
1470
1471                // hash
1472                // amount <-> hash is already checked
1473                test[payment_hash => HASH, conflict: payment_package_hash => PACKAGE_HASH, requires[payment_entry_point => ENTRY_POINT], hash_conflicts_with_package_hash]
1474                test[payment_hash => HASH, conflict: payment_package_name => PACKAGE_NAME, requires[payment_entry_point => ENTRY_POINT], hash_conflicts_with_package_name]
1475                test[payment_hash => HASH, conflict: payment_version =>      VERSION,      requires[payment_entry_point => ENTRY_POINT], hash_conflicts_with_version]
1476                // name <-> hash is already checked
1477                // name <-> path is already checked
1478
1479                // package_name
1480                // amount <-> package_name is already checked
1481                test[payment_package_name => PACKAGE_NAME, conflict: payment_package_hash => PACKAGE_HASH, requires[payment_entry_point => ENTRY_POINT], package_name_conflicts_with_package_hash]
1482                // package_name <-> hash is already checked
1483                // package_name <-> name is already checked
1484                // package_name <-> path is already checked
1485
1486                // package_hash
1487                // package_hash + session_version is optional and allowed
1488                // amount <-> package_hash is already checked
1489                // package_hash <-> package_name is already checked
1490                // package_hash <-> hash is already checked
1491                // package_hash <-> name is already checked
1492                // package_hash <-> path is already checked
1493            ]
1494        ];
1495    }
1496
1497    mod param_tests {
1498        use super::*;
1499
1500        const HASH: &str = "09dcee4b212cfd53642ab323fbef07dafafc6f945a80a00147f62910a915c4e6";
1501        const NAME: &str = "name";
1502        const PKG_NAME: &str = "pkg_name";
1503        const PKG_HASH: &str = "09dcee4b212cfd53642ab323fbef07dafafc6f945a80a00147f62910a915c4e6";
1504        const ENTRYPOINT: &str = "entrypoint";
1505        const VERSION: &str = "4";
1506
1507        fn args_simple() -> Vec<&'static str> {
1508            vec!["name_01:bool='false'", "name_02:u32='42'"]
1509        }
1510
1511        /// Sample data creation methods for PaymentStrParams
1512        mod session_params {
1513            use std::collections::BTreeMap;
1514
1515            use casper_types::CLValue;
1516
1517            use super::*;
1518
1519            #[test]
1520            pub fn with_hash() {
1521                let params: Result<ExecutableDeployItem, CliError> =
1522                    SessionStrParams::with_hash(HASH, ENTRYPOINT, args_simple(), "").try_into();
1523                match params {
1524                    Ok(item @ ExecutableDeployItem::StoredContractByHash { .. }) => {
1525                        let actual: BTreeMap<String, CLValue> = item.args().clone().into();
1526                        let mut expected = BTreeMap::new();
1527                        expected.insert("name_01".to_owned(), CLValue::from_t(false).unwrap());
1528                        expected.insert("name_02".to_owned(), CLValue::from_t(42u32).unwrap());
1529                        assert_eq!(actual, expected);
1530                    }
1531                    other => panic!("incorrect type parsed {:?}", other),
1532                }
1533            }
1534
1535            #[test]
1536            pub fn with_name() {
1537                let params: Result<ExecutableDeployItem, CliError> =
1538                    SessionStrParams::with_name(NAME, ENTRYPOINT, args_simple(), "").try_into();
1539                match params {
1540                    Ok(item @ ExecutableDeployItem::StoredContractByName { .. }) => {
1541                        let actual: BTreeMap<String, CLValue> = item.args().clone().into();
1542                        let mut expected = BTreeMap::new();
1543                        expected.insert("name_01".to_owned(), CLValue::from_t(false).unwrap());
1544                        expected.insert("name_02".to_owned(), CLValue::from_t(42u32).unwrap());
1545                        assert_eq!(actual, expected);
1546                    }
1547                    other => panic!("incorrect type parsed {:?}", other),
1548                }
1549            }
1550
1551            #[test]
1552            pub fn with_package_name() {
1553                let params: Result<ExecutableDeployItem, CliError> =
1554                    SessionStrParams::with_package_name(
1555                        PKG_NAME,
1556                        VERSION,
1557                        ENTRYPOINT,
1558                        args_simple(),
1559                        "",
1560                    )
1561                    .try_into();
1562                match params {
1563                    Ok(item @ ExecutableDeployItem::StoredVersionedContractByName { .. }) => {
1564                        let actual: BTreeMap<String, CLValue> = item.args().clone().into();
1565                        let mut expected = BTreeMap::new();
1566                        expected.insert("name_01".to_owned(), CLValue::from_t(false).unwrap());
1567                        expected.insert("name_02".to_owned(), CLValue::from_t(42u32).unwrap());
1568                        assert_eq!(actual, expected);
1569                    }
1570                    other => panic!("incorrect type parsed {:?}", other),
1571                }
1572            }
1573
1574            #[test]
1575            pub fn with_package_hash() {
1576                let params: Result<ExecutableDeployItem, CliError> =
1577                    SessionStrParams::with_package_hash(
1578                        PKG_HASH,
1579                        VERSION,
1580                        ENTRYPOINT,
1581                        args_simple(),
1582                        "",
1583                    )
1584                    .try_into();
1585                match params {
1586                    Ok(item @ ExecutableDeployItem::StoredVersionedContractByHash { .. }) => {
1587                        let actual: BTreeMap<String, CLValue> = item.args().clone().into();
1588                        let mut expected = BTreeMap::new();
1589                        expected.insert("name_01".to_owned(), CLValue::from_t(false).unwrap());
1590                        expected.insert("name_02".to_owned(), CLValue::from_t(42u32).unwrap());
1591                        assert_eq!(actual, expected);
1592                    }
1593                    other => panic!("incorrect type parsed {:?}", other),
1594                }
1595            }
1596        }
1597        /// Sample data creation methods for PaymentStrParams
1598        mod payment_params {
1599            use std::collections::BTreeMap;
1600
1601            use casper_types::{CLValue, U512};
1602
1603            use super::*;
1604
1605            #[test]
1606            pub fn with_amount() {
1607                let params: Result<ExecutableDeployItem, CliError> =
1608                    PaymentStrParams::with_amount("100").try_into();
1609                match params {
1610                    Ok(item @ ExecutableDeployItem::ModuleBytes { .. }) => {
1611                        let amount = CLValue::from_t(U512::from(100)).unwrap();
1612                        assert_eq!(item.args().get("amount"), Some(&amount));
1613                    }
1614                    other => panic!("incorrect type parsed {:?}", other),
1615                }
1616            }
1617
1618            #[test]
1619            pub fn with_hash() {
1620                let params: Result<ExecutableDeployItem, CliError> =
1621                    PaymentStrParams::with_hash(HASH, ENTRYPOINT, args_simple(), "").try_into();
1622                match params {
1623                    Ok(item @ ExecutableDeployItem::StoredContractByHash { .. }) => {
1624                        let actual: BTreeMap<String, CLValue> = item.args().clone().into();
1625                        let mut expected = BTreeMap::new();
1626                        expected.insert("name_01".to_owned(), CLValue::from_t(false).unwrap());
1627                        expected.insert("name_02".to_owned(), CLValue::from_t(42u32).unwrap());
1628                        assert_eq!(actual, expected);
1629                    }
1630                    other => panic!("incorrect type parsed {:?}", other),
1631                }
1632            }
1633
1634            #[test]
1635            pub fn with_name() {
1636                let params: Result<ExecutableDeployItem, CliError> =
1637                    PaymentStrParams::with_name(NAME, ENTRYPOINT, args_simple(), "").try_into();
1638                match params {
1639                    Ok(item @ ExecutableDeployItem::StoredContractByName { .. }) => {
1640                        let actual: BTreeMap<String, CLValue> = item.args().clone().into();
1641                        let mut expected = BTreeMap::new();
1642                        expected.insert("name_01".to_owned(), CLValue::from_t(false).unwrap());
1643                        expected.insert("name_02".to_owned(), CLValue::from_t(42u32).unwrap());
1644                        assert_eq!(actual, expected);
1645                    }
1646                    other => panic!("incorrect type parsed {:?}", other),
1647                }
1648            }
1649
1650            #[test]
1651            pub fn with_package_name() {
1652                let params: Result<ExecutableDeployItem, CliError> =
1653                    PaymentStrParams::with_package_name(
1654                        PKG_NAME,
1655                        VERSION,
1656                        ENTRYPOINT,
1657                        args_simple(),
1658                        "",
1659                    )
1660                    .try_into();
1661                match params {
1662                    Ok(item @ ExecutableDeployItem::StoredVersionedContractByName { .. }) => {
1663                        let actual: BTreeMap<String, CLValue> = item.args().clone().into();
1664                        let mut expected = BTreeMap::new();
1665                        expected.insert("name_01".to_owned(), CLValue::from_t(false).unwrap());
1666                        expected.insert("name_02".to_owned(), CLValue::from_t(42u32).unwrap());
1667                        assert_eq!(actual, expected);
1668                    }
1669                    other => panic!("incorrect type parsed {:?}", other),
1670                }
1671            }
1672
1673            #[test]
1674            pub fn with_package_hash() {
1675                let params: Result<ExecutableDeployItem, CliError> =
1676                    PaymentStrParams::with_package_hash(
1677                        PKG_HASH,
1678                        VERSION,
1679                        ENTRYPOINT,
1680                        args_simple(),
1681                        "",
1682                    )
1683                    .try_into();
1684                match params {
1685                    Ok(item @ ExecutableDeployItem::StoredVersionedContractByHash { .. }) => {
1686                        let actual: BTreeMap<String, CLValue> = item.args().clone().into();
1687                        let mut expected = BTreeMap::new();
1688                        expected.insert("name_01".to_owned(), CLValue::from_t(false).unwrap());
1689                        expected.insert("name_02".to_owned(), CLValue::from_t(42u32).unwrap());
1690                        assert_eq!(actual, expected);
1691                    }
1692                    other => panic!("incorrect type parsed {:?}", other),
1693                }
1694            }
1695        }
1696    }
1697
1698    mod account_identifier {
1699        use super::*;
1700
1701        #[test]
1702        pub fn should_parse_valid_account_hash() {
1703            let account_hash =
1704                "account-hash-c029c14904b870e64c1d443d428c606740e82f341bea0f8542ca6494cef1383e";
1705            let parsed = account_identifier(account_hash).unwrap();
1706            let expected = AccountHash::from_formatted_str(account_hash).unwrap();
1707            assert_eq!(parsed, AccountIdentifier::AccountHash(expected));
1708        }
1709
1710        #[test]
1711        pub fn should_parse_valid_public_key() {
1712            let public_key = "01567f0f205e83291312cd82988d66143d376cee7de904dd2605d3f4bbb69b3c80";
1713            let parsed = account_identifier(public_key).unwrap();
1714            let expected = PublicKey::from_hex(public_key).unwrap();
1715            assert_eq!(parsed, AccountIdentifier::PublicKey(expected));
1716        }
1717
1718        #[test]
1719        pub fn should_fail_to_parse_invalid_account_hash() {
1720            //This is the account hash from above with several characters removed
1721            let account_hash =
1722                "account-hash-c029c14904b870e1d443d428c606740e82f341bea0f8542ca6494cef1383e";
1723            let parsed = account_identifier(account_hash);
1724            assert!(parsed.is_err());
1725        }
1726
1727        #[test]
1728        pub fn should_fail_to_parse_invalid_public_key() {
1729            //This is the public key from above with several characters removed
1730            let public_key = "01567f0f205e83291312cd82988d66143d376cee7de904dd26054bbb69b3c80";
1731            let parsed = account_identifier(public_key);
1732            assert!(parsed.is_err());
1733        }
1734    }
1735
1736    mod entity_identifier {
1737        use super::*;
1738
1739        #[test]
1740        pub fn should_parse_valid_contract_entity_addr() {
1741            let entity_addr =
1742                "entity-contract-c029c14904b870e64c1d443d428c606740e82f341bea0f8542ca6494cef1383e";
1743            let parsed = entity_identifier(entity_addr).unwrap();
1744            assert_eq!(
1745                parsed,
1746                EntityIdentifier::EntityAddr(
1747                    EntityAddr::from_formatted_str(entity_addr).expect("should parse EntityAddr")
1748                )
1749            );
1750        }
1751
1752        #[test]
1753        pub fn should_parse_valid_account_entity_addr() {
1754            let entity_addr =
1755                "entity-account-c029c14904b870e64c1d443d428c606740e82f341bea0f8542ca6494cef1383e";
1756            let parsed = entity_identifier(entity_addr).unwrap();
1757            assert_eq!(
1758                parsed,
1759                EntityIdentifier::EntityAddr(
1760                    EntityAddr::from_formatted_str(entity_addr).expect("should parse EntityAddr")
1761                )
1762            );
1763        }
1764
1765        #[test]
1766        pub fn should_parse_valid_public_key() {
1767            let public_key = "01567f0f205e83291312cd82988d66143d376cee7de904dd2605d3f4bbb69b3c80";
1768            let parsed = entity_identifier(public_key).unwrap();
1769            let expected = PublicKey::from_hex(public_key).unwrap();
1770            assert_eq!(parsed, EntityIdentifier::PublicKey(expected));
1771        }
1772
1773        #[test]
1774        pub fn should_fail_to_parse_invalid_entity_hash() {
1775            //This is the account hash from above with several characters removed
1776            let entity_hash =
1777                "contract-addressable-entity-c029c14904b870e64c1d443d428c606740e82f341bea0f8542ca6494cef138";
1778            let parsed = entity_identifier(entity_hash);
1779            assert!(parsed.is_err());
1780        }
1781
1782        #[test]
1783        pub fn should_fail_to_parse_invalid_public_key() {
1784            //This is the public key from above with several characters removed
1785            let public_key = "01567f0f205e83291312cd82988d66143d376cee7de904dd26054bbb69b3c80";
1786            let parsed = entity_identifier(public_key);
1787            assert!(parsed.is_err());
1788        }
1789    }
1790
1791    mod era_identifier {
1792        use casper_types::EraId;
1793
1794        use super::*;
1795
1796        #[test]
1797        pub fn should_parse_valid_era_id() {
1798            let era_id = "123";
1799            let parsed = era_identifier(era_id).unwrap();
1800            assert!(
1801                matches!(parsed, Some(EraIdentifier::Era(id)) if id == EraId::new(123)),
1802                "{:?}",
1803                parsed
1804            );
1805        }
1806
1807        #[test]
1808        pub fn should_fail_to_parse_invalid_era_id() {
1809            let era_id = "invalid";
1810            let parsed = era_identifier(era_id);
1811            assert!(parsed.is_err());
1812        }
1813    }
1814
1815    mod public_key {
1816        use super::*;
1817
1818        #[test]
1819        pub fn should_parse_valid_public_key() {
1820            let str = "01567f0f205e83291312cd82988d66143d376cee7de904dd2605d3f4bbb69b3c80";
1821            let parsed = public_key(str).unwrap();
1822            let expected = PublicKey::from_hex(str).unwrap();
1823            assert_eq!(parsed, Some(expected));
1824        }
1825
1826        #[test]
1827        pub fn should_fail_to_parse_invalid_public_key() {
1828            //This is the public key from above with several characters removed
1829            let str = "01567f0f205e83291312cd82988d66143d376cee7de904dd26054bbb69b3c80";
1830            let parsed = public_key(str);
1831            assert!(parsed.is_err());
1832        }
1833    }
1834
1835    mod pricing_mode {
1836        use super::*;
1837
1838        const VALID_HASH: &str = "09dcee4b212cfd53642ab323fbef07dafafc6f945a80a00147f62910a915c4e6";
1839        #[test]
1840        fn should_parse_fixed_pricing_mode_identifier() {
1841            let pricing_mode_str = "fixed";
1842            let payment_amount = "";
1843            let gas_price_tolerance = "10";
1844            let additional_computation_factor = "1";
1845            let standard_payment = "";
1846            let parsed = pricing_mode(
1847                pricing_mode_str,
1848                payment_amount,
1849                gas_price_tolerance,
1850                additional_computation_factor,
1851                standard_payment,
1852                None,
1853            )
1854            .unwrap();
1855            assert_eq!(
1856                parsed,
1857                PricingMode::Fixed {
1858                    additional_computation_factor: 1,
1859                    gas_price_tolerance: 10,
1860                }
1861            );
1862        }
1863
1864        #[test]
1865        fn should_parse_fixed_pricing_mode_identifier_without_additional_computation_factor() {
1866            let pricing_mode_str = "fixed";
1867            let payment_amount = "";
1868            let gas_price_tolerance = "10";
1869            let additional_computation_factor = "";
1870            let standard_payment = "";
1871            let parsed = pricing_mode(
1872                pricing_mode_str,
1873                payment_amount,
1874                gas_price_tolerance,
1875                additional_computation_factor,
1876                standard_payment,
1877                None,
1878            )
1879            .unwrap();
1880            assert_eq!(
1881                parsed,
1882                PricingMode::Fixed {
1883                    additional_computation_factor: 0,
1884                    gas_price_tolerance: 10,
1885                }
1886            );
1887        }
1888
1889        #[test]
1890        fn should_parse_reserved_pricing_mode() {
1891            let pricing_mode_str = "reserved";
1892            let payment_amount = "";
1893            let gas_price_tolerance = "";
1894            let additional_computation_factor = "0";
1895            let standard_payment = "";
1896            let parsed = pricing_mode(
1897                pricing_mode_str,
1898                payment_amount,
1899                gas_price_tolerance,
1900                additional_computation_factor,
1901                standard_payment,
1902                Some(Digest::from_hex(VALID_HASH).unwrap()),
1903            )
1904            .unwrap();
1905            assert_eq!(
1906                parsed,
1907                PricingMode::Prepaid {
1908                    receipt: Digest::from_hex(VALID_HASH).unwrap(),
1909                }
1910            );
1911        }
1912        #[test]
1913        fn should_parse_classic_pricing_mode() {
1914            let pricing_mode_str = "classic";
1915            let payment_amount = "10";
1916            let standard_payment = "true";
1917            let gas_price_tolerance = "10";
1918            let additional_computation_factor = "0";
1919            let parsed = pricing_mode(
1920                pricing_mode_str,
1921                payment_amount,
1922                gas_price_tolerance,
1923                additional_computation_factor,
1924                standard_payment,
1925                None,
1926            )
1927            .unwrap();
1928            assert_eq!(
1929                parsed,
1930                PricingMode::PaymentLimited {
1931                    payment_amount: 10,
1932                    gas_price_tolerance: 10,
1933                    standard_payment: true,
1934                }
1935            );
1936        }
1937
1938        #[test]
1939        fn should_fail_to_parse_invalid_pricing_mode() {
1940            let pricing_mode_str = "invalid";
1941            let payment_amount = "10";
1942            let standard_payment = "true";
1943            let gas_price_tolerance = "10";
1944            let additional_computation_factor = "0";
1945            let parsed = pricing_mode(
1946                pricing_mode_str,
1947                payment_amount,
1948                gas_price_tolerance,
1949                additional_computation_factor,
1950                standard_payment,
1951                None,
1952            );
1953            assert!(parsed.is_err());
1954            assert!(matches!(parsed, Err(CliError::InvalidArgument { .. })));
1955        }
1956
1957        #[test]
1958        fn should_fail_to_parse_invalid_additional_computation_factor() {
1959            let pricing_mode_str = "fixed";
1960            let payment_amount = "10";
1961            let standard_payment = "true";
1962            let gas_price_tolerance = "10";
1963            let additional_computation_factor = "invalid";
1964            let parsed = pricing_mode(
1965                pricing_mode_str,
1966                payment_amount,
1967                gas_price_tolerance,
1968                additional_computation_factor,
1969                standard_payment,
1970                None,
1971            );
1972            assert!(parsed.is_err());
1973            assert!(matches!(parsed, Err(CliError::FailedToParseInt { .. })));
1974        }
1975
1976        #[test]
1977        fn should_fail_to_parse_classic_without_amount() {
1978            let pricing_mode_str = "classic";
1979            let payment_amount = "";
1980            let standard_payment = "true";
1981            let gas_price_tolerance = "10";
1982            let additional_computation_factor = "0";
1983            let parsed = pricing_mode(
1984                pricing_mode_str,
1985                payment_amount,
1986                gas_price_tolerance,
1987                additional_computation_factor,
1988                standard_payment,
1989                None,
1990            );
1991            assert!(parsed.is_err());
1992            assert!(matches!(parsed, Err(CliError::InvalidArgument { .. })));
1993        }
1994    }
1995    mod transaction_hash {
1996        use super::*;
1997        const VALID_HASH: &str = "09dcee4b212cfd53642ab323fbef07dafafc6f945a80a00147f62910a915c4e6";
1998        const INVALID_HASH: &str =
1999            "09dcee4b212cfd53642ab323fbef07dafafc6f945a80a00147f62910a915c4e";
2000        #[test]
2001        fn should_parse_transaction_hash() {
2002            let parsed = transaction_hash(VALID_HASH);
2003            assert!(parsed.is_ok());
2004            assert_eq!(
2005                parsed.unwrap(),
2006                TransactionHash::from(TransactionV1Hash::from(
2007                    Digest::from_hex(VALID_HASH).unwrap()
2008                ))
2009            );
2010        }
2011        #[test]
2012        fn should_fail_to_parse_incorrect_hash() {
2013            let parsed = transaction_hash(INVALID_HASH);
2014            assert!(parsed.is_err());
2015            assert!(matches!(
2016                parsed,
2017                Err(CliError::FailedToParseDigest {
2018                    context: "failed to parse digest from string for transaction hash",
2019                    ..
2020                })
2021            ));
2022        }
2023    }
2024}