1pub mod deploy_category;
2mod deploy_hash;
3mod deploy_header;
4mod deploy_id;
5mod error;
6mod executable_deploy_item;
7
8use alloc::{collections::BTreeSet, vec::Vec};
9use core::{
10 cmp,
11 fmt::{self, Debug, Display, Formatter},
12 hash,
13};
14
15#[cfg(any(feature = "std", test))]
16use std::convert::TryFrom;
17
18#[cfg(feature = "datasize")]
19use datasize::DataSize;
20#[cfg(any(feature = "std", test))]
21use itertools::Itertools;
22#[cfg(feature = "json-schema")]
23use once_cell::sync::Lazy;
24#[cfg(any(feature = "once_cell", test))]
25use once_cell::sync::OnceCell;
26#[cfg(any(all(feature = "std", feature = "testing"), test))]
27use rand::Rng;
28#[cfg(feature = "json-schema")]
29use schemars::JsonSchema;
30#[cfg(any(feature = "std", test))]
31use serde::{Deserialize, Serialize};
32#[cfg(any(all(feature = "std", feature = "testing"), test))]
33use tracing::{debug, warn};
34
35#[cfg(any(feature = "std", test))]
36use super::{get_lane_for_non_install_wasm, InitiatorAddr, InitiatorAddrAndSecretKey, PricingMode};
37#[cfg(any(
38 all(feature = "std", feature = "testing"),
39 feature = "json-schema",
40 test
41))]
42use crate::runtime_args;
43#[cfg(any(all(feature = "std", feature = "testing"), test))]
44use crate::{
45 bytesrepr::Bytes,
46 system::auction::{
47 ARG_AMOUNT as ARG_AUCTION_AMOUNT, ARG_DELEGATION_RATE, ARG_DELEGATOR, ARG_NEW_VALIDATOR,
48 ARG_PUBLIC_KEY as ARG_AUCTION_PUBLIC_KEY, ARG_VALIDATOR, METHOD_ADD_BID, METHOD_DELEGATE,
49 METHOD_REDELEGATE, METHOD_UNDELEGATE, METHOD_WITHDRAW_BID,
50 },
51 testing::TestRng,
52 transaction::RuntimeArgs,
53 AddressableEntityHash, URef, DEFAULT_MAX_PAYMENT_MOTES, DEFAULT_MIN_TRANSFER_MOTES,
54};
55use crate::{
56 bytesrepr::{self, FromBytes, ToBytes},
57 crypto,
58 transaction::{Approval, ApprovalsHash},
59 Digest, DisplayIter, PublicKey, SecretKey, TimeDiff, Timestamp,
60};
61
62#[cfg(any(feature = "std", test))]
63use crate::{chainspec::PricingHandling, Chainspec, Phase, TransactionV1Config, MINT_LANE_ID};
64#[cfg(any(feature = "std", test))]
65use crate::{system::auction::ARG_AMOUNT, transaction::GasLimited, Gas, Motes, U512};
66pub use deploy_hash::DeployHash;
67pub use deploy_header::DeployHeader;
68pub use deploy_id::DeployId;
69pub use error::{
70 DecodeFromJsonError as DeployDecodeFromJsonError, Error as DeployError,
71 ExcessiveSizeError as DeployExcessiveSizeError, InvalidDeploy,
72};
73pub use executable_deploy_item::{ExecutableDeployItem, ExecutableDeployItemIdentifier};
74
75#[cfg(feature = "json-schema")]
76static DEPLOY: Lazy<Deploy> = Lazy::new(|| {
77 let payment_args = runtime_args! {
78 "amount" => 1000
79 };
80 let payment = ExecutableDeployItem::StoredContractByName {
81 name: String::from("casper-example"),
82 entry_point: String::from("example-entry-point"),
83 args: payment_args,
84 };
85 let session_args = runtime_args! {
86 "amount" => 1000
87 };
88 let session = ExecutableDeployItem::Transfer { args: session_args };
89 let serialized_body = serialize_body(&payment, &session);
90 let body_hash = Digest::hash(serialized_body);
91
92 let secret_key = SecretKey::example();
93 let timestamp = *Timestamp::example();
94 let header = DeployHeader::new(
95 PublicKey::from(secret_key),
96 timestamp,
97 TimeDiff::from_seconds(3_600),
98 1,
99 body_hash,
100 vec![DeployHash::new(Digest::from([1u8; Digest::LENGTH]))],
101 String::from("casper-example"),
102 );
103 let serialized_header = serialize_header(&header);
104 let hash = DeployHash::new(Digest::hash(serialized_header));
105
106 let mut approvals = BTreeSet::new();
107 let approval = Approval::create(&hash.into(), secret_key);
108 approvals.insert(approval);
109
110 Deploy {
111 hash,
112 header,
113 payment,
114 session,
115 approvals,
116 is_valid: OnceCell::new(),
117 }
118});
119
120#[derive(Clone, Eq, Debug)]
122#[cfg_attr(
123 any(feature = "std", test),
124 derive(Serialize, Deserialize),
125 serde(deny_unknown_fields)
126)]
127#[cfg_attr(feature = "datasize", derive(DataSize))]
128#[cfg_attr(
129 feature = "json-schema",
130 derive(JsonSchema),
131 schemars(description = "A signed smart contract.")
132)]
133pub struct Deploy {
134 hash: DeployHash,
135 header: DeployHeader,
136 payment: ExecutableDeployItem,
137 session: ExecutableDeployItem,
138 approvals: BTreeSet<Approval>,
139 #[cfg_attr(any(all(feature = "std", feature = "once_cell"), test), serde(skip))]
140 #[cfg_attr(
141 all(any(feature = "once_cell", test), feature = "datasize"),
142 data_size(skip)
143 )]
144 #[cfg(any(feature = "once_cell", test))]
145 is_valid: OnceCell<Result<(), InvalidDeploy>>,
146}
147
148impl Deploy {
149 pub fn new(
151 hash: DeployHash,
152 header: DeployHeader,
153 payment: ExecutableDeployItem,
154 session: ExecutableDeployItem,
155 ) -> Deploy {
156 Deploy {
157 hash,
158 header,
159 payment,
160 session,
161 approvals: BTreeSet::new(),
162 #[cfg(any(feature = "once_cell", test))]
163 is_valid: OnceCell::new(),
164 }
165 }
166 #[cfg(any(all(feature = "std", feature = "testing"), test))]
168 #[allow(clippy::too_many_arguments)]
169 pub fn new_signed(
170 timestamp: Timestamp,
171 ttl: TimeDiff,
172 gas_price: u64,
173 dependencies: Vec<DeployHash>,
174 chain_name: String,
175 payment: ExecutableDeployItem,
176 session: ExecutableDeployItem,
177 secret_key: &SecretKey,
178 account: Option<PublicKey>,
179 ) -> Deploy {
180 let account_and_secret_key = match account {
181 Some(account) => InitiatorAddrAndSecretKey::Both {
182 initiator_addr: InitiatorAddr::PublicKey(account),
183 secret_key,
184 },
185 None => InitiatorAddrAndSecretKey::SecretKey(secret_key),
186 };
187
188 Deploy::build(
189 timestamp,
190 ttl,
191 gas_price,
192 dependencies,
193 chain_name,
194 payment,
195 session,
196 account_and_secret_key,
197 )
198 }
199
200 #[cfg(any(feature = "std", test))]
201 #[allow(clippy::too_many_arguments)]
202 fn build(
203 timestamp: Timestamp,
204 ttl: TimeDiff,
205 gas_price: u64,
206 dependencies: Vec<DeployHash>,
207 chain_name: String,
208 payment: ExecutableDeployItem,
209 session: ExecutableDeployItem,
210 initiator_addr_and_secret_key: InitiatorAddrAndSecretKey,
211 ) -> Deploy {
212 let serialized_body = serialize_body(&payment, &session);
213 let body_hash = Digest::hash(serialized_body);
214
215 let account = match initiator_addr_and_secret_key.initiator_addr() {
216 InitiatorAddr::PublicKey(public_key) => public_key,
217 InitiatorAddr::AccountHash(_) => unreachable!(),
218 };
219
220 let dependencies = dependencies.into_iter().unique().collect();
221 let header = DeployHeader::new(
222 account,
223 timestamp,
224 ttl,
225 gas_price,
226 body_hash,
227 dependencies,
228 chain_name,
229 );
230 let serialized_header = serialize_header(&header);
231 let hash = DeployHash::new(Digest::hash(serialized_header));
232
233 let mut deploy = Deploy {
234 hash,
235 header,
236 payment,
237 session,
238 approvals: BTreeSet::new(),
239 #[cfg(any(feature = "once_cell", test))]
240 is_valid: OnceCell::new(),
241 };
242
243 if let Some(secret_key) = initiator_addr_and_secret_key.secret_key() {
244 deploy.sign(secret_key);
245 }
246 deploy
247 }
248
249 pub fn hash(&self) -> &DeployHash {
251 &self.hash
252 }
253
254 pub fn account(&self) -> &PublicKey {
256 self.header.account()
257 }
258
259 pub fn timestamp(&self) -> Timestamp {
261 self.header.timestamp()
262 }
263
264 pub fn ttl(&self) -> TimeDiff {
268 self.header.ttl()
269 }
270
271 pub fn expired(&self, current_instant: Timestamp) -> bool {
273 self.header.expired(current_instant)
274 }
275
276 pub fn gas_price(&self) -> u64 {
278 self.header.gas_price()
279 }
280
281 pub fn body_hash(&self) -> &Digest {
283 self.header.body_hash()
284 }
285
286 pub fn chain_name(&self) -> &str {
288 self.header.chain_name()
289 }
290
291 pub fn header(&self) -> &DeployHeader {
293 &self.header
294 }
295
296 pub fn take_header(self) -> DeployHeader {
298 self.header
299 }
300
301 pub fn payment(&self) -> &ExecutableDeployItem {
303 &self.payment
304 }
305
306 pub fn session(&self) -> &ExecutableDeployItem {
308 &self.session
309 }
310
311 pub fn approvals(&self) -> &BTreeSet<Approval> {
313 &self.approvals
314 }
315
316 pub fn destructure(
318 self,
319 ) -> (
320 DeployHash,
321 DeployHeader,
322 ExecutableDeployItem,
323 ExecutableDeployItem,
324 BTreeSet<Approval>,
325 ) {
326 (
327 self.hash,
328 self.header,
329 self.payment,
330 self.session,
331 self.approvals,
332 )
333 }
334
335 pub fn sign(&mut self, secret_key: &SecretKey) {
337 let approval = Approval::create(&self.hash.into(), secret_key);
338 self.approvals.insert(approval);
339 }
340
341 pub fn compute_approvals_hash(&self) -> Result<ApprovalsHash, bytesrepr::Error> {
343 ApprovalsHash::compute(&self.approvals)
344 }
345
346 #[cfg(any(feature = "std", test))]
349 pub fn is_valid_size(&self, max_transaction_size: u32) -> Result<(), DeployExcessiveSizeError> {
350 let deploy_size = self.serialized_length();
351 if deploy_size > max_transaction_size as usize {
352 return Err(DeployExcessiveSizeError {
353 max_transaction_size,
354 actual_deploy_size: deploy_size,
355 });
356 }
357 Ok(())
358 }
359
360 pub fn has_valid_hash(&self) -> Result<(), InvalidDeploy> {
363 let serialized_body = serialize_body(&self.payment, &self.session);
364 let body_hash = Digest::hash(serialized_body);
365 if body_hash != *self.header.body_hash() {
366 #[cfg(any(all(feature = "std", feature = "testing"), test))]
367 warn!(?self, ?body_hash, "invalid deploy body hash");
368 return Err(InvalidDeploy::InvalidBodyHash);
369 }
370
371 let serialized_header = serialize_header(&self.header);
372 let hash = DeployHash::new(Digest::hash(serialized_header));
373 if hash != self.hash {
374 #[cfg(any(all(feature = "std", feature = "testing"), test))]
375 warn!(?self, ?hash, "invalid deploy hash");
376 return Err(InvalidDeploy::InvalidDeployHash);
377 }
378 Ok(())
379 }
380
381 pub fn is_valid(&self) -> Result<(), InvalidDeploy> {
387 #[cfg(any(feature = "once_cell", test))]
388 return self.is_valid.get_or_init(|| validate_deploy(self)).clone();
389
390 #[cfg(not(any(feature = "once_cell", test)))]
391 validate_deploy(self)
392 }
393
394 pub fn is_transfer(&self) -> bool {
396 self.session.is_transfer()
397 }
398
399 pub fn is_account_session(&self) -> bool {
401 true
403 }
404
405 #[cfg(any(all(feature = "std", feature = "testing"), test))]
409 pub fn is_config_compliant(
410 &self,
411 chainspec: &Chainspec,
412 timestamp_leeway: TimeDiff,
413 at: Timestamp,
414 ) -> Result<(), InvalidDeploy> {
415 let config = &chainspec.transaction_config;
416
417 if !config.runtime_config.vm_casper_v1 {
418 return Err(InvalidDeploy::InvalidRuntime);
420 }
421 let pricing_handling = chainspec.core_config.pricing_handling;
422 let v1_config = &chainspec.transaction_config.transaction_v1_config;
423 let lane_id = calculate_lane_id_for_deploy(self, pricing_handling, v1_config)?;
424 let lane_definition = v1_config
425 .get_lane_by_id(lane_id)
426 .ok_or(InvalidDeploy::NoLaneMatch)?;
427
428 self.is_valid_size(lane_definition.max_transaction_length as u32)?;
429
430 let header = self.header();
431 let chain_name = &chainspec.network_config.name;
432
433 if header.chain_name() != chain_name {
434 debug!(
435 deploy_hash = %self.hash(),
436 deploy_header = %header,
437 chain_name = %header.chain_name(),
438 "invalid chain identifier"
439 );
440 return Err(InvalidDeploy::InvalidChainName {
441 expected: chain_name.to_string(),
442 got: header.chain_name().to_string(),
443 });
444 }
445
446 let min_gas_price = chainspec.vacancy_config.min_gas_price;
447 let gas_price_tolerance = self.gas_price_tolerance()?;
448 if gas_price_tolerance < min_gas_price {
449 return Err(InvalidDeploy::GasPriceToleranceTooLow {
450 min_gas_price_tolerance: min_gas_price,
451 provided_gas_price_tolerance: gas_price_tolerance,
452 });
453 }
454
455 header.is_valid(config, timestamp_leeway, at, &self.hash)?;
456
457 let max_associated_keys = chainspec.core_config.max_associated_keys;
458 if self.approvals.len() > max_associated_keys as usize {
459 debug!(
460 deploy_hash = %self.hash(),
461 number_of_associated_keys = %self.approvals.len(),
462 max_associated_keys = %max_associated_keys,
463 "number of associated keys exceeds the maximum limit"
464 );
465 return Err(InvalidDeploy::ExcessiveApprovals {
466 got: self.approvals.len() as u32,
467 max_associated_keys,
468 });
469 }
470
471 let gas_limit = self.gas_limit(chainspec)?;
472 if gas_limit == Gas::zero() {
473 return Err(InvalidDeploy::InvalidPaymentAmount);
474 }
475
476 let block_gas_limit = Gas::new(config.block_gas_limit);
477 if gas_limit > block_gas_limit {
478 debug!(
479 payment_amount = %gas_limit,
480 %block_gas_limit,
481 "transaction gas limit exceeds block gas limit"
482 );
483 return Err(InvalidDeploy::ExceededBlockGasLimit {
484 block_gas_limit: config.block_gas_limit,
485 got: Box::new(gas_limit.value()),
486 });
487 }
488 let lane_limit = lane_definition.max_transaction_gas_limit;
489 let lane_limit_as_gas = Gas::new(lane_limit);
490 if gas_limit > lane_limit_as_gas {
491 debug!(
492 calculated_lane = lane_definition.id,
493 payment_amount = %gas_limit,
494 %block_gas_limit,
495 "transaction gas limit exceeds lane limit"
496 );
497 return Err(InvalidDeploy::ExceededLaneGasLimit {
498 lane_gas_limit: lane_limit,
499 got: Box::new(gas_limit.value()),
500 });
501 }
502
503 let payment_args_length = self.payment().args().serialized_length();
504 if payment_args_length > config.deploy_config.payment_args_max_length as usize {
505 debug!(
506 payment_args_length,
507 payment_args_max_length = config.deploy_config.payment_args_max_length,
508 "payment args excessive"
509 );
510 return Err(InvalidDeploy::ExcessivePaymentArgsLength {
511 max_length: config.deploy_config.payment_args_max_length as usize,
512 got: payment_args_length,
513 });
514 }
515
516 let session_args_length = self.session().args().serialized_length();
517 if session_args_length > config.deploy_config.session_args_max_length as usize {
518 debug!(
519 session_args_length,
520 session_args_max_length = config.deploy_config.session_args_max_length,
521 "session args excessive"
522 );
523 return Err(InvalidDeploy::ExcessiveSessionArgsLength {
524 max_length: config.deploy_config.session_args_max_length as usize,
525 got: session_args_length,
526 });
527 }
528
529 if self.session().is_transfer() {
530 let item = self.session().clone();
531 let attempted = item
532 .args()
533 .get(ARG_AMOUNT)
534 .ok_or_else(|| {
535 debug!("missing transfer 'amount' runtime argument");
536 InvalidDeploy::MissingTransferAmount
537 })?
538 .clone()
539 .into_t::<U512>()
540 .map_err(|_| {
541 debug!("failed to parse transfer 'amount' runtime argument as a U512");
542 InvalidDeploy::FailedToParseTransferAmount
543 })?;
544 let minimum = U512::from(config.native_transfer_minimum_motes);
545 if attempted < minimum {
546 debug!(
547 minimum = %config.native_transfer_minimum_motes,
548 amount = %attempted,
549 "insufficient transfer amount"
550 );
551 return Err(InvalidDeploy::InsufficientTransferAmount {
552 minimum: Box::new(minimum),
553 attempted: Box::new(attempted),
554 });
555 }
556 } else {
557 let payment_args = self.payment().args();
558 let payment_amount = payment_args
559 .get(ARG_AMOUNT)
560 .ok_or_else(|| {
561 debug!("missing transfer 'amount' runtime argument");
562 InvalidDeploy::MissingTransferAmount
563 })?
564 .clone()
565 .into_t::<U512>()
566 .map_err(|_| {
567 debug!("failed to parse transfer 'amount' runtime argument as a U512");
568 InvalidDeploy::FailedToParseTransferAmount
569 })?;
570 if payment_amount < U512::from(chainspec.core_config.baseline_motes_amount) {
571 return Err(InvalidDeploy::InvalidPaymentAmount);
572 }
573 }
574
575 Ok(())
576 }
577
578 #[doc(hidden)]
583 pub fn with_approvals(mut self, approvals: BTreeSet<Approval>) -> Self {
584 self.approvals = approvals;
585 self
586 }
587
588 #[doc(hidden)]
590 #[cfg(feature = "json-schema")]
591 pub fn example() -> &'static Self {
592 &DEPLOY
593 }
594
595 #[cfg(any(all(feature = "std", feature = "testing"), test))]
597 pub fn random(rng: &mut TestRng) -> Self {
598 let timestamp = Timestamp::random(rng);
599 let ttl = TimeDiff::from_seconds(rng.gen_range(60..300));
600 Deploy::random_with_timestamp_and_ttl(rng, timestamp, ttl)
601 }
602
603 #[cfg(any(all(feature = "std", feature = "testing"), test))]
605 pub fn random_with_timestamp_and_ttl(
606 rng: &mut TestRng,
607 timestamp: Timestamp,
608 ttl: TimeDiff,
609 ) -> Self {
610 let gas_price = rng.gen_range(1..100);
611
612 let dependencies = vec![];
613 let chain_name = String::from("casper-example");
614
615 let payment_args = runtime_args! {
617 "amount" => U512::from(DEFAULT_MAX_PAYMENT_MOTES),
618 };
619 let payment = ExecutableDeployItem::StoredContractByName {
620 name: String::from("casper-example"),
621 entry_point: String::from("example-entry-point"),
622 args: payment_args,
623 };
624
625 let session = rng.gen();
626
627 let secret_key = SecretKey::random(rng);
628
629 Deploy::new_signed(
630 timestamp,
631 ttl,
632 gas_price,
633 dependencies,
634 chain_name,
635 payment,
636 session,
637 &secret_key,
638 None,
639 )
640 }
641
642 #[cfg(any(all(feature = "std", feature = "testing"), test))]
645 pub fn invalidate(&mut self) {
646 self.header.invalidate();
647 }
648
649 #[cfg(any(all(feature = "std", feature = "testing"), test))]
651 pub fn random_valid_native_transfer(rng: &mut TestRng) -> Self {
652 let timestamp = Timestamp::now();
653 let ttl = TimeDiff::from_seconds(rng.gen_range(60..300));
654 Self::random_valid_native_transfer_with_timestamp_and_ttl(rng, timestamp, ttl)
655 }
656
657 #[cfg(any(all(feature = "std", feature = "testing"), test))]
659 pub fn random_valid_native_transfer_with_timestamp_and_ttl(
660 rng: &mut TestRng,
661 timestamp: Timestamp,
662 ttl: TimeDiff,
663 ) -> Self {
664 let deploy = Self::random_with_timestamp_and_ttl(rng, timestamp, ttl);
665 let transfer_args = runtime_args! {
666 "amount" => U512::from(DEFAULT_MIN_TRANSFER_MOTES),
667 "source" => PublicKey::random(rng).to_account_hash(),
668 "target" => PublicKey::random(rng).to_account_hash(),
669 };
670 let payment_amount = 10_000_000_000u64;
671 let payment_args = runtime_args! {
672 "amount" => U512::from(payment_amount),
673 };
674 let session = ExecutableDeployItem::Transfer {
675 args: transfer_args,
676 };
677 let payment = ExecutableDeployItem::ModuleBytes {
678 module_bytes: Bytes::new(),
679 args: payment_args,
680 };
681 let secret_key = SecretKey::random(rng);
682 Deploy::new_signed(
683 timestamp,
684 ttl,
685 deploy.header.gas_price(),
686 deploy.header.dependencies().clone(),
687 deploy.header.chain_name().to_string(),
688 payment,
689 session,
690 &secret_key,
691 None,
692 )
693 }
694
695 #[cfg(any(all(feature = "std", feature = "testing"), test))]
697 pub fn random_valid_native_transfer_without_deps(rng: &mut TestRng) -> Self {
698 let deploy = Self::random(rng);
699 let transfer_args = runtime_args! {
700 "amount" => U512::from(DEFAULT_MIN_TRANSFER_MOTES),
701 "source" => PublicKey::random(rng).to_account_hash(),
702 "target" => PublicKey::random(rng).to_account_hash(),
703 };
704 let payment_args = runtime_args! {
705 "amount" => U512::from(10),
706 };
707 let session = ExecutableDeployItem::Transfer {
708 args: transfer_args,
709 };
710 let payment = ExecutableDeployItem::ModuleBytes {
711 module_bytes: Bytes::new(),
712 args: payment_args,
713 };
714 let secret_key = SecretKey::random(rng);
715 Deploy::new_signed(
716 Timestamp::now(),
717 deploy.header.ttl(),
718 deploy.header.gas_price(),
719 vec![],
720 deploy.header.chain_name().to_string(),
721 payment,
722 session,
723 &secret_key,
724 None,
725 )
726 }
727
728 #[cfg(any(all(feature = "std", feature = "testing"), test))]
730 pub fn random_without_payment_amount(rng: &mut TestRng) -> Self {
731 let payment = ExecutableDeployItem::ModuleBytes {
732 module_bytes: Bytes::new(),
733 args: RuntimeArgs::default(),
734 };
735 Self::random_transfer_with_payment(rng, payment)
736 }
737
738 #[cfg(any(all(feature = "std", feature = "testing"), test))]
740 pub fn random_with_mangled_payment_amount(rng: &mut TestRng) -> Self {
741 let payment_args = runtime_args! {
742 "amount" => "invalid-argument"
743 };
744 let payment = ExecutableDeployItem::ModuleBytes {
745 module_bytes: Bytes::new(),
746 args: payment_args,
747 };
748 Self::random_transfer_with_payment(rng, payment)
749 }
750
751 #[cfg(any(all(feature = "std", feature = "testing"), test))]
753 pub fn random_with_payment_one(rng: &mut TestRng) -> Self {
754 let timestamp = Timestamp::now();
755 let ttl = TimeDiff::from_seconds(rng.gen_range(60..3600));
756 let payment_args = runtime_args! {
757 "amount" => U512::one()
758 };
759 let payment = ExecutableDeployItem::ModuleBytes {
760 module_bytes: Bytes::new(),
761 args: payment_args,
762 };
763 let gas_price = rng.gen_range(1..4);
764
765 let dependencies = vec![];
766 let chain_name = String::from("casper-example");
767 let session = rng.gen();
768
769 let secret_key = SecretKey::random(rng);
770
771 Deploy::new_signed(
772 timestamp,
773 ttl,
774 gas_price,
775 dependencies,
776 chain_name,
777 payment,
778 session,
779 &secret_key,
780 None,
781 )
782 }
783
784 #[cfg(any(all(feature = "std", feature = "testing"), test))]
786 pub fn random_with_insufficient_payment_amount(
787 rng: &mut TestRng,
788 payment_amount: U512,
789 ) -> Self {
790 let payment_args = runtime_args! {
791 "amount" => payment_amount
792 };
793 let payment = ExecutableDeployItem::ModuleBytes {
794 module_bytes: Bytes::new(),
795 args: payment_args,
796 };
797 Self::random_transfer_with_payment(rng, payment)
798 }
799
800 #[cfg(any(all(feature = "std", feature = "testing"), test))]
802 pub fn random_with_oversized_payment_amount(rng: &mut TestRng) -> Self {
803 let payment_args = runtime_args! {
804 "amount" => U512::from(1_000_000_000_001u64)
805 };
806 let payment = ExecutableDeployItem::ModuleBytes {
807 module_bytes: Bytes::new(),
808 args: payment_args,
809 };
810
811 let session = ExecutableDeployItem::StoredContractByName {
812 name: "Test".to_string(),
813 entry_point: "call".to_string(),
814 args: Default::default(),
815 };
816
817 let deploy = Self::random_valid_native_transfer(rng);
818 let secret_key = SecretKey::random(rng);
819
820 Deploy::new_signed(
821 deploy.header.timestamp(),
822 deploy.header.ttl(),
823 deploy.header.gas_price(),
824 deploy.header.dependencies().clone(),
825 deploy.header.chain_name().to_string(),
826 payment,
827 session,
828 &secret_key,
829 None,
830 )
831 }
832
833 #[cfg(any(all(feature = "std", feature = "testing"), test))]
835 pub fn random_with_valid_custom_payment_contract_by_name(rng: &mut TestRng) -> Self {
836 let payment = ExecutableDeployItem::StoredContractByName {
837 name: "Test".to_string(),
838 entry_point: "call".to_string(),
839 args: Default::default(),
840 };
841 Self::random_transfer_with_payment(rng, payment)
842 }
843
844 #[cfg(any(all(feature = "std", feature = "testing"), test))]
847 pub fn random_with_missing_payment_contract_by_hash(rng: &mut TestRng) -> Self {
848 let payment = ExecutableDeployItem::StoredContractByHash {
849 hash: [19; 32].into(),
850 entry_point: "call".to_string(),
851 args: Default::default(),
852 };
853 Self::random_transfer_with_payment(rng, payment)
854 }
855
856 #[cfg(any(all(feature = "std", feature = "testing"), test))]
859 pub fn random_with_missing_entry_point_in_payment_contract(rng: &mut TestRng) -> Self {
860 let payment = ExecutableDeployItem::StoredContractByHash {
861 hash: [19; 32].into(),
862 entry_point: "non-existent-entry-point".to_string(),
863 args: Default::default(),
864 };
865 Self::random_transfer_with_payment(rng, payment)
866 }
867
868 #[cfg(any(all(feature = "std", feature = "testing"), test))]
871 pub fn random_with_versioned_payment_package_by_name(
872 version: Option<u32>,
873 rng: &mut TestRng,
874 ) -> Self {
875 let payment = ExecutableDeployItem::StoredVersionedContractByName {
876 name: "Test".to_string(),
877 version,
878 entry_point: "call".to_string(),
879 args: Default::default(),
880 };
881 Self::random_transfer_with_payment(rng, payment)
882 }
883
884 #[cfg(any(all(feature = "std", feature = "testing"), test))]
887 pub fn random_with_valid_custom_payment_package_by_name(rng: &mut TestRng) -> Self {
888 Self::random_with_versioned_payment_package_by_name(None, rng)
889 }
890
891 #[cfg(any(all(feature = "std", feature = "testing"), test))]
894 pub fn random_with_missing_payment_package_by_hash(rng: &mut TestRng) -> Self {
895 Self::random_with_payment_package_version_by_hash(None, rng)
896 }
897
898 #[cfg(any(all(feature = "std", feature = "testing"), test))]
901 pub fn random_with_nonexistent_contract_version_in_payment_package(rng: &mut TestRng) -> Self {
902 let payment = ExecutableDeployItem::StoredVersionedContractByHash {
903 hash: [19; 32].into(),
904 version: Some(6u32),
905 entry_point: "non-existent-entry-point".to_string(),
906 args: Default::default(),
907 };
908 Self::random_transfer_with_payment(rng, payment)
909 }
910
911 #[cfg(any(all(feature = "std", feature = "testing"), test))]
914 pub fn random_with_payment_package_version_by_hash(
915 version: Option<u32>,
916 rng: &mut TestRng,
917 ) -> Self {
918 let payment = ExecutableDeployItem::StoredVersionedContractByHash {
919 hash: Default::default(),
920 version,
921 entry_point: "call".to_string(),
922 args: Default::default(),
923 };
924 Self::random_transfer_with_payment(rng, payment)
925 }
926
927 #[cfg(any(all(feature = "std", feature = "testing"), test))]
929 pub fn random_with_valid_session_contract_by_name(rng: &mut TestRng) -> Self {
930 let session = ExecutableDeployItem::StoredContractByName {
931 name: "Test".to_string(),
932 entry_point: "call".to_string(),
933 args: Default::default(),
934 };
935 Self::random_transfer_with_session(rng, session)
936 }
937
938 #[cfg(any(all(feature = "std", feature = "testing"), test))]
941 pub fn random_with_missing_session_contract_by_hash(rng: &mut TestRng) -> Self {
942 let session = ExecutableDeployItem::StoredContractByHash {
943 hash: Default::default(),
944 entry_point: "call".to_string(),
945 args: Default::default(),
946 };
947 Self::random_transfer_with_session(rng, session)
948 }
949
950 #[cfg(any(all(feature = "std", feature = "testing"), test))]
953 pub fn random_with_missing_entry_point_in_session_contract(rng: &mut TestRng) -> Self {
954 let timestamp = Timestamp::now();
955 let ttl = TimeDiff::from_seconds(rng.gen_range(60..3600));
956 let session = ExecutableDeployItem::StoredContractByHash {
957 hash: [19; 32].into(),
958 entry_point: "non-existent-entry-point".to_string(),
959 args: Default::default(),
960 };
961
962 let payment_amount = 10_000_000_000u64;
963 let payment_args = runtime_args! {
964 "amount" => U512::from(payment_amount)
965 };
966 let payment = ExecutableDeployItem::ModuleBytes {
967 module_bytes: Bytes::new(),
968 args: payment_args,
969 };
970 let gas_price = rng.gen_range(1..4);
971
972 let dependencies = vec![];
973 let chain_name = String::from("casper-example");
974
975 let secret_key = SecretKey::random(rng);
976
977 Deploy::new_signed(
978 timestamp,
979 ttl,
980 gas_price,
981 dependencies,
982 chain_name,
983 payment,
984 session,
985 &secret_key,
986 None,
987 )
988 }
989
990 #[cfg(any(all(feature = "std", feature = "testing"), test))]
993 pub fn random_with_valid_session_package_by_name(rng: &mut TestRng) -> Self {
994 Self::random_with_versioned_session_package_by_name(None, rng)
995 }
996
997 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1000 pub fn random_with_versioned_session_package_by_name(
1001 version: Option<u32>,
1002 rng: &mut TestRng,
1003 ) -> Self {
1004 let session = ExecutableDeployItem::StoredVersionedContractByName {
1005 name: "Test".to_string(),
1006 version,
1007 entry_point: "call".to_string(),
1008 args: Default::default(),
1009 };
1010 Self::random_transfer_with_session(rng, session)
1011 }
1012
1013 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1016 pub fn random_contract_by_name(
1017 rng: &mut TestRng,
1018 maybe_secret_key: Option<SecretKey>,
1019 maybe_contract_name: Option<String>,
1020 maybe_entry_point_name: Option<String>,
1021 maybe_timestamp: Option<Timestamp>,
1022 maybe_ttl: Option<TimeDiff>,
1023 ) -> Self {
1024 let payment_args = runtime_args! {
1025 "amount" => U512::from(10),
1026 };
1027 let payment = ExecutableDeployItem::ModuleBytes {
1028 module_bytes: Bytes::new(),
1029 args: payment_args,
1030 };
1031 let contract_name = maybe_contract_name.unwrap_or_else(|| "Test".to_string());
1032 let entry_point_name = maybe_entry_point_name.unwrap_or_else(|| "Test".to_string());
1033 let session = ExecutableDeployItem::StoredVersionedContractByName {
1034 name: contract_name,
1035 version: None,
1036 entry_point: entry_point_name,
1037 args: Default::default(),
1038 };
1039 let secret_key = match maybe_secret_key {
1040 None => SecretKey::random(rng),
1041 Some(secret_key) => secret_key,
1042 };
1043 let timestamp = maybe_timestamp.unwrap_or_else(Timestamp::now);
1044 let ttl = match maybe_ttl {
1045 None => TimeDiff::from_seconds(rng.gen_range(60..3600)),
1046 Some(ttl) => ttl,
1047 };
1048 Deploy::new_signed(
1049 timestamp,
1050 ttl,
1051 1,
1052 vec![],
1053 "test_chain".to_string(),
1054 payment,
1055 session,
1056 &secret_key,
1057 None,
1058 )
1059 }
1060
1061 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1064 pub fn random_with_missing_session_package_by_hash(rng: &mut TestRng) -> Self {
1065 Self::random_with_versioned_session_package_by_hash(None, rng)
1066 }
1067
1068 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1069 pub fn random_with_versioned_session_package_by_hash(
1070 version: Option<u32>,
1071 rng: &mut TestRng,
1072 ) -> Self {
1073 let session = ExecutableDeployItem::StoredVersionedContractByHash {
1074 hash: Default::default(),
1075 version,
1076 entry_point: "call".to_string(),
1077 args: Default::default(),
1078 };
1079 Self::random_transfer_with_session(rng, session)
1080 }
1081
1082 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1084 pub fn random_without_transfer_target(rng: &mut TestRng) -> Self {
1085 let transfer_args = runtime_args! {
1086 "amount" => U512::from(DEFAULT_MIN_TRANSFER_MOTES),
1087 "source" => PublicKey::random(rng).to_account_hash(),
1088 };
1089 let session = ExecutableDeployItem::Transfer {
1090 args: transfer_args,
1091 };
1092 Self::random_transfer_with_session(rng, session)
1093 }
1094
1095 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1097 pub fn random_without_transfer_amount(rng: &mut TestRng) -> Self {
1098 let transfer_args = runtime_args! {
1099 "source" => PublicKey::random(rng).to_account_hash(),
1100 "target" => PublicKey::random(rng).to_account_hash(),
1101 };
1102 let session = ExecutableDeployItem::Transfer {
1103 args: transfer_args,
1104 };
1105 Self::random_transfer_with_session(rng, session)
1106 }
1107
1108 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1110 pub fn random_with_mangled_transfer_amount(rng: &mut TestRng) -> Self {
1111 let transfer_args = runtime_args! {
1112 "amount" => "mangled-transfer-amount",
1113 "source" => PublicKey::random(rng).to_account_hash(),
1114 "target" => PublicKey::random(rng).to_account_hash(),
1115 };
1116 let session = ExecutableDeployItem::Transfer {
1117 args: transfer_args,
1118 };
1119 Self::random_transfer_with_session(rng, session)
1120 }
1121
1122 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1124 pub fn random_with_empty_session_module_bytes(rng: &mut TestRng) -> Self {
1125 let session = ExecutableDeployItem::ModuleBytes {
1126 module_bytes: Bytes::new(),
1127 args: Default::default(),
1128 };
1129 let timestamp = Timestamp::now();
1130 let ttl = TimeDiff::from_seconds(rng.gen_range(60..3600));
1131 let amount = 10_000_000_000u64;
1132 let payment_args = runtime_args! {
1133 "amount" => U512::from(amount)
1134 };
1135 let payment = ExecutableDeployItem::ModuleBytes {
1136 module_bytes: Bytes::new(),
1137 args: payment_args,
1138 };
1139 let gas_price = 1;
1140
1141 let dependencies = vec![];
1142 let chain_name = String::from("casper-example");
1143
1144 let secret_key = SecretKey::random(rng);
1145
1146 Deploy::new_signed(
1147 timestamp,
1148 ttl,
1149 gas_price,
1150 dependencies,
1151 chain_name,
1152 payment,
1153 session,
1154 &secret_key,
1155 None,
1156 )
1157 }
1158
1159 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1161 pub fn random_expired_deploy(rng: &mut TestRng) -> Self {
1162 let deploy = Self::random_valid_native_transfer(rng);
1163 let secret_key = SecretKey::random(rng);
1164
1165 Deploy::new_signed(
1166 Timestamp::zero(),
1167 TimeDiff::from_seconds(1u32),
1168 deploy.header.gas_price(),
1169 deploy.header.dependencies().clone(),
1170 deploy.header.chain_name().to_string(),
1171 deploy.payment,
1172 deploy.session,
1173 &secret_key,
1174 None,
1175 )
1176 }
1177
1178 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1180 pub fn random_with_native_transfer_in_payment_logic(rng: &mut TestRng) -> Self {
1181 let transfer_args = runtime_args! {
1182 "amount" => U512::from(DEFAULT_MIN_TRANSFER_MOTES),
1183 "source" => PublicKey::random(rng).to_account_hash(),
1184 "target" => PublicKey::random(rng).to_account_hash(),
1185 };
1186 let payment = ExecutableDeployItem::Transfer {
1187 args: transfer_args,
1188 };
1189 Self::random_transfer_with_payment(rng, payment)
1190 }
1191
1192 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1193 fn random_transfer_with_payment(rng: &mut TestRng, payment: ExecutableDeployItem) -> Self {
1194 let deploy = Self::random_valid_native_transfer(rng);
1195 let secret_key = SecretKey::random(rng);
1196
1197 Deploy::new_signed(
1198 deploy.header.timestamp(),
1199 deploy.header.ttl(),
1200 deploy.header.gas_price(),
1201 deploy.header.dependencies().clone(),
1202 deploy.header.chain_name().to_string(),
1203 payment,
1204 deploy.session,
1205 &secret_key,
1206 None,
1207 )
1208 }
1209
1210 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1211 fn random_transfer_with_session(rng: &mut TestRng, session: ExecutableDeployItem) -> Self {
1212 let deploy = Self::random_valid_native_transfer(rng);
1213 let secret_key = SecretKey::random(rng);
1214
1215 Deploy::new_signed(
1216 deploy.header.timestamp(),
1217 deploy.header.ttl(),
1218 deploy.header.gas_price(),
1219 deploy.header.dependencies().clone(),
1220 deploy.header.chain_name().to_string(),
1221 deploy.payment,
1222 session,
1223 &secret_key,
1224 None,
1225 )
1226 }
1227
1228 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1230 pub fn random_with_gas_price(rng: &mut TestRng, gas_price: u64) -> Self {
1231 let deploy = Self::random(rng);
1232 let secret_key = SecretKey::random(rng);
1233
1234 Deploy::new_signed(
1235 deploy.header.timestamp(),
1236 deploy.header.ttl(),
1237 gas_price,
1238 deploy.header.dependencies().clone(),
1239 deploy.header.chain_name().to_string(),
1240 deploy.payment,
1241 deploy.session,
1242 &secret_key,
1243 None,
1244 )
1245 }
1246
1247 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1249 pub fn add_bid(
1250 chain_name: String,
1251 auction_contract_hash: AddressableEntityHash,
1252 public_key: PublicKey,
1253 bid_amount: U512,
1254 delegation_rate: u8,
1255 timestamp: Timestamp,
1256 ttl: TimeDiff,
1257 ) -> Self {
1258 let payment = ExecutableDeployItem::ModuleBytes {
1259 module_bytes: Bytes::new(),
1260 args: runtime_args! { ARG_AMOUNT => U512::from(100_000_000_000u64) },
1261 };
1262 let args = runtime_args! {
1263 ARG_AUCTION_AMOUNT => bid_amount,
1264 ARG_AUCTION_PUBLIC_KEY => public_key.clone(),
1265 ARG_DELEGATION_RATE => delegation_rate,
1266 };
1267 let session = ExecutableDeployItem::StoredContractByHash {
1268 hash: auction_contract_hash.into(),
1269 entry_point: METHOD_ADD_BID.to_string(),
1270 args,
1271 };
1272
1273 Deploy::build(
1274 timestamp,
1275 ttl,
1276 1,
1277 vec![],
1278 chain_name,
1279 payment,
1280 session,
1281 InitiatorAddrAndSecretKey::InitiatorAddr(InitiatorAddr::PublicKey(public_key)),
1282 )
1283 }
1284
1285 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1287 pub fn withdraw_bid(
1288 chain_name: String,
1289 auction_contract_hash: AddressableEntityHash,
1290 public_key: PublicKey,
1291 amount: U512,
1292 timestamp: Timestamp,
1293 ttl: TimeDiff,
1294 ) -> Self {
1295 let payment = ExecutableDeployItem::ModuleBytes {
1296 module_bytes: Bytes::new(),
1297 args: runtime_args! { ARG_AMOUNT => U512::from(3_000_000_000_u64) },
1298 };
1299 let args = runtime_args! {
1300 ARG_AUCTION_AMOUNT => amount,
1301 ARG_AUCTION_PUBLIC_KEY => public_key.clone(),
1302 };
1303 let session = ExecutableDeployItem::StoredContractByHash {
1304 hash: auction_contract_hash.into(),
1305 entry_point: METHOD_WITHDRAW_BID.to_string(),
1306 args,
1307 };
1308
1309 Deploy::build(
1310 timestamp,
1311 ttl,
1312 1,
1313 vec![],
1314 chain_name,
1315 payment,
1316 session,
1317 InitiatorAddrAndSecretKey::InitiatorAddr(InitiatorAddr::PublicKey(public_key)),
1318 )
1319 }
1320
1321 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1323 pub fn delegate(
1324 chain_name: String,
1325 auction_contract_hash: AddressableEntityHash,
1326 validator_public_key: PublicKey,
1327 delegator_public_key: PublicKey,
1328 amount: U512,
1329 timestamp: Timestamp,
1330 ttl: TimeDiff,
1331 ) -> Self {
1332 let payment = ExecutableDeployItem::ModuleBytes {
1333 module_bytes: Bytes::new(),
1334 args: runtime_args! { ARG_AMOUNT => U512::from(3_000_000_000_u64) },
1335 };
1336 let args = runtime_args! {
1337 ARG_DELEGATOR => delegator_public_key.clone(),
1338 ARG_VALIDATOR => validator_public_key,
1339 ARG_AUCTION_AMOUNT => amount,
1340 };
1341 let session = ExecutableDeployItem::StoredContractByHash {
1342 hash: auction_contract_hash.into(),
1343 entry_point: METHOD_DELEGATE.to_string(),
1344 args,
1345 };
1346
1347 Deploy::build(
1348 timestamp,
1349 ttl,
1350 1,
1351 vec![],
1352 chain_name,
1353 payment,
1354 session,
1355 InitiatorAddrAndSecretKey::InitiatorAddr(InitiatorAddr::PublicKey(
1356 delegator_public_key,
1357 )),
1358 )
1359 }
1360
1361 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1363 pub fn undelegate(
1364 chain_name: String,
1365 auction_contract_hash: AddressableEntityHash,
1366 validator_public_key: PublicKey,
1367 delegator_public_key: PublicKey,
1368 amount: U512,
1369 timestamp: Timestamp,
1370 ttl: TimeDiff,
1371 ) -> Self {
1372 let payment = ExecutableDeployItem::ModuleBytes {
1373 module_bytes: Bytes::new(),
1374 args: runtime_args! { ARG_AMOUNT => U512::from(3_000_000_000_u64) },
1375 };
1376 let args = runtime_args! {
1377 ARG_DELEGATOR => delegator_public_key.clone(),
1378 ARG_VALIDATOR => validator_public_key,
1379 ARG_AUCTION_AMOUNT => amount,
1380 };
1381 let session = ExecutableDeployItem::StoredContractByHash {
1382 hash: auction_contract_hash.into(),
1383 entry_point: METHOD_UNDELEGATE.to_string(),
1384 args,
1385 };
1386
1387 Deploy::build(
1388 timestamp,
1389 ttl,
1390 1,
1391 vec![],
1392 chain_name,
1393 payment,
1394 session,
1395 InitiatorAddrAndSecretKey::InitiatorAddr(InitiatorAddr::PublicKey(
1396 delegator_public_key,
1397 )),
1398 )
1399 }
1400
1401 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1403 #[allow(clippy::too_many_arguments)]
1404 pub fn redelegate(
1405 chain_name: String,
1406 auction_contract_hash: AddressableEntityHash,
1407 validator_public_key: PublicKey,
1408 delegator_public_key: PublicKey,
1409 redelegate_validator_public_key: PublicKey,
1410 amount: U512,
1411 timestamp: Timestamp,
1412 ttl: TimeDiff,
1413 ) -> Self {
1414 let payment = ExecutableDeployItem::ModuleBytes {
1415 module_bytes: Bytes::new(),
1416 args: runtime_args! { ARG_AMOUNT => U512::from(3_000_000_000_u64) },
1417 };
1418 let args = runtime_args! {
1419 ARG_DELEGATOR => delegator_public_key.clone(),
1420 ARG_VALIDATOR => validator_public_key,
1421 ARG_NEW_VALIDATOR => redelegate_validator_public_key,
1422 ARG_AUCTION_AMOUNT => amount,
1423 };
1424 let session = ExecutableDeployItem::StoredContractByHash {
1425 hash: auction_contract_hash.into(),
1426 entry_point: METHOD_REDELEGATE.to_string(),
1427 args,
1428 };
1429
1430 Deploy::build(
1431 timestamp,
1432 ttl,
1433 1,
1434 vec![],
1435 chain_name,
1436 payment,
1437 session,
1438 InitiatorAddrAndSecretKey::InitiatorAddr(InitiatorAddr::PublicKey(
1439 delegator_public_key,
1440 )),
1441 )
1442 }
1443
1444 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1446 #[allow(clippy::too_many_arguments)]
1447 pub fn native_transfer(
1448 chain_name: String,
1449 source_purse: Option<URef>,
1450 sender_public_key: PublicKey,
1451 receiver_public_key: PublicKey,
1452 amount: Option<U512>,
1453 timestamp: Timestamp,
1454 ttl: TimeDiff,
1455 gas_price: u64,
1456 ) -> Self {
1457 let amount = amount.unwrap_or_else(|| U512::from(DEFAULT_MIN_TRANSFER_MOTES));
1458
1459 let payment = ExecutableDeployItem::ModuleBytes {
1460 module_bytes: Bytes::new(),
1461 args: runtime_args! { ARG_AMOUNT => U512::from(3_000_000_000_u64) },
1462 };
1463
1464 let mut transfer_args = runtime_args! {
1465 "amount" => amount,
1466 "target" => receiver_public_key.to_account_hash(),
1467 };
1468
1469 if let Some(source) = source_purse {
1470 transfer_args
1471 .insert("source", source)
1472 .expect("should serialize source arg");
1473 }
1474
1475 let session = ExecutableDeployItem::Transfer {
1476 args: transfer_args,
1477 };
1478
1479 Deploy::build(
1480 timestamp,
1481 ttl,
1482 gas_price,
1483 vec![],
1484 chain_name,
1485 payment,
1486 session,
1487 InitiatorAddrAndSecretKey::InitiatorAddr(InitiatorAddr::PublicKey(sender_public_key)),
1488 )
1489 }
1490}
1491
1492#[cfg(any(feature = "std", test))]
1493impl GasLimited for Deploy {
1494 type Error = InvalidDeploy;
1495
1496 fn gas_cost(&self, chainspec: &Chainspec, gas_price: u8) -> Result<Motes, Self::Error> {
1497 let gas_limit = self.gas_limit(chainspec)?;
1498 let motes =
1499 Motes::from_gas(gas_limit, gas_price).ok_or(InvalidDeploy::UnableToCalculateGasCost)?;
1500 Ok(motes)
1501 }
1502
1503 fn gas_limit(&self, chainspec: &Chainspec) -> Result<Gas, Self::Error> {
1504 let pricing_handling = chainspec.core_config.pricing_handling;
1505 let costs = &chainspec.system_costs_config;
1506 let gas_limit = match pricing_handling {
1507 PricingHandling::PaymentLimited => {
1508 if self.is_transfer() {
1511 Gas::new(costs.mint_costs().transfer)
1512 } else {
1513 let value = self
1514 .payment()
1515 .args()
1516 .get(ARG_AMOUNT)
1517 .ok_or(InvalidDeploy::MissingPaymentAmount)?;
1518 let payment_amount = value
1519 .clone()
1520 .into_t::<U512>()
1521 .map_err(|_| InvalidDeploy::FailedToParsePaymentAmount)?;
1522 Gas::new(payment_amount)
1523 }
1524 }
1525 PricingHandling::Fixed => {
1526 let v1_config = &chainspec.transaction_config.transaction_v1_config;
1527 let lane_id = calculate_lane_id_for_deploy(self, pricing_handling, v1_config)?;
1528 let lane_definition = v1_config
1529 .get_lane_by_id(lane_id)
1530 .ok_or(InvalidDeploy::NoLaneMatch)?;
1531 let computation_limit = lane_definition.max_transaction_gas_limit;
1532 Gas::new(computation_limit)
1533 } };
1535 Ok(gas_limit)
1536 }
1537
1538 fn gas_price_tolerance(&self) -> Result<u8, Self::Error> {
1539 u8::try_from(self.gas_price()).map_err(|_| Self::Error::UnableToCalculateGasLimit)
1540 }
1541}
1542
1543impl hash::Hash for Deploy {
1544 fn hash<H: hash::Hasher>(&self, state: &mut H) {
1545 #[cfg(any(feature = "once_cell", test))]
1547 let Deploy {
1548 hash,
1549 header,
1550 payment,
1551 session,
1552 approvals,
1553 is_valid: _,
1554 } = self;
1555 #[cfg(not(any(feature = "once_cell", test)))]
1556 let Deploy {
1557 hash,
1558 header,
1559 payment,
1560 session,
1561 approvals,
1562 } = self;
1563 hash.hash(state);
1564 header.hash(state);
1565 payment.hash(state);
1566 session.hash(state);
1567 approvals.hash(state);
1568 }
1569}
1570
1571impl PartialEq for Deploy {
1572 fn eq(&self, other: &Deploy) -> bool {
1573 #[cfg(any(feature = "once_cell", test))]
1575 let Deploy {
1576 hash,
1577 header,
1578 payment,
1579 session,
1580 approvals,
1581 is_valid: _,
1582 } = self;
1583 #[cfg(not(any(feature = "once_cell", test)))]
1584 let Deploy {
1585 hash,
1586 header,
1587 payment,
1588 session,
1589 approvals,
1590 } = self;
1591 *hash == other.hash
1592 && *header == other.header
1593 && *payment == other.payment
1594 && *session == other.session
1595 && *approvals == other.approvals
1596 }
1597}
1598
1599impl Ord for Deploy {
1600 fn cmp(&self, other: &Deploy) -> cmp::Ordering {
1601 #[cfg(any(feature = "once_cell", test))]
1603 let Deploy {
1604 hash,
1605 header,
1606 payment,
1607 session,
1608 approvals,
1609 is_valid: _,
1610 } = self;
1611 #[cfg(not(any(feature = "once_cell", test)))]
1612 let Deploy {
1613 hash,
1614 header,
1615 payment,
1616 session,
1617 approvals,
1618 } = self;
1619 hash.cmp(&other.hash)
1620 .then_with(|| header.cmp(&other.header))
1621 .then_with(|| payment.cmp(&other.payment))
1622 .then_with(|| session.cmp(&other.session))
1623 .then_with(|| approvals.cmp(&other.approvals))
1624 }
1625}
1626
1627impl PartialOrd for Deploy {
1628 fn partial_cmp(&self, other: &Deploy) -> Option<cmp::Ordering> {
1629 Some(self.cmp(other))
1630 }
1631}
1632
1633impl ToBytes for Deploy {
1634 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
1635 let mut buffer = bytesrepr::allocate_buffer(self)?;
1636 self.write_bytes(&mut buffer)?;
1637 Ok(buffer)
1638 }
1639
1640 fn serialized_length(&self) -> usize {
1641 self.header.serialized_length()
1642 + self.hash.serialized_length()
1643 + self.payment.serialized_length()
1644 + self.session.serialized_length()
1645 + self.approvals.serialized_length()
1646 }
1647
1648 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
1649 self.header.write_bytes(writer)?;
1650 self.hash.write_bytes(writer)?;
1651 self.payment.write_bytes(writer)?;
1652 self.session.write_bytes(writer)?;
1653 self.approvals.write_bytes(writer)
1654 }
1655}
1656
1657impl FromBytes for Deploy {
1658 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
1659 let (header, remainder) = DeployHeader::from_bytes(bytes)?;
1660 let (hash, remainder) = DeployHash::from_bytes(remainder)?;
1661 let (payment, remainder) = ExecutableDeployItem::from_bytes(remainder)?;
1662 let (session, remainder) = ExecutableDeployItem::from_bytes(remainder)?;
1663 let (approvals, remainder) = BTreeSet::<Approval>::from_bytes(remainder)?;
1664 let maybe_valid_deploy = Deploy {
1665 header,
1666 hash,
1667 payment,
1668 session,
1669 approvals,
1670 #[cfg(any(feature = "once_cell", test))]
1671 is_valid: OnceCell::new(),
1672 };
1673 Ok((maybe_valid_deploy, remainder))
1674 }
1675}
1676
1677impl Display for Deploy {
1678 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
1679 write!(
1680 formatter,
1681 "deploy[{}, {}, payment_code: {}, session_code: {}, approvals: {}]",
1682 self.hash,
1683 self.header,
1684 self.payment,
1685 self.session,
1686 DisplayIter::new(self.approvals.iter())
1687 )
1688 }
1689}
1690
1691fn serialize_header(header: &DeployHeader) -> Vec<u8> {
1692 header
1693 .to_bytes()
1694 .unwrap_or_else(|error| panic!("should serialize deploy header: {}", error))
1695}
1696
1697fn serialize_body(payment: &ExecutableDeployItem, session: &ExecutableDeployItem) -> Vec<u8> {
1698 let mut buffer = Vec::with_capacity(payment.serialized_length() + session.serialized_length());
1699 payment
1700 .write_bytes(&mut buffer)
1701 .unwrap_or_else(|error| panic!("should serialize payment code: {}", error));
1702 session
1703 .write_bytes(&mut buffer)
1704 .unwrap_or_else(|error| panic!("should serialize session code: {}", error));
1705 buffer
1706}
1707
1708fn validate_deploy(deploy: &Deploy) -> Result<(), InvalidDeploy> {
1711 if deploy.approvals.is_empty() {
1712 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1713 warn!(?deploy, "deploy has no approvals");
1714 return Err(InvalidDeploy::EmptyApprovals);
1715 }
1716
1717 deploy.has_valid_hash()?;
1718
1719 for (index, approval) in deploy.approvals.iter().enumerate() {
1720 if let Err(error) = crypto::verify(deploy.hash, approval.signature(), approval.signer()) {
1721 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1722 warn!(?deploy, "failed to verify approval {}: {}", index, error);
1723 return Err(InvalidDeploy::InvalidApproval { index, error });
1724 }
1725 }
1726
1727 Ok(())
1728}
1729
1730#[cfg(any(feature = "std", test))]
1731pub fn calculate_lane_id_for_deploy(
1733 deploy: &Deploy,
1734 pricing_handling: PricingHandling,
1735 config: &TransactionV1Config,
1736) -> Result<u8, InvalidDeploy> {
1737 if deploy.is_transfer() {
1738 return Ok(MINT_LANE_ID);
1739 }
1740 let size_estimation = deploy.serialized_length() as u64;
1741 let runtime_args_size = (deploy.payment().args().serialized_length()
1742 + deploy.session().args().serialized_length()) as u64;
1743
1744 let gas_price_tolerance = deploy.gas_price_tolerance()?;
1745 let pricing_mode = match pricing_handling {
1746 PricingHandling::PaymentLimited => {
1747 let is_standard_payment = deploy.payment().is_standard_payment(Phase::Payment);
1748 let value = deploy
1749 .payment()
1750 .args()
1751 .get(ARG_AMOUNT)
1752 .ok_or(InvalidDeploy::MissingPaymentAmount)?;
1753 let payment_amount = value
1754 .clone()
1755 .into_t::<U512>()
1756 .map_err(|_| InvalidDeploy::FailedToParsePaymentAmount)?
1757 .as_u64();
1758 PricingMode::PaymentLimited {
1759 payment_amount,
1760 gas_price_tolerance,
1761 standard_payment: is_standard_payment,
1762 }
1763 }
1764 PricingHandling::Fixed => PricingMode::Fixed {
1765 gas_price_tolerance,
1766 additional_computation_factor: 0,
1768 },
1769 };
1770
1771 get_lane_for_non_install_wasm(config, &pricing_mode, size_estimation, runtime_args_size)
1772 .map_err(Into::into)
1773}
1774
1775#[cfg(test)]
1776mod tests {
1777 use std::{iter, time::Duration};
1778
1779 use super::*;
1780 use crate::{CLValue, TransactionConfig};
1781
1782 #[test]
1783 fn json_roundtrip() {
1784 let mut rng = TestRng::new();
1785 let deploy = Deploy::random(&mut rng);
1786 let json_string = serde_json::to_string_pretty(&deploy).unwrap();
1787 let decoded = serde_json::from_str(&json_string).unwrap();
1788 assert_eq!(deploy, decoded);
1789 }
1790
1791 #[test]
1792 fn bincode_roundtrip() {
1793 let mut rng = TestRng::new();
1794 let deploy = Deploy::random(&mut rng);
1795 let serialized = bincode::serialize(&deploy).unwrap();
1796 let deserialized = bincode::deserialize(&serialized).unwrap();
1797 assert_eq!(deploy, deserialized);
1798 }
1799
1800 #[test]
1801 fn bytesrepr_roundtrip() {
1802 let mut rng = TestRng::new();
1803 let deploy = Deploy::random(&mut rng);
1804 bytesrepr::test_serialization_roundtrip(deploy.header());
1805 bytesrepr::test_serialization_roundtrip(&deploy);
1806 }
1807
1808 fn create_deploy(
1809 rng: &mut TestRng,
1810 ttl: TimeDiff,
1811 dependency_count: usize,
1812 chain_name: &str,
1813 gas_price: u64,
1814 ) -> Deploy {
1815 let secret_key = SecretKey::random(rng);
1816 let dependencies = iter::repeat_with(|| DeployHash::random(rng))
1817 .take(dependency_count)
1818 .collect();
1819 let transfer_args = {
1820 let mut transfer_args = RuntimeArgs::new();
1821 let value = CLValue::from_t(U512::from(DEFAULT_MIN_TRANSFER_MOTES))
1822 .expect("should create CLValue");
1823 transfer_args.insert_cl_value("amount", value);
1824 transfer_args
1825 };
1826 Deploy::new_signed(
1827 Timestamp::now(),
1828 ttl,
1829 gas_price,
1830 dependencies,
1831 chain_name.to_string(),
1832 ExecutableDeployItem::ModuleBytes {
1833 module_bytes: Bytes::new(),
1834 args: RuntimeArgs::new(),
1835 },
1836 ExecutableDeployItem::Transfer {
1837 args: transfer_args,
1838 },
1839 &secret_key,
1840 None,
1841 )
1842 }
1843
1844 #[test]
1845 fn is_valid() {
1846 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
1847
1848 let mut rng = TestRng::new();
1849
1850 let deploy = create_deploy(
1851 &mut rng,
1852 TransactionConfig::default().max_ttl,
1853 0,
1854 "net-1",
1855 GAS_PRICE_TOLERANCE as u64,
1856 );
1857 assert_eq!(
1858 deploy.is_valid.get(),
1859 None,
1860 "is valid should initially be None"
1861 );
1862 deploy.is_valid().expect("should be valid");
1863 assert_eq!(
1864 deploy.is_valid.get(),
1865 Some(&Ok(())),
1866 "is valid should be true"
1867 );
1868 }
1869
1870 fn check_is_not_valid(invalid_deploy: Deploy, expected_error: InvalidDeploy) {
1871 assert!(
1872 invalid_deploy.is_valid.get().is_none(),
1873 "is valid should initially be None"
1874 );
1875 let actual_error = invalid_deploy.is_valid().unwrap_err();
1876
1877 match expected_error {
1881 InvalidDeploy::InvalidApproval {
1882 index: expected_index,
1883 ..
1884 } => match actual_error {
1885 InvalidDeploy::InvalidApproval {
1886 index: actual_index,
1887 ..
1888 } => {
1889 assert_eq!(actual_index, expected_index);
1890 }
1891 _ => panic!("expected {}, got: {}", expected_error, actual_error),
1892 },
1893 _ => {
1894 assert_eq!(actual_error, expected_error,);
1895 }
1896 }
1897
1898 assert_eq!(
1900 invalid_deploy.is_valid.get(),
1901 Some(&Err(actual_error)),
1902 "is valid should now be Some"
1903 );
1904 }
1905
1906 #[test]
1907 fn not_valid_due_to_invalid_body_hash() {
1908 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
1909
1910 let mut rng = TestRng::new();
1911 let mut deploy = create_deploy(
1912 &mut rng,
1913 TransactionConfig::default().max_ttl,
1914 0,
1915 "net-1",
1916 GAS_PRICE_TOLERANCE as u64,
1917 );
1918
1919 deploy.session = ExecutableDeployItem::Transfer {
1920 args: runtime_args! {
1921 "amount" => 1
1922 },
1923 };
1924 check_is_not_valid(deploy, InvalidDeploy::InvalidBodyHash);
1925 }
1926
1927 #[test]
1928 fn not_valid_due_to_invalid_deploy_hash() {
1929 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
1930
1931 let mut rng = TestRng::new();
1932 let mut deploy = create_deploy(
1933 &mut rng,
1934 TransactionConfig::default().max_ttl,
1935 0,
1936 "net-1",
1937 GAS_PRICE_TOLERANCE as u64,
1938 );
1939
1940 deploy.invalidate();
1942 check_is_not_valid(deploy, InvalidDeploy::InvalidDeployHash);
1943 }
1944
1945 #[test]
1946 fn not_valid_due_to_empty_approvals() {
1947 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
1948
1949 let mut rng = TestRng::new();
1950 let mut deploy = create_deploy(
1951 &mut rng,
1952 TransactionConfig::default().max_ttl,
1953 0,
1954 "net-1",
1955 GAS_PRICE_TOLERANCE as u64,
1956 );
1957 deploy.approvals = BTreeSet::new();
1958 assert!(deploy.approvals.is_empty());
1959 check_is_not_valid(deploy, InvalidDeploy::EmptyApprovals)
1960 }
1961
1962 #[test]
1963 fn not_valid_due_to_invalid_approval() {
1964 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
1965
1966 let mut rng = TestRng::new();
1967 let mut deploy = create_deploy(
1968 &mut rng,
1969 TransactionConfig::default().max_ttl,
1970 0,
1971 "net-1",
1972 GAS_PRICE_TOLERANCE as u64,
1973 );
1974
1975 let deploy2 = Deploy::random(&mut rng);
1976
1977 deploy.approvals.extend(deploy2.approvals.clone());
1978 let expected_index = deploy
1981 .approvals
1982 .iter()
1983 .enumerate()
1984 .find(|(_, approval)| deploy2.approvals.contains(approval))
1985 .map(|(index, _)| index)
1986 .unwrap();
1987 check_is_not_valid(
1988 deploy,
1989 InvalidDeploy::InvalidApproval {
1990 index: expected_index,
1991 error: crypto::Error::SignatureError, },
1993 );
1994 }
1995
1996 #[test]
1997 fn is_acceptable() {
1998 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
1999
2000 let mut rng = TestRng::new();
2001 let chain_name = "net-1".to_string();
2002 let mut chainspec = Chainspec::default();
2003 chainspec.with_chain_name(chain_name.to_string());
2004 let config = chainspec.transaction_config.clone();
2005
2006 let deploy = create_deploy(
2007 &mut rng,
2008 config.max_ttl,
2009 0,
2010 &chain_name,
2011 GAS_PRICE_TOLERANCE as u64,
2012 );
2013 let current_timestamp = deploy.header().timestamp();
2014 deploy
2015 .is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp)
2016 .expect("should be acceptable");
2017 }
2018
2019 #[test]
2020 fn not_acceptable_due_to_invalid_chain_name() {
2021 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2022
2023 let mut rng = TestRng::new();
2024 let expected_chain_name = "net-1";
2025 let wrong_chain_name = "net-2".to_string();
2026
2027 let mut chainspec = Chainspec::default();
2028 chainspec.with_chain_name(expected_chain_name.to_string());
2029 let config = chainspec.transaction_config.clone();
2030
2031 let deploy = create_deploy(
2032 &mut rng,
2033 config.max_ttl,
2034 0,
2035 &wrong_chain_name,
2036 GAS_PRICE_TOLERANCE as u64,
2037 );
2038
2039 let expected_error = InvalidDeploy::InvalidChainName {
2040 expected: expected_chain_name.to_string(),
2041 got: wrong_chain_name,
2042 };
2043
2044 let current_timestamp = deploy.header().timestamp();
2045 assert_eq!(
2046 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp),
2047 Err(expected_error)
2048 );
2049 assert!(
2050 deploy.is_valid.get().is_none(),
2051 "deploy should not have run expensive `is_valid` call"
2052 );
2053 }
2054
2055 #[test]
2056 fn not_acceptable_due_to_excessive_dependencies() {
2057 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2058
2059 let mut rng = TestRng::new();
2060 let chain_name = "net-1";
2061
2062 let mut chainspec = Chainspec::default();
2063 chainspec.with_chain_name(chain_name.to_string());
2064 let config = chainspec.transaction_config.clone();
2065
2066 let deploy = create_deploy(
2067 &mut rng,
2068 config.max_ttl,
2069 1,
2070 chain_name,
2071 GAS_PRICE_TOLERANCE as u64,
2072 );
2073
2074 let expected_error = InvalidDeploy::DependenciesNoLongerSupported;
2075
2076 let current_timestamp = deploy.header().timestamp();
2077 assert_eq!(
2078 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp),
2079 Err(expected_error)
2080 );
2081 assert!(
2082 deploy.is_valid.get().is_none(),
2083 "deploy should not have run expensive `is_valid` call"
2084 );
2085 }
2086
2087 #[test]
2088 fn not_acceptable_due_to_excessive_ttl() {
2089 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2090
2091 let mut rng = TestRng::new();
2092 let chain_name = "net-1";
2093
2094 let mut chainspec = Chainspec::default();
2095 chainspec.with_chain_name(chain_name.to_string());
2096 let config = chainspec.transaction_config.clone();
2097
2098 let ttl = config.max_ttl + TimeDiff::from(Duration::from_secs(1));
2099
2100 let deploy = create_deploy(&mut rng, ttl, 0, chain_name, GAS_PRICE_TOLERANCE as u64);
2101
2102 let expected_error = InvalidDeploy::ExcessiveTimeToLive {
2103 max_ttl: config.max_ttl,
2104 got: ttl,
2105 };
2106
2107 let current_timestamp = deploy.header().timestamp();
2108 assert_eq!(
2109 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp),
2110 Err(expected_error)
2111 );
2112 assert!(
2113 deploy.is_valid.get().is_none(),
2114 "deploy should not have run expensive `is_valid` call"
2115 );
2116 }
2117
2118 #[test]
2119 fn not_acceptable_due_to_timestamp_in_future() {
2120 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2121
2122 let mut rng = TestRng::new();
2123 let chain_name = "net-1";
2124
2125 let mut chainspec = Chainspec::default();
2126 chainspec.with_chain_name(chain_name.to_string());
2127 let config = chainspec.transaction_config.clone();
2128 let leeway = TimeDiff::from_seconds(2);
2129
2130 let deploy = create_deploy(
2131 &mut rng,
2132 config.max_ttl,
2133 0,
2134 chain_name,
2135 GAS_PRICE_TOLERANCE as u64,
2136 );
2137 let current_timestamp = deploy.header.timestamp() - leeway - TimeDiff::from_seconds(1);
2138
2139 let expected_error = InvalidDeploy::TimestampInFuture {
2140 validation_timestamp: current_timestamp,
2141 timestamp_leeway: leeway,
2142 got: deploy.header.timestamp(),
2143 };
2144
2145 assert_eq!(
2146 deploy.is_config_compliant(&chainspec, leeway, current_timestamp),
2147 Err(expected_error)
2148 );
2149 assert!(
2150 deploy.is_valid.get().is_none(),
2151 "deploy should not have run expensive `is_valid` call"
2152 );
2153 }
2154
2155 #[test]
2156 fn acceptable_if_timestamp_slightly_in_future() {
2157 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2158
2159 let mut rng = TestRng::new();
2160 let chain_name = "net-1";
2161
2162 let mut chainspec = Chainspec::default();
2163 chainspec.with_chain_name(chain_name.to_string());
2164 let config = chainspec.transaction_config.clone();
2165 let leeway = TimeDiff::from_seconds(2);
2166
2167 let deploy = create_deploy(
2168 &mut rng,
2169 config.max_ttl,
2170 0,
2171 chain_name,
2172 GAS_PRICE_TOLERANCE as u64,
2173 );
2174 let current_timestamp = deploy.header.timestamp() - (leeway / 2);
2175 deploy
2176 .is_config_compliant(&chainspec, leeway, current_timestamp)
2177 .expect("should be acceptable");
2178 }
2179
2180 #[test]
2181 fn not_acceptable_due_to_missing_payment_amount() {
2182 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2183
2184 let mut rng = TestRng::new();
2185 let chain_name = "net-1";
2186
2187 let mut chainspec = Chainspec::default();
2188 chainspec.with_chain_name(chain_name.to_string());
2189 chainspec.with_pricing_handling(PricingHandling::PaymentLimited);
2190 let config = chainspec.transaction_config.clone();
2191
2192 let payment = ExecutableDeployItem::ModuleBytes {
2193 module_bytes: Bytes::new(),
2194 args: RuntimeArgs::default(),
2195 };
2196
2197 let session = ExecutableDeployItem::StoredContractByName {
2200 name: "".to_string(),
2201 entry_point: "".to_string(),
2202 args: Default::default(),
2203 };
2204
2205 let mut deploy = create_deploy(
2206 &mut rng,
2207 config.max_ttl,
2208 0,
2209 chain_name,
2210 GAS_PRICE_TOLERANCE as u64,
2211 );
2212
2213 deploy.payment = payment;
2214 deploy.session = session;
2215
2216 let current_timestamp = deploy.header().timestamp();
2217 assert_eq!(
2218 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp),
2219 Err(InvalidDeploy::MissingPaymentAmount)
2220 );
2221 assert!(
2222 deploy.is_valid.get().is_none(),
2223 "deploy should not have run expensive `is_valid` call"
2224 );
2225 }
2226
2227 #[test]
2228 fn not_acceptable_due_to_mangled_payment_amount() {
2229 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2230
2231 let mut rng = TestRng::new();
2232 let chain_name = "net-1";
2233
2234 let mut chainspec = Chainspec::default();
2235 chainspec.with_chain_name(chain_name.to_string());
2236 chainspec.with_pricing_handling(PricingHandling::PaymentLimited);
2237 let config = chainspec.transaction_config.clone();
2238
2239 let payment = ExecutableDeployItem::ModuleBytes {
2240 module_bytes: Bytes::new(),
2241 args: runtime_args! {
2242 "amount" => "mangled-amount"
2243 },
2244 };
2245
2246 let session = ExecutableDeployItem::StoredContractByName {
2249 name: "".to_string(),
2250 entry_point: "".to_string(),
2251 args: Default::default(),
2252 };
2253
2254 let mut deploy = create_deploy(
2255 &mut rng,
2256 config.max_ttl,
2257 0,
2258 chain_name,
2259 GAS_PRICE_TOLERANCE as u64,
2260 );
2261
2262 deploy.payment = payment;
2263 deploy.session = session;
2264
2265 let current_timestamp = deploy.header().timestamp();
2266 assert_eq!(
2267 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp),
2268 Err(InvalidDeploy::FailedToParsePaymentAmount)
2269 );
2270 assert!(
2271 deploy.is_valid.get().is_none(),
2272 "deploy should not have run expensive `is_valid` call"
2273 );
2274 }
2275
2276 #[test]
2277 fn not_acceptable_if_doesnt_fit_in_any_lane() {
2278 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2279
2280 let mut rng = TestRng::new();
2281 let chain_name = "net-1";
2282
2283 let mut chainspec = Chainspec::default();
2284 chainspec.with_chain_name(chain_name.to_string());
2285 chainspec.with_pricing_handling(PricingHandling::PaymentLimited);
2286 let config = chainspec.transaction_config.clone();
2287 let max_lane = chainspec
2288 .transaction_config
2289 .transaction_v1_config
2290 .get_max_wasm_lane_by_gas_limit()
2291 .unwrap();
2292 let amount = U512::from(max_lane.max_transaction_gas_limit + 1);
2293
2294 let payment = ExecutableDeployItem::ModuleBytes {
2295 module_bytes: Bytes::new(),
2296 args: runtime_args! {
2297 "amount" => amount
2298 },
2299 };
2300
2301 let session = ExecutableDeployItem::StoredContractByName {
2304 name: "".to_string(),
2305 entry_point: "".to_string(),
2306 args: Default::default(),
2307 };
2308
2309 let mut deploy = create_deploy(
2310 &mut rng,
2311 config.max_ttl,
2312 0,
2313 chain_name,
2314 GAS_PRICE_TOLERANCE as u64,
2315 );
2316
2317 deploy.payment = payment;
2318 deploy.session = session;
2319
2320 let expected_error = InvalidDeploy::NoLaneMatch;
2321
2322 let current_timestamp = deploy.header().timestamp();
2323 assert_eq!(
2324 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp),
2325 Err(expected_error)
2326 );
2327 assert!(
2328 deploy.is_valid.get().is_none(),
2329 "deploy should not have run expensive `is_valid` call"
2330 );
2331 }
2332
2333 #[test]
2334 fn not_acceptable_due_to_transaction_bigger_than_block_limit() {
2335 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2339
2340 let mut rng = TestRng::new();
2341 let chain_name = "net-1";
2342
2343 let mut chainspec = Chainspec::default();
2344 chainspec.with_block_gas_limit(100); chainspec.with_chain_name(chain_name.to_string());
2346 chainspec.with_pricing_handling(PricingHandling::PaymentLimited);
2347 let config = chainspec.transaction_config.clone();
2348 let max_lane = chainspec
2349 .transaction_config
2350 .transaction_v1_config
2351 .get_max_wasm_lane_by_gas_limit()
2352 .unwrap();
2353 let amount = U512::from(max_lane.max_transaction_gas_limit);
2354
2355 let payment = ExecutableDeployItem::ModuleBytes {
2356 module_bytes: Bytes::new(),
2357 args: runtime_args! {
2358 "amount" => amount
2359 },
2360 };
2361
2362 let session = ExecutableDeployItem::StoredContractByName {
2365 name: "".to_string(),
2366 entry_point: "".to_string(),
2367 args: Default::default(),
2368 };
2369
2370 let mut deploy = create_deploy(
2371 &mut rng,
2372 config.max_ttl,
2373 0,
2374 chain_name,
2375 GAS_PRICE_TOLERANCE as u64,
2376 );
2377
2378 deploy.payment = payment;
2379 deploy.session = session;
2380
2381 let expected_error = InvalidDeploy::ExceededBlockGasLimit {
2382 block_gas_limit: config.block_gas_limit,
2383 got: Box::new(amount),
2384 };
2385
2386 let current_timestamp = deploy.header().timestamp();
2387 assert_eq!(
2388 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp),
2389 Err(expected_error)
2390 );
2391 assert!(
2392 deploy.is_valid.get().is_none(),
2393 "deploy should not have run expensive `is_valid` call"
2394 );
2395 }
2396
2397 #[test]
2398 fn transfer_acceptable_regardless_of_excessive_payment_amount() {
2399 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2400
2401 let mut rng = TestRng::new();
2402 let secret_key = SecretKey::random(&mut rng);
2403 let chain_name = "net-1";
2404
2405 let mut chainspec = Chainspec::default();
2406 chainspec.with_chain_name(chain_name.to_string());
2407 let config = chainspec.transaction_config.clone();
2408 let amount = U512::from(config.block_gas_limit + 1);
2409
2410 let payment = ExecutableDeployItem::ModuleBytes {
2411 module_bytes: Bytes::new(),
2412 args: runtime_args! {
2413 "amount" => amount
2414 },
2415 };
2416
2417 let transfer_args = {
2418 let mut transfer_args = RuntimeArgs::new();
2419 let value = CLValue::from_t(U512::from(DEFAULT_MIN_TRANSFER_MOTES))
2420 .expect("should create CLValue");
2421 transfer_args.insert_cl_value("amount", value);
2422 transfer_args
2423 };
2424
2425 let deploy = Deploy::new_signed(
2426 Timestamp::now(),
2427 config.max_ttl,
2428 GAS_PRICE_TOLERANCE as u64,
2429 vec![],
2430 chain_name.to_string(),
2431 payment,
2432 ExecutableDeployItem::Transfer {
2433 args: transfer_args,
2434 },
2435 &secret_key,
2436 None,
2437 );
2438
2439 let current_timestamp = deploy.header().timestamp();
2440 assert_eq!(
2441 Ok(()),
2442 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp)
2443 )
2444 }
2445
2446 #[test]
2447 fn not_acceptable_due_to_excessive_approvals() {
2448 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2449
2450 let mut rng = TestRng::new();
2451 let chain_name = "net-1";
2452
2453 let mut chainspec = Chainspec::default();
2454 chainspec.with_chain_name(chain_name.to_string());
2455 let config = chainspec.transaction_config.clone();
2456 let deploy = create_deploy(
2457 &mut rng,
2458 config.max_ttl,
2459 0,
2460 chain_name,
2461 GAS_PRICE_TOLERANCE as u64,
2462 );
2463 let max_associated_keys = (deploy.approvals.len() - 1) as u32;
2466 chainspec.with_max_associated_keys(max_associated_keys);
2467 let current_timestamp = deploy.header().timestamp();
2468 assert_eq!(
2469 Err(InvalidDeploy::ExcessiveApprovals {
2470 got: deploy.approvals.len() as u32,
2471 max_associated_keys,
2472 }),
2473 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp)
2474 )
2475 }
2476
2477 #[test]
2478 fn not_acceptable_due_to_missing_transfer_amount() {
2479 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2480
2481 let mut rng = TestRng::new();
2482 let chain_name = "net-1";
2483 let mut chainspec = Chainspec::default();
2484 chainspec.with_chain_name(chain_name.to_string());
2485
2486 let config = chainspec.transaction_config.clone();
2487 let mut deploy = create_deploy(
2488 &mut rng,
2489 config.max_ttl,
2490 0,
2491 chain_name,
2492 GAS_PRICE_TOLERANCE as u64,
2493 );
2494
2495 let transfer_args = RuntimeArgs::default();
2496 let session = ExecutableDeployItem::Transfer {
2497 args: transfer_args,
2498 };
2499 deploy.session = session;
2500
2501 let current_timestamp = deploy.header().timestamp();
2502 assert_eq!(
2503 Err(InvalidDeploy::MissingTransferAmount),
2504 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp)
2505 )
2506 }
2507
2508 #[test]
2509 fn not_acceptable_due_to_mangled_transfer_amount() {
2510 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2511
2512 let mut rng = TestRng::new();
2513 let chain_name = "net-1";
2514 let mut chainspec = Chainspec::default();
2515 chainspec.with_chain_name(chain_name.to_string());
2516
2517 let config = chainspec.transaction_config.clone();
2518 let mut deploy = create_deploy(
2519 &mut rng,
2520 config.max_ttl,
2521 0,
2522 chain_name,
2523 GAS_PRICE_TOLERANCE as u64,
2524 );
2525
2526 let transfer_args = runtime_args! {
2527 "amount" => "mangled-amount",
2528 "source" => PublicKey::random(&mut rng).to_account_hash(),
2529 "target" => PublicKey::random(&mut rng).to_account_hash(),
2530 };
2531 let session = ExecutableDeployItem::Transfer {
2532 args: transfer_args,
2533 };
2534 deploy.session = session;
2535
2536 let current_timestamp = deploy.header().timestamp();
2537 assert_eq!(
2538 Err(InvalidDeploy::FailedToParseTransferAmount),
2539 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp)
2540 )
2541 }
2542
2543 #[test]
2544 fn not_acceptable_due_to_too_low_gas_price_tolerance() {
2545 const GAS_PRICE_TOLERANCE: u8 = 0;
2546
2547 let mut rng = TestRng::new();
2548 let chain_name = "net-1";
2549 let mut chainspec = Chainspec::default();
2550 chainspec.with_chain_name(chain_name.to_string());
2551
2552 let config = chainspec.transaction_config.clone();
2553 let deploy = create_deploy(
2554 &mut rng,
2555 config.max_ttl,
2556 0,
2557 chain_name,
2558 GAS_PRICE_TOLERANCE as u64,
2559 );
2560
2561 let current_timestamp = deploy.header().timestamp();
2562 assert!(matches!(
2563 deploy.is_config_compliant(
2564 &chainspec,
2565 TimeDiff::default(),
2566 current_timestamp
2567 ),
2568 Err(InvalidDeploy::GasPriceToleranceTooLow { min_gas_price_tolerance, provided_gas_price_tolerance })
2569 if min_gas_price_tolerance == chainspec.vacancy_config.min_gas_price && provided_gas_price_tolerance == GAS_PRICE_TOLERANCE
2570 ))
2571 }
2572
2573 #[test]
2574 fn not_acceptable_due_to_insufficient_transfer_amount() {
2575 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2576
2577 let mut rng = TestRng::new();
2578 let chain_name = "net-1";
2579 let mut chainspec = Chainspec::default();
2580 chainspec.with_chain_name(chain_name.to_string());
2581
2582 let config = chainspec.transaction_config.clone();
2583 let mut deploy = create_deploy(
2584 &mut rng,
2585 config.max_ttl,
2586 0,
2587 chain_name,
2588 GAS_PRICE_TOLERANCE as u64,
2589 );
2590
2591 let amount = config.native_transfer_minimum_motes - 1;
2592 let insufficient_amount = U512::from(amount);
2593
2594 let transfer_args = runtime_args! {
2595 "amount" => insufficient_amount,
2596 "source" => PublicKey::random(&mut rng).to_account_hash(),
2597 "target" => PublicKey::random(&mut rng).to_account_hash(),
2598 };
2599 let session = ExecutableDeployItem::Transfer {
2600 args: transfer_args,
2601 };
2602 deploy.session = session;
2603
2604 let current_timestamp = deploy.header().timestamp();
2605 assert_eq!(
2606 Err(InvalidDeploy::InsufficientTransferAmount {
2607 minimum: Box::new(U512::from(config.native_transfer_minimum_motes)),
2608 attempted: Box::new(insufficient_amount),
2609 }),
2610 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp,)
2611 )
2612 }
2613
2614 #[test]
2615 fn should_use_payment_amount_for_payment_limited_payment() {
2616 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2617
2618 let payment_amount = 500u64;
2619 let mut rng = TestRng::new();
2620 let chain_name = "net-1";
2621 let mut chainspec = Chainspec::default();
2622 chainspec
2623 .with_chain_name(chain_name.to_string())
2624 .with_pricing_handling(PricingHandling::PaymentLimited);
2625
2626 let config = chainspec.transaction_config.clone();
2627
2628 let payment = ExecutableDeployItem::ModuleBytes {
2629 module_bytes: Bytes::new(),
2630 args: runtime_args! {
2631 "amount" => U512::from(payment_amount)
2632 },
2633 };
2634
2635 let session = ExecutableDeployItem::StoredContractByName {
2638 name: "".to_string(),
2639 entry_point: "".to_string(),
2640 args: Default::default(),
2641 };
2642
2643 let mut deploy = create_deploy(
2644 &mut rng,
2645 config.max_ttl,
2646 0,
2647 chain_name,
2648 GAS_PRICE_TOLERANCE as u64,
2649 );
2650 deploy.payment = payment;
2651 deploy.session = session;
2652
2653 let mut gas_price = 1;
2654 let cost = deploy
2655 .gas_cost(&chainspec, gas_price)
2656 .expect("should cost")
2657 .value();
2658 assert_eq!(
2659 cost,
2660 U512::from(payment_amount),
2661 "in payment limited pricing, the user selected amount should be the cost if gas price is 1"
2662 );
2663 gas_price += 1;
2664 let cost = deploy
2665 .gas_cost(&chainspec, gas_price)
2666 .expect("should cost")
2667 .value();
2668 assert_eq!(
2669 cost,
2670 U512::from(payment_amount) * gas_price,
2671 "in payment limited pricing, the cost should == user selected amount * gas_price"
2672 );
2673 }
2674
2675 #[test]
2676 fn should_use_cost_table_for_fixed_payment() {
2677 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2678
2679 let payment_amount = 500u64;
2680 let mut rng = TestRng::new();
2681 let chain_name = "net-1";
2682 let mut chainspec = Chainspec::default();
2683 chainspec
2684 .with_chain_name(chain_name.to_string())
2685 .with_pricing_handling(PricingHandling::PaymentLimited);
2686
2687 let config = chainspec.transaction_config.clone();
2688
2689 let payment = ExecutableDeployItem::ModuleBytes {
2690 module_bytes: Bytes::new(),
2691 args: runtime_args! {
2692 "amount" => U512::from(payment_amount)
2693 },
2694 };
2695
2696 let session = ExecutableDeployItem::StoredContractByName {
2699 name: "".to_string(),
2700 entry_point: "".to_string(),
2701 args: Default::default(),
2702 };
2703
2704 let mut deploy = create_deploy(
2705 &mut rng,
2706 config.max_ttl,
2707 0,
2708 chain_name,
2709 GAS_PRICE_TOLERANCE as u64,
2710 );
2711 deploy.payment = payment;
2712 deploy.session = session;
2713
2714 let mut gas_price = 1;
2715 let limit = deploy.gas_limit(&chainspec).expect("should limit").value();
2716 let cost = deploy
2717 .gas_cost(&chainspec, gas_price)
2718 .expect("should cost")
2719 .value();
2720 assert_eq!(
2721 cost, limit,
2722 "in fixed pricing, the cost & limit should == if gas price is 1"
2723 );
2724 gas_price += 1;
2725 let cost = deploy
2726 .gas_cost(&chainspec, gas_price)
2727 .expect("should cost")
2728 .value();
2729 assert_eq!(
2730 cost,
2731 limit * gas_price,
2732 "in fixed pricing, the cost should == limit * gas_price"
2733 );
2734 }
2735
2736 #[test]
2737 fn should_use_lane_specific_size_constraints() {
2738 let mut rng = TestRng::new();
2739 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2742 let chain_name = "net-1";
2743 let mut chainspec = Chainspec::default();
2744 chainspec
2745 .with_chain_name(chain_name.to_string())
2746 .with_pricing_handling(PricingHandling::PaymentLimited);
2747
2748 let config = chainspec.transaction_config.clone();
2749
2750 let transfer_args = runtime_args! {
2751 "amount" => U512::from(DEFAULT_MIN_TRANSFER_MOTES),
2752 "source" => PublicKey::random(&mut rng).to_account_hash(),
2753 "target" => PublicKey::random(&mut rng).to_account_hash(),
2754 "some_other" => vec![1; 1_000_000], };
2756 let payment_amount = 10_000_000_000u64;
2757 let payment_args = runtime_args! {
2758 "amount" => U512::from(payment_amount),
2759 };
2760 let session = ExecutableDeployItem::Transfer {
2761 args: transfer_args,
2762 };
2763 let payment = ExecutableDeployItem::ModuleBytes {
2764 module_bytes: Bytes::new(),
2765 args: payment_args,
2766 };
2767
2768 let mut deploy = create_deploy(
2769 &mut rng,
2770 config.max_ttl,
2771 0,
2772 chain_name,
2773 GAS_PRICE_TOLERANCE as u64,
2774 );
2775 deploy.payment = payment;
2776 deploy.session = session;
2777 assert_eq!(
2778 calculate_lane_id_for_deploy(
2779 &deploy,
2780 chainspec.core_config.pricing_handling,
2781 &config.transaction_v1_config,
2782 ),
2783 Ok(MINT_LANE_ID)
2784 );
2785 let current_timestamp = deploy.header().timestamp();
2786 let ret = deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp);
2787 assert!(ret.is_err());
2788 let err = ret.err().unwrap();
2789 assert!(matches!(
2790 err,
2791 InvalidDeploy::ExcessiveSize(DeployExcessiveSizeError { .. })
2792 ))
2793 }
2794}