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> {
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 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
186fn 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
223pub fn args_from_simple_or_json(
247 simple: Option<RuntimeArgs>,
248 json: Option<RuntimeArgs>,
249 chunked: Option<Vec<u8>>,
250) -> TransactionArgs {
251 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
265macro_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 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 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 let is_session_transfer = if session_transfer { "true" } else { "" };
377 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, 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, 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
484pub 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
493pub 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
513pub 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 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, 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, 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
731pub(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
758pub 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
808pub 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
841pub 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
883pub(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
897pub(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 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 macro_rules! impl_test_matrix {
1332 (
1333 type: $t:ident,
1335 context: $context:expr,
1337
1338 $module:ident [$(
1340 test[
1342 $arg:tt => $arg_value:expr,
1344 conflict: $con:tt => $con_value:expr,
1346 requires[$($req:tt => $req_value:expr),*],
1348 $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 impl_test_matrix![
1391 type: SessionStrParams,
1392 context: "parse_session_info",
1393 session_str_params[
1394
1395 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 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 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 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 test[session_package_hash => PACKAGE_HASH, conflict: is_session_transfer => TRANSFER, requires[session_entry_point => ENTRY_POINT], package_hash_conflicts_with_transfer]
1430 ]
1436 ];
1437
1438 impl_test_matrix![
1439 type: PaymentStrParams,
1440 context: "parse_payment_info",
1441 payment_str_params[
1442
1443 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 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 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 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 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 ]
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 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 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 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 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 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 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 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}