1use crate::cli::deploy::do_withdraw_amount_checks;
2use crate::cli::{get_block, query_global_state};
3#[cfg(feature = "std-fs-io")]
4use crate::read_transaction_file;
5#[cfg(feature = "std-fs-io")]
6use crate::rpcs::v2_0_0::speculative_exec_transaction::SpeculativeExecTxnResult;
7#[cfg(feature = "std-fs-io")]
8use crate::speculative_exec_txn;
9use crate::{
10 cli::{parse, CliError, TransactionBuilderParams, TransactionStrParams, TransactionV1Builder},
11 put_transaction as put_transaction_rpc_handler,
12 rpcs::results::PutTransactionResult,
13 SuccessResponse,
14};
15use casper_types::{
16 Digest, InitiatorAddr, Key, PublicKey, SecretKey, Transaction, TransactionArgs,
17 TransactionEntryPoint, TransactionInvocationTarget, TransactionRuntimeParams,
18 TransactionTarget, U512,
19};
20
21pub fn create_transaction(
22 builder_params: TransactionBuilderParams,
23 transaction_params: TransactionStrParams,
24 allow_unsigned_transaction: bool,
25) -> Result<Transaction, CliError> {
26 let chain_name = transaction_params.chain_name.to_string();
27
28 let maybe_secret_key = get_maybe_secret_key(
29 transaction_params.secret_key,
30 allow_unsigned_transaction,
31 "create_transaction",
32 )?;
33
34 let timestamp = parse::timestamp(transaction_params.timestamp)?;
35 let ttl = parse::ttl(transaction_params.ttl)?;
36 let maybe_session_account = parse::session_account(&transaction_params.initiator_addr)?;
37
38 let is_v2_wasm = matches!(&builder_params, TransactionBuilderParams::Session { runtime, .. } if matches!(runtime, &TransactionRuntimeParams::VmCasperV2 { .. }));
39
40 let mut transaction_builder = make_transaction_builder(builder_params)?;
41
42 transaction_builder = transaction_builder
43 .with_timestamp(timestamp)
44 .with_ttl(ttl)
45 .with_chain_name(chain_name);
46
47 if transaction_params.pricing_mode.is_empty() {
48 return Err(CliError::InvalidArgument {
49 context: "create_transaction (pricing_mode)",
50 error: "pricing_mode is required to be non empty".to_string(),
51 });
52 }
53
54 let pricing_mode = if transaction_params.pricing_mode.to_lowercase().as_str() == "reserved" {
55 let digest = Digest::from_hex(transaction_params.receipt).map_err(|error| {
56 CliError::FailedToParseDigest {
57 context: "pricing_digest",
58 error,
59 }
60 })?;
61
62 parse::pricing_mode(
63 transaction_params.pricing_mode,
64 transaction_params.payment_amount,
65 transaction_params.gas_price_tolerance,
66 transaction_params.additional_computation_factor,
67 transaction_params.standard_payment,
68 Some(digest),
69 )?
70 } else {
71 parse::pricing_mode(
72 transaction_params.pricing_mode,
73 transaction_params.payment_amount,
74 transaction_params.gas_price_tolerance,
75 transaction_params.additional_computation_factor,
76 transaction_params.standard_payment,
77 None,
78 )?
79 };
80
81 transaction_builder = transaction_builder.with_pricing_mode(pricing_mode);
82
83 let maybe_json_args = parse::args_json::session::parse(transaction_params.session_args_json)?;
84 let maybe_simple_args =
85 parse::arg_simple::session::parse(&transaction_params.session_args_simple)?;
86 let chunked = transaction_params.chunked_args;
87
88 let args = parse::args_from_simple_or_json(maybe_simple_args, maybe_json_args, chunked);
89 match args {
90 TransactionArgs::Named(named_args) => {
91 if !named_args.is_empty() {
92 transaction_builder = transaction_builder.with_runtime_args(named_args);
93 }
94 }
95 TransactionArgs::Bytesrepr(chunked_args) => {
96 transaction_builder = transaction_builder.with_chunked_args(chunked_args);
97 }
98 }
99
100 if is_v2_wasm {
101 if let Some(entry_point) = transaction_params.session_entry_point {
102 transaction_builder = transaction_builder
103 .with_entry_point(TransactionEntryPoint::Custom(entry_point.to_owned()));
104 }
105 }
106
107 if let Some(secret_key) = &maybe_secret_key {
108 transaction_builder = transaction_builder.with_secret_key(secret_key);
109 }
110
111 if let Some(account) = maybe_session_account {
112 transaction_builder =
113 transaction_builder.with_initiator_addr(InitiatorAddr::PublicKey(account));
114 }
115
116 let txn = transaction_builder.build().map_err(crate::Error::from)?;
117 Ok(Transaction::V1(txn))
118}
119
120pub fn make_transaction(
131 builder_params: TransactionBuilderParams,
132 transaction_params: TransactionStrParams<'_>,
133 #[allow(unused_variables)] force: bool,
134) -> Result<Transaction, CliError> {
135 let transaction = create_transaction(builder_params, transaction_params.clone(), true)?;
136 #[cfg(feature = "std-fs-io")]
137 {
138 let output = parse::output_kind(transaction_params.output_path, force);
139 crate::output_transaction(output, &transaction).map_err(CliError::from)?;
140 }
141 Ok(transaction)
142}
143
144pub async fn put_transaction(
150 rpc_id_str: &str,
151 node_address: &str,
152 verbosity_level: u64,
153 builder_params: TransactionBuilderParams<'_>,
154 transaction_params: TransactionStrParams<'_>,
155) -> Result<SuccessResponse<PutTransactionResult>, CliError> {
156 let rpc_id = parse::rpc_id(rpc_id_str);
157 let verbosity_level = parse::verbosity(verbosity_level);
158 let min_bid_override = transaction_params.min_bid_override;
159 let transaction = create_transaction(builder_params, transaction_params, false)?;
160 if let Err(err) =
161 check_auction_state_for_withdraw(node_address, 0, min_bid_override, &transaction).await
162 {
163 if !min_bid_override {
164 return Err(err);
165 } else {
166 println!("[WARN] Skipping withdraw amount checks {}", err)
167 }
168 };
169 put_transaction_rpc_handler(rpc_id, node_address, verbosity_level, transaction)
170 .await
171 .map_err(CliError::from)
172}
173#[cfg(feature = "std-fs-io")]
180pub async fn send_transaction_file(
181 rpc_id_str: &str,
182 node_address: &str,
183 verbosity_level: u64,
184 input_path: &str,
185) -> Result<SuccessResponse<PutTransactionResult>, CliError> {
186 let rpc_id = parse::rpc_id(rpc_id_str);
187 let verbosity_level = parse::verbosity(verbosity_level);
188 let transaction = read_transaction_file(input_path)?;
189 put_transaction_rpc_handler(rpc_id, node_address, verbosity_level, transaction)
190 .await
191 .map_err(CliError::from)
192}
193
194#[cfg(feature = "std-fs-io")]
201pub async fn speculative_send_transaction_file(
202 rpc_id_str: &str,
203 node_address: &str,
204 verbosity_level: u64,
205 input_path: &str,
206) -> Result<SuccessResponse<SpeculativeExecTxnResult>, CliError> {
207 let rpc_id = parse::rpc_id(rpc_id_str);
208 let verbosity_level = parse::verbosity(verbosity_level);
209 let transaction = read_transaction_file(input_path).unwrap();
210 speculative_exec_txn(rpc_id, node_address, verbosity_level, transaction)
211 .await
212 .map_err(CliError::from)
213}
214
215#[cfg(feature = "std-fs-io")]
223pub fn sign_transaction_file(
224 input_path: &str,
225 secret_key_path: &str,
226 maybe_output_path: Option<&str>,
227 force: bool,
228) -> Result<(), CliError> {
229 let output = parse::output_kind(maybe_output_path.unwrap_or(""), force);
230 let secret_key = parse::secret_key_from_file(secret_key_path)?;
231 crate::sign_transaction_file(input_path, &secret_key, output).map_err(CliError::from)
232}
233
234pub fn make_transaction_builder(
235 transaction_builder_params: TransactionBuilderParams,
236) -> Result<TransactionV1Builder, CliError> {
237 match transaction_builder_params {
238 TransactionBuilderParams::AddBid {
239 public_key,
240 delegation_rate,
241 amount,
242 minimum_delegation_amount,
243 maximum_delegation_amount,
244 reserved_slots,
245 } => {
246 let transaction_builder = TransactionV1Builder::new_add_bid(
247 public_key,
248 delegation_rate,
249 amount,
250 minimum_delegation_amount,
251 maximum_delegation_amount,
252 reserved_slots,
253 )?;
254 Ok(transaction_builder)
255 }
256 TransactionBuilderParams::Delegate {
257 delegator,
258 validator,
259 amount,
260 } => {
261 let transaction_builder =
262 TransactionV1Builder::new_delegate(delegator, validator, amount)?;
263 Ok(transaction_builder)
264 }
265 TransactionBuilderParams::Undelegate {
266 delegator,
267 validator,
268 amount,
269 } => {
270 let transaction_builder =
271 TransactionV1Builder::new_undelegate(delegator, validator, amount)?;
272 Ok(transaction_builder)
273 }
274 TransactionBuilderParams::Redelegate {
275 delegator,
276 validator,
277 amount,
278 new_validator,
279 } => {
280 let transaction_builder =
281 TransactionV1Builder::new_redelegate(delegator, validator, amount, new_validator)?;
282 Ok(transaction_builder)
283 }
284 TransactionBuilderParams::InvocableEntity {
285 entity_hash,
286 entry_point,
287 runtime,
288 } => {
289 let transaction_builder = TransactionV1Builder::new_targeting_invocable_entity(
290 entity_hash,
291 entry_point,
292 runtime,
293 );
294 Ok(transaction_builder)
295 }
296 TransactionBuilderParams::InvocableEntityAlias {
297 entity_alias,
298 entry_point,
299 runtime,
300 } => {
301 let transaction_builder =
302 TransactionV1Builder::new_targeting_invocable_entity_via_alias(
303 entity_alias,
304 entry_point,
305 runtime,
306 );
307 Ok(transaction_builder)
308 }
309 TransactionBuilderParams::Package {
310 package_hash,
311 maybe_entity_version,
312 entry_point,
313 runtime,
314 } => {
315 let transaction_builder = TransactionV1Builder::new_targeting_package(
316 package_hash,
317 maybe_entity_version,
318 entry_point,
319 runtime,
320 );
321 Ok(transaction_builder)
322 }
323 TransactionBuilderParams::PackageWithMajorVersion {
324 package_hash,
325 maybe_entity_version,
326 entry_point,
327 runtime,
328 major_protocol_version,
329 } => {
330 let transaction_builder = TransactionV1Builder::new_targeting_package_with_version_key(
331 package_hash,
332 maybe_entity_version,
333 major_protocol_version,
334 entry_point,
335 runtime,
336 );
337 Ok(transaction_builder)
338 }
339 TransactionBuilderParams::PackageAlias {
340 package_alias,
341 maybe_entity_version,
342 entry_point,
343 runtime,
344 } => {
345 let new_targeting_package_via_alias =
346 TransactionV1Builder::new_targeting_package_via_alias(
347 package_alias,
348 maybe_entity_version,
349 entry_point,
350 runtime,
351 );
352 let transaction_builder = new_targeting_package_via_alias;
353 Ok(transaction_builder)
354 }
355 TransactionBuilderParams::PackageAliasWithMajorVersion {
356 package_alias,
357 maybe_entity_version,
358 entry_point,
359 runtime,
360 major_protocol_version,
361 } => {
362 let new_targeting_package_via_alias =
363 TransactionV1Builder::new_targeting_package_via_alias_with_version_key(
364 package_alias,
365 maybe_entity_version,
366 major_protocol_version,
367 entry_point,
368 runtime,
369 );
370 let transaction_builder = new_targeting_package_via_alias;
371 Ok(transaction_builder)
372 }
373 TransactionBuilderParams::Session {
374 is_install_upgrade,
375 transaction_bytes,
376 runtime,
377 } => {
378 let transaction_builder =
379 TransactionV1Builder::new_session(is_install_upgrade, transaction_bytes, runtime);
380 Ok(transaction_builder)
381 }
382 TransactionBuilderParams::Transfer {
383 maybe_source,
384 target,
385 amount,
386 maybe_id,
387 } => {
388 let transaction_builder =
389 TransactionV1Builder::new_transfer(amount, maybe_source, target, maybe_id)?;
390
391 Ok(transaction_builder)
392 }
393 TransactionBuilderParams::WithdrawBid {
394 public_key, amount, ..
395 } => {
396 let transaction_builder = TransactionV1Builder::new_withdraw_bid(public_key, amount)?;
397 Ok(transaction_builder)
398 }
399 TransactionBuilderParams::ActivateBid { validator } => {
400 let transaction_builder = TransactionV1Builder::new_activate_bid(validator)?;
401 Ok(transaction_builder)
402 }
403 TransactionBuilderParams::ChangeBidPublicKey {
404 public_key,
405 new_public_key,
406 } => {
407 let transaction_builder =
408 TransactionV1Builder::new_change_bid_public_key(public_key, new_public_key)?;
409 Ok(transaction_builder)
410 }
411 TransactionBuilderParams::AddReservations { reservations } => {
412 let transaction_builder = TransactionV1Builder::new_add_reservations(reservations)?;
413 Ok(transaction_builder)
414 }
415 TransactionBuilderParams::CancelReservations {
416 validator,
417 delegators,
418 } => {
419 let transaction_builder =
420 TransactionV1Builder::new_cancel_reservations(validator, delegators)?;
421 Ok(transaction_builder)
422 }
423 }
424}
425
426pub fn get_maybe_secret_key(
446 secret_key: &str,
447 allow_unsigned_deploy: bool,
448 context: &'static str,
449) -> Result<Option<SecretKey>, CliError> {
450 match (secret_key.is_empty(), allow_unsigned_deploy) {
451 (false, _) => {
452 #[cfg(feature = "std-fs-io")]
453 {
454 Ok(Some(parse::secret_key_from_file(secret_key)?))
455 }
456 #[cfg(not(feature = "std-fs-io"))]
457 {
458 let secret_key = SecretKey::from_pem(secret_key).map_err(|error| {
459 CliError::Core(crate::Error::CryptoError { context, error })
460 })?;
461 Ok(Some(secret_key))
462 }
463 }
464 (true, true) => Ok(None),
465 (true, false) => Err(CliError::InvalidArgument {
466 context,
467 error: "No secret key provided and unsigned deploys are not allowed".to_string(),
468 }),
469 }
470}
471
472async fn check_auction_state_for_withdraw(
473 node_address: &str,
474 verbosity_level: u64,
475 min_bid_override: bool,
476 transaction: &Transaction,
477) -> Result<(), CliError> {
478 let state_root_hash = *get_block("", node_address, 0, "")
479 .await?
480 .result
481 .block_with_signatures
482 .ok_or_else(|| CliError::FailedToGetStateRootHash)?
483 .block
484 .state_root_hash();
485 let encoded_hash = base16::encode_lower(&state_root_hash);
486 match transaction {
487 Transaction::Deploy(_) => return Ok(()),
488 Transaction::V1(transaction_v1) => {
489 let entry_point = transaction_v1
490 .deserialize_field::<TransactionEntryPoint>(2)
491 .map_err(|err| {
492 CliError::FailedToParseTransactionPayloadField(format!("{:?}", err))
493 })?;
494 let do_amount_checks = match entry_point {
495 TransactionEntryPoint::WithdrawBid => true,
496 TransactionEntryPoint::Custom(name) => {
497 if *"withdraw_bid" != name {
498 return Ok(());
500 }
501 let transaction_invocation_target = transaction_v1
502 .payload()
503 .deserialize_field::<TransactionTarget>(1)
504 .map_err(|err| {
505 CliError::FailedToParseTransactionPayloadField(format!("{:?}", err))
506 })?;
507 let registry = crate::cli::get_system_hash_registry(
508 node_address,
509 verbosity_level,
510 &encoded_hash,
511 )
512 .await?;
513 let auction_hash_addr = *registry
514 .get("auction")
515 .ok_or_else(|| CliError::MissingAuctionHash)?;
516
517 let do_amount_checks = if let TransactionTarget::Stored { id, .. } =
518 transaction_invocation_target
519 {
520 match id {
521 TransactionInvocationTarget::ByHash(hash) => hash == auction_hash_addr,
522 TransactionInvocationTarget::ByName(name) => {
523 let base_key =
524 Key::Account(transaction_v1.initiator_addr().account_hash());
525 let account = query_global_state(
526 "",
527 node_address,
528 0,
529 "",
530 &encoded_hash,
531 &base_key.to_formatted_string(),
532 "",
533 )
534 .await?
535 .result
536 .stored_value
537 .into_account()
538 .ok_or_else(|| CliError::UnexpectedStoredValue)?;
539 let key = account.named_keys().get(&name);
540 match key {
541 Some(key) => match *key {
542 Key::Hash(addr) => addr == auction_hash_addr,
543 Key::AddressableEntity(addr) => {
544 addr.value() == auction_hash_addr
545 }
546 _ => false,
547 },
548 None => false,
549 }
550 }
551 TransactionInvocationTarget::ByPackageHash { addr, .. } => {
552 let key = Key::Hash(auction_hash_addr);
553 let package_addr = query_global_state(
554 "",
555 node_address,
556 verbosity_level,
557 "",
558 &encoded_hash,
559 &key.to_formatted_string(),
560 "",
561 )
562 .await?
563 .result
564 .stored_value
565 .as_contract()
566 .ok_or_else(|| CliError::FailedToGetSystemHashRegistry)?
567 .contract_package_hash()
568 .value();
569 package_addr == addr
570 }
571 TransactionInvocationTarget::ByPackageName { name, .. } => {
572 let base_key =
573 Key::Account(transaction_v1.initiator_addr().account_hash());
574 let account = query_global_state(
575 "",
576 node_address,
577 0,
578 "",
579 &encoded_hash,
580 &base_key.to_formatted_string(),
581 "",
582 )
583 .await?
584 .result
585 .stored_value
586 .into_account()
587 .ok_or_else(|| CliError::UnexpectedStoredValue)?;
588 let key = account.named_keys().get(&name);
589 match key {
590 Some(key) => match *key {
591 Key::Hash(addr) => {
592 let key = Key::Hash(auction_hash_addr);
593 let package_addr = query_global_state(
594 "",
595 node_address,
596 verbosity_level,
597 "",
598 &encoded_hash,
599 &key.to_formatted_string(),
600 "",
601 )
602 .await?
603 .result
604 .stored_value
605 .as_contract()
606 .ok_or_else(|| CliError::FailedToGetSystemHashRegistry)?
607 .contract_package_hash()
608 .value();
609 addr == package_addr
610 }
611 Key::SmartContract(addr) => {
612 let key = Key::Hash(auction_hash_addr);
613 let package_addr = query_global_state(
614 "",
615 node_address,
616 verbosity_level,
617 "",
618 &encoded_hash,
619 &key.to_formatted_string(),
620 "",
621 )
622 .await?
623 .result
624 .stored_value
625 .as_contract()
626 .ok_or_else(|| CliError::FailedToGetSystemHashRegistry)?
627 .contract_package_hash()
628 .value();
629 addr == package_addr
630 }
631 _ => false,
632 },
633 None => false,
634 }
635 }
636 }
637 } else {
638 false
639 };
640
641 do_amount_checks
642 }
643 _ => false,
644 };
645 if do_amount_checks {
646 let args = transaction_v1
647 .payload()
648 .deserialize_field::<TransactionArgs>(0)
649 .map_err(|err| {
650 CliError::FailedToParseTransactionPayloadField(format!("{:?}", err))
651 })?;
652 if let Some(named_args) = args.into_named() {
653 let amount = named_args
654 .get("amount")
655 .ok_or_else(|| {
656 CliError::InvalidCLValue("failed to get amount arg".to_string())
657 })?
658 .to_t::<U512>()
659 .map_err(|err| CliError::InvalidCLValue(err.to_string()))?;
660
661 let public_key = named_args
662 .get("public_key")
663 .ok_or_else(|| {
664 CliError::InvalidCLValue("failed to get public key arg".to_string())
665 })?
666 .to_t::<PublicKey>()
667 .map_err(|err| CliError::InvalidCLValue(err.to_string()))?;
668
669 do_withdraw_amount_checks(node_address, 0, public_key, amount, min_bid_override)
670 .await?
671 }
672 } else {
673 println!("Skipping amount checks for withdraw bid")
674 }
675 }
676 }
677 Ok(())
678}