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_valid_custom_payment_package_by_name(rng: &mut TestRng) -> Self {
872 let payment = ExecutableDeployItem::StoredVersionedContractByName {
873 name: "Test".to_string(),
874 version: None,
875 entry_point: "call".to_string(),
876 args: Default::default(),
877 };
878 Self::random_transfer_with_payment(rng, payment)
879 }
880
881 #[cfg(any(all(feature = "std", feature = "testing"), test))]
884 pub fn random_with_missing_payment_package_by_hash(rng: &mut TestRng) -> Self {
885 let payment = ExecutableDeployItem::StoredVersionedContractByHash {
886 hash: Default::default(),
887 version: None,
888 entry_point: "call".to_string(),
889 args: Default::default(),
890 };
891 Self::random_transfer_with_payment(rng, payment)
892 }
893
894 #[cfg(any(all(feature = "std", feature = "testing"), test))]
897 pub fn random_with_nonexistent_contract_version_in_payment_package(rng: &mut TestRng) -> Self {
898 let payment = ExecutableDeployItem::StoredVersionedContractByHash {
899 hash: [19; 32].into(),
900 version: Some(6u32),
901 entry_point: "non-existent-entry-point".to_string(),
902 args: Default::default(),
903 };
904 Self::random_transfer_with_payment(rng, payment)
905 }
906
907 #[cfg(any(all(feature = "std", feature = "testing"), test))]
909 pub fn random_with_valid_session_contract_by_name(rng: &mut TestRng) -> Self {
910 let session = ExecutableDeployItem::StoredContractByName {
911 name: "Test".to_string(),
912 entry_point: "call".to_string(),
913 args: Default::default(),
914 };
915 Self::random_transfer_with_session(rng, session)
916 }
917
918 #[cfg(any(all(feature = "std", feature = "testing"), test))]
921 pub fn random_with_missing_session_contract_by_hash(rng: &mut TestRng) -> Self {
922 let session = ExecutableDeployItem::StoredContractByHash {
923 hash: Default::default(),
924 entry_point: "call".to_string(),
925 args: Default::default(),
926 };
927 Self::random_transfer_with_session(rng, session)
928 }
929
930 #[cfg(any(all(feature = "std", feature = "testing"), test))]
933 pub fn random_with_missing_entry_point_in_session_contract(rng: &mut TestRng) -> Self {
934 let timestamp = Timestamp::now();
935 let ttl = TimeDiff::from_seconds(rng.gen_range(60..3600));
936 let session = ExecutableDeployItem::StoredContractByHash {
937 hash: [19; 32].into(),
938 entry_point: "non-existent-entry-point".to_string(),
939 args: Default::default(),
940 };
941
942 let payment_amount = 10_000_000_000u64;
943 let payment_args = runtime_args! {
944 "amount" => U512::from(payment_amount)
945 };
946 let payment = ExecutableDeployItem::ModuleBytes {
947 module_bytes: Bytes::new(),
948 args: payment_args,
949 };
950 let gas_price = rng.gen_range(1..4);
951
952 let dependencies = vec![];
953 let chain_name = String::from("casper-example");
954
955 let secret_key = SecretKey::random(rng);
956
957 Deploy::new_signed(
958 timestamp,
959 ttl,
960 gas_price,
961 dependencies,
962 chain_name,
963 payment,
964 session,
965 &secret_key,
966 None,
967 )
968 }
969
970 #[cfg(any(all(feature = "std", feature = "testing"), test))]
973 pub fn random_with_valid_session_package_by_name(rng: &mut TestRng) -> Self {
974 let session = ExecutableDeployItem::StoredVersionedContractByName {
975 name: "Test".to_string(),
976 version: None,
977 entry_point: "call".to_string(),
978 args: Default::default(),
979 };
980 Self::random_transfer_with_session(rng, session)
981 }
982
983 #[cfg(any(all(feature = "std", feature = "testing"), test))]
986 pub fn random_contract_by_name(
987 rng: &mut TestRng,
988 maybe_secret_key: Option<SecretKey>,
989 maybe_contract_name: Option<String>,
990 maybe_entry_point_name: Option<String>,
991 maybe_timestamp: Option<Timestamp>,
992 maybe_ttl: Option<TimeDiff>,
993 ) -> Self {
994 let payment_args = runtime_args! {
995 "amount" => U512::from(10),
996 };
997 let payment = ExecutableDeployItem::ModuleBytes {
998 module_bytes: Bytes::new(),
999 args: payment_args,
1000 };
1001 let contract_name = maybe_contract_name.unwrap_or_else(|| "Test".to_string());
1002 let entry_point_name = maybe_entry_point_name.unwrap_or_else(|| "Test".to_string());
1003 let session = ExecutableDeployItem::StoredVersionedContractByName {
1004 name: contract_name,
1005 version: None,
1006 entry_point: entry_point_name,
1007 args: Default::default(),
1008 };
1009 let secret_key = match maybe_secret_key {
1010 None => SecretKey::random(rng),
1011 Some(secret_key) => secret_key,
1012 };
1013 let timestamp = maybe_timestamp.unwrap_or_else(Timestamp::now);
1014 let ttl = match maybe_ttl {
1015 None => TimeDiff::from_seconds(rng.gen_range(60..3600)),
1016 Some(ttl) => ttl,
1017 };
1018 Deploy::new_signed(
1019 timestamp,
1020 ttl,
1021 1,
1022 vec![],
1023 "test_chain".to_string(),
1024 payment,
1025 session,
1026 &secret_key,
1027 None,
1028 )
1029 }
1030
1031 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1034 pub fn random_with_missing_session_package_by_hash(rng: &mut TestRng) -> Self {
1035 let session = ExecutableDeployItem::StoredVersionedContractByHash {
1036 hash: Default::default(),
1037 version: None,
1038 entry_point: "call".to_string(),
1039 args: Default::default(),
1040 };
1041 Self::random_transfer_with_session(rng, session)
1042 }
1043
1044 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1047 pub fn random_with_nonexistent_contract_version_in_session_package(rng: &mut TestRng) -> Self {
1048 let session = ExecutableDeployItem::StoredVersionedContractByHash {
1049 hash: [19; 32].into(),
1050 version: Some(6u32),
1051 entry_point: "non-existent-entry-point".to_string(),
1052 args: Default::default(),
1053 };
1054 Self::random_transfer_with_session(rng, session)
1055 }
1056
1057 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1059 pub fn random_without_transfer_target(rng: &mut TestRng) -> Self {
1060 let transfer_args = runtime_args! {
1061 "amount" => U512::from(DEFAULT_MIN_TRANSFER_MOTES),
1062 "source" => PublicKey::random(rng).to_account_hash(),
1063 };
1064 let session = ExecutableDeployItem::Transfer {
1065 args: transfer_args,
1066 };
1067 Self::random_transfer_with_session(rng, session)
1068 }
1069
1070 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1072 pub fn random_without_transfer_amount(rng: &mut TestRng) -> Self {
1073 let transfer_args = runtime_args! {
1074 "source" => PublicKey::random(rng).to_account_hash(),
1075 "target" => PublicKey::random(rng).to_account_hash(),
1076 };
1077 let session = ExecutableDeployItem::Transfer {
1078 args: transfer_args,
1079 };
1080 Self::random_transfer_with_session(rng, session)
1081 }
1082
1083 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1085 pub fn random_with_mangled_transfer_amount(rng: &mut TestRng) -> Self {
1086 let transfer_args = runtime_args! {
1087 "amount" => "mangled-transfer-amount",
1088 "source" => PublicKey::random(rng).to_account_hash(),
1089 "target" => PublicKey::random(rng).to_account_hash(),
1090 };
1091 let session = ExecutableDeployItem::Transfer {
1092 args: transfer_args,
1093 };
1094 Self::random_transfer_with_session(rng, session)
1095 }
1096
1097 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1099 pub fn random_with_empty_session_module_bytes(rng: &mut TestRng) -> Self {
1100 let session = ExecutableDeployItem::ModuleBytes {
1101 module_bytes: Bytes::new(),
1102 args: Default::default(),
1103 };
1104 let timestamp = Timestamp::now();
1105 let ttl = TimeDiff::from_seconds(rng.gen_range(60..3600));
1106 let amount = 10_000_000_000u64;
1107 let payment_args = runtime_args! {
1108 "amount" => U512::from(amount)
1109 };
1110 let payment = ExecutableDeployItem::ModuleBytes {
1111 module_bytes: Bytes::new(),
1112 args: payment_args,
1113 };
1114 let gas_price = 1;
1115
1116 let dependencies = vec![];
1117 let chain_name = String::from("casper-example");
1118
1119 let secret_key = SecretKey::random(rng);
1120
1121 Deploy::new_signed(
1122 timestamp,
1123 ttl,
1124 gas_price,
1125 dependencies,
1126 chain_name,
1127 payment,
1128 session,
1129 &secret_key,
1130 None,
1131 )
1132 }
1133
1134 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1136 pub fn random_expired_deploy(rng: &mut TestRng) -> Self {
1137 let deploy = Self::random_valid_native_transfer(rng);
1138 let secret_key = SecretKey::random(rng);
1139
1140 Deploy::new_signed(
1141 Timestamp::zero(),
1142 TimeDiff::from_seconds(1u32),
1143 deploy.header.gas_price(),
1144 deploy.header.dependencies().clone(),
1145 deploy.header.chain_name().to_string(),
1146 deploy.payment,
1147 deploy.session,
1148 &secret_key,
1149 None,
1150 )
1151 }
1152
1153 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1155 pub fn random_with_native_transfer_in_payment_logic(rng: &mut TestRng) -> Self {
1156 let transfer_args = runtime_args! {
1157 "amount" => U512::from(DEFAULT_MIN_TRANSFER_MOTES),
1158 "source" => PublicKey::random(rng).to_account_hash(),
1159 "target" => PublicKey::random(rng).to_account_hash(),
1160 };
1161 let payment = ExecutableDeployItem::Transfer {
1162 args: transfer_args,
1163 };
1164 Self::random_transfer_with_payment(rng, payment)
1165 }
1166
1167 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1168 fn random_transfer_with_payment(rng: &mut TestRng, payment: ExecutableDeployItem) -> Self {
1169 let deploy = Self::random_valid_native_transfer(rng);
1170 let secret_key = SecretKey::random(rng);
1171
1172 Deploy::new_signed(
1173 deploy.header.timestamp(),
1174 deploy.header.ttl(),
1175 deploy.header.gas_price(),
1176 deploy.header.dependencies().clone(),
1177 deploy.header.chain_name().to_string(),
1178 payment,
1179 deploy.session,
1180 &secret_key,
1181 None,
1182 )
1183 }
1184
1185 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1186 fn random_transfer_with_session(rng: &mut TestRng, session: ExecutableDeployItem) -> Self {
1187 let deploy = Self::random_valid_native_transfer(rng);
1188 let secret_key = SecretKey::random(rng);
1189
1190 Deploy::new_signed(
1191 deploy.header.timestamp(),
1192 deploy.header.ttl(),
1193 deploy.header.gas_price(),
1194 deploy.header.dependencies().clone(),
1195 deploy.header.chain_name().to_string(),
1196 deploy.payment,
1197 session,
1198 &secret_key,
1199 None,
1200 )
1201 }
1202
1203 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1205 pub fn random_with_gas_price(rng: &mut TestRng, gas_price: u64) -> Self {
1206 let deploy = Self::random(rng);
1207 let secret_key = SecretKey::random(rng);
1208
1209 Deploy::new_signed(
1210 deploy.header.timestamp(),
1211 deploy.header.ttl(),
1212 gas_price,
1213 deploy.header.dependencies().clone(),
1214 deploy.header.chain_name().to_string(),
1215 deploy.payment,
1216 deploy.session,
1217 &secret_key,
1218 None,
1219 )
1220 }
1221
1222 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1224 pub fn add_bid(
1225 chain_name: String,
1226 auction_contract_hash: AddressableEntityHash,
1227 public_key: PublicKey,
1228 bid_amount: U512,
1229 delegation_rate: u8,
1230 timestamp: Timestamp,
1231 ttl: TimeDiff,
1232 ) -> Self {
1233 let payment = ExecutableDeployItem::ModuleBytes {
1234 module_bytes: Bytes::new(),
1235 args: runtime_args! { ARG_AMOUNT => U512::from(100_000_000_000u64) },
1236 };
1237 let args = runtime_args! {
1238 ARG_AUCTION_AMOUNT => bid_amount,
1239 ARG_AUCTION_PUBLIC_KEY => public_key.clone(),
1240 ARG_DELEGATION_RATE => delegation_rate,
1241 };
1242 let session = ExecutableDeployItem::StoredContractByHash {
1243 hash: auction_contract_hash.into(),
1244 entry_point: METHOD_ADD_BID.to_string(),
1245 args,
1246 };
1247
1248 Deploy::build(
1249 timestamp,
1250 ttl,
1251 1,
1252 vec![],
1253 chain_name,
1254 payment,
1255 session,
1256 InitiatorAddrAndSecretKey::InitiatorAddr(InitiatorAddr::PublicKey(public_key)),
1257 )
1258 }
1259
1260 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1262 pub fn withdraw_bid(
1263 chain_name: String,
1264 auction_contract_hash: AddressableEntityHash,
1265 public_key: PublicKey,
1266 amount: U512,
1267 timestamp: Timestamp,
1268 ttl: TimeDiff,
1269 ) -> Self {
1270 let payment = ExecutableDeployItem::ModuleBytes {
1271 module_bytes: Bytes::new(),
1272 args: runtime_args! { ARG_AMOUNT => U512::from(3_000_000_000_u64) },
1273 };
1274 let args = runtime_args! {
1275 ARG_AUCTION_AMOUNT => amount,
1276 ARG_AUCTION_PUBLIC_KEY => public_key.clone(),
1277 };
1278 let session = ExecutableDeployItem::StoredContractByHash {
1279 hash: auction_contract_hash.into(),
1280 entry_point: METHOD_WITHDRAW_BID.to_string(),
1281 args,
1282 };
1283
1284 Deploy::build(
1285 timestamp,
1286 ttl,
1287 1,
1288 vec![],
1289 chain_name,
1290 payment,
1291 session,
1292 InitiatorAddrAndSecretKey::InitiatorAddr(InitiatorAddr::PublicKey(public_key)),
1293 )
1294 }
1295
1296 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1298 pub fn delegate(
1299 chain_name: String,
1300 auction_contract_hash: AddressableEntityHash,
1301 validator_public_key: PublicKey,
1302 delegator_public_key: PublicKey,
1303 amount: U512,
1304 timestamp: Timestamp,
1305 ttl: TimeDiff,
1306 ) -> Self {
1307 let payment = ExecutableDeployItem::ModuleBytes {
1308 module_bytes: Bytes::new(),
1309 args: runtime_args! { ARG_AMOUNT => U512::from(3_000_000_000_u64) },
1310 };
1311 let args = runtime_args! {
1312 ARG_DELEGATOR => delegator_public_key.clone(),
1313 ARG_VALIDATOR => validator_public_key,
1314 ARG_AUCTION_AMOUNT => amount,
1315 };
1316 let session = ExecutableDeployItem::StoredContractByHash {
1317 hash: auction_contract_hash.into(),
1318 entry_point: METHOD_DELEGATE.to_string(),
1319 args,
1320 };
1321
1322 Deploy::build(
1323 timestamp,
1324 ttl,
1325 1,
1326 vec![],
1327 chain_name,
1328 payment,
1329 session,
1330 InitiatorAddrAndSecretKey::InitiatorAddr(InitiatorAddr::PublicKey(
1331 delegator_public_key,
1332 )),
1333 )
1334 }
1335
1336 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1338 pub fn undelegate(
1339 chain_name: String,
1340 auction_contract_hash: AddressableEntityHash,
1341 validator_public_key: PublicKey,
1342 delegator_public_key: PublicKey,
1343 amount: U512,
1344 timestamp: Timestamp,
1345 ttl: TimeDiff,
1346 ) -> Self {
1347 let payment = ExecutableDeployItem::ModuleBytes {
1348 module_bytes: Bytes::new(),
1349 args: runtime_args! { ARG_AMOUNT => U512::from(3_000_000_000_u64) },
1350 };
1351 let args = runtime_args! {
1352 ARG_DELEGATOR => delegator_public_key.clone(),
1353 ARG_VALIDATOR => validator_public_key,
1354 ARG_AUCTION_AMOUNT => amount,
1355 };
1356 let session = ExecutableDeployItem::StoredContractByHash {
1357 hash: auction_contract_hash.into(),
1358 entry_point: METHOD_UNDELEGATE.to_string(),
1359 args,
1360 };
1361
1362 Deploy::build(
1363 timestamp,
1364 ttl,
1365 1,
1366 vec![],
1367 chain_name,
1368 payment,
1369 session,
1370 InitiatorAddrAndSecretKey::InitiatorAddr(InitiatorAddr::PublicKey(
1371 delegator_public_key,
1372 )),
1373 )
1374 }
1375
1376 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1378 #[allow(clippy::too_many_arguments)]
1379 pub fn redelegate(
1380 chain_name: String,
1381 auction_contract_hash: AddressableEntityHash,
1382 validator_public_key: PublicKey,
1383 delegator_public_key: PublicKey,
1384 redelegate_validator_public_key: PublicKey,
1385 amount: U512,
1386 timestamp: Timestamp,
1387 ttl: TimeDiff,
1388 ) -> Self {
1389 let payment = ExecutableDeployItem::ModuleBytes {
1390 module_bytes: Bytes::new(),
1391 args: runtime_args! { ARG_AMOUNT => U512::from(3_000_000_000_u64) },
1392 };
1393 let args = runtime_args! {
1394 ARG_DELEGATOR => delegator_public_key.clone(),
1395 ARG_VALIDATOR => validator_public_key,
1396 ARG_NEW_VALIDATOR => redelegate_validator_public_key,
1397 ARG_AUCTION_AMOUNT => amount,
1398 };
1399 let session = ExecutableDeployItem::StoredContractByHash {
1400 hash: auction_contract_hash.into(),
1401 entry_point: METHOD_REDELEGATE.to_string(),
1402 args,
1403 };
1404
1405 Deploy::build(
1406 timestamp,
1407 ttl,
1408 1,
1409 vec![],
1410 chain_name,
1411 payment,
1412 session,
1413 InitiatorAddrAndSecretKey::InitiatorAddr(InitiatorAddr::PublicKey(
1414 delegator_public_key,
1415 )),
1416 )
1417 }
1418
1419 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1421 #[allow(clippy::too_many_arguments)]
1422 pub fn native_transfer(
1423 chain_name: String,
1424 source_purse: Option<URef>,
1425 sender_public_key: PublicKey,
1426 receiver_public_key: PublicKey,
1427 amount: Option<U512>,
1428 timestamp: Timestamp,
1429 ttl: TimeDiff,
1430 gas_price: u64,
1431 ) -> Self {
1432 let amount = amount.unwrap_or_else(|| U512::from(DEFAULT_MIN_TRANSFER_MOTES));
1433
1434 let payment = ExecutableDeployItem::ModuleBytes {
1435 module_bytes: Bytes::new(),
1436 args: runtime_args! { ARG_AMOUNT => U512::from(3_000_000_000_u64) },
1437 };
1438
1439 let mut transfer_args = runtime_args! {
1440 "amount" => amount,
1441 "target" => receiver_public_key.to_account_hash(),
1442 };
1443
1444 if let Some(source) = source_purse {
1445 transfer_args
1446 .insert("source", source)
1447 .expect("should serialize source arg");
1448 }
1449
1450 let session = ExecutableDeployItem::Transfer {
1451 args: transfer_args,
1452 };
1453
1454 Deploy::build(
1455 timestamp,
1456 ttl,
1457 gas_price,
1458 vec![],
1459 chain_name,
1460 payment,
1461 session,
1462 InitiatorAddrAndSecretKey::InitiatorAddr(InitiatorAddr::PublicKey(sender_public_key)),
1463 )
1464 }
1465}
1466
1467#[cfg(any(feature = "std", test))]
1468impl GasLimited for Deploy {
1469 type Error = InvalidDeploy;
1470
1471 fn gas_cost(&self, chainspec: &Chainspec, gas_price: u8) -> Result<Motes, Self::Error> {
1472 let gas_limit = self.gas_limit(chainspec)?;
1473 let motes =
1474 Motes::from_gas(gas_limit, gas_price).ok_or(InvalidDeploy::UnableToCalculateGasCost)?;
1475 Ok(motes)
1476 }
1477
1478 fn gas_limit(&self, chainspec: &Chainspec) -> Result<Gas, Self::Error> {
1479 let pricing_handling = chainspec.core_config.pricing_handling;
1480 let costs = &chainspec.system_costs_config;
1481 let gas_limit = match pricing_handling {
1482 PricingHandling::PaymentLimited => {
1483 if self.is_transfer() {
1486 Gas::new(costs.mint_costs().transfer)
1487 } else {
1488 let value = self
1489 .payment()
1490 .args()
1491 .get(ARG_AMOUNT)
1492 .ok_or(InvalidDeploy::MissingPaymentAmount)?;
1493 let payment_amount = value
1494 .clone()
1495 .into_t::<U512>()
1496 .map_err(|_| InvalidDeploy::FailedToParsePaymentAmount)?;
1497 Gas::new(payment_amount)
1498 }
1499 }
1500 PricingHandling::Fixed => {
1501 let v1_config = &chainspec.transaction_config.transaction_v1_config;
1502 let lane_id = calculate_lane_id_for_deploy(self, pricing_handling, v1_config)?;
1503 let lane_definition = v1_config
1504 .get_lane_by_id(lane_id)
1505 .ok_or(InvalidDeploy::NoLaneMatch)?;
1506 let computation_limit = lane_definition.max_transaction_gas_limit;
1507 Gas::new(computation_limit)
1508 } };
1510 Ok(gas_limit)
1511 }
1512
1513 fn gas_price_tolerance(&self) -> Result<u8, Self::Error> {
1514 u8::try_from(self.gas_price()).map_err(|_| Self::Error::UnableToCalculateGasLimit)
1515 }
1516}
1517
1518impl hash::Hash for Deploy {
1519 fn hash<H: hash::Hasher>(&self, state: &mut H) {
1520 #[cfg(any(feature = "once_cell", test))]
1522 let Deploy {
1523 hash,
1524 header,
1525 payment,
1526 session,
1527 approvals,
1528 is_valid: _,
1529 } = self;
1530 #[cfg(not(any(feature = "once_cell", test)))]
1531 let Deploy {
1532 hash,
1533 header,
1534 payment,
1535 session,
1536 approvals,
1537 } = self;
1538 hash.hash(state);
1539 header.hash(state);
1540 payment.hash(state);
1541 session.hash(state);
1542 approvals.hash(state);
1543 }
1544}
1545
1546impl PartialEq for Deploy {
1547 fn eq(&self, other: &Deploy) -> bool {
1548 #[cfg(any(feature = "once_cell", test))]
1550 let Deploy {
1551 hash,
1552 header,
1553 payment,
1554 session,
1555 approvals,
1556 is_valid: _,
1557 } = self;
1558 #[cfg(not(any(feature = "once_cell", test)))]
1559 let Deploy {
1560 hash,
1561 header,
1562 payment,
1563 session,
1564 approvals,
1565 } = self;
1566 *hash == other.hash
1567 && *header == other.header
1568 && *payment == other.payment
1569 && *session == other.session
1570 && *approvals == other.approvals
1571 }
1572}
1573
1574impl Ord for Deploy {
1575 fn cmp(&self, other: &Deploy) -> cmp::Ordering {
1576 #[cfg(any(feature = "once_cell", test))]
1578 let Deploy {
1579 hash,
1580 header,
1581 payment,
1582 session,
1583 approvals,
1584 is_valid: _,
1585 } = self;
1586 #[cfg(not(any(feature = "once_cell", test)))]
1587 let Deploy {
1588 hash,
1589 header,
1590 payment,
1591 session,
1592 approvals,
1593 } = self;
1594 hash.cmp(&other.hash)
1595 .then_with(|| header.cmp(&other.header))
1596 .then_with(|| payment.cmp(&other.payment))
1597 .then_with(|| session.cmp(&other.session))
1598 .then_with(|| approvals.cmp(&other.approvals))
1599 }
1600}
1601
1602impl PartialOrd for Deploy {
1603 fn partial_cmp(&self, other: &Deploy) -> Option<cmp::Ordering> {
1604 Some(self.cmp(other))
1605 }
1606}
1607
1608impl ToBytes for Deploy {
1609 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
1610 let mut buffer = bytesrepr::allocate_buffer(self)?;
1611 self.write_bytes(&mut buffer)?;
1612 Ok(buffer)
1613 }
1614
1615 fn serialized_length(&self) -> usize {
1616 self.header.serialized_length()
1617 + self.hash.serialized_length()
1618 + self.payment.serialized_length()
1619 + self.session.serialized_length()
1620 + self.approvals.serialized_length()
1621 }
1622
1623 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
1624 self.header.write_bytes(writer)?;
1625 self.hash.write_bytes(writer)?;
1626 self.payment.write_bytes(writer)?;
1627 self.session.write_bytes(writer)?;
1628 self.approvals.write_bytes(writer)
1629 }
1630}
1631
1632impl FromBytes for Deploy {
1633 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
1634 let (header, remainder) = DeployHeader::from_bytes(bytes)?;
1635 let (hash, remainder) = DeployHash::from_bytes(remainder)?;
1636 let (payment, remainder) = ExecutableDeployItem::from_bytes(remainder)?;
1637 let (session, remainder) = ExecutableDeployItem::from_bytes(remainder)?;
1638 let (approvals, remainder) = BTreeSet::<Approval>::from_bytes(remainder)?;
1639 let maybe_valid_deploy = Deploy {
1640 header,
1641 hash,
1642 payment,
1643 session,
1644 approvals,
1645 #[cfg(any(feature = "once_cell", test))]
1646 is_valid: OnceCell::new(),
1647 };
1648 Ok((maybe_valid_deploy, remainder))
1649 }
1650}
1651
1652impl Display for Deploy {
1653 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
1654 write!(
1655 formatter,
1656 "deploy[{}, {}, payment_code: {}, session_code: {}, approvals: {}]",
1657 self.hash,
1658 self.header,
1659 self.payment,
1660 self.session,
1661 DisplayIter::new(self.approvals.iter())
1662 )
1663 }
1664}
1665
1666fn serialize_header(header: &DeployHeader) -> Vec<u8> {
1667 header
1668 .to_bytes()
1669 .unwrap_or_else(|error| panic!("should serialize deploy header: {}", error))
1670}
1671
1672fn serialize_body(payment: &ExecutableDeployItem, session: &ExecutableDeployItem) -> Vec<u8> {
1673 let mut buffer = Vec::with_capacity(payment.serialized_length() + session.serialized_length());
1674 payment
1675 .write_bytes(&mut buffer)
1676 .unwrap_or_else(|error| panic!("should serialize payment code: {}", error));
1677 session
1678 .write_bytes(&mut buffer)
1679 .unwrap_or_else(|error| panic!("should serialize session code: {}", error));
1680 buffer
1681}
1682
1683fn validate_deploy(deploy: &Deploy) -> Result<(), InvalidDeploy> {
1686 if deploy.approvals.is_empty() {
1687 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1688 warn!(?deploy, "deploy has no approvals");
1689 return Err(InvalidDeploy::EmptyApprovals);
1690 }
1691
1692 deploy.has_valid_hash()?;
1693
1694 for (index, approval) in deploy.approvals.iter().enumerate() {
1695 if let Err(error) = crypto::verify(deploy.hash, approval.signature(), approval.signer()) {
1696 #[cfg(any(all(feature = "std", feature = "testing"), test))]
1697 warn!(?deploy, "failed to verify approval {}: {}", index, error);
1698 return Err(InvalidDeploy::InvalidApproval { index, error });
1699 }
1700 }
1701
1702 Ok(())
1703}
1704
1705#[cfg(any(feature = "std", test))]
1706pub fn calculate_lane_id_for_deploy(
1708 deploy: &Deploy,
1709 pricing_handling: PricingHandling,
1710 config: &TransactionV1Config,
1711) -> Result<u8, InvalidDeploy> {
1712 if deploy.is_transfer() {
1713 return Ok(MINT_LANE_ID);
1714 }
1715 let size_estimation = deploy.serialized_length() as u64;
1716 let runtime_args_size = (deploy.payment().args().serialized_length()
1717 + deploy.session().args().serialized_length()) as u64;
1718
1719 let gas_price_tolerance = deploy.gas_price_tolerance()?;
1720 let pricing_mode = match pricing_handling {
1721 PricingHandling::PaymentLimited => {
1722 let is_standard_payment = deploy.payment().is_standard_payment(Phase::Payment);
1723 let value = deploy
1724 .payment()
1725 .args()
1726 .get(ARG_AMOUNT)
1727 .ok_or(InvalidDeploy::MissingPaymentAmount)?;
1728 let payment_amount = value
1729 .clone()
1730 .into_t::<U512>()
1731 .map_err(|_| InvalidDeploy::FailedToParsePaymentAmount)?
1732 .as_u64();
1733 PricingMode::PaymentLimited {
1734 payment_amount,
1735 gas_price_tolerance,
1736 standard_payment: is_standard_payment,
1737 }
1738 }
1739 PricingHandling::Fixed => PricingMode::Fixed {
1740 gas_price_tolerance,
1741 additional_computation_factor: 0,
1743 },
1744 };
1745
1746 get_lane_for_non_install_wasm(config, &pricing_mode, size_estimation, runtime_args_size)
1747 .map_err(Into::into)
1748}
1749
1750#[cfg(test)]
1751mod tests {
1752 use std::{iter, time::Duration};
1753
1754 use super::*;
1755 use crate::{CLValue, TransactionConfig};
1756
1757 #[test]
1758 fn json_roundtrip() {
1759 let mut rng = TestRng::new();
1760 let deploy = Deploy::random(&mut rng);
1761 let json_string = serde_json::to_string_pretty(&deploy).unwrap();
1762 let decoded = serde_json::from_str(&json_string).unwrap();
1763 assert_eq!(deploy, decoded);
1764 }
1765
1766 #[test]
1767 fn bincode_roundtrip() {
1768 let mut rng = TestRng::new();
1769 let deploy = Deploy::random(&mut rng);
1770 let serialized = bincode::serialize(&deploy).unwrap();
1771 let deserialized = bincode::deserialize(&serialized).unwrap();
1772 assert_eq!(deploy, deserialized);
1773 }
1774
1775 #[test]
1776 fn bytesrepr_roundtrip() {
1777 let mut rng = TestRng::new();
1778 let deploy = Deploy::random(&mut rng);
1779 bytesrepr::test_serialization_roundtrip(deploy.header());
1780 bytesrepr::test_serialization_roundtrip(&deploy);
1781 }
1782
1783 fn create_deploy(
1784 rng: &mut TestRng,
1785 ttl: TimeDiff,
1786 dependency_count: usize,
1787 chain_name: &str,
1788 gas_price: u64,
1789 ) -> Deploy {
1790 let secret_key = SecretKey::random(rng);
1791 let dependencies = iter::repeat_with(|| DeployHash::random(rng))
1792 .take(dependency_count)
1793 .collect();
1794 let transfer_args = {
1795 let mut transfer_args = RuntimeArgs::new();
1796 let value = CLValue::from_t(U512::from(DEFAULT_MIN_TRANSFER_MOTES))
1797 .expect("should create CLValue");
1798 transfer_args.insert_cl_value("amount", value);
1799 transfer_args
1800 };
1801 Deploy::new_signed(
1802 Timestamp::now(),
1803 ttl,
1804 gas_price,
1805 dependencies,
1806 chain_name.to_string(),
1807 ExecutableDeployItem::ModuleBytes {
1808 module_bytes: Bytes::new(),
1809 args: RuntimeArgs::new(),
1810 },
1811 ExecutableDeployItem::Transfer {
1812 args: transfer_args,
1813 },
1814 &secret_key,
1815 None,
1816 )
1817 }
1818
1819 #[test]
1820 fn is_valid() {
1821 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
1822
1823 let mut rng = TestRng::new();
1824
1825 let deploy = create_deploy(
1826 &mut rng,
1827 TransactionConfig::default().max_ttl,
1828 0,
1829 "net-1",
1830 GAS_PRICE_TOLERANCE as u64,
1831 );
1832 assert_eq!(
1833 deploy.is_valid.get(),
1834 None,
1835 "is valid should initially be None"
1836 );
1837 deploy.is_valid().expect("should be valid");
1838 assert_eq!(
1839 deploy.is_valid.get(),
1840 Some(&Ok(())),
1841 "is valid should be true"
1842 );
1843 }
1844
1845 fn check_is_not_valid(invalid_deploy: Deploy, expected_error: InvalidDeploy) {
1846 assert!(
1847 invalid_deploy.is_valid.get().is_none(),
1848 "is valid should initially be None"
1849 );
1850 let actual_error = invalid_deploy.is_valid().unwrap_err();
1851
1852 match expected_error {
1856 InvalidDeploy::InvalidApproval {
1857 index: expected_index,
1858 ..
1859 } => match actual_error {
1860 InvalidDeploy::InvalidApproval {
1861 index: actual_index,
1862 ..
1863 } => {
1864 assert_eq!(actual_index, expected_index);
1865 }
1866 _ => panic!("expected {}, got: {}", expected_error, actual_error),
1867 },
1868 _ => {
1869 assert_eq!(actual_error, expected_error,);
1870 }
1871 }
1872
1873 assert_eq!(
1875 invalid_deploy.is_valid.get(),
1876 Some(&Err(actual_error)),
1877 "is valid should now be Some"
1878 );
1879 }
1880
1881 #[test]
1882 fn not_valid_due_to_invalid_body_hash() {
1883 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
1884
1885 let mut rng = TestRng::new();
1886 let mut deploy = create_deploy(
1887 &mut rng,
1888 TransactionConfig::default().max_ttl,
1889 0,
1890 "net-1",
1891 GAS_PRICE_TOLERANCE as u64,
1892 );
1893
1894 deploy.session = ExecutableDeployItem::Transfer {
1895 args: runtime_args! {
1896 "amount" => 1
1897 },
1898 };
1899 check_is_not_valid(deploy, InvalidDeploy::InvalidBodyHash);
1900 }
1901
1902 #[test]
1903 fn not_valid_due_to_invalid_deploy_hash() {
1904 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
1905
1906 let mut rng = TestRng::new();
1907 let mut deploy = create_deploy(
1908 &mut rng,
1909 TransactionConfig::default().max_ttl,
1910 0,
1911 "net-1",
1912 GAS_PRICE_TOLERANCE as u64,
1913 );
1914
1915 deploy.invalidate();
1917 check_is_not_valid(deploy, InvalidDeploy::InvalidDeployHash);
1918 }
1919
1920 #[test]
1921 fn not_valid_due_to_empty_approvals() {
1922 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
1923
1924 let mut rng = TestRng::new();
1925 let mut deploy = create_deploy(
1926 &mut rng,
1927 TransactionConfig::default().max_ttl,
1928 0,
1929 "net-1",
1930 GAS_PRICE_TOLERANCE as u64,
1931 );
1932 deploy.approvals = BTreeSet::new();
1933 assert!(deploy.approvals.is_empty());
1934 check_is_not_valid(deploy, InvalidDeploy::EmptyApprovals)
1935 }
1936
1937 #[test]
1938 fn not_valid_due_to_invalid_approval() {
1939 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
1940
1941 let mut rng = TestRng::new();
1942 let mut deploy = create_deploy(
1943 &mut rng,
1944 TransactionConfig::default().max_ttl,
1945 0,
1946 "net-1",
1947 GAS_PRICE_TOLERANCE as u64,
1948 );
1949
1950 let deploy2 = Deploy::random(&mut rng);
1951
1952 deploy.approvals.extend(deploy2.approvals.clone());
1953 let expected_index = deploy
1956 .approvals
1957 .iter()
1958 .enumerate()
1959 .find(|(_, approval)| deploy2.approvals.contains(approval))
1960 .map(|(index, _)| index)
1961 .unwrap();
1962 check_is_not_valid(
1963 deploy,
1964 InvalidDeploy::InvalidApproval {
1965 index: expected_index,
1966 error: crypto::Error::SignatureError, },
1968 );
1969 }
1970
1971 #[test]
1972 fn is_acceptable() {
1973 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
1974
1975 let mut rng = TestRng::new();
1976 let chain_name = "net-1".to_string();
1977 let mut chainspec = Chainspec::default();
1978 chainspec.with_chain_name(chain_name.to_string());
1979 let config = chainspec.transaction_config.clone();
1980
1981 let deploy = create_deploy(
1982 &mut rng,
1983 config.max_ttl,
1984 0,
1985 &chain_name,
1986 GAS_PRICE_TOLERANCE as u64,
1987 );
1988 let current_timestamp = deploy.header().timestamp();
1989 deploy
1990 .is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp)
1991 .expect("should be acceptable");
1992 }
1993
1994 #[test]
1995 fn not_acceptable_due_to_invalid_chain_name() {
1996 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
1997
1998 let mut rng = TestRng::new();
1999 let expected_chain_name = "net-1";
2000 let wrong_chain_name = "net-2".to_string();
2001
2002 let mut chainspec = Chainspec::default();
2003 chainspec.with_chain_name(expected_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 &wrong_chain_name,
2011 GAS_PRICE_TOLERANCE as u64,
2012 );
2013
2014 let expected_error = InvalidDeploy::InvalidChainName {
2015 expected: expected_chain_name.to_string(),
2016 got: wrong_chain_name,
2017 };
2018
2019 let current_timestamp = deploy.header().timestamp();
2020 assert_eq!(
2021 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp),
2022 Err(expected_error)
2023 );
2024 assert!(
2025 deploy.is_valid.get().is_none(),
2026 "deploy should not have run expensive `is_valid` call"
2027 );
2028 }
2029
2030 #[test]
2031 fn not_acceptable_due_to_excessive_dependencies() {
2032 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2033
2034 let mut rng = TestRng::new();
2035 let chain_name = "net-1";
2036
2037 let mut chainspec = Chainspec::default();
2038 chainspec.with_chain_name(chain_name.to_string());
2039 let config = chainspec.transaction_config.clone();
2040
2041 let deploy = create_deploy(
2042 &mut rng,
2043 config.max_ttl,
2044 1,
2045 chain_name,
2046 GAS_PRICE_TOLERANCE as u64,
2047 );
2048
2049 let expected_error = InvalidDeploy::DependenciesNoLongerSupported;
2050
2051 let current_timestamp = deploy.header().timestamp();
2052 assert_eq!(
2053 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp),
2054 Err(expected_error)
2055 );
2056 assert!(
2057 deploy.is_valid.get().is_none(),
2058 "deploy should not have run expensive `is_valid` call"
2059 );
2060 }
2061
2062 #[test]
2063 fn not_acceptable_due_to_excessive_ttl() {
2064 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2065
2066 let mut rng = TestRng::new();
2067 let chain_name = "net-1";
2068
2069 let mut chainspec = Chainspec::default();
2070 chainspec.with_chain_name(chain_name.to_string());
2071 let config = chainspec.transaction_config.clone();
2072
2073 let ttl = config.max_ttl + TimeDiff::from(Duration::from_secs(1));
2074
2075 let deploy = create_deploy(&mut rng, ttl, 0, chain_name, GAS_PRICE_TOLERANCE as u64);
2076
2077 let expected_error = InvalidDeploy::ExcessiveTimeToLive {
2078 max_ttl: config.max_ttl,
2079 got: ttl,
2080 };
2081
2082 let current_timestamp = deploy.header().timestamp();
2083 assert_eq!(
2084 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp),
2085 Err(expected_error)
2086 );
2087 assert!(
2088 deploy.is_valid.get().is_none(),
2089 "deploy should not have run expensive `is_valid` call"
2090 );
2091 }
2092
2093 #[test]
2094 fn not_acceptable_due_to_timestamp_in_future() {
2095 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2096
2097 let mut rng = TestRng::new();
2098 let chain_name = "net-1";
2099
2100 let mut chainspec = Chainspec::default();
2101 chainspec.with_chain_name(chain_name.to_string());
2102 let config = chainspec.transaction_config.clone();
2103 let leeway = TimeDiff::from_seconds(2);
2104
2105 let deploy = create_deploy(
2106 &mut rng,
2107 config.max_ttl,
2108 0,
2109 chain_name,
2110 GAS_PRICE_TOLERANCE as u64,
2111 );
2112 let current_timestamp = deploy.header.timestamp() - leeway - TimeDiff::from_seconds(1);
2113
2114 let expected_error = InvalidDeploy::TimestampInFuture {
2115 validation_timestamp: current_timestamp,
2116 timestamp_leeway: leeway,
2117 got: deploy.header.timestamp(),
2118 };
2119
2120 assert_eq!(
2121 deploy.is_config_compliant(&chainspec, leeway, current_timestamp),
2122 Err(expected_error)
2123 );
2124 assert!(
2125 deploy.is_valid.get().is_none(),
2126 "deploy should not have run expensive `is_valid` call"
2127 );
2128 }
2129
2130 #[test]
2131 fn acceptable_if_timestamp_slightly_in_future() {
2132 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2133
2134 let mut rng = TestRng::new();
2135 let chain_name = "net-1";
2136
2137 let mut chainspec = Chainspec::default();
2138 chainspec.with_chain_name(chain_name.to_string());
2139 let config = chainspec.transaction_config.clone();
2140 let leeway = TimeDiff::from_seconds(2);
2141
2142 let deploy = create_deploy(
2143 &mut rng,
2144 config.max_ttl,
2145 0,
2146 chain_name,
2147 GAS_PRICE_TOLERANCE as u64,
2148 );
2149 let current_timestamp = deploy.header.timestamp() - (leeway / 2);
2150 deploy
2151 .is_config_compliant(&chainspec, leeway, current_timestamp)
2152 .expect("should be acceptable");
2153 }
2154
2155 #[test]
2156 fn not_acceptable_due_to_missing_payment_amount() {
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 chainspec.with_pricing_handling(PricingHandling::PaymentLimited);
2165 let config = chainspec.transaction_config.clone();
2166
2167 let payment = ExecutableDeployItem::ModuleBytes {
2168 module_bytes: Bytes::new(),
2169 args: RuntimeArgs::default(),
2170 };
2171
2172 let session = ExecutableDeployItem::StoredContractByName {
2175 name: "".to_string(),
2176 entry_point: "".to_string(),
2177 args: Default::default(),
2178 };
2179
2180 let mut deploy = create_deploy(
2181 &mut rng,
2182 config.max_ttl,
2183 0,
2184 chain_name,
2185 GAS_PRICE_TOLERANCE as u64,
2186 );
2187
2188 deploy.payment = payment;
2189 deploy.session = session;
2190
2191 let current_timestamp = deploy.header().timestamp();
2192 assert_eq!(
2193 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp),
2194 Err(InvalidDeploy::MissingPaymentAmount)
2195 );
2196 assert!(
2197 deploy.is_valid.get().is_none(),
2198 "deploy should not have run expensive `is_valid` call"
2199 );
2200 }
2201
2202 #[test]
2203 fn not_acceptable_due_to_mangled_payment_amount() {
2204 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2205
2206 let mut rng = TestRng::new();
2207 let chain_name = "net-1";
2208
2209 let mut chainspec = Chainspec::default();
2210 chainspec.with_chain_name(chain_name.to_string());
2211 chainspec.with_pricing_handling(PricingHandling::PaymentLimited);
2212 let config = chainspec.transaction_config.clone();
2213
2214 let payment = ExecutableDeployItem::ModuleBytes {
2215 module_bytes: Bytes::new(),
2216 args: runtime_args! {
2217 "amount" => "mangled-amount"
2218 },
2219 };
2220
2221 let session = ExecutableDeployItem::StoredContractByName {
2224 name: "".to_string(),
2225 entry_point: "".to_string(),
2226 args: Default::default(),
2227 };
2228
2229 let mut deploy = create_deploy(
2230 &mut rng,
2231 config.max_ttl,
2232 0,
2233 chain_name,
2234 GAS_PRICE_TOLERANCE as u64,
2235 );
2236
2237 deploy.payment = payment;
2238 deploy.session = session;
2239
2240 let current_timestamp = deploy.header().timestamp();
2241 assert_eq!(
2242 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp),
2243 Err(InvalidDeploy::FailedToParsePaymentAmount)
2244 );
2245 assert!(
2246 deploy.is_valid.get().is_none(),
2247 "deploy should not have run expensive `is_valid` call"
2248 );
2249 }
2250
2251 #[test]
2252 fn not_acceptable_if_doesnt_fit_in_any_lane() {
2253 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2254
2255 let mut rng = TestRng::new();
2256 let chain_name = "net-1";
2257
2258 let mut chainspec = Chainspec::default();
2259 chainspec.with_chain_name(chain_name.to_string());
2260 chainspec.with_pricing_handling(PricingHandling::PaymentLimited);
2261 let config = chainspec.transaction_config.clone();
2262 let max_lane = chainspec
2263 .transaction_config
2264 .transaction_v1_config
2265 .get_max_wasm_lane_by_gas_limit()
2266 .unwrap();
2267 let amount = U512::from(max_lane.max_transaction_gas_limit + 1);
2268
2269 let payment = ExecutableDeployItem::ModuleBytes {
2270 module_bytes: Bytes::new(),
2271 args: runtime_args! {
2272 "amount" => amount
2273 },
2274 };
2275
2276 let session = ExecutableDeployItem::StoredContractByName {
2279 name: "".to_string(),
2280 entry_point: "".to_string(),
2281 args: Default::default(),
2282 };
2283
2284 let mut deploy = create_deploy(
2285 &mut rng,
2286 config.max_ttl,
2287 0,
2288 chain_name,
2289 GAS_PRICE_TOLERANCE as u64,
2290 );
2291
2292 deploy.payment = payment;
2293 deploy.session = session;
2294
2295 let expected_error = InvalidDeploy::NoLaneMatch;
2296
2297 let current_timestamp = deploy.header().timestamp();
2298 assert_eq!(
2299 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp),
2300 Err(expected_error)
2301 );
2302 assert!(
2303 deploy.is_valid.get().is_none(),
2304 "deploy should not have run expensive `is_valid` call"
2305 );
2306 }
2307
2308 #[test]
2309 fn not_acceptable_due_to_transaction_bigger_than_block_limit() {
2310 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2314
2315 let mut rng = TestRng::new();
2316 let chain_name = "net-1";
2317
2318 let mut chainspec = Chainspec::default();
2319 chainspec.with_block_gas_limit(100); chainspec.with_chain_name(chain_name.to_string());
2321 chainspec.with_pricing_handling(PricingHandling::PaymentLimited);
2322 let config = chainspec.transaction_config.clone();
2323 let max_lane = chainspec
2324 .transaction_config
2325 .transaction_v1_config
2326 .get_max_wasm_lane_by_gas_limit()
2327 .unwrap();
2328 let amount = U512::from(max_lane.max_transaction_gas_limit);
2329
2330 let payment = ExecutableDeployItem::ModuleBytes {
2331 module_bytes: Bytes::new(),
2332 args: runtime_args! {
2333 "amount" => amount
2334 },
2335 };
2336
2337 let session = ExecutableDeployItem::StoredContractByName {
2340 name: "".to_string(),
2341 entry_point: "".to_string(),
2342 args: Default::default(),
2343 };
2344
2345 let mut deploy = create_deploy(
2346 &mut rng,
2347 config.max_ttl,
2348 0,
2349 chain_name,
2350 GAS_PRICE_TOLERANCE as u64,
2351 );
2352
2353 deploy.payment = payment;
2354 deploy.session = session;
2355
2356 let expected_error = InvalidDeploy::ExceededBlockGasLimit {
2357 block_gas_limit: config.block_gas_limit,
2358 got: Box::new(amount),
2359 };
2360
2361 let current_timestamp = deploy.header().timestamp();
2362 assert_eq!(
2363 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp),
2364 Err(expected_error)
2365 );
2366 assert!(
2367 deploy.is_valid.get().is_none(),
2368 "deploy should not have run expensive `is_valid` call"
2369 );
2370 }
2371
2372 #[test]
2373 fn transfer_acceptable_regardless_of_excessive_payment_amount() {
2374 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2375
2376 let mut rng = TestRng::new();
2377 let secret_key = SecretKey::random(&mut rng);
2378 let chain_name = "net-1";
2379
2380 let mut chainspec = Chainspec::default();
2381 chainspec.with_chain_name(chain_name.to_string());
2382 let config = chainspec.transaction_config.clone();
2383 let amount = U512::from(config.block_gas_limit + 1);
2384
2385 let payment = ExecutableDeployItem::ModuleBytes {
2386 module_bytes: Bytes::new(),
2387 args: runtime_args! {
2388 "amount" => amount
2389 },
2390 };
2391
2392 let transfer_args = {
2393 let mut transfer_args = RuntimeArgs::new();
2394 let value = CLValue::from_t(U512::from(DEFAULT_MIN_TRANSFER_MOTES))
2395 .expect("should create CLValue");
2396 transfer_args.insert_cl_value("amount", value);
2397 transfer_args
2398 };
2399
2400 let deploy = Deploy::new_signed(
2401 Timestamp::now(),
2402 config.max_ttl,
2403 GAS_PRICE_TOLERANCE as u64,
2404 vec![],
2405 chain_name.to_string(),
2406 payment,
2407 ExecutableDeployItem::Transfer {
2408 args: transfer_args,
2409 },
2410 &secret_key,
2411 None,
2412 );
2413
2414 let current_timestamp = deploy.header().timestamp();
2415 assert_eq!(
2416 Ok(()),
2417 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp)
2418 )
2419 }
2420
2421 #[test]
2422 fn not_acceptable_due_to_excessive_approvals() {
2423 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2424
2425 let mut rng = TestRng::new();
2426 let chain_name = "net-1";
2427
2428 let mut chainspec = Chainspec::default();
2429 chainspec.with_chain_name(chain_name.to_string());
2430 let config = chainspec.transaction_config.clone();
2431 let deploy = create_deploy(
2432 &mut rng,
2433 config.max_ttl,
2434 0,
2435 chain_name,
2436 GAS_PRICE_TOLERANCE as u64,
2437 );
2438 let max_associated_keys = (deploy.approvals.len() - 1) as u32;
2441 chainspec.with_max_associated_keys(max_associated_keys);
2442 let current_timestamp = deploy.header().timestamp();
2443 assert_eq!(
2444 Err(InvalidDeploy::ExcessiveApprovals {
2445 got: deploy.approvals.len() as u32,
2446 max_associated_keys,
2447 }),
2448 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp)
2449 )
2450 }
2451
2452 #[test]
2453 fn not_acceptable_due_to_missing_transfer_amount() {
2454 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2455
2456 let mut rng = TestRng::new();
2457 let chain_name = "net-1";
2458 let mut chainspec = Chainspec::default();
2459 chainspec.with_chain_name(chain_name.to_string());
2460
2461 let config = chainspec.transaction_config.clone();
2462 let mut deploy = create_deploy(
2463 &mut rng,
2464 config.max_ttl,
2465 0,
2466 chain_name,
2467 GAS_PRICE_TOLERANCE as u64,
2468 );
2469
2470 let transfer_args = RuntimeArgs::default();
2471 let session = ExecutableDeployItem::Transfer {
2472 args: transfer_args,
2473 };
2474 deploy.session = session;
2475
2476 let current_timestamp = deploy.header().timestamp();
2477 assert_eq!(
2478 Err(InvalidDeploy::MissingTransferAmount),
2479 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp)
2480 )
2481 }
2482
2483 #[test]
2484 fn not_acceptable_due_to_mangled_transfer_amount() {
2485 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2486
2487 let mut rng = TestRng::new();
2488 let chain_name = "net-1";
2489 let mut chainspec = Chainspec::default();
2490 chainspec.with_chain_name(chain_name.to_string());
2491
2492 let config = chainspec.transaction_config.clone();
2493 let mut deploy = create_deploy(
2494 &mut rng,
2495 config.max_ttl,
2496 0,
2497 chain_name,
2498 GAS_PRICE_TOLERANCE as u64,
2499 );
2500
2501 let transfer_args = runtime_args! {
2502 "amount" => "mangled-amount",
2503 "source" => PublicKey::random(&mut rng).to_account_hash(),
2504 "target" => PublicKey::random(&mut rng).to_account_hash(),
2505 };
2506 let session = ExecutableDeployItem::Transfer {
2507 args: transfer_args,
2508 };
2509 deploy.session = session;
2510
2511 let current_timestamp = deploy.header().timestamp();
2512 assert_eq!(
2513 Err(InvalidDeploy::FailedToParseTransferAmount),
2514 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp)
2515 )
2516 }
2517
2518 #[test]
2519 fn not_acceptable_due_to_too_low_gas_price_tolerance() {
2520 const GAS_PRICE_TOLERANCE: u8 = 0;
2521
2522 let mut rng = TestRng::new();
2523 let chain_name = "net-1";
2524 let mut chainspec = Chainspec::default();
2525 chainspec.with_chain_name(chain_name.to_string());
2526
2527 let config = chainspec.transaction_config.clone();
2528 let deploy = create_deploy(
2529 &mut rng,
2530 config.max_ttl,
2531 0,
2532 chain_name,
2533 GAS_PRICE_TOLERANCE as u64,
2534 );
2535
2536 let current_timestamp = deploy.header().timestamp();
2537 assert!(matches!(
2538 deploy.is_config_compliant(
2539 &chainspec,
2540 TimeDiff::default(),
2541 current_timestamp
2542 ),
2543 Err(InvalidDeploy::GasPriceToleranceTooLow { min_gas_price_tolerance, provided_gas_price_tolerance })
2544 if min_gas_price_tolerance == chainspec.vacancy_config.min_gas_price && provided_gas_price_tolerance == GAS_PRICE_TOLERANCE
2545 ))
2546 }
2547
2548 #[test]
2549 fn not_acceptable_due_to_insufficient_transfer_amount() {
2550 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2551
2552 let mut rng = TestRng::new();
2553 let chain_name = "net-1";
2554 let mut chainspec = Chainspec::default();
2555 chainspec.with_chain_name(chain_name.to_string());
2556
2557 let config = chainspec.transaction_config.clone();
2558 let mut deploy = create_deploy(
2559 &mut rng,
2560 config.max_ttl,
2561 0,
2562 chain_name,
2563 GAS_PRICE_TOLERANCE as u64,
2564 );
2565
2566 let amount = config.native_transfer_minimum_motes - 1;
2567 let insufficient_amount = U512::from(amount);
2568
2569 let transfer_args = runtime_args! {
2570 "amount" => insufficient_amount,
2571 "source" => PublicKey::random(&mut rng).to_account_hash(),
2572 "target" => PublicKey::random(&mut rng).to_account_hash(),
2573 };
2574 let session = ExecutableDeployItem::Transfer {
2575 args: transfer_args,
2576 };
2577 deploy.session = session;
2578
2579 let current_timestamp = deploy.header().timestamp();
2580 assert_eq!(
2581 Err(InvalidDeploy::InsufficientTransferAmount {
2582 minimum: Box::new(U512::from(config.native_transfer_minimum_motes)),
2583 attempted: Box::new(insufficient_amount),
2584 }),
2585 deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp,)
2586 )
2587 }
2588
2589 #[test]
2590 fn should_use_payment_amount_for_payment_limited_payment() {
2591 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2592
2593 let payment_amount = 500u64;
2594 let mut rng = TestRng::new();
2595 let chain_name = "net-1";
2596 let mut chainspec = Chainspec::default();
2597 chainspec
2598 .with_chain_name(chain_name.to_string())
2599 .with_pricing_handling(PricingHandling::PaymentLimited);
2600
2601 let config = chainspec.transaction_config.clone();
2602
2603 let payment = ExecutableDeployItem::ModuleBytes {
2604 module_bytes: Bytes::new(),
2605 args: runtime_args! {
2606 "amount" => U512::from(payment_amount)
2607 },
2608 };
2609
2610 let session = ExecutableDeployItem::StoredContractByName {
2613 name: "".to_string(),
2614 entry_point: "".to_string(),
2615 args: Default::default(),
2616 };
2617
2618 let mut deploy = create_deploy(
2619 &mut rng,
2620 config.max_ttl,
2621 0,
2622 chain_name,
2623 GAS_PRICE_TOLERANCE as u64,
2624 );
2625 deploy.payment = payment;
2626 deploy.session = session;
2627
2628 let mut gas_price = 1;
2629 let cost = deploy
2630 .gas_cost(&chainspec, gas_price)
2631 .expect("should cost")
2632 .value();
2633 assert_eq!(
2634 cost,
2635 U512::from(payment_amount),
2636 "in payment limited pricing, the user selected amount should be the cost if gas price is 1"
2637 );
2638 gas_price += 1;
2639 let cost = deploy
2640 .gas_cost(&chainspec, gas_price)
2641 .expect("should cost")
2642 .value();
2643 assert_eq!(
2644 cost,
2645 U512::from(payment_amount) * gas_price,
2646 "in payment limited pricing, the cost should == user selected amount * gas_price"
2647 );
2648 }
2649
2650 #[test]
2651 fn should_use_cost_table_for_fixed_payment() {
2652 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2653
2654 let payment_amount = 500u64;
2655 let mut rng = TestRng::new();
2656 let chain_name = "net-1";
2657 let mut chainspec = Chainspec::default();
2658 chainspec
2659 .with_chain_name(chain_name.to_string())
2660 .with_pricing_handling(PricingHandling::PaymentLimited);
2661
2662 let config = chainspec.transaction_config.clone();
2663
2664 let payment = ExecutableDeployItem::ModuleBytes {
2665 module_bytes: Bytes::new(),
2666 args: runtime_args! {
2667 "amount" => U512::from(payment_amount)
2668 },
2669 };
2670
2671 let session = ExecutableDeployItem::StoredContractByName {
2674 name: "".to_string(),
2675 entry_point: "".to_string(),
2676 args: Default::default(),
2677 };
2678
2679 let mut deploy = create_deploy(
2680 &mut rng,
2681 config.max_ttl,
2682 0,
2683 chain_name,
2684 GAS_PRICE_TOLERANCE as u64,
2685 );
2686 deploy.payment = payment;
2687 deploy.session = session;
2688
2689 let mut gas_price = 1;
2690 let limit = deploy.gas_limit(&chainspec).expect("should limit").value();
2691 let cost = deploy
2692 .gas_cost(&chainspec, gas_price)
2693 .expect("should cost")
2694 .value();
2695 assert_eq!(
2696 cost, limit,
2697 "in fixed pricing, the cost & limit should == if gas price is 1"
2698 );
2699 gas_price += 1;
2700 let cost = deploy
2701 .gas_cost(&chainspec, gas_price)
2702 .expect("should cost")
2703 .value();
2704 assert_eq!(
2705 cost,
2706 limit * gas_price,
2707 "in fixed pricing, the cost should == limit * gas_price"
2708 );
2709 }
2710
2711 #[test]
2712 fn should_use_lane_specific_size_constraints() {
2713 let mut rng = TestRng::new();
2714 const GAS_PRICE_TOLERANCE: u8 = u8::MAX;
2717 let chain_name = "net-1";
2718 let mut chainspec = Chainspec::default();
2719 chainspec
2720 .with_chain_name(chain_name.to_string())
2721 .with_pricing_handling(PricingHandling::PaymentLimited);
2722
2723 let config = chainspec.transaction_config.clone();
2724
2725 let transfer_args = runtime_args! {
2726 "amount" => U512::from(DEFAULT_MIN_TRANSFER_MOTES),
2727 "source" => PublicKey::random(&mut rng).to_account_hash(),
2728 "target" => PublicKey::random(&mut rng).to_account_hash(),
2729 "some_other" => vec![1; 1_000_000], };
2731 let payment_amount = 10_000_000_000u64;
2732 let payment_args = runtime_args! {
2733 "amount" => U512::from(payment_amount),
2734 };
2735 let session = ExecutableDeployItem::Transfer {
2736 args: transfer_args,
2737 };
2738 let payment = ExecutableDeployItem::ModuleBytes {
2739 module_bytes: Bytes::new(),
2740 args: payment_args,
2741 };
2742
2743 let mut deploy = create_deploy(
2744 &mut rng,
2745 config.max_ttl,
2746 0,
2747 chain_name,
2748 GAS_PRICE_TOLERANCE as u64,
2749 );
2750 deploy.payment = payment;
2751 deploy.session = session;
2752 assert_eq!(
2753 calculate_lane_id_for_deploy(
2754 &deploy,
2755 chainspec.core_config.pricing_handling,
2756 &config.transaction_v1_config,
2757 ),
2758 Ok(MINT_LANE_ID)
2759 );
2760 let current_timestamp = deploy.header().timestamp();
2761 let ret = deploy.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp);
2762 assert!(ret.is_err());
2763 let err = ret.err().unwrap();
2764 assert!(matches!(
2765 err,
2766 InvalidDeploy::ExcessiveSize(DeployExcessiveSizeError { .. })
2767 ))
2768 }
2769}