casper_types/
transaction.rs

1mod addressable_entity_identifier;
2mod approval;
3mod approvals_hash;
4mod deploy;
5mod error;
6mod execution_info;
7mod initiator_addr;
8#[cfg(any(feature = "std", test, feature = "testing"))]
9mod initiator_addr_and_secret_key;
10mod package_identifier;
11mod pricing_mode;
12mod runtime_args;
13mod serialization;
14mod transaction_entry_point;
15mod transaction_hash;
16mod transaction_id;
17mod transaction_invocation_target;
18mod transaction_scheduling;
19mod transaction_target;
20mod transaction_v1;
21mod transfer_target;
22
23#[cfg(feature = "json-schema")]
24use crate::URef;
25use alloc::{
26    collections::BTreeSet,
27    string::{String, ToString},
28    vec::Vec,
29};
30use core::fmt::{self, Debug, Display, Formatter};
31#[cfg(feature = "datasize")]
32use datasize::DataSize;
33#[cfg(feature = "json-schema")]
34use once_cell::sync::Lazy;
35#[cfg(any(all(feature = "std", feature = "testing"), test))]
36use rand::Rng;
37#[cfg(feature = "json-schema")]
38use schemars::JsonSchema;
39#[cfg(any(feature = "std", test))]
40use serde::{de, ser, Deserializer, Serializer};
41#[cfg(any(feature = "std", test))]
42use serde::{Deserialize, Serialize};
43#[cfg(any(feature = "std", test))]
44use serde_bytes::ByteBuf;
45#[cfg(any(feature = "std", test))]
46use std::hash::Hash;
47#[cfg(any(feature = "std", test))]
48use thiserror::Error;
49use tracing::error;
50#[cfg(any(feature = "std", test))]
51pub use transaction_v1::calculate_transaction_lane;
52#[cfg(any(feature = "std", test))]
53use transaction_v1::TransactionV1Json;
54
55#[cfg(any(all(feature = "std", feature = "testing"), test))]
56use crate::testing::TestRng;
57use crate::{
58    account::AccountHash,
59    bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH},
60    Digest, Phase, SecretKey, TimeDiff, Timestamp,
61};
62#[cfg(any(feature = "std", test))]
63use crate::{Chainspec, Gas, Motes, TransactionV1Config};
64pub use addressable_entity_identifier::AddressableEntityIdentifier;
65pub use approval::Approval;
66pub use approvals_hash::ApprovalsHash;
67#[cfg(any(feature = "std", test))]
68pub use deploy::calculate_lane_id_for_deploy;
69pub use deploy::{
70    Deploy, DeployDecodeFromJsonError, DeployError, DeployExcessiveSizeError, DeployHash,
71    DeployHeader, DeployId, ExecutableDeployItem, ExecutableDeployItemIdentifier, InvalidDeploy,
72};
73pub use error::InvalidTransaction;
74pub use execution_info::ExecutionInfo;
75pub use initiator_addr::InitiatorAddr;
76#[cfg(any(feature = "std", feature = "testing", test))]
77pub(crate) use initiator_addr_and_secret_key::InitiatorAddrAndSecretKey;
78pub use package_identifier::PackageIdentifier;
79pub use pricing_mode::{PricingMode, PricingModeError};
80pub use runtime_args::{NamedArg, RuntimeArgs};
81pub use transaction_entry_point::TransactionEntryPoint;
82pub use transaction_hash::TransactionHash;
83pub use transaction_id::TransactionId;
84pub use transaction_invocation_target::TransactionInvocationTarget;
85pub use transaction_scheduling::TransactionScheduling;
86pub use transaction_target::{TransactionRuntimeParams, TransactionTarget};
87#[cfg(feature = "json-schema")]
88pub(crate) use transaction_v1::arg_handling;
89#[cfg(any(feature = "std", feature = "testing", feature = "gens", test))]
90pub(crate) use transaction_v1::fields_container::FieldsContainer;
91pub use transaction_v1::{
92    InvalidTransactionV1, TransactionArgs, TransactionV1, TransactionV1DecodeFromJsonError,
93    TransactionV1Error, TransactionV1ExcessiveSizeError, TransactionV1Hash, TransactionV1Payload,
94};
95pub use transfer_target::TransferTarget;
96
97const DEPLOY_TAG: u8 = 0;
98const V1_TAG: u8 = 1;
99
100#[cfg(feature = "json-schema")]
101pub(super) static TRANSACTION: Lazy<Transaction> = Lazy::new(|| {
102    let secret_key = SecretKey::example();
103    let source = URef::from_formatted_str(
104        "uref-0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a-007",
105    )
106    .unwrap();
107    let target = URef::from_formatted_str(
108        "uref-1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b-000",
109    )
110    .unwrap();
111    let id = Some(999);
112    let amount = 30_000_000_000_u64;
113    let args = arg_handling::new_transfer_args(amount, Some(source), target, id).unwrap();
114    let container = FieldsContainer::new(
115        TransactionArgs::Named(args),
116        TransactionTarget::Native,
117        TransactionEntryPoint::Transfer,
118        TransactionScheduling::Standard,
119    );
120    let pricing_mode = PricingMode::Fixed {
121        gas_price_tolerance: 5,
122        additional_computation_factor: 0,
123    };
124    let initiator_addr_and_secret_key = InitiatorAddrAndSecretKey::SecretKey(secret_key);
125    let v1_txn = TransactionV1::build(
126        "casper-example".to_string(),
127        *Timestamp::example(),
128        TimeDiff::from_seconds(3_600),
129        pricing_mode,
130        container.to_map().unwrap(),
131        initiator_addr_and_secret_key,
132    );
133    Transaction::V1(v1_txn)
134});
135
136/// A versioned wrapper for a transaction or deploy.
137#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
138#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
139#[cfg_attr(feature = "datasize", derive(DataSize))]
140pub enum Transaction {
141    /// A deploy.
142    Deploy(Deploy),
143    /// A version 1 transaction.
144    #[cfg_attr(
145        feature = "json-schema",
146        serde(rename = "Version1"),
147        schemars(with = "TransactionV1Json")
148    )]
149    V1(TransactionV1),
150}
151
152impl Transaction {
153    // Deploy variant ctor
154    pub fn from_deploy(deploy: Deploy) -> Self {
155        Transaction::Deploy(deploy)
156    }
157
158    // V1 variant ctor
159    pub fn from_v1(v1: TransactionV1) -> Self {
160        Transaction::V1(v1)
161    }
162
163    /// Returns the `TransactionHash` identifying this transaction.
164    pub fn hash(&self) -> TransactionHash {
165        match self {
166            Transaction::Deploy(deploy) => TransactionHash::from(*deploy.hash()),
167            Transaction::V1(txn) => TransactionHash::from(*txn.hash()),
168        }
169    }
170
171    /// Size estimate.
172    pub fn size_estimate(&self) -> usize {
173        match self {
174            Transaction::Deploy(deploy) => deploy.serialized_length(),
175            Transaction::V1(v1) => v1.serialized_length(),
176        }
177    }
178
179    /// Timestamp.
180    pub fn timestamp(&self) -> Timestamp {
181        match self {
182            Transaction::Deploy(deploy) => deploy.header().timestamp(),
183            Transaction::V1(v1) => v1.payload().timestamp(),
184        }
185    }
186
187    /// Time to live.
188    pub fn ttl(&self) -> TimeDiff {
189        match self {
190            Transaction::Deploy(deploy) => deploy.header().ttl(),
191            Transaction::V1(v1) => v1.payload().ttl(),
192        }
193    }
194
195    /// Returns `Ok` if the given transaction is valid. Verification procedure is delegated to the
196    /// implementation of the particular variant of the transaction.
197    pub fn verify(&self) -> Result<(), InvalidTransaction> {
198        match self {
199            Transaction::Deploy(deploy) => deploy.is_valid().map_err(Into::into),
200            Transaction::V1(v1) => v1.verify().map_err(Into::into),
201        }
202    }
203
204    /// Adds a signature of this transaction's hash to its approvals.
205    pub fn sign(&mut self, secret_key: &SecretKey) {
206        match self {
207            Transaction::Deploy(deploy) => deploy.sign(secret_key),
208            Transaction::V1(v1) => v1.sign(secret_key),
209        }
210    }
211
212    /// Returns the `Approval`s for this transaction.
213    pub fn approvals(&self) -> BTreeSet<Approval> {
214        match self {
215            Transaction::Deploy(deploy) => deploy.approvals().clone(),
216            Transaction::V1(v1) => v1.approvals().clone(),
217        }
218    }
219
220    /// Returns the computed approvals hash identifying this transaction's approvals.
221    pub fn compute_approvals_hash(&self) -> Result<ApprovalsHash, bytesrepr::Error> {
222        let approvals_hash = match self {
223            Transaction::Deploy(deploy) => deploy.compute_approvals_hash()?,
224            Transaction::V1(txn) => txn.compute_approvals_hash()?,
225        };
226        Ok(approvals_hash)
227    }
228
229    /// Returns the chain name for the transaction, whether it's a `Deploy` or `V1` transaction.
230    pub fn chain_name(&self) -> String {
231        match self {
232            Transaction::Deploy(txn) => txn.chain_name().to_string(),
233            Transaction::V1(txn) => txn.chain_name().to_string(),
234        }
235    }
236
237    /// Checks if the transaction is a standard payment.
238    ///
239    /// For `Deploy` transactions, it checks if the session is a standard payment
240    /// in the payment phase. For `V1` transactions, it returns the value of
241    /// `standard_payment` if the pricing mode is `PaymentLimited`, otherwise it returns `true`.
242    pub fn is_standard_payment(&self) -> bool {
243        match self {
244            Transaction::Deploy(txn) => txn.session().is_standard_payment(Phase::Payment),
245            Transaction::V1(txn) => match txn.pricing_mode() {
246                PricingMode::PaymentLimited {
247                    standard_payment, ..
248                } => *standard_payment,
249                _ => true,
250            },
251        }
252    }
253
254    /// Returns the computed `TransactionId` uniquely identifying this transaction and its
255    /// approvals.
256    pub fn compute_id(&self) -> TransactionId {
257        match self {
258            Transaction::Deploy(deploy) => {
259                let deploy_hash = *deploy.hash();
260                let approvals_hash = deploy.compute_approvals_hash().unwrap_or_else(|error| {
261                    error!(%error, "failed to serialize deploy approvals");
262                    ApprovalsHash::from(Digest::default())
263                });
264                TransactionId::new(TransactionHash::Deploy(deploy_hash), approvals_hash)
265            }
266            Transaction::V1(txn) => {
267                let txn_hash = *txn.hash();
268                let approvals_hash = txn.compute_approvals_hash().unwrap_or_else(|error| {
269                    error!(%error, "failed to serialize transaction approvals");
270                    ApprovalsHash::from(Digest::default())
271                });
272                TransactionId::new(TransactionHash::V1(txn_hash), approvals_hash)
273            }
274        }
275    }
276
277    /// Returns the address of the initiator of the transaction.
278    pub fn initiator_addr(&self) -> InitiatorAddr {
279        match self {
280            Transaction::Deploy(deploy) => InitiatorAddr::PublicKey(deploy.account().clone()),
281            Transaction::V1(txn) => txn.initiator_addr().clone(),
282        }
283    }
284
285    /// Returns `true` if the transaction has expired.
286    pub fn expired(&self, current_instant: Timestamp) -> bool {
287        match self {
288            Transaction::Deploy(deploy) => deploy.expired(current_instant),
289            Transaction::V1(txn) => txn.expired(current_instant),
290        }
291    }
292
293    /// Returns the timestamp of when the transaction expires, i.e. `self.timestamp + self.ttl`.
294    pub fn expires(&self) -> Timestamp {
295        match self {
296            Transaction::Deploy(deploy) => deploy.header().expires(),
297            Transaction::V1(txn) => txn.payload().expires(),
298        }
299    }
300
301    /// Returns the set of account hashes corresponding to the public keys of the approvals.
302    pub fn signers(&self) -> BTreeSet<AccountHash> {
303        match self {
304            Transaction::Deploy(deploy) => deploy
305                .approvals()
306                .iter()
307                .map(|approval| approval.signer().to_account_hash())
308                .collect(),
309            Transaction::V1(txn) => txn
310                .approvals()
311                .iter()
312                .map(|approval| approval.signer().to_account_hash())
313                .collect(),
314        }
315    }
316
317    // This method is not intended to be used by third party crates.
318    //
319    // It is required to allow finalized approvals to be injected after reading a `Deploy` from
320    // storage.
321    #[doc(hidden)]
322    pub fn with_approvals(self, approvals: BTreeSet<Approval>) -> Self {
323        match self {
324            Transaction::Deploy(deploy) => Transaction::Deploy(deploy.with_approvals(approvals)),
325            Transaction::V1(transaction_v1) => {
326                Transaction::V1(transaction_v1.with_approvals(approvals))
327            }
328        }
329    }
330
331    /// Get [`TransactionV1`]
332    pub fn as_transaction_v1(&self) -> Option<&TransactionV1> {
333        match self {
334            Transaction::Deploy(_) => None,
335            Transaction::V1(v1) => Some(v1),
336        }
337    }
338
339    /// Authorization keys.
340    pub fn authorization_keys(&self) -> BTreeSet<AccountHash> {
341        match self {
342            Transaction::Deploy(deploy) => deploy
343                .approvals()
344                .iter()
345                .map(|approval| approval.signer().to_account_hash())
346                .collect(),
347            Transaction::V1(transaction_v1) => transaction_v1
348                .approvals()
349                .iter()
350                .map(|approval| approval.signer().to_account_hash())
351                .collect(),
352        }
353    }
354
355    /// Is the transaction the legacy deploy variant.
356    pub fn is_legacy_transaction(&self) -> bool {
357        match self {
358            Transaction::Deploy(_) => true,
359            Transaction::V1(_) => false,
360        }
361    }
362
363    #[cfg(any(all(feature = "std", feature = "testing"), test))]
364    /// Calcualates the gas limit for the transaction.
365    pub fn gas_limit(&self, chainspec: &Chainspec, lane_id: u8) -> Result<Gas, InvalidTransaction> {
366        match self {
367            Transaction::Deploy(deploy) => deploy
368                .gas_limit(chainspec)
369                .map_err(InvalidTransaction::from),
370            Transaction::V1(v1) => {
371                let pricing_mode = v1.pricing_mode();
372                pricing_mode
373                    .gas_limit(chainspec, lane_id)
374                    .map_err(InvalidTransaction::from)
375            }
376        }
377    }
378
379    #[cfg(any(all(feature = "std", feature = "testing"), test))]
380    /// Returns a gas cost based upon the gas_limit, the gas price,
381    /// and the chainspec settings.
382    pub fn gas_cost(
383        &self,
384        chainspec: &Chainspec,
385        lane_id: u8,
386        gas_price: u8,
387    ) -> Result<Motes, InvalidTransaction> {
388        match self {
389            Transaction::Deploy(deploy) => deploy
390                .gas_cost(chainspec, gas_price)
391                .map_err(InvalidTransaction::from),
392            Transaction::V1(v1) => {
393                let pricing_mode = v1.pricing_mode();
394                pricing_mode
395                    .gas_cost(chainspec, lane_id, gas_price)
396                    .map_err(InvalidTransaction::from)
397            }
398        }
399    }
400
401    // This method is not intended to be used by third party crates.
402    #[doc(hidden)]
403    #[cfg(feature = "json-schema")]
404    pub fn example() -> &'static Self {
405        &TRANSACTION
406    }
407
408    /// Returns a random, valid but possibly expired transaction.
409    #[cfg(any(all(feature = "std", feature = "testing"), test))]
410    pub fn random(rng: &mut TestRng) -> Self {
411        if rng.gen() {
412            Transaction::Deploy(Deploy::random_valid_native_transfer(rng))
413        } else {
414            Transaction::V1(TransactionV1::random(rng))
415        }
416    }
417}
418
419#[cfg(any(feature = "std", test))]
420impl Serialize for Transaction {
421    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
422        if serializer.is_human_readable() {
423            TransactionJson::try_from(self.clone())
424                .map_err(|error| ser::Error::custom(format!("{:?}", error)))?
425                .serialize(serializer)
426        } else {
427            let bytes = self
428                .to_bytes()
429                .map_err(|error| ser::Error::custom(format!("{:?}", error)))?;
430            ByteBuf::from(bytes).serialize(serializer)
431        }
432    }
433}
434
435#[cfg(any(feature = "std", test))]
436impl<'de> Deserialize<'de> for Transaction {
437    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
438        if deserializer.is_human_readable() {
439            let json_helper = TransactionJson::deserialize(deserializer)?;
440            Transaction::try_from(json_helper)
441                .map_err(|error| de::Error::custom(format!("{:?}", error)))
442        } else {
443            let bytes = ByteBuf::deserialize(deserializer)?.into_vec();
444            bytesrepr::deserialize::<Transaction>(bytes)
445                .map_err(|error| de::Error::custom(format!("{:?}", error)))
446        }
447    }
448}
449
450/// A util structure to json-serialize a transaction.
451#[cfg(any(feature = "std", test))]
452#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
453#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
454#[serde(deny_unknown_fields)]
455enum TransactionJson {
456    /// A deploy.
457    Deploy(Deploy),
458    /// A version 1 transaction.
459    #[serde(rename = "Version1")]
460    V1(TransactionV1Json),
461}
462
463#[cfg(any(feature = "std", test))]
464#[derive(Error, Debug)]
465enum TransactionJsonError {
466    #[error("{0}")]
467    FailedToMap(String),
468}
469
470#[cfg(any(feature = "std", test))]
471impl TryFrom<TransactionJson> for Transaction {
472    type Error = TransactionJsonError;
473    fn try_from(transaction: TransactionJson) -> Result<Self, Self::Error> {
474        match transaction {
475            TransactionJson::Deploy(deploy) => Ok(Transaction::Deploy(deploy)),
476            TransactionJson::V1(v1) => {
477                TransactionV1::try_from(v1)
478                    .map(Transaction::V1)
479                    .map_err(|error| {
480                        TransactionJsonError::FailedToMap(format!(
481                            "Failed to map TransactionJson::V1 to Transaction::V1, err: {}",
482                            error
483                        ))
484                    })
485            }
486        }
487    }
488}
489
490#[cfg(any(feature = "std", test))]
491impl TryFrom<Transaction> for TransactionJson {
492    type Error = TransactionJsonError;
493    fn try_from(transaction: Transaction) -> Result<Self, Self::Error> {
494        match transaction {
495            Transaction::Deploy(deploy) => Ok(TransactionJson::Deploy(deploy)),
496            Transaction::V1(v1) => TransactionV1Json::try_from(v1)
497                .map(TransactionJson::V1)
498                .map_err(|error| {
499                    TransactionJsonError::FailedToMap(format!(
500                        "Failed to map Transaction::V1 to TransactionJson::V1, err: {}",
501                        error
502                    ))
503                }),
504        }
505    }
506}
507/// Calculates gas limit.
508#[cfg(any(feature = "std", test))]
509pub trait GasLimited {
510    /// The error type.
511    type Error;
512
513    /// The minimum allowed gas price (aka the floor).
514    const GAS_PRICE_FLOOR: u8 = 1;
515
516    /// Returns a gas cost based upon the gas_limit, the gas price,
517    /// and the chainspec settings.
518    fn gas_cost(&self, chainspec: &Chainspec, gas_price: u8) -> Result<Motes, Self::Error>;
519
520    /// Returns the gas / computation limit prior to execution.
521    fn gas_limit(&self, chainspec: &Chainspec) -> Result<Gas, Self::Error>;
522
523    /// Returns the gas price tolerance.
524    fn gas_price_tolerance(&self) -> Result<u8, Self::Error>;
525}
526
527impl From<Deploy> for Transaction {
528    fn from(deploy: Deploy) -> Self {
529        Self::Deploy(deploy)
530    }
531}
532
533impl From<TransactionV1> for Transaction {
534    fn from(txn: TransactionV1) -> Self {
535        Self::V1(txn)
536    }
537}
538
539impl ToBytes for Transaction {
540    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
541        let mut buffer = bytesrepr::allocate_buffer(self)?;
542        self.write_bytes(&mut buffer)?;
543        Ok(buffer)
544    }
545
546    fn serialized_length(&self) -> usize {
547        U8_SERIALIZED_LENGTH
548            + match self {
549                Transaction::Deploy(deploy) => deploy.serialized_length(),
550                Transaction::V1(txn) => txn.serialized_length(),
551            }
552    }
553
554    fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
555        match self {
556            Transaction::Deploy(deploy) => {
557                DEPLOY_TAG.write_bytes(writer)?;
558                deploy.write_bytes(writer)
559            }
560            Transaction::V1(txn) => {
561                V1_TAG.write_bytes(writer)?;
562                txn.write_bytes(writer)
563            }
564        }
565    }
566}
567
568impl FromBytes for Transaction {
569    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
570        let (tag, remainder) = u8::from_bytes(bytes)?;
571        match tag {
572            DEPLOY_TAG => {
573                let (deploy, remainder) = Deploy::from_bytes(remainder)?;
574                Ok((Transaction::Deploy(deploy), remainder))
575            }
576            V1_TAG => {
577                let (txn, remainder) = TransactionV1::from_bytes(remainder)?;
578                Ok((Transaction::V1(txn), remainder))
579            }
580            _ => Err(bytesrepr::Error::Formatting),
581        }
582    }
583}
584
585impl Display for Transaction {
586    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
587        match self {
588            Transaction::Deploy(deploy) => Display::fmt(deploy, formatter),
589            Transaction::V1(txn) => Display::fmt(txn, formatter),
590        }
591    }
592}
593
594#[cfg(any(feature = "std", test))]
595pub(crate) enum GetLaneError {
596    NoLaneMatch,
597    PricingModeNotSupported,
598}
599
600#[cfg(any(feature = "std", test))]
601impl From<GetLaneError> for InvalidTransactionV1 {
602    fn from(value: GetLaneError) -> Self {
603        match value {
604            GetLaneError::NoLaneMatch => InvalidTransactionV1::NoLaneMatch,
605            GetLaneError::PricingModeNotSupported => InvalidTransactionV1::PricingModeNotSupported,
606        }
607    }
608}
609
610#[cfg(any(feature = "std", test))]
611impl From<GetLaneError> for InvalidDeploy {
612    fn from(value: GetLaneError) -> Self {
613        match value {
614            GetLaneError::NoLaneMatch => InvalidDeploy::NoLaneMatch,
615            GetLaneError::PricingModeNotSupported => InvalidDeploy::PricingModeNotSupported,
616        }
617    }
618}
619
620#[cfg(any(feature = "std", test))]
621pub(crate) fn get_lane_for_non_install_wasm(
622    config: &TransactionV1Config,
623    pricing_mode: &PricingMode,
624    transaction_size: u64,
625    runtime_args_size: u64,
626) -> Result<u8, GetLaneError> {
627    match pricing_mode {
628        PricingMode::PaymentLimited { payment_amount, .. } => config
629            .get_wasm_lane_id_by_payment_limited(
630                *payment_amount,
631                transaction_size,
632                runtime_args_size,
633            )
634            .ok_or(GetLaneError::NoLaneMatch),
635        PricingMode::Fixed {
636            additional_computation_factor,
637            ..
638        } => config
639            .get_wasm_lane_id_by_size(
640                transaction_size,
641                *additional_computation_factor,
642                runtime_args_size,
643            )
644            .ok_or(GetLaneError::NoLaneMatch),
645        PricingMode::Prepaid { .. } => Err(GetLaneError::PricingModeNotSupported),
646    }
647}
648
649/// Proptest generators for [`Transaction`].
650#[cfg(any(feature = "testing", feature = "gens", test))]
651pub mod gens {
652    use super::*;
653    use proptest::{
654        array,
655        prelude::{Arbitrary, Strategy},
656    };
657
658    /// Generates a random `DeployHash` for testing purposes.
659    ///
660    /// This function is used to generate random `DeployHash` values for testing purposes.
661    /// It produces a proptest `Strategy` that can be used to generate arbitrary `DeployHash`
662    /// values.
663    pub fn deploy_hash_arb() -> impl Strategy<Value = DeployHash> {
664        array::uniform32(<u8>::arbitrary()).prop_map(DeployHash::from_raw)
665    }
666}
667
668#[cfg(test)]
669mod tests {
670    use super::*;
671    use crate::testing::TestRng;
672
673    #[test]
674    fn json_roundtrip() {
675        let rng = &mut TestRng::new();
676
677        let transaction = Transaction::from(Deploy::random(rng));
678        let json_string = serde_json::to_string_pretty(&transaction).unwrap();
679        let decoded = serde_json::from_str(&json_string).unwrap();
680        assert_eq!(transaction, decoded);
681
682        let transaction = Transaction::from(TransactionV1::random(rng));
683        let json_string = serde_json::to_string_pretty(&transaction).unwrap();
684        let decoded = serde_json::from_str(&json_string).unwrap();
685        assert_eq!(transaction, decoded);
686    }
687
688    #[test]
689    fn bincode_roundtrip() {
690        let rng = &mut TestRng::new();
691
692        let transaction = Transaction::from(Deploy::random(rng));
693        let serialized = bincode::serialize(&transaction).unwrap();
694        let deserialized = bincode::deserialize(&serialized).unwrap();
695        assert_eq!(transaction, deserialized);
696
697        let transaction = Transaction::from(TransactionV1::random(rng));
698        let serialized = bincode::serialize(&transaction).unwrap();
699        let deserialized = bincode::deserialize(&serialized).unwrap();
700        assert_eq!(transaction, deserialized);
701    }
702
703    #[test]
704    fn bytesrepr_roundtrip() {
705        let rng = &mut TestRng::new();
706
707        let transaction = Transaction::from(Deploy::random(rng));
708        bytesrepr::test_serialization_roundtrip(&transaction);
709
710        let transaction = Transaction::from(TransactionV1::random(rng));
711        bytesrepr::test_serialization_roundtrip(&transaction);
712    }
713}
714
715#[cfg(test)]
716mod proptests {
717    use super::*;
718    use crate::{
719        bytesrepr,
720        gens::{legal_transaction_arb, transaction_arb},
721    };
722    use proptest::prelude::*;
723
724    proptest! {
725        #[test]
726        fn bytesrepr_roundtrip(transaction in transaction_arb()) {
727            bytesrepr::test_serialization_roundtrip(&transaction);
728        }
729
730        #[test]
731        fn json_roundtrip(transaction in legal_transaction_arb()) {
732            let json_string = serde_json::to_string_pretty(&transaction).unwrap();
733            let decoded = serde_json::from_str::<Transaction>(&json_string).unwrap();
734            assert_eq!(transaction, decoded);
735        }
736    }
737}