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    match block_identifier(maybe_block_id)? {
737        Some(BlockIdentifier::Hash(hash)) => {
738            return Ok(Some(GlobalStateIdentifier::BlockHash(hash)))
739        }
740        Some(BlockIdentifier::Height(height)) => {
741            return Ok(Some(GlobalStateIdentifier::BlockHeight(height)))
742        }
743        None => (),
744    }
745
746    if maybe_state_root_hash.is_empty() {
747        return Ok(None);
748    }
749
750    let state_root_hash =
751        Digest::from_hex(maybe_state_root_hash).map_err(|error| CliError::FailedToParseDigest {
752            context: "state root hash in global_state_identifier",
753            error,
754        })?;
755    Ok(Some(GlobalStateIdentifier::StateRootHash(state_root_hash)))
756}
757
758/// `purse_id` can be a formatted public key, account hash, or URef.  It may not be empty.
759pub fn purse_identifier(purse_id: &str) -> Result<PurseIdentifier, CliError> {
760    const ACCOUNT_HASH_PREFIX: &str = "account-hash-";
761    const UREF_PREFIX: &str = "uref-";
762    const ENTITY_PREFIX: &str = "entity-";
763
764    if purse_id.is_empty() {
765        return Err(CliError::InvalidArgument {
766            context: "purse_identifier",
767            error: "cannot be empty string".to_string(),
768        });
769    }
770
771    if purse_id.starts_with(ACCOUNT_HASH_PREFIX) {
772        let account_hash = AccountHash::from_formatted_str(purse_id).map_err(|error| {
773            CliError::FailedToParseAccountHash {
774                context: "purse_identifier",
775                error,
776            }
777        })?;
778        return Ok(PurseIdentifier::MainPurseUnderAccountHash(account_hash));
779    }
780
781    if purse_id.starts_with(ENTITY_PREFIX) {
782        let entity_addr = EntityAddr::from_formatted_str(purse_id).map_err(|error| {
783            CliError::FailedToParseAddressableEntityHash {
784                context: "purse_identifier",
785                error,
786            }
787        })?;
788        return Ok(PurseIdentifier::MainPurseUnderEntityAddr(entity_addr));
789    }
790
791    if purse_id.starts_with(UREF_PREFIX) {
792        let uref =
793            URef::from_formatted_str(purse_id).map_err(|error| CliError::FailedToParseURef {
794                context: "purse_identifier",
795                error,
796            })?;
797        return Ok(PurseIdentifier::PurseUref(uref));
798    }
799
800    let public_key =
801        PublicKey::from_hex(purse_id).map_err(|error| CliError::FailedToParsePublicKey {
802            context: "purse_identifier".to_string(),
803            error,
804        })?;
805    Ok(PurseIdentifier::MainPurseUnderPublicKey(public_key))
806}
807
808/// `account_identifier` can be a formatted public key, in the form of a hex-formatted string,
809/// a pem file, or a file containing a hex formatted string, or a formatted string representing
810/// an account hash.  It may not be empty.
811pub fn account_identifier(account_identifier: &str) -> Result<AccountIdentifier, CliError> {
812    const ACCOUNT_HASH_PREFIX: &str = "account-hash-";
813
814    if account_identifier.is_empty() {
815        return Err(CliError::InvalidArgument {
816            context: "account_identifier",
817            error: "cannot be empty string".to_string(),
818        });
819    }
820
821    if account_identifier.starts_with(ACCOUNT_HASH_PREFIX) {
822        let account_hash =
823            AccountHash::from_formatted_str(account_identifier).map_err(|error| {
824                CliError::FailedToParseAccountHash {
825                    context: "account_identifier",
826                    error,
827                }
828            })?;
829        return Ok(AccountIdentifier::AccountHash(account_hash));
830    }
831
832    let public_key = PublicKey::from_hex(account_identifier).map_err(|error| {
833        CliError::FailedToParsePublicKey {
834            context: "account_identifier".to_string(),
835            error,
836        }
837    })?;
838    Ok(AccountIdentifier::PublicKey(public_key))
839}
840
841/// `entity_identifier` can be a formatted public key, in the form of a hex-formatted string,
842/// a pem file, or a file containing a hex formatted string, or a formatted string representing
843/// an account hash.  It may not be empty.
844pub fn entity_identifier(entity_identifier: &str) -> Result<EntityIdentifier, CliError> {
845    const ENTITY_PREFIX: &str = "entity-";
846    const ACCOUNT_HASH_PREFIX: &str = "account-hash-";
847
848    if entity_identifier.is_empty() {
849        return Err(CliError::InvalidArgument {
850            context: "entity_identifier",
851            error: "cannot be empty string".to_string(),
852        });
853    }
854
855    if entity_identifier.starts_with(ACCOUNT_HASH_PREFIX) {
856        let account_hash = AccountHash::from_formatted_str(entity_identifier).map_err(|error| {
857            CliError::FailedToParseAccountHash {
858                context: "entity_identifier",
859                error,
860            }
861        })?;
862        return Ok(EntityIdentifier::AccountHash(account_hash));
863    }
864    if entity_identifier.starts_with(ENTITY_PREFIX) {
865        let entity_addr = EntityAddr::from_formatted_str(entity_identifier).map_err(|error| {
866            CliError::FailedToParseAddressableEntityHash {
867                context: "entity_identifier",
868                error,
869            }
870        })?;
871        return Ok(EntityIdentifier::EntityAddr(entity_addr));
872    }
873
874    let public_key = PublicKey::from_hex(entity_identifier).map_err(|error| {
875        CliError::FailedToParsePublicKey {
876            context: "entity_identifier".to_string(),
877            error,
878        }
879    })?;
880    Ok(EntityIdentifier::PublicKey(public_key))
881}
882
883/// `era_identifier` must be an integer representing the era ID.
884pub(super) fn era_identifier(era_identifier: &str) -> Result<Option<EraIdentifier>, CliError> {
885    if era_identifier.is_empty() {
886        return Ok(None);
887    }
888    let era_id = era_identifier
889        .parse()
890        .map_err(|error| CliError::FailedToParseInt {
891            context: "era_identifier",
892            error,
893        })?;
894    Ok(Some(EraIdentifier::Era(era_id)))
895}
896
897/// `public_key` must be a public key formatted as a hex-encoded string,
898pub(super) fn public_key(public_key: &str) -> Result<Option<PublicKey>, CliError> {
899    if public_key.is_empty() {
900        return Ok(None);
901    }
902    let key =
903        PublicKey::from_hex(public_key).map_err(|error| CliError::FailedToParsePublicKey {
904            context: "public_key".to_owned(),
905            error,
906        })?;
907    Ok(Some(key))
908}
909
910pub(super) fn pricing_mode(
911    pricing_mode_identifier_str: &str,
912    payment_amount_str: &str,
913    gas_price_tolerance_str: &str,
914    additional_computation_factor_str: &str,
915    standard_payment_str: &str,
916    maybe_receipt: Option<Digest>,
917) -> Result<PricingMode, CliError> {
918    match pricing_mode_identifier_str.to_lowercase().as_str() {
919        "classic" => {
920            if gas_price_tolerance_str.is_empty() {
921                return Err(CliError::InvalidArgument {
922                    context: "gas_price_tolerance",
923                    error: "Gas price tolerance is required".to_string(),
924                });
925            }
926            if payment_amount_str.is_empty() {
927                return Err(CliError::InvalidArgument {
928                    context: "payment_amount",
929                    error: "Payment amount is required".to_string(),
930                });
931            }
932            if standard_payment_str.is_empty() {
933                return Err(CliError::InvalidArgument {
934                    context: "standard_payment",
935                    error: "Standard payment flag is required".to_string(),
936                });
937            }
938            let gas_price_tolerance = gas_price_tolerance_str.parse::<u8>().map_err(|error| {
939                CliError::FailedToParseInt {
940                    context: "gas_price_tolerance",
941                    error,
942                }
943            })?;
944            let payment_amount =
945                payment_amount_str
946                    .parse::<u64>()
947                    .map_err(|error| CliError::FailedToParseInt {
948                        context: "payment_amount",
949                        error,
950                    })?;
951            let standard_payment = standard_payment_str.parse::<bool>().map_err(|error| {
952                CliError::FailedToParseBool {
953                    context: "standard_payment",
954                    error,
955                }
956            })?;
957            Ok(PricingMode::PaymentLimited {
958                payment_amount,
959                gas_price_tolerance,
960                standard_payment,
961            })
962        }
963        "fixed" => {
964            if gas_price_tolerance_str.is_empty() {
965                return Err(CliError::InvalidArgument {
966                    context: "gas_price_tolerance",
967                    error: "Gas price tolerance is required".to_string(),
968                });
969            }
970            let gas_price_tolerance = gas_price_tolerance_str.parse::<u8>().map_err(|error| {
971                CliError::FailedToParseInt {
972                    context: "gas_price_tolerance",
973                    error,
974                }
975            })?;
976
977            // Additional Computation Factor defaults to 0 if the string is empty
978            let additional_computation_factor = if additional_computation_factor_str.is_empty() {
979                u8::default()
980            } else {
981                additional_computation_factor_str
982                    .parse::<u8>()
983                    .map_err(|error| CliError::FailedToParseInt {
984                        context: "additional_computation_factor",
985                        error,
986                    })?
987            };
988            Ok(PricingMode::Fixed {
989                gas_price_tolerance,
990                additional_computation_factor,
991            })
992        }
993        "reserved" => {
994            if maybe_receipt.is_none() {
995                return Err(CliError::InvalidArgument {
996                    context: "receipt",
997                    error: "Receipt is required for reserved pricing mode".to_string(),
998                });
999            }
1000            Ok(PricingMode::Prepaid {
1001                receipt: maybe_receipt.unwrap_or_default(),
1002            })
1003        }
1004        _ => Err(CliError::InvalidArgument {
1005            context: "pricing_mode",
1006            error: "Invalid pricing mode identifier".to_string(),
1007        }),
1008    }
1009}
1010
1011pub(super) fn transaction_hash(transaction_hash: &str) -> Result<TransactionHash, CliError> {
1012    let digest =
1013        Digest::from_hex(transaction_hash).map_err(|error| CliError::FailedToParseDigest {
1014            context: "failed to parse digest from string for transaction hash",
1015            error,
1016        })?;
1017    Ok(TransactionHash::from(TransactionV1Hash::from(digest)))
1018}
1019
1020#[cfg(test)]
1021mod tests {
1022    use std::convert::TryFrom;
1023
1024    use super::*;
1025
1026    const HASH: &str = "09dcee4b212cfd53642ab323fbef07dafafc6f945a80a00147f62910a915c4e6";
1027    const NAME: &str = "name";
1028    const PACKAGE_HASH: &str = "09dcee4b212cfd53642ab323fbef07dafafc6f945a80a00147f62910a915c4e6";
1029    const PACKAGE_NAME: &str = "package_name";
1030    const PATH: &str = "./session.wasm";
1031    const ENTRY_POINT: &str = "entrypoint";
1032    const VERSION: &str = "3";
1033    const TRANSFER: bool = true;
1034
1035    impl<'a> TryFrom<SessionStrParams<'a>> for ExecutableDeployItem {
1036        type Error = CliError;
1037
1038        fn try_from(params: SessionStrParams<'a>) -> Result<ExecutableDeployItem, Self::Error> {
1039            session_executable_deploy_item(params)
1040        }
1041    }
1042
1043    impl<'a> TryFrom<PaymentStrParams<'a>> for ExecutableDeployItem {
1044        type Error = CliError;
1045
1046        fn try_from(params: PaymentStrParams<'a>) -> Result<ExecutableDeployItem, Self::Error> {
1047            payment_executable_deploy_item(params)
1048        }
1049    }
1050
1051    #[test]
1052    fn should_fail_to_parse_conflicting_arg_types() {
1053        let test_context = "parse_session_info args conflict (simple json)".to_string();
1054        let actual_error = session_executable_deploy_item(SessionStrParams {
1055            session_hash: "",
1056            session_name: "name",
1057            session_package_hash: "",
1058            session_package_name: "",
1059            session_path: "",
1060            session_bytes: Bytes::new(),
1061            session_args_simple: vec!["something:u32='0'"],
1062            session_args_json: "{\"name\":\"entry_point_name\",\"type\":\"Bool\",\"value\":false}",
1063            session_version: "",
1064            session_entry_point: "entrypoint",
1065            is_session_transfer: false,
1066            session_chunked_args: None,
1067        })
1068        .unwrap_err();
1069
1070        assert!(
1071            matches!(actual_error, CliError::ConflictingArguments { ref context, .. } if *context == test_context),
1072            "{:?}",
1073            actual_error
1074        );
1075
1076        let test_context = "parse_payment_info args conflict (simple json)";
1077        let actual_error = payment_executable_deploy_item(PaymentStrParams {
1078            payment_amount: "",
1079            payment_hash: "name",
1080            payment_name: "",
1081            payment_package_hash: "",
1082            payment_package_name: "",
1083            payment_path: "",
1084            payment_bytes: Bytes::new(),
1085            payment_args_simple: vec!["something:u32='0'"],
1086            payment_args_json: "{\"name\":\"entry_point_name\",\"type\":\"Bool\",\"value\":false}",
1087            payment_version: "",
1088            payment_entry_point: "entrypoint",
1089        })
1090        .unwrap_err();
1091        assert!(
1092            matches!(
1093                actual_error,
1094                CliError::ConflictingArguments { ref context, .. } if context == test_context
1095            ),
1096            "{:?}",
1097            actual_error
1098        );
1099    }
1100
1101    #[test]
1102    fn should_fail_to_parse_conflicting_session_parameters() {
1103        let test_context = String::from("parse_session_info");
1104        assert!(matches!(
1105            session_executable_deploy_item(SessionStrParams {
1106                session_hash: HASH,
1107                session_name: NAME,
1108                session_package_hash: PACKAGE_HASH,
1109                session_package_name: PACKAGE_NAME,
1110                session_path: PATH,
1111                session_bytes: Bytes::new(),
1112                session_args_simple: vec![],
1113                session_args_json: "",
1114                session_version: "",
1115                session_entry_point: "",
1116                is_session_transfer: false,
1117                session_chunked_args: None,
1118            }),
1119            Err(CliError::ConflictingArguments { context, .. }) if context == test_context
1120        ));
1121    }
1122
1123    #[test]
1124    fn should_fail_to_parse_conflicting_payment_parameters() {
1125        let test_context = String::from("parse_payment_info");
1126        assert!(matches!(
1127            payment_executable_deploy_item(PaymentStrParams {
1128                payment_amount: "12345",
1129                payment_hash: HASH,
1130                payment_name: NAME,
1131                payment_package_hash: PACKAGE_HASH,
1132                payment_package_name: PACKAGE_NAME,
1133                payment_path: PATH,
1134                payment_bytes: Bytes::new(),
1135                payment_args_simple: vec![],
1136                payment_args_json: "",
1137                payment_version: "",
1138                payment_entry_point: "",
1139            }),
1140            Err(CliError::ConflictingArguments { context, .. }) if context == test_context
1141        ));
1142    }
1143
1144    mod missing_args {
1145        use super::*;
1146
1147        #[test]
1148        fn session_name_should_fail_to_parse_missing_entry_point() {
1149            let result = session_executable_deploy_item(SessionStrParams {
1150                session_name: NAME,
1151                ..Default::default()
1152            });
1153
1154            assert!(matches!(
1155                result,
1156                Err(CliError::InvalidArgument {
1157                    context: "parse_session_info",
1158                    ..
1159                })
1160            ));
1161        }
1162
1163        #[test]
1164        fn session_hash_should_fail_to_parse_missing_entry_point() {
1165            let result = session_executable_deploy_item(SessionStrParams {
1166                session_hash: HASH,
1167                ..Default::default()
1168            });
1169
1170            assert!(matches!(
1171                result,
1172                Err(CliError::InvalidArgument {
1173                    context: "parse_session_info",
1174                    ..
1175                })
1176            ));
1177        }
1178
1179        #[test]
1180        fn session_package_hash_should_fail_to_parse_missing_entry_point() {
1181            let result = session_executable_deploy_item(SessionStrParams {
1182                session_package_hash: PACKAGE_HASH,
1183                ..Default::default()
1184            });
1185
1186            assert!(matches!(
1187                result,
1188                Err(CliError::InvalidArgument {
1189                    context: "parse_session_info",
1190                    ..
1191                })
1192            ));
1193        }
1194
1195        #[test]
1196        fn session_package_name_should_fail_to_parse_missing_entry_point() {
1197            let result = session_executable_deploy_item(SessionStrParams {
1198                session_package_name: PACKAGE_NAME,
1199                ..Default::default()
1200            });
1201
1202            assert!(matches!(
1203                result,
1204                Err(CliError::InvalidArgument {
1205                    context: "parse_session_info",
1206                    ..
1207                })
1208            ));
1209        }
1210
1211        #[test]
1212        fn payment_name_should_fail_to_parse_missing_entry_point() {
1213            let result = payment_executable_deploy_item(PaymentStrParams {
1214                payment_name: NAME,
1215                ..Default::default()
1216            });
1217
1218            assert!(matches!(
1219                result,
1220                Err(CliError::InvalidArgument {
1221                    context: "parse_payment_info",
1222                    ..
1223                })
1224            ));
1225        }
1226
1227        #[test]
1228        fn payment_hash_should_fail_to_parse_missing_entry_point() {
1229            let result = payment_executable_deploy_item(PaymentStrParams {
1230                payment_hash: HASH,
1231                ..Default::default()
1232            });
1233
1234            assert!(matches!(
1235                result,
1236                Err(CliError::InvalidArgument {
1237                    context: "parse_payment_info",
1238                    ..
1239                })
1240            ));
1241        }
1242
1243        #[test]
1244        fn payment_package_hash_should_fail_to_parse_missing_entry_point() {
1245            let result = payment_executable_deploy_item(PaymentStrParams {
1246                payment_package_hash: PACKAGE_HASH,
1247                ..Default::default()
1248            });
1249
1250            assert!(matches!(
1251                result,
1252                Err(CliError::InvalidArgument {
1253                    context: "parse_payment_info",
1254                    ..
1255                })
1256            ));
1257        }
1258
1259        #[test]
1260        fn payment_package_name_should_fail_to_parse_missing_entry_point() {
1261            let result = payment_executable_deploy_item(PaymentStrParams {
1262                payment_package_name: PACKAGE_NAME,
1263                ..Default::default()
1264            });
1265
1266            assert!(matches!(
1267                result,
1268                Err(CliError::InvalidArgument {
1269                    context: "parse_payment_info",
1270                    ..
1271                })
1272            ));
1273        }
1274    }
1275
1276    mod conflicting_args {
1277        use super::*;
1278
1279        /// impl_test_matrix - implements many tests for SessionStrParams or PaymentStrParams which
1280        /// ensures that an error is returned when the permutation they define is executed.
1281        ///
1282        /// For instance, it is neccesary to check that when `session_path` is set, other arguments
1283        /// are not.
1284        ///
1285        /// For example, a sample invocation with one test:
1286        /// ```
1287        /// impl_test_matrix![
1288        ///     type: SessionStrParams,
1289        ///     context: "parse_session_info",
1290        ///     session_str_params[
1291        ///         test[
1292        ///             session_path => PATH,
1293        ///             conflict: session_package_hash => PACKAGE_HASH,
1294        ///             requires[],
1295        ///             path_conflicts_with_package_hash
1296        ///         ]
1297        ///     ]
1298        /// ];
1299        /// ```
1300        /// This generates the following test module (with the fn name passed), with one test per
1301        /// line in `session_str_params[]`:
1302        /// ```
1303        /// #[cfg(test)]
1304        /// mod session_str_params {
1305        ///     use super::*;
1306        ///
1307        ///     #[test]
1308        ///     fn path_conflicts_with_package_hash() {
1309        ///         let info: StdResult<ExecutableDeployItem, _> = SessionStrParams {
1310        ///                 session_path: PATH,
1311        ///                 session_package_hash: PACKAGE_HASH,
1312        ///                 ..Default::default()
1313        ///             }
1314        ///             .try_into();
1315        ///         let mut conflicting = vec![
1316        ///             format!("{}={}", "session_path", PATH),
1317        ///             format!("{}={}", "session_package_hash", PACKAGE_HASH),
1318        ///         ];
1319        ///         conflicting.sort();
1320        ///         assert!(matches!(
1321        ///             info,
1322        ///             Err(CliError::ConflictingArguments {
1323        ///                 context: "parse_session_info".to_string(),
1324        ///                 args: conflicting
1325        ///             }
1326        ///             ))
1327        ///         );
1328        ///     }
1329        /// }
1330        /// ```
1331        macro_rules! impl_test_matrix {
1332            (
1333                /// Struct for which to define the following tests. In our case, SessionStrParams or PaymentStrParams.
1334                type: $t:ident,
1335                /// Expected `context` field to be returned in the `CliError::ConflictingArguments{ context, .. }` field.
1336                context: $context:expr,
1337
1338                /// $module will be our module name.
1339                $module:ident [$(
1340                    // many tests can be defined
1341                    test[
1342                        /// The argument's ident to be tested, followed by it's value.
1343                        $arg:tt => $arg_value:expr,
1344                        /// The conflicting argument's ident to be tested, followed by it's value.
1345                        conflict: $con:tt => $con_value:expr,
1346                        /// A list of any additional fields required by the argument, and their values.
1347                        requires[$($req:tt => $req_value:expr),*],
1348                        /// fn name for the defined test.
1349                        $test_fn_name:ident
1350                    ]
1351                )+]
1352            ) => {
1353                #[cfg(test)]
1354                mod $module {
1355                    use super::*;
1356
1357                    $(
1358                        #[test]
1359                        fn $test_fn_name() {
1360                            let info: Result<ExecutableDeployItem, _> = $t {
1361                                $arg: $arg_value,
1362                                $con: $con_value,
1363                                $($req: $req_value,),*
1364                                ..Default::default()
1365                            }
1366                            .try_into();
1367                            let mut conflicting = vec![
1368                                format!("{}={}", stringify!($arg), $arg_value),
1369                                format!("{}={}", stringify!($con), $con_value),
1370                            ];
1371                            conflicting.sort();
1372                            let _context_string = $context.to_string();
1373                            assert!(matches!(
1374                                info,
1375                                Err(CliError::ConflictingArguments {
1376                                    context: _context_string,
1377                                    ..
1378                                }
1379                                ))
1380                            );
1381                        }
1382                    )+
1383                }
1384            };
1385        }
1386
1387        // NOTE: there's no need to test a conflicting argument in both directions, since they
1388        // amount to passing two fields to a structs constructor.
1389        // Where a reverse test like this is omitted, a comment should be left.
1390        impl_test_matrix![
1391            type: SessionStrParams,
1392            context: "parse_session_info",
1393            session_str_params[
1394
1395                // path
1396                test[session_path => PATH, conflict: session_package_hash => PACKAGE_HASH, requires[], path_conflicts_with_package_hash]
1397                test[session_path => PATH, conflict: session_package_name => PACKAGE_NAME, requires[], path_conflicts_with_package_name]
1398                test[session_path => PATH, conflict: session_hash =>         HASH,         requires[], path_conflicts_with_hash]
1399                test[session_path => PATH, conflict: session_name =>         HASH,         requires[], path_conflicts_with_name]
1400                test[session_path => PATH, conflict: session_version =>      VERSION,      requires[], path_conflicts_with_version]
1401                test[session_path => PATH, conflict: session_entry_point =>  ENTRY_POINT,  requires[], path_conflicts_with_entry_point]
1402                test[session_path => PATH, conflict: is_session_transfer =>  TRANSFER,     requires[], path_conflicts_with_transfer]
1403
1404                // name
1405                test[session_name => NAME, conflict: session_package_hash => PACKAGE_HASH, requires[session_entry_point => ENTRY_POINT], name_conflicts_with_package_hash]
1406                test[session_name => NAME, conflict: session_package_name => PACKAGE_NAME, requires[session_entry_point => ENTRY_POINT], name_conflicts_with_package_name]
1407                test[session_name => NAME, conflict: session_hash =>         HASH,         requires[session_entry_point => ENTRY_POINT], name_conflicts_with_hash]
1408                test[session_name => NAME, conflict: session_version =>      VERSION,      requires[session_entry_point => ENTRY_POINT], name_conflicts_with_version]
1409                test[session_name => NAME, conflict: is_session_transfer =>  TRANSFER,     requires[session_entry_point => ENTRY_POINT], name_conflicts_with_transfer]
1410
1411                // hash
1412                test[session_hash => HASH, conflict: session_package_hash => PACKAGE_HASH, requires[session_entry_point => ENTRY_POINT], hash_conflicts_with_package_hash]
1413                test[session_hash => HASH, conflict: session_package_name => PACKAGE_NAME, requires[session_entry_point => ENTRY_POINT], hash_conflicts_with_package_name]
1414                test[session_hash => HASH, conflict: session_version =>      VERSION,      requires[session_entry_point => ENTRY_POINT], hash_conflicts_with_version]
1415                test[session_hash => HASH, conflict: is_session_transfer =>  TRANSFER,     requires[session_entry_point => ENTRY_POINT], hash_conflicts_with_transfer]
1416                // name <-> hash is already checked
1417                // name <-> path is already checked
1418
1419                // package_name
1420                // package_name + session_version is optional and allowed
1421                test[session_package_name => PACKAGE_NAME, conflict: session_package_hash => PACKAGE_HASH, requires[session_entry_point => ENTRY_POINT], package_name_conflicts_with_package_hash]
1422                test[session_package_name => VERSION, conflict: is_session_transfer => TRANSFER, requires[session_entry_point => ENTRY_POINT], package_name_conflicts_with_transfer]
1423                // package_name <-> hash is already checked
1424                // package_name <-> name is already checked
1425                // package_name <-> path is already checked
1426
1427                // package_hash
1428                // package_hash + session_version is optional and allowed
1429                test[session_package_hash => PACKAGE_HASH, conflict: is_session_transfer => TRANSFER, requires[session_entry_point => ENTRY_POINT], package_hash_conflicts_with_transfer]
1430                // package_hash <-> package_name is already checked
1431                // package_hash <-> hash is already checked
1432                // package_hash <-> name is already checked
1433                // package_hash <-> path is already checked
1434
1435            ]
1436        ];
1437
1438        impl_test_matrix![
1439            type: PaymentStrParams,
1440            context: "parse_payment_info",
1441            payment_str_params[
1442
1443                // amount
1444                test[payment_amount => PATH, conflict: payment_package_hash => PACKAGE_HASH, requires[], amount_conflicts_with_package_hash]
1445                test[payment_amount => PATH, conflict: payment_package_name => PACKAGE_NAME, requires[], amount_conflicts_with_package_name]
1446                test[payment_amount => PATH, conflict: payment_hash =>         HASH,         requires[], amount_conflicts_with_hash]
1447                test[payment_amount => PATH, conflict: payment_name =>         HASH,         requires[], amount_conflicts_with_name]
1448                test[payment_amount => PATH, conflict: payment_version =>      VERSION,      requires[], amount_conflicts_with_version]
1449                test[payment_amount => PATH, conflict: payment_entry_point =>  ENTRY_POINT,  requires[], amount_conflicts_with_entry_point]
1450
1451                // path
1452                // amount <-> path is already checked
1453                test[payment_path => PATH, conflict: payment_package_hash => PACKAGE_HASH, requires[], path_conflicts_with_package_hash]
1454                test[payment_path => PATH, conflict: payment_package_name => PACKAGE_NAME, requires[], path_conflicts_with_package_name]
1455                test[payment_path => PATH, conflict: payment_hash =>         HASH,         requires[], path_conflicts_with_hash]
1456                test[payment_path => PATH, conflict: payment_name =>         HASH,         requires[], path_conflicts_with_name]
1457                test[payment_path => PATH, conflict: payment_version =>      VERSION,      requires[], path_conflicts_with_version]
1458                test[payment_path => PATH, conflict: payment_entry_point =>  ENTRY_POINT,  requires[], path_conflicts_with_entry_point]
1459
1460                // name
1461                // amount <-> path is already checked
1462                test[payment_name => NAME, conflict: payment_package_hash => PACKAGE_HASH, requires[payment_entry_point => ENTRY_POINT], name_conflicts_with_package_hash]
1463                test[payment_name => NAME, conflict: payment_package_name => PACKAGE_NAME, requires[payment_entry_point => ENTRY_POINT], name_conflicts_with_package_name]
1464                test[payment_name => NAME, conflict: payment_hash =>         HASH,         requires[payment_entry_point => ENTRY_POINT], name_conflicts_with_hash]
1465                test[payment_name => NAME, conflict: payment_version =>      VERSION,      requires[payment_entry_point => ENTRY_POINT], name_conflicts_with_version]
1466
1467                // hash
1468                // amount <-> hash is already checked
1469                test[payment_hash => HASH, conflict: payment_package_hash => PACKAGE_HASH, requires[payment_entry_point => ENTRY_POINT], hash_conflicts_with_package_hash]
1470                test[payment_hash => HASH, conflict: payment_package_name => PACKAGE_NAME, requires[payment_entry_point => ENTRY_POINT], hash_conflicts_with_package_name]
1471                test[payment_hash => HASH, conflict: payment_version =>      VERSION,      requires[payment_entry_point => ENTRY_POINT], hash_conflicts_with_version]
1472                // name <-> hash is already checked
1473                // name <-> path is already checked
1474
1475                // package_name
1476                // amount <-> package_name is already checked
1477                test[payment_package_name => PACKAGE_NAME, conflict: payment_package_hash => PACKAGE_HASH, requires[payment_entry_point => ENTRY_POINT], package_name_conflicts_with_package_hash]
1478                // package_name <-> hash is already checked
1479                // package_name <-> name is already checked
1480                // package_name <-> path is already checked
1481
1482                // package_hash
1483                // package_hash + session_version is optional and allowed
1484                // amount <-> package_hash is already checked
1485                // package_hash <-> package_name is already checked
1486                // package_hash <-> hash is already checked
1487                // package_hash <-> name is already checked
1488                // package_hash <-> path is already checked
1489            ]
1490        ];
1491    }
1492
1493    mod param_tests {
1494        use super::*;
1495
1496        const HASH: &str = "09dcee4b212cfd53642ab323fbef07dafafc6f945a80a00147f62910a915c4e6";
1497        const NAME: &str = "name";
1498        const PKG_NAME: &str = "pkg_name";
1499        const PKG_HASH: &str = "09dcee4b212cfd53642ab323fbef07dafafc6f945a80a00147f62910a915c4e6";
1500        const ENTRYPOINT: &str = "entrypoint";
1501        const VERSION: &str = "4";
1502
1503        fn args_simple() -> Vec<&'static str> {
1504            vec!["name_01:bool='false'", "name_02:u32='42'"]
1505        }
1506
1507        /// Sample data creation methods for PaymentStrParams
1508        mod session_params {
1509            use std::collections::BTreeMap;
1510
1511            use casper_types::CLValue;
1512
1513            use super::*;
1514
1515            #[test]
1516            pub fn with_hash() {
1517                let params: Result<ExecutableDeployItem, CliError> =
1518                    SessionStrParams::with_hash(HASH, ENTRYPOINT, args_simple(), "").try_into();
1519                match params {
1520                    Ok(item @ ExecutableDeployItem::StoredContractByHash { .. }) => {
1521                        let actual: BTreeMap<String, CLValue> = item.args().clone().into();
1522                        let mut expected = BTreeMap::new();
1523                        expected.insert("name_01".to_owned(), CLValue::from_t(false).unwrap());
1524                        expected.insert("name_02".to_owned(), CLValue::from_t(42u32).unwrap());
1525                        assert_eq!(actual, expected);
1526                    }
1527                    other => panic!("incorrect type parsed {:?}", other),
1528                }
1529            }
1530
1531            #[test]
1532            pub fn with_name() {
1533                let params: Result<ExecutableDeployItem, CliError> =
1534                    SessionStrParams::with_name(NAME, ENTRYPOINT, args_simple(), "").try_into();
1535                match params {
1536                    Ok(item @ ExecutableDeployItem::StoredContractByName { .. }) => {
1537                        let actual: BTreeMap<String, CLValue> = item.args().clone().into();
1538                        let mut expected = BTreeMap::new();
1539                        expected.insert("name_01".to_owned(), CLValue::from_t(false).unwrap());
1540                        expected.insert("name_02".to_owned(), CLValue::from_t(42u32).unwrap());
1541                        assert_eq!(actual, expected);
1542                    }
1543                    other => panic!("incorrect type parsed {:?}", other),
1544                }
1545            }
1546
1547            #[test]
1548            pub fn with_package_name() {
1549                let params: Result<ExecutableDeployItem, CliError> =
1550                    SessionStrParams::with_package_name(
1551                        PKG_NAME,
1552                        VERSION,
1553                        ENTRYPOINT,
1554                        args_simple(),
1555                        "",
1556                    )
1557                    .try_into();
1558                match params {
1559                    Ok(item @ ExecutableDeployItem::StoredVersionedContractByName { .. }) => {
1560                        let actual: BTreeMap<String, CLValue> = item.args().clone().into();
1561                        let mut expected = BTreeMap::new();
1562                        expected.insert("name_01".to_owned(), CLValue::from_t(false).unwrap());
1563                        expected.insert("name_02".to_owned(), CLValue::from_t(42u32).unwrap());
1564                        assert_eq!(actual, expected);
1565                    }
1566                    other => panic!("incorrect type parsed {:?}", other),
1567                }
1568            }
1569
1570            #[test]
1571            pub fn with_package_hash() {
1572                let params: Result<ExecutableDeployItem, CliError> =
1573                    SessionStrParams::with_package_hash(
1574                        PKG_HASH,
1575                        VERSION,
1576                        ENTRYPOINT,
1577                        args_simple(),
1578                        "",
1579                    )
1580                    .try_into();
1581                match params {
1582                    Ok(item @ ExecutableDeployItem::StoredVersionedContractByHash { .. }) => {
1583                        let actual: BTreeMap<String, CLValue> = item.args().clone().into();
1584                        let mut expected = BTreeMap::new();
1585                        expected.insert("name_01".to_owned(), CLValue::from_t(false).unwrap());
1586                        expected.insert("name_02".to_owned(), CLValue::from_t(42u32).unwrap());
1587                        assert_eq!(actual, expected);
1588                    }
1589                    other => panic!("incorrect type parsed {:?}", other),
1590                }
1591            }
1592        }
1593        /// Sample data creation methods for PaymentStrParams
1594        mod payment_params {
1595            use std::collections::BTreeMap;
1596
1597            use casper_types::{CLValue, U512};
1598
1599            use super::*;
1600
1601            #[test]
1602            pub fn with_amount() {
1603                let params: Result<ExecutableDeployItem, CliError> =
1604                    PaymentStrParams::with_amount("100").try_into();
1605                match params {
1606                    Ok(item @ ExecutableDeployItem::ModuleBytes { .. }) => {
1607                        let amount = CLValue::from_t(U512::from(100)).unwrap();
1608                        assert_eq!(item.args().get("amount"), Some(&amount));
1609                    }
1610                    other => panic!("incorrect type parsed {:?}", other),
1611                }
1612            }
1613
1614            #[test]
1615            pub fn with_hash() {
1616                let params: Result<ExecutableDeployItem, CliError> =
1617                    PaymentStrParams::with_hash(HASH, ENTRYPOINT, args_simple(), "").try_into();
1618                match params {
1619                    Ok(item @ ExecutableDeployItem::StoredContractByHash { .. }) => {
1620                        let actual: BTreeMap<String, CLValue> = item.args().clone().into();
1621                        let mut expected = BTreeMap::new();
1622                        expected.insert("name_01".to_owned(), CLValue::from_t(false).unwrap());
1623                        expected.insert("name_02".to_owned(), CLValue::from_t(42u32).unwrap());
1624                        assert_eq!(actual, expected);
1625                    }
1626                    other => panic!("incorrect type parsed {:?}", other),
1627                }
1628            }
1629
1630            #[test]
1631            pub fn with_name() {
1632                let params: Result<ExecutableDeployItem, CliError> =
1633                    PaymentStrParams::with_name(NAME, ENTRYPOINT, args_simple(), "").try_into();
1634                match params {
1635                    Ok(item @ ExecutableDeployItem::StoredContractByName { .. }) => {
1636                        let actual: BTreeMap<String, CLValue> = item.args().clone().into();
1637                        let mut expected = BTreeMap::new();
1638                        expected.insert("name_01".to_owned(), CLValue::from_t(false).unwrap());
1639                        expected.insert("name_02".to_owned(), CLValue::from_t(42u32).unwrap());
1640                        assert_eq!(actual, expected);
1641                    }
1642                    other => panic!("incorrect type parsed {:?}", other),
1643                }
1644            }
1645
1646            #[test]
1647            pub fn with_package_name() {
1648                let params: Result<ExecutableDeployItem, CliError> =
1649                    PaymentStrParams::with_package_name(
1650                        PKG_NAME,
1651                        VERSION,
1652                        ENTRYPOINT,
1653                        args_simple(),
1654                        "",
1655                    )
1656                    .try_into();
1657                match params {
1658                    Ok(item @ ExecutableDeployItem::StoredVersionedContractByName { .. }) => {
1659                        let actual: BTreeMap<String, CLValue> = item.args().clone().into();
1660                        let mut expected = BTreeMap::new();
1661                        expected.insert("name_01".to_owned(), CLValue::from_t(false).unwrap());
1662                        expected.insert("name_02".to_owned(), CLValue::from_t(42u32).unwrap());
1663                        assert_eq!(actual, expected);
1664                    }
1665                    other => panic!("incorrect type parsed {:?}", other),
1666                }
1667            }
1668
1669            #[test]
1670            pub fn with_package_hash() {
1671                let params: Result<ExecutableDeployItem, CliError> =
1672                    PaymentStrParams::with_package_hash(
1673                        PKG_HASH,
1674                        VERSION,
1675                        ENTRYPOINT,
1676                        args_simple(),
1677                        "",
1678                    )
1679                    .try_into();
1680                match params {
1681                    Ok(item @ ExecutableDeployItem::StoredVersionedContractByHash { .. }) => {
1682                        let actual: BTreeMap<String, CLValue> = item.args().clone().into();
1683                        let mut expected = BTreeMap::new();
1684                        expected.insert("name_01".to_owned(), CLValue::from_t(false).unwrap());
1685                        expected.insert("name_02".to_owned(), CLValue::from_t(42u32).unwrap());
1686                        assert_eq!(actual, expected);
1687                    }
1688                    other => panic!("incorrect type parsed {:?}", other),
1689                }
1690            }
1691        }
1692    }
1693
1694    mod account_identifier {
1695        use super::*;
1696
1697        #[test]
1698        pub fn should_parse_valid_account_hash() {
1699            let account_hash =
1700                "account-hash-c029c14904b870e64c1d443d428c606740e82f341bea0f8542ca6494cef1383e";
1701            let parsed = account_identifier(account_hash).unwrap();
1702            let expected = AccountHash::from_formatted_str(account_hash).unwrap();
1703            assert_eq!(parsed, AccountIdentifier::AccountHash(expected));
1704        }
1705
1706        #[test]
1707        pub fn should_parse_valid_public_key() {
1708            let public_key = "01567f0f205e83291312cd82988d66143d376cee7de904dd2605d3f4bbb69b3c80";
1709            let parsed = account_identifier(public_key).unwrap();
1710            let expected = PublicKey::from_hex(public_key).unwrap();
1711            assert_eq!(parsed, AccountIdentifier::PublicKey(expected));
1712        }
1713
1714        #[test]
1715        pub fn should_fail_to_parse_invalid_account_hash() {
1716            //This is the account hash from above with several characters removed
1717            let account_hash =
1718                "account-hash-c029c14904b870e1d443d428c606740e82f341bea0f8542ca6494cef1383e";
1719            let parsed = account_identifier(account_hash);
1720            assert!(parsed.is_err());
1721        }
1722
1723        #[test]
1724        pub fn should_fail_to_parse_invalid_public_key() {
1725            //This is the public key from above with several characters removed
1726            let public_key = "01567f0f205e83291312cd82988d66143d376cee7de904dd26054bbb69b3c80";
1727            let parsed = account_identifier(public_key);
1728            assert!(parsed.is_err());
1729        }
1730    }
1731
1732    mod entity_identifier {
1733        use super::*;
1734
1735        #[test]
1736        pub fn should_parse_valid_contract_entity_addr() {
1737            let entity_addr =
1738                "entity-contract-c029c14904b870e64c1d443d428c606740e82f341bea0f8542ca6494cef1383e";
1739            let parsed = entity_identifier(entity_addr).unwrap();
1740            assert_eq!(
1741                parsed,
1742                EntityIdentifier::EntityAddr(
1743                    EntityAddr::from_formatted_str(entity_addr).expect("should parse EntityAddr")
1744                )
1745            );
1746        }
1747
1748        #[test]
1749        pub fn should_parse_valid_account_entity_addr() {
1750            let entity_addr =
1751                "entity-account-c029c14904b870e64c1d443d428c606740e82f341bea0f8542ca6494cef1383e";
1752            let parsed = entity_identifier(entity_addr).unwrap();
1753            assert_eq!(
1754                parsed,
1755                EntityIdentifier::EntityAddr(
1756                    EntityAddr::from_formatted_str(entity_addr).expect("should parse EntityAddr")
1757                )
1758            );
1759        }
1760
1761        #[test]
1762        pub fn should_parse_valid_public_key() {
1763            let public_key = "01567f0f205e83291312cd82988d66143d376cee7de904dd2605d3f4bbb69b3c80";
1764            let parsed = entity_identifier(public_key).unwrap();
1765            let expected = PublicKey::from_hex(public_key).unwrap();
1766            assert_eq!(parsed, EntityIdentifier::PublicKey(expected));
1767        }
1768
1769        #[test]
1770        pub fn should_fail_to_parse_invalid_entity_hash() {
1771            //This is the account hash from above with several characters removed
1772            let entity_hash =
1773                "contract-addressable-entity-c029c14904b870e64c1d443d428c606740e82f341bea0f8542ca6494cef138";
1774            let parsed = entity_identifier(entity_hash);
1775            assert!(parsed.is_err());
1776        }
1777
1778        #[test]
1779        pub fn should_fail_to_parse_invalid_public_key() {
1780            //This is the public key from above with several characters removed
1781            let public_key = "01567f0f205e83291312cd82988d66143d376cee7de904dd26054bbb69b3c80";
1782            let parsed = entity_identifier(public_key);
1783            assert!(parsed.is_err());
1784        }
1785    }
1786
1787    mod era_identifier {
1788        use casper_types::EraId;
1789
1790        use super::*;
1791
1792        #[test]
1793        pub fn should_parse_valid_era_id() {
1794            let era_id = "123";
1795            let parsed = era_identifier(era_id).unwrap();
1796            assert!(
1797                matches!(parsed, Some(EraIdentifier::Era(id)) if id == EraId::new(123)),
1798                "{:?}",
1799                parsed
1800            );
1801        }
1802
1803        #[test]
1804        pub fn should_fail_to_parse_invalid_era_id() {
1805            let era_id = "invalid";
1806            let parsed = era_identifier(era_id);
1807            assert!(parsed.is_err());
1808        }
1809    }
1810
1811    mod public_key {
1812        use super::*;
1813
1814        #[test]
1815        pub fn should_parse_valid_public_key() {
1816            let str = "01567f0f205e83291312cd82988d66143d376cee7de904dd2605d3f4bbb69b3c80";
1817            let parsed = public_key(str).unwrap();
1818            let expected = PublicKey::from_hex(str).unwrap();
1819            assert_eq!(parsed, Some(expected));
1820        }
1821
1822        #[test]
1823        pub fn should_fail_to_parse_invalid_public_key() {
1824            //This is the public key from above with several characters removed
1825            let str = "01567f0f205e83291312cd82988d66143d376cee7de904dd26054bbb69b3c80";
1826            let parsed = public_key(str);
1827            assert!(parsed.is_err());
1828        }
1829    }
1830
1831    mod pricing_mode {
1832        use super::*;
1833
1834        const VALID_HASH: &str = "09dcee4b212cfd53642ab323fbef07dafafc6f945a80a00147f62910a915c4e6";
1835        #[test]
1836        fn should_parse_fixed_pricing_mode_identifier() {
1837            let pricing_mode_str = "fixed";
1838            let payment_amount = "";
1839            let gas_price_tolerance = "10";
1840            let additional_computation_factor = "1";
1841            let standard_payment = "";
1842            let parsed = pricing_mode(
1843                pricing_mode_str,
1844                payment_amount,
1845                gas_price_tolerance,
1846                additional_computation_factor,
1847                standard_payment,
1848                None,
1849            )
1850            .unwrap();
1851            assert_eq!(
1852                parsed,
1853                PricingMode::Fixed {
1854                    additional_computation_factor: 1,
1855                    gas_price_tolerance: 10,
1856                }
1857            );
1858        }
1859
1860        #[test]
1861        fn should_parse_fixed_pricing_mode_identifier_without_additional_computation_factor() {
1862            let pricing_mode_str = "fixed";
1863            let payment_amount = "";
1864            let gas_price_tolerance = "10";
1865            let additional_computation_factor = "";
1866            let standard_payment = "";
1867            let parsed = pricing_mode(
1868                pricing_mode_str,
1869                payment_amount,
1870                gas_price_tolerance,
1871                additional_computation_factor,
1872                standard_payment,
1873                None,
1874            )
1875            .unwrap();
1876            assert_eq!(
1877                parsed,
1878                PricingMode::Fixed {
1879                    additional_computation_factor: 0,
1880                    gas_price_tolerance: 10,
1881                }
1882            );
1883        }
1884
1885        #[test]
1886        fn should_parse_reserved_pricing_mode() {
1887            let pricing_mode_str = "reserved";
1888            let payment_amount = "";
1889            let gas_price_tolerance = "";
1890            let additional_computation_factor = "0";
1891            let standard_payment = "";
1892            let parsed = pricing_mode(
1893                pricing_mode_str,
1894                payment_amount,
1895                gas_price_tolerance,
1896                additional_computation_factor,
1897                standard_payment,
1898                Some(Digest::from_hex(VALID_HASH).unwrap()),
1899            )
1900            .unwrap();
1901            assert_eq!(
1902                parsed,
1903                PricingMode::Prepaid {
1904                    receipt: Digest::from_hex(VALID_HASH).unwrap(),
1905                }
1906            );
1907        }
1908        #[test]
1909        fn should_parse_classic_pricing_mode() {
1910            let pricing_mode_str = "classic";
1911            let payment_amount = "10";
1912            let standard_payment = "true";
1913            let gas_price_tolerance = "10";
1914            let additional_computation_factor = "0";
1915            let parsed = pricing_mode(
1916                pricing_mode_str,
1917                payment_amount,
1918                gas_price_tolerance,
1919                additional_computation_factor,
1920                standard_payment,
1921                None,
1922            )
1923            .unwrap();
1924            assert_eq!(
1925                parsed,
1926                PricingMode::PaymentLimited {
1927                    payment_amount: 10,
1928                    gas_price_tolerance: 10,
1929                    standard_payment: true,
1930                }
1931            );
1932        }
1933
1934        #[test]
1935        fn should_fail_to_parse_invalid_pricing_mode() {
1936            let pricing_mode_str = "invalid";
1937            let payment_amount = "10";
1938            let standard_payment = "true";
1939            let gas_price_tolerance = "10";
1940            let additional_computation_factor = "0";
1941            let parsed = pricing_mode(
1942                pricing_mode_str,
1943                payment_amount,
1944                gas_price_tolerance,
1945                additional_computation_factor,
1946                standard_payment,
1947                None,
1948            );
1949            assert!(parsed.is_err());
1950            assert!(matches!(parsed, Err(CliError::InvalidArgument { .. })));
1951        }
1952
1953        #[test]
1954        fn should_fail_to_parse_invalid_additional_computation_factor() {
1955            let pricing_mode_str = "fixed";
1956            let payment_amount = "10";
1957            let standard_payment = "true";
1958            let gas_price_tolerance = "10";
1959            let additional_computation_factor = "invalid";
1960            let parsed = pricing_mode(
1961                pricing_mode_str,
1962                payment_amount,
1963                gas_price_tolerance,
1964                additional_computation_factor,
1965                standard_payment,
1966                None,
1967            );
1968            assert!(parsed.is_err());
1969            assert!(matches!(parsed, Err(CliError::FailedToParseInt { .. })));
1970        }
1971
1972        #[test]
1973        fn should_fail_to_parse_classic_without_amount() {
1974            let pricing_mode_str = "classic";
1975            let payment_amount = "";
1976            let standard_payment = "true";
1977            let gas_price_tolerance = "10";
1978            let additional_computation_factor = "0";
1979            let parsed = pricing_mode(
1980                pricing_mode_str,
1981                payment_amount,
1982                gas_price_tolerance,
1983                additional_computation_factor,
1984                standard_payment,
1985                None,
1986            );
1987            assert!(parsed.is_err());
1988            assert!(matches!(parsed, Err(CliError::InvalidArgument { .. })));
1989        }
1990    }
1991    mod transaction_hash {
1992        use super::*;
1993        const VALID_HASH: &str = "09dcee4b212cfd53642ab323fbef07dafafc6f945a80a00147f62910a915c4e6";
1994        const INVALID_HASH: &str =
1995            "09dcee4b212cfd53642ab323fbef07dafafc6f945a80a00147f62910a915c4e";
1996        #[test]
1997        fn should_parse_transaction_hash() {
1998            let parsed = transaction_hash(VALID_HASH);
1999            assert!(parsed.is_ok());
2000            assert_eq!(
2001                parsed.unwrap(),
2002                TransactionHash::from(TransactionV1Hash::from(
2003                    Digest::from_hex(VALID_HASH).unwrap()
2004                ))
2005            );
2006        }
2007        #[test]
2008        fn should_fail_to_parse_incorrect_hash() {
2009            let parsed = transaction_hash(INVALID_HASH);
2010            assert!(parsed.is_err());
2011            assert!(matches!(
2012                parsed,
2013                Err(CliError::FailedToParseDigest {
2014                    context: "failed to parse digest from string for transaction hash",
2015                    ..
2016                })
2017            ));
2018        }
2019    }
2020}