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