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