1use casper_types::{
4 account::AccountHash, ActivationPoint, AsymmetricType, CoreConfig, Deploy,
5 ExecutableDeployItem, HashAddr, HighwayConfig, Key, ProtocolVersion, PublicKey, RuntimeArgs,
6 StorageCosts, SystemConfig, TransactionConfig, TransferTarget, UIntParseError, URef,
7 VacancyConfig, WasmConfig, U512,
8};
9use serde::{Deserialize, Serialize};
10
11use super::{
12 get_block, parse, query_global_state, transaction::get_maybe_secret_key, CliError,
13 DeployStrParams, PaymentStrParams, SessionStrParams,
14};
15use crate::{
16 cli::DeployBuilder,
17 rpcs::results::{PutDeployResult, SpeculativeExecResult},
18 SuccessResponse, MAX_SERIALIZED_SIZE_OF_DEPLOY,
19};
20
21const DEFAULT_GAS_PRICE: u64 = 1;
22
23#[derive(PartialEq, Eq, Serialize, Deserialize, Debug)]
24#[serde(deny_unknown_fields)]
26struct TomlNetwork {
27 name: String,
28 maximum_net_message_size: u32,
29}
30
31#[derive(PartialEq, Eq, Serialize, Deserialize, Debug)]
32#[serde(deny_unknown_fields)]
34struct TomlProtocol {
35 version: ProtocolVersion,
36 hard_reset: bool,
37 activation_point: ActivationPoint,
38}
39
40#[derive(PartialEq, Eq, Serialize, Deserialize, Debug)]
42#[serde(deny_unknown_fields)]
44pub(super) struct TomlChainspec {
45 protocol: TomlProtocol,
46 network: TomlNetwork,
47 core: CoreConfig,
48 transactions: TransactionConfig,
49 highway: HighwayConfig,
50 wasm: WasmConfig,
51 system_costs: SystemConfig,
52 vacancy: VacancyConfig,
53 storage_costs: StorageCosts,
54}
55
56pub(crate) async fn do_withdraw_amount_checks(
57 node_address: &str,
58 verbosity_level: u64,
59 public_key: PublicKey,
60 amount: U512,
61 min_bid_override: bool,
62) -> Result<(), CliError> {
63 let chainspec_bytes = crate::cli::get_chainspec("", node_address, verbosity_level)
64 .await?
65 .result
66 .chainspec_bytes;
67
68 let chainspec_as_str = std::str::from_utf8(chainspec_bytes.chainspec_bytes())
69 .map_err(|_| CliError::FailedToParseChainspecBytes)?;
70 let toml_chainspec: TomlChainspec =
71 toml::from_str(chainspec_as_str).map_err(|_| CliError::FailedToParseChainspecBytes)?;
72
73 let minimum_validator_bid = toml_chainspec.core.minimum_bid_amount;
74
75 match crate::cli::get_auction_info("", node_address, verbosity_level, "")
76 .await?
77 .result
78 .auction_state
79 .bids()
80 .find(|(bid_key, _bid)| **bid_key == public_key)
81 {
82 Some((_, bid)) => {
83 let staked_amount = *bid.staked_amount();
84 let remainder = staked_amount.saturating_sub(amount);
85 if remainder < U512::from(minimum_validator_bid) {
86 if !min_bid_override {
87 return Err(CliError::ReducedStakeBelowMinAmount);
88 } else {
89 println!("[WARN] Execution of this withdraw bid will result in unbonding of all stake")
90 }
91 }
92 }
93 None => return Err(CliError::FailedToGetAuctionState),
94 };
95
96 Ok(())
97}
98
99async fn check_auction_state_for_withdraw(
100 node_address: &str,
101 verbosity_level: u64,
102 hash_addr: HashAddr,
103 entry_point_name: String,
104 runtime_args: &RuntimeArgs,
105 min_bid_override: bool,
106) -> Result<(), CliError> {
107 if entry_point_name == *"withdraw_bid" {
109 let state_root_hash = *get_block("", node_address, 0, "")
110 .await?
111 .result
112 .block_with_signatures
113 .ok_or_else(|| CliError::FailedToGetStateRootHash)?
114 .block
115 .state_root_hash();
116 let encoded_hash = base16::encode_lower(&state_root_hash);
117 let registry =
118 crate::cli::get_system_hash_registry(node_address, verbosity_level, &encoded_hash)
119 .await?;
120 let auction_hash_addr = *registry
121 .get("auction")
122 .ok_or_else(|| CliError::MissingAuctionHash)?;
123 if auction_hash_addr == hash_addr {
125 } else {
127 let key = Key::Hash(auction_hash_addr);
129 let package_addr = query_global_state(
130 "",
131 node_address,
132 verbosity_level,
133 "",
134 &encoded_hash,
135 &key.to_formatted_string(),
136 "",
137 )
138 .await?
139 .result
140 .stored_value
141 .as_contract()
142 .ok_or_else(|| CliError::FailedToGetSystemHashRegistry)?
143 .contract_package_hash()
144 .value();
145 if package_addr != hash_addr {
146 return Ok(());
147 }
148 }
149 let amount = runtime_args
150 .get("amount")
151 .ok_or_else(|| CliError::InvalidCLValue("count not parse amount".to_string()))?
152 .to_t::<U512>()
153 .map_err(|err| CliError::InvalidCLValue(err.to_string()))?;
154 let public_key = runtime_args
155 .get("public_key")
156 .ok_or_else(|| CliError::InvalidCLValue("count not parse amount".to_string()))?
157 .to_t::<PublicKey>()
158 .map_err(|err| CliError::InvalidCLValue(err.to_string()))?;
159 return do_withdraw_amount_checks(node_address, 0, public_key, amount, min_bid_override)
160 .await;
161 }
162 Ok(())
163}
164
165async fn do_deploy_checks(
166 node_address: &str,
167 min_bid_override: bool,
168 deploy: &Deploy,
169) -> Result<(), CliError> {
170 let session = deploy.session();
171 let state_root_hash = *get_block("", node_address, 0, "")
172 .await?
173 .result
174 .block_with_signatures
175 .ok_or_else(|| CliError::FailedToGetStateRootHash)?
176 .block
177 .state_root_hash();
178 let encoded_hash = base16::encode_lower(&state_root_hash);
179 match session {
180 ExecutableDeployItem::ModuleBytes { .. } | ExecutableDeployItem::Transfer { .. } => Ok(()),
181 ExecutableDeployItem::StoredContractByHash {
182 entry_point,
183 hash,
184 args,
185 } => {
186 let hash_addr = hash.value();
187 check_auction_state_for_withdraw(
188 node_address,
189 0,
190 hash_addr,
191 entry_point.clone(),
192 args,
193 min_bid_override,
194 )
195 .await
196 }
197 ExecutableDeployItem::StoredContractByName {
198 name,
199 entry_point,
200 args,
201 } => {
202 let account = Key::Account(deploy.account().to_account_hash());
203 let cl_value = query_global_state(
204 "",
205 node_address,
206 0,
207 "",
208 &encoded_hash,
209 &account.to_formatted_string(),
210 "",
211 )
212 .await?
213 .result
214 .stored_value
215 .into_account()
216 .ok_or_else(|| CliError::InvalidCLValue("unable to parse as cl _value".to_string()))?;
217 let key = cl_value.named_keys().get(name);
218 match key {
219 Some(key) => {
220 let hash_addr = match *key {
221 Key::Hash(addr) => addr,
222 Key::SmartContract(addr) => addr,
223 _ => return Ok(()),
224 };
225 check_auction_state_for_withdraw(
226 node_address,
227 0,
228 hash_addr,
229 entry_point.clone(),
230 args,
231 min_bid_override,
232 )
233 .await
234 }
235 None => {
236 println!("unable to get named key skipping withdrawal checks");
237 Ok(())
238 }
239 }
240 }
241 ExecutableDeployItem::StoredVersionedContractByHash {
242 entry_point,
243 hash,
244 args,
245 ..
246 } => {
247 let hash_addr = hash.value();
248 check_auction_state_for_withdraw(
249 node_address,
250 0,
251 hash_addr,
252 entry_point.clone(),
253 args,
254 min_bid_override,
255 )
256 .await
257 }
258 ExecutableDeployItem::StoredVersionedContractByName {
259 name,
260 entry_point,
261 args,
262 ..
263 } => {
264 let account = Key::Account(deploy.account().to_account_hash());
265 let account = query_global_state(
266 "",
267 node_address,
268 0,
269 "",
270 &encoded_hash,
271 &account.to_formatted_string(),
272 "",
273 )
274 .await?
275 .result
276 .stored_value
277 .into_account()
278 .ok_or_else(|| CliError::InvalidCLValue("unable to parse as cl _value".to_string()))?;
279 let key = account.named_keys().get(name);
280 match key {
281 Some(key) => {
282 let hash_addr = match *key {
283 Key::Hash(addr) => addr,
284 Key::SmartContract(addr) => addr,
285 _ => return Ok(()),
286 };
287 check_auction_state_for_withdraw(
288 node_address,
289 0,
290 hash_addr,
291 entry_point.clone(),
292 args,
293 min_bid_override,
294 )
295 .await
296 }
297 None => {
298 println!("unable to get named key skipping withdrawal checks");
299 Ok(())
300 }
301 }
302 }
303 }
304}
305
306pub async fn put_deploy(
311 maybe_rpc_id: &str,
312 node_address: &str,
313 verbosity_level: u64,
314 deploy_params: DeployStrParams<'_>,
315 session_params: SessionStrParams<'_>,
316 payment_params: PaymentStrParams<'_>,
317) -> Result<SuccessResponse<PutDeployResult>, CliError> {
318 let rpc_id = parse::rpc_id(maybe_rpc_id);
319 let verbosity = parse::verbosity(verbosity_level);
320 let deploy = with_payment_and_session(deploy_params, payment_params, session_params, false)?;
321 do_deploy_checks(node_address, true, &deploy).await?;
322 #[allow(deprecated)]
323 crate::put_deploy(rpc_id, node_address, verbosity, deploy)
324 .await
325 .map_err(CliError::from)
326}
327
328pub async fn put_deploy_with_min_bid_override(
333 maybe_rpc_id: &str,
334 node_address: &str,
335 verbosity_level: u64,
336 min_bid_override: bool,
337 deploy_params: DeployStrParams<'_>,
338 session_params: SessionStrParams<'_>,
339 payment_params: PaymentStrParams<'_>,
340) -> Result<SuccessResponse<PutDeployResult>, CliError> {
341 let rpc_id = parse::rpc_id(maybe_rpc_id);
342 let verbosity = parse::verbosity(verbosity_level);
343 let deploy = with_payment_and_session(deploy_params, payment_params, session_params, false)?;
344 if let Err(err) = do_deploy_checks(node_address, min_bid_override, &deploy).await {
345 if !min_bid_override {
346 return Err(err);
347 } else {
348 println!("[WARN]: Skipping withdraw bid amount checks: {}", err)
349 }
350 };
351 #[allow(deprecated)]
352 crate::put_deploy(rpc_id, node_address, verbosity, deploy)
353 .await
354 .map_err(CliError::from)
355}
356
357pub async fn speculative_put_deploy(
362 maybe_rpc_id: &str,
363 node_address: &str,
364 verbosity_level: u64,
365 deploy_params: DeployStrParams<'_>,
366 session_params: SessionStrParams<'_>,
367 payment_params: PaymentStrParams<'_>,
368) -> Result<SuccessResponse<SpeculativeExecResult>, CliError> {
369 let rpc_id = parse::rpc_id(maybe_rpc_id);
370 let verbosity = parse::verbosity(verbosity_level);
371 let deploy = with_payment_and_session(deploy_params, payment_params, session_params, false)?;
372 #[allow(deprecated)]
373 crate::speculative_exec(rpc_id, node_address, verbosity, deploy)
374 .await
375 .map_err(CliError::from)
376}
377
378pub fn make_deploy(
389 #[allow(unused_variables)] maybe_output_path: &str,
390 deploy_params: DeployStrParams<'_>,
391 session_params: SessionStrParams<'_>,
392 payment_params: PaymentStrParams<'_>,
393 #[allow(unused_variables)] force: bool,
394) -> Result<Deploy, CliError> {
395 let deploy = with_payment_and_session(deploy_params, payment_params, session_params, true)?;
396 #[cfg(feature = "std-fs-io")]
397 {
398 let output = parse::output_kind(maybe_output_path, force);
399 #[allow(deprecated)]
400 crate::output_deploy(output, &deploy).map_err(CliError::from)?;
401 }
402 Ok(deploy)
403}
404
405#[cfg(feature = "std-fs-io")]
413pub fn sign_deploy_file(
414 input_path: &str,
415 secret_key_path: &str,
416 maybe_output_path: &str,
417 force: bool,
418) -> Result<(), CliError> {
419 let secret_key = parse::secret_key_from_file(secret_key_path)?;
420 let output = parse::output_kind(maybe_output_path, force);
421 #[allow(deprecated)]
422 crate::sign_deploy_file(input_path, &secret_key, output).map_err(CliError::from)
423}
424
425#[cfg(feature = "std-fs-io")]
429pub async fn send_deploy_file(
430 maybe_rpc_id: &str,
431 node_address: &str,
432 verbosity_level: u64,
433 input_path: &str,
434) -> Result<SuccessResponse<PutDeployResult>, CliError> {
435 let rpc_id = parse::rpc_id(maybe_rpc_id);
436 let verbosity = parse::verbosity(verbosity_level);
437 #[allow(deprecated)]
438 let deploy = crate::read_deploy_file(input_path)?;
439 #[allow(deprecated)]
440 crate::put_deploy(rpc_id, node_address, verbosity, deploy)
441 .await
442 .map_err(CliError::from)
443}
444
445#[cfg(feature = "std-fs-io")]
449pub async fn speculative_send_deploy_file(
450 maybe_rpc_id: &str,
451 node_address: &str,
452 verbosity_level: u64,
453 input_path: &str,
454) -> Result<SuccessResponse<SpeculativeExecResult>, CliError> {
455 let rpc_id = parse::rpc_id(maybe_rpc_id);
456 let verbosity = parse::verbosity(verbosity_level);
457 #[allow(deprecated)]
458 let deploy = crate::read_deploy_file(input_path)?;
459 #[allow(deprecated)]
460 crate::speculative_exec(rpc_id, node_address, verbosity, deploy)
461 .await
462 .map_err(CliError::from)
463}
464
465#[allow(clippy::too_many_arguments)]
476pub async fn transfer(
477 maybe_rpc_id: &str,
478 node_address: &str,
479 verbosity_level: u64,
480 amount: &str,
481 target_account: &str,
482 transfer_id: &str,
483 deploy_params: DeployStrParams<'_>,
484 payment_params: PaymentStrParams<'_>,
485) -> Result<SuccessResponse<PutDeployResult>, CliError> {
486 let rpc_id = parse::rpc_id(maybe_rpc_id);
487 let verbosity = parse::verbosity(verbosity_level);
488 let deploy = new_transfer(
489 amount,
490 None,
491 target_account,
492 transfer_id,
493 deploy_params,
494 payment_params,
495 false,
496 )?;
497 #[allow(deprecated)]
498 crate::put_deploy(rpc_id, node_address, verbosity, deploy)
499 .await
500 .map_err(CliError::from)
501}
502
503#[allow(clippy::too_many_arguments)]
515pub async fn speculative_transfer(
516 maybe_rpc_id: &str,
517 node_address: &str,
518 verbosity_level: u64,
519 amount: &str,
520 target_account: &str,
521 transfer_id: &str,
522 deploy_params: DeployStrParams<'_>,
523 payment_params: PaymentStrParams<'_>,
524) -> Result<SuccessResponse<SpeculativeExecResult>, CliError> {
525 let rpc_id = parse::rpc_id(maybe_rpc_id);
526 let verbosity = parse::verbosity(verbosity_level);
527 let deploy = new_transfer(
528 amount,
529 None,
530 target_account,
531 transfer_id,
532 deploy_params,
533 payment_params,
534 false,
535 )?;
536 #[allow(deprecated)]
537 crate::speculative_exec(rpc_id, node_address, verbosity, deploy)
538 .await
539 .map_err(CliError::from)
540}
541
542pub fn make_transfer(
554 #[allow(unused_variables)] maybe_output_path: &str,
555 amount: &str,
556 target_account: &str,
557 transfer_id: &str,
558 deploy_params: DeployStrParams<'_>,
559 payment_params: PaymentStrParams<'_>,
560 #[allow(unused_variables)] force: bool,
561) -> Result<Deploy, CliError> {
562 let deploy = new_transfer(
563 amount,
564 None,
565 target_account,
566 transfer_id,
567 deploy_params,
568 payment_params,
569 true,
570 )?;
571 #[cfg(feature = "std-fs-io")]
572 {
573 let output = parse::output_kind(maybe_output_path, force);
574 #[allow(deprecated)]
575 crate::output_deploy(output, &deploy).map_err(CliError::from)?;
576 }
577 Ok(deploy)
578}
579
580pub fn with_payment_and_session(
582 deploy_params: DeployStrParams,
583 payment_params: PaymentStrParams,
584 session_params: SessionStrParams,
585 allow_unsigned_deploy: bool,
586) -> Result<Deploy, CliError> {
587 let gas_price: u64 = deploy_params
588 .gas_price_tolerance
589 .parse::<u64>()
590 .unwrap_or(DEFAULT_GAS_PRICE);
591 let chain_name = deploy_params.chain_name.to_string();
592 let session = parse::session_executable_deploy_item(session_params)?;
593 let payment = parse::payment_executable_deploy_item(payment_params)?;
594 let timestamp = parse::timestamp(deploy_params.timestamp)?;
595 let ttl = parse::ttl(deploy_params.ttl)?;
596 let maybe_session_account = parse::session_account(deploy_params.session_account)?;
597
598 let mut deploy_builder = DeployBuilder::new(chain_name, session)
599 .with_payment(payment)
600 .with_timestamp(timestamp)
601 .with_gas_price(gas_price)
602 .with_ttl(ttl);
603 let maybe_secret_key = get_maybe_secret_key(
604 deploy_params.secret_key,
605 allow_unsigned_deploy,
606 "with_payment_and_session",
607 )?;
608 if let Some(secret_key) = &maybe_secret_key {
609 deploy_builder = deploy_builder.with_secret_key(secret_key);
610 }
611 if let Some(account) = maybe_session_account {
612 deploy_builder = deploy_builder.with_account(account);
613 }
614
615 let deploy = deploy_builder.build().map_err(crate::Error::from)?;
616 deploy
617 .is_valid_size(MAX_SERIALIZED_SIZE_OF_DEPLOY)
618 .map_err(crate::Error::from)?;
619 Ok(deploy)
620}
621
622pub fn new_transfer(
624 amount: &str,
625 source_purse: Option<URef>,
626 target_account: &str,
627 transfer_id: &str,
628 deploy_params: DeployStrParams,
629 payment_params: PaymentStrParams,
630 allow_unsigned_deploy: bool,
631) -> Result<Deploy, CliError> {
632 let chain_name = deploy_params.chain_name.to_string();
633 let payment = parse::payment_executable_deploy_item(payment_params)?;
634 let amount = U512::from_dec_str(amount).map_err(|err| CliError::FailedToParseUint {
635 context: "new_transfer amount",
636 error: UIntParseError::FromDecStr(err),
637 })?;
638
639 let target = if let Ok(public_key) = PublicKey::from_hex(target_account) {
640 TransferTarget::PublicKey(public_key)
641 } else if let Ok(account_hash) = AccountHash::from_formatted_str(target_account) {
642 TransferTarget::AccountHash(account_hash)
643 } else if let Ok(uref) = URef::from_formatted_str(target_account) {
644 TransferTarget::URef(uref)
645 } else {
646 return Err(CliError::InvalidArgument {
647 context: "new_transfer target_account",
648 error: format!(
649 "allowed types: PublicKey, AccountHash or URef, got {}",
650 target_account
651 ),
652 });
653 };
654
655 let transfer_id = parse::transfer_id(transfer_id)?;
656 let maybe_transfer_id = Some(transfer_id);
657
658 let timestamp = parse::timestamp(deploy_params.timestamp)?;
659 let ttl = parse::ttl(deploy_params.ttl)?;
660 let maybe_session_account = parse::session_account(deploy_params.session_account)?;
661 let gas_price: u64 = deploy_params
662 .gas_price_tolerance
663 .parse::<u64>()
664 .unwrap_or(DEFAULT_GAS_PRICE);
665
666 let mut deploy_builder =
667 DeployBuilder::new_transfer(chain_name, amount, source_purse, target, maybe_transfer_id)
668 .with_payment(payment)
669 .with_timestamp(timestamp)
670 .with_gas_price(gas_price)
671 .with_ttl(ttl);
672
673 let maybe_secret_key = get_maybe_secret_key(
674 deploy_params.secret_key,
675 allow_unsigned_deploy,
676 "new_transfer",
677 )?;
678 if let Some(secret_key) = &maybe_secret_key {
679 deploy_builder = deploy_builder.with_secret_key(secret_key);
680 }
681 if let Some(account) = maybe_session_account {
682 deploy_builder = deploy_builder.with_account(account);
683 }
684 let deploy = deploy_builder.build().map_err(crate::Error::from)?;
685 deploy
686 .is_valid_size(MAX_SERIALIZED_SIZE_OF_DEPLOY)
687 .map_err(crate::Error::from)?;
688 Ok(deploy)
689}