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