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