casper_types/transaction/
deploy.rs

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/// A signed smart contract.
121#[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    /// Constructs a new `Deploy`.
150    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    /// Constructs a new signed `Deploy`.
167    #[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    /// Returns the `DeployHash` identifying this `Deploy`.
250    pub fn hash(&self) -> &DeployHash {
251        &self.hash
252    }
253
254    /// Returns the public key of the account providing the context in which to run the `Deploy`.
255    pub fn account(&self) -> &PublicKey {
256        self.header.account()
257    }
258
259    /// Returns the creation timestamp of the `Deploy`.
260    pub fn timestamp(&self) -> Timestamp {
261        self.header.timestamp()
262    }
263
264    /// Returns the duration after the creation timestamp for which the `Deploy` will stay valid.
265    ///
266    /// After this duration has ended, the `Deploy` will be considered expired.
267    pub fn ttl(&self) -> TimeDiff {
268        self.header.ttl()
269    }
270
271    /// Returns `true` if the `Deploy` has expired.
272    pub fn expired(&self, current_instant: Timestamp) -> bool {
273        self.header.expired(current_instant)
274    }
275
276    /// Returns the sender's gas price tolerance for block inclusion.
277    pub fn gas_price(&self) -> u64 {
278        self.header.gas_price()
279    }
280
281    /// Returns the hash of the body (i.e. the Wasm code) of the `Deploy`.
282    pub fn body_hash(&self) -> &Digest {
283        self.header.body_hash()
284    }
285
286    /// Returns the name of the chain the `Deploy` should be executed on.
287    pub fn chain_name(&self) -> &str {
288        self.header.chain_name()
289    }
290
291    /// Returns a reference to the `DeployHeader` of this `Deploy`.
292    pub fn header(&self) -> &DeployHeader {
293        &self.header
294    }
295
296    /// Consumes `self`, returning the `DeployHeader` of this `Deploy`.
297    pub fn take_header(self) -> DeployHeader {
298        self.header
299    }
300
301    /// Returns the `ExecutableDeployItem` for payment code.
302    pub fn payment(&self) -> &ExecutableDeployItem {
303        &self.payment
304    }
305
306    /// Returns the `ExecutableDeployItem` for session code.
307    pub fn session(&self) -> &ExecutableDeployItem {
308        &self.session
309    }
310
311    /// Returns the `Approval`s for this deploy.
312    pub fn approvals(&self) -> &BTreeSet<Approval> {
313        &self.approvals
314    }
315
316    /// Consumes `self`, returning a tuple of its constituent parts.
317    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    /// Adds a signature of this `Deploy`'s hash to its approvals.
336    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    /// Returns the `ApprovalsHash` of this `Deploy`'s approvals.
342    pub fn compute_approvals_hash(&self) -> Result<ApprovalsHash, bytesrepr::Error> {
343        ApprovalsHash::compute(&self.approvals)
344    }
345
346    /// Returns `true` if the serialized size of the deploy is not greater than
347    /// `max_transaction_size`.
348    #[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    /// Returns `Ok` if and only if this `Deploy`'s body hashes to the value of `body_hash()`, and
361    /// if this `Deploy`'s header hashes to the value claimed as the deploy hash.
362    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    /// Returns `Ok` if and only if:
382    ///   * the deploy hash is correct (should be the hash of the header), and
383    ///   * the body hash is correct (should be the hash of the body), and
384    ///   * approvals are non empty, and
385    ///   * all approvals are valid signatures of the deploy hash
386    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    /// Returns `true` if this deploy is a native transfer.
395    pub fn is_transfer(&self) -> bool {
396        self.session.is_transfer()
397    }
398
399    /// Should this transaction start in the initiating accounts context?
400    pub fn is_account_session(&self) -> bool {
401        // legacy deploys are always initiated by an account
402        true
403    }
404
405    /// Returns `Ok` if and only if:
406    ///   * the chain_name is correct,
407    ///   * the configured parameters are complied with at the given timestamp
408    #[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            // Not config compliant if V1 runtime is disabled.
419            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    // This method is not intended to be used by third party crates.
579    //
580    // It is required to allow finalized approvals to be injected after reading a `Deploy` from
581    // storage.
582    #[doc(hidden)]
583    pub fn with_approvals(mut self, approvals: BTreeSet<Approval>) -> Self {
584        self.approvals = approvals;
585        self
586    }
587
588    // This method is not intended to be used by third party crates.
589    #[doc(hidden)]
590    #[cfg(feature = "json-schema")]
591    pub fn example() -> &'static Self {
592        &DEPLOY
593    }
594
595    /// Returns a random `Deploy`.
596    #[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    /// Returns a random `Deploy` but using the specified `timestamp` and `ttl`.
604    #[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        // We need "amount" in order to be able to get correct info via `deploy_info()`.
616        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    /// Turns `self` into an invalid `Deploy` by clearing the `chain_name`, invalidating the deploy
643    /// hash.
644    #[cfg(any(all(feature = "std", feature = "testing"), test))]
645    pub fn invalidate(&mut self) {
646        self.header.invalidate();
647    }
648
649    /// Returns a random `Deploy` for a native transfer.
650    #[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    /// Returns a random `Deploy` for a native transfer with timestamp and ttl.
658    #[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    /// Returns a random `Deploy` for a native transfer with no dependencies.
696    #[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    /// Returns a random invalid `Deploy` without a payment amount specified.
729    #[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    /// Returns a random invalid `Deploy` with an invalid value for the payment amount.
739    #[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    /// Returns a random invalid `Deploy` with insufficient payment amount.
752    #[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    /// Returns a random invalid `Deploy` with insufficient payment amount.
785    #[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    /// Returns a random invalid `Deploy` with an invalid value for the payment amount.
801    #[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    /// Returns a random `Deploy` with custom payment specified as a stored contract by name.
834    #[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    /// Returns a random invalid `Deploy` with custom payment specified as a stored contract by
845    /// hash, but missing the runtime args.
846    #[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    /// Returns a random invalid `Deploy` with custom payment specified as a stored contract by
857    /// hash, but calling an invalid entry point.
858    #[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    /// Returns a random `Deploy` with custom payment specified as a stored versioned contract by
869    /// name.
870    #[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    /// Returns a random invalid `Deploy` with custom payment specified as a stored versioned
882    /// contract by hash, but missing the runtime args.
883    #[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    /// Returns a random invalid `Deploy` with custom payment specified as a stored versioned
895    /// contract by hash, but calling an invalid entry point.
896    #[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    /// Returns a random `Deploy` with custom session specified as a stored contract by name.
908    #[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    /// Returns a random invalid `Deploy` with custom session specified as a stored contract by
919    /// hash, but missing the runtime args.
920    #[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    /// Returns a random invalid `Deploy` with custom session specified as a stored contract by
931    /// hash, but calling an invalid entry point.
932    #[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    /// Returns a random `Deploy` with custom session specified as a stored versioned contract by
971    /// name.
972    #[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    /// Returns a random deploy with custom session specified as a stored versioned contract by
984    /// name.
985    #[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    /// Returns a random invalid `Deploy` with custom session specified as a stored versioned
1032    /// contract by hash, but missing the runtime args.
1033    #[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    /// Returns a random invalid `Deploy` with custom session specified as a stored versioned
1045    /// contract by hash, but calling an invalid entry point.
1046    #[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    /// Returns a random invalid transfer `Deploy` with the "target" runtime arg missing.
1058    #[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    /// Returns a random invalid transfer `Deploy` with the "amount" runtime arg missing.
1071    #[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    /// Returns a random invalid transfer `Deploy` with an invalid "amount" runtime arg.
1084    #[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    /// Returns a random invalid `Deploy` with empty session bytes.
1098    #[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    /// Returns a random invalid `Deploy` with an expired TTL.
1135    #[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    /// Returns a random `Deploy` with native transfer as payment code.
1154    #[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    /// Returns a random valid `Deploy` with specified gas price.
1204    #[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    /// Creates an add bid deploy, for testing.
1223    #[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    /// Creates a withdraw bid deploy, for testing.
1261    #[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    /// Creates a delegate deploy, for testing.
1297    #[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    /// Creates an undelegate deploy, for testing.
1337    #[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    /// Creates an redelegate deploy, for testing.
1377    #[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    /// Creates a native transfer, for testing.
1420    #[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                // in the original implementation, for standard deploys the payment amount
1484                // specified by the sender is the gas limit (up to the max block limit).
1485                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            } // legacy deploys do not support prepaid
1509        };
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        // Destructure to make sure we don't accidentally omit fields.
1521        #[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        // Destructure to make sure we don't accidentally omit fields.
1549        #[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        // Destructure to make sure we don't accidentally omit fields.
1577        #[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
1683/// Computationally expensive validity check for a given deploy instance, including asymmetric_key
1684/// signing verification.
1685fn 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))]
1706/// Calculate lane id for deploy
1707pub 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 is not representable for Deploys, we default to 0
1742            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        // Ignore the `error_msg` field of `InvalidApproval` when comparing to expected error, as
1853        // this makes the test too fragile.  Otherwise expect the actual error should exactly match
1854        // the expected error.
1855        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        // The actual error should have been lazily initialized correctly.
1874        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.header.gas_price = 2;
1916        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        // the expected index for the invalid approval will be the first index at which there is an
1954        // approval coming from deploy2
1955        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, // This field is ignored in the check.
1967            },
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        // Create an empty session object that is not transfer to ensure
2173        // that the payment amount is checked.
2174        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        // Create an empty session object that is not transfer to ensure
2222        // that the payment amount is checked.
2223        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        // Create an empty session object that is not transfer to ensure
2277        // that the payment amount is checked.
2278        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        //TODO we should consider validating on startup if the
2311        // chainspec doesn't defined wasm lanes that are bigger than
2312        // the block limit
2313        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); // The default wasm lane is much bigger than
2320        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        // Create an empty session object that is not transfer to ensure
2338        // that the payment amount is checked.
2339        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        // This test is to ensure a given limit is being checked.
2439        // Therefore, set the limit to one less than the approvals in the deploy.
2440        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        // Create an empty session object that is not transfer to ensure
2611        // that the payment amount is checked.
2612        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        // Create an empty session object that is not transfer to ensure
2672        // that the payment amount is checked.
2673        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        // Deploy is a transfer; should select MINT_LANE_ID
2715        // and apply size limitations appropriate to that
2716        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], //pumping a big runtime arg to make sure that we don't fit in the mint lane
2730        };
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}