iota_sdk_types/transaction/
serialization.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2025 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use serde_with::{DeserializeAs, SerializeAs};
7
8use super::Argument;
9use crate::{ObjectId, ObjectReference};
10
11mod transaction_kind {
12    use super::*;
13    use crate::transaction::{
14        AuthenticatorStateUpdateV1, ConsensusCommitPrologueV1, EndOfEpochTransactionKind,
15        GenesisTransaction, ProgrammableTransaction, RandomnessStateUpdate, TransactionKind,
16    };
17
18    #[derive(serde::Serialize)]
19    #[serde(tag = "kind", rename_all = "snake_case")]
20    enum ReadableTransactionKindRef<'a> {
21        ProgrammableTransaction(&'a ProgrammableTransaction),
22        Genesis(&'a GenesisTransaction),
23        ConsensusCommitPrologueV1(&'a ConsensusCommitPrologueV1),
24        AuthenticatorStateUpdateV1(&'a AuthenticatorStateUpdateV1),
25        EndOfEpoch {
26            commands: &'a Vec<EndOfEpochTransactionKind>,
27        },
28        RandomnessStateUpdate(&'a RandomnessStateUpdate),
29    }
30
31    #[derive(serde::Deserialize)]
32    #[serde(tag = "kind", rename_all = "snake_case")]
33    #[serde(rename = "TransactionKind")]
34    #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
35    enum ReadableTransactionKind {
36        ProgrammableTransaction(ProgrammableTransaction),
37        Genesis(GenesisTransaction),
38        ConsensusCommitPrologueV1(ConsensusCommitPrologueV1),
39        AuthenticatorStateUpdateV1(AuthenticatorStateUpdateV1),
40        EndOfEpoch {
41            commands: Vec<EndOfEpochTransactionKind>,
42        },
43        RandomnessStateUpdate(RandomnessStateUpdate),
44    }
45
46    #[cfg(feature = "schemars")]
47    impl schemars::JsonSchema for TransactionKind {
48        fn schema_name() -> String {
49            ReadableTransactionKind::schema_name()
50        }
51
52        fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
53            ReadableTransactionKind::json_schema(gen)
54        }
55    }
56
57    #[derive(serde::Serialize)]
58    enum BinaryTransactionKindRef<'a> {
59        ProgrammableTransaction(&'a ProgrammableTransaction),
60        Genesis(&'a GenesisTransaction),
61        ConsensusCommitPrologueV1(&'a ConsensusCommitPrologueV1),
62        AuthenticatorStateUpdateV1(&'a AuthenticatorStateUpdateV1),
63        EndOfEpoch(&'a Vec<EndOfEpochTransactionKind>),
64        RandomnessStateUpdate(&'a RandomnessStateUpdate),
65    }
66    #[derive(serde::Deserialize)]
67    enum BinaryTransactionKind {
68        ProgrammableTransaction(ProgrammableTransaction),
69        Genesis(GenesisTransaction),
70        ConsensusCommitPrologueV1(ConsensusCommitPrologueV1),
71        AuthenticatorStateUpdateV1(AuthenticatorStateUpdateV1),
72        EndOfEpoch(Vec<EndOfEpochTransactionKind>),
73        RandomnessStateUpdate(RandomnessStateUpdate),
74    }
75
76    impl Serialize for TransactionKind {
77        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
78        where
79            S: Serializer,
80        {
81            if serializer.is_human_readable() {
82                let readable = match self {
83                    Self::ProgrammableTransaction(k) => {
84                        ReadableTransactionKindRef::ProgrammableTransaction(k)
85                    }
86                    Self::Genesis(k) => ReadableTransactionKindRef::Genesis(k),
87                    Self::ConsensusCommitPrologueV1(k) => {
88                        ReadableTransactionKindRef::ConsensusCommitPrologueV1(k)
89                    }
90                    Self::AuthenticatorStateUpdateV1(k) => {
91                        ReadableTransactionKindRef::AuthenticatorStateUpdateV1(k)
92                    }
93                    Self::EndOfEpoch(commands) => {
94                        ReadableTransactionKindRef::EndOfEpoch { commands }
95                    }
96                    Self::RandomnessStateUpdate(k) => {
97                        ReadableTransactionKindRef::RandomnessStateUpdate(k)
98                    }
99                };
100                readable.serialize(serializer)
101            } else {
102                let binary = match self {
103                    Self::ProgrammableTransaction(k) => {
104                        BinaryTransactionKindRef::ProgrammableTransaction(k)
105                    }
106                    Self::Genesis(k) => BinaryTransactionKindRef::Genesis(k),
107                    Self::ConsensusCommitPrologueV1(k) => {
108                        BinaryTransactionKindRef::ConsensusCommitPrologueV1(k)
109                    }
110                    Self::AuthenticatorStateUpdateV1(k) => {
111                        BinaryTransactionKindRef::AuthenticatorStateUpdateV1(k)
112                    }
113                    Self::EndOfEpoch(k) => BinaryTransactionKindRef::EndOfEpoch(k),
114                    Self::RandomnessStateUpdate(k) => {
115                        BinaryTransactionKindRef::RandomnessStateUpdate(k)
116                    }
117                };
118                binary.serialize(serializer)
119            }
120        }
121    }
122
123    impl<'de> Deserialize<'de> for TransactionKind {
124        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
125        where
126            D: Deserializer<'de>,
127        {
128            if deserializer.is_human_readable() {
129                ReadableTransactionKind::deserialize(deserializer).map(|readable| match readable {
130                    ReadableTransactionKind::ProgrammableTransaction(k) => {
131                        Self::ProgrammableTransaction(k)
132                    }
133                    ReadableTransactionKind::Genesis(k) => Self::Genesis(k),
134                    ReadableTransactionKind::ConsensusCommitPrologueV1(k) => {
135                        Self::ConsensusCommitPrologueV1(k)
136                    }
137                    ReadableTransactionKind::AuthenticatorStateUpdateV1(k) => {
138                        Self::AuthenticatorStateUpdateV1(k)
139                    }
140                    ReadableTransactionKind::EndOfEpoch { commands } => Self::EndOfEpoch(commands),
141                    ReadableTransactionKind::RandomnessStateUpdate(k) => {
142                        Self::RandomnessStateUpdate(k)
143                    }
144                })
145            } else {
146                BinaryTransactionKind::deserialize(deserializer).map(|binary| match binary {
147                    BinaryTransactionKind::ProgrammableTransaction(k) => {
148                        Self::ProgrammableTransaction(k)
149                    }
150                    BinaryTransactionKind::Genesis(k) => Self::Genesis(k),
151                    BinaryTransactionKind::ConsensusCommitPrologueV1(k) => {
152                        Self::ConsensusCommitPrologueV1(k)
153                    }
154                    BinaryTransactionKind::AuthenticatorStateUpdateV1(k) => {
155                        Self::AuthenticatorStateUpdateV1(k)
156                    }
157                    BinaryTransactionKind::EndOfEpoch(k) => Self::EndOfEpoch(k),
158                    BinaryTransactionKind::RandomnessStateUpdate(k) => {
159                        Self::RandomnessStateUpdate(k)
160                    }
161                })
162            }
163        }
164    }
165}
166
167mod end_of_epoch {
168    use super::*;
169    use crate::transaction::{
170        AuthenticatorStateExpire, ChangeEpoch, ChangeEpochV2, ChangeEpochV3,
171        EndOfEpochTransactionKind,
172    };
173
174    #[derive(serde::Serialize)]
175    #[serde(tag = "kind", rename_all = "snake_case")]
176    enum ReadableEndOfEpochTransactionKindRef<'a> {
177        ChangeEpoch(&'a ChangeEpoch),
178        ChangeEpochV2(&'a ChangeEpochV2),
179        ChangeEpochV3(&'a ChangeEpochV3),
180        AuthenticatorStateCreate,
181        AuthenticatorStateExpire(&'a AuthenticatorStateExpire),
182    }
183
184    #[derive(serde::Deserialize)]
185    #[serde(tag = "kind", rename_all = "snake_case")]
186    enum ReadableEndOfEpochTransactionKind {
187        ChangeEpoch(ChangeEpoch),
188        ChangeEpochV2(ChangeEpochV2),
189        ChangeEpochV3(ChangeEpochV3),
190        AuthenticatorStateCreate,
191        AuthenticatorStateExpire(AuthenticatorStateExpire),
192    }
193
194    #[derive(serde::Serialize)]
195    enum BinaryEndOfEpochTransactionKindRef<'a> {
196        ChangeEpoch(&'a ChangeEpoch),
197        ChangeEpochV2(&'a ChangeEpochV2),
198        ChangeEpochV3(&'a ChangeEpochV3),
199        AuthenticatorStateCreate,
200        AuthenticatorStateExpire(&'a AuthenticatorStateExpire),
201    }
202
203    #[derive(serde::Deserialize)]
204    enum BinaryEndOfEpochTransactionKind {
205        ChangeEpoch(ChangeEpoch),
206        ChangeEpochV2(ChangeEpochV2),
207        ChangeEpochV3(ChangeEpochV3),
208        AuthenticatorStateCreate,
209        AuthenticatorStateExpire(AuthenticatorStateExpire),
210    }
211
212    impl Serialize for EndOfEpochTransactionKind {
213        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
214        where
215            S: Serializer,
216        {
217            if serializer.is_human_readable() {
218                let readable = match self {
219                    Self::ChangeEpoch(k) => ReadableEndOfEpochTransactionKindRef::ChangeEpoch(k),
220                    Self::ChangeEpochV2(k) => {
221                        ReadableEndOfEpochTransactionKindRef::ChangeEpochV2(k)
222                    }
223                    Self::ChangeEpochV3(k) => {
224                        ReadableEndOfEpochTransactionKindRef::ChangeEpochV3(k)
225                    }
226                    Self::AuthenticatorStateCreate => {
227                        ReadableEndOfEpochTransactionKindRef::AuthenticatorStateCreate
228                    }
229                    Self::AuthenticatorStateExpire(k) => {
230                        ReadableEndOfEpochTransactionKindRef::AuthenticatorStateExpire(k)
231                    }
232                };
233                readable.serialize(serializer)
234            } else {
235                let binary = match self {
236                    Self::ChangeEpoch(k) => BinaryEndOfEpochTransactionKindRef::ChangeEpoch(k),
237                    Self::ChangeEpochV2(k) => BinaryEndOfEpochTransactionKindRef::ChangeEpochV2(k),
238                    Self::ChangeEpochV3(k) => BinaryEndOfEpochTransactionKindRef::ChangeEpochV3(k),
239
240                    Self::AuthenticatorStateCreate => {
241                        BinaryEndOfEpochTransactionKindRef::AuthenticatorStateCreate
242                    }
243                    Self::AuthenticatorStateExpire(k) => {
244                        BinaryEndOfEpochTransactionKindRef::AuthenticatorStateExpire(k)
245                    }
246                };
247                binary.serialize(serializer)
248            }
249        }
250    }
251
252    impl<'de> Deserialize<'de> for EndOfEpochTransactionKind {
253        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
254        where
255            D: Deserializer<'de>,
256        {
257            if deserializer.is_human_readable() {
258                ReadableEndOfEpochTransactionKind::deserialize(deserializer).map(|readable| {
259                    match readable {
260                        ReadableEndOfEpochTransactionKind::ChangeEpoch(k) => Self::ChangeEpoch(k),
261                        ReadableEndOfEpochTransactionKind::ChangeEpochV2(k) => {
262                            Self::ChangeEpochV2(k)
263                        }
264                        ReadableEndOfEpochTransactionKind::ChangeEpochV3(k) => {
265                            Self::ChangeEpochV3(k)
266                        }
267                        ReadableEndOfEpochTransactionKind::AuthenticatorStateCreate => {
268                            Self::AuthenticatorStateCreate
269                        }
270                        ReadableEndOfEpochTransactionKind::AuthenticatorStateExpire(k) => {
271                            Self::AuthenticatorStateExpire(k)
272                        }
273                    }
274                })
275            } else {
276                BinaryEndOfEpochTransactionKind::deserialize(deserializer).map(
277                    |binary| match binary {
278                        BinaryEndOfEpochTransactionKind::ChangeEpoch(k) => Self::ChangeEpoch(k),
279                        BinaryEndOfEpochTransactionKind::ChangeEpochV2(k) => Self::ChangeEpochV2(k),
280                        BinaryEndOfEpochTransactionKind::ChangeEpochV3(k) => Self::ChangeEpochV3(k),
281
282                        BinaryEndOfEpochTransactionKind::AuthenticatorStateCreate => {
283                            Self::AuthenticatorStateCreate
284                        }
285                        BinaryEndOfEpochTransactionKind::AuthenticatorStateExpire(k) => {
286                            Self::AuthenticatorStateExpire(k)
287                        }
288                    },
289                )
290            }
291        }
292    }
293}
294
295mod version_assignments {
296    use super::*;
297    use crate::transaction::{CancelledTransaction, ConsensusDeterminedVersionAssignments};
298
299    #[derive(serde::Serialize)]
300    #[serde(tag = "kind", rename_all = "snake_case")]
301    enum ReadableConsensusDeterminedVersionAssignmentsRef<'a> {
302        CancelledTransactions {
303            cancelled_transactions: &'a Vec<CancelledTransaction>,
304        },
305    }
306
307    #[derive(serde::Deserialize)]
308    #[serde(tag = "kind", rename_all = "snake_case")]
309    enum ReadableConsensusDeterminedVersionAssignments {
310        CancelledTransactions {
311            cancelled_transactions: Vec<CancelledTransaction>,
312        },
313    }
314
315    #[derive(serde::Serialize)]
316    enum BinaryConsensusDeterminedVersionAssignmentsRef<'a> {
317        CancelledTransactions {
318            cancelled_transactions: &'a Vec<CancelledTransaction>,
319        },
320    }
321
322    #[derive(serde::Deserialize)]
323    enum BinaryConsensusDeterminedVersionAssignments {
324        CancelledTransactions {
325            cancelled_transactions: Vec<CancelledTransaction>,
326        },
327    }
328
329    impl Serialize for ConsensusDeterminedVersionAssignments {
330        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
331        where
332            S: Serializer,
333        {
334            if serializer.is_human_readable() {
335                let readable = match self {
336                    Self::CancelledTransactions {
337                        cancelled_transactions,
338                    } => ReadableConsensusDeterminedVersionAssignmentsRef::CancelledTransactions {
339                        cancelled_transactions,
340                    },
341                };
342                readable.serialize(serializer)
343            } else {
344                let binary = match self {
345                    Self::CancelledTransactions {
346                        cancelled_transactions,
347                    } => BinaryConsensusDeterminedVersionAssignmentsRef::CancelledTransactions {
348                        cancelled_transactions,
349                    },
350                };
351                binary.serialize(serializer)
352            }
353        }
354    }
355
356    impl<'de> Deserialize<'de> for ConsensusDeterminedVersionAssignments {
357        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
358        where
359            D: Deserializer<'de>,
360        {
361            if deserializer.is_human_readable() {
362                ReadableConsensusDeterminedVersionAssignments::deserialize(deserializer).map(
363                    |readable| match readable {
364                        ReadableConsensusDeterminedVersionAssignments::CancelledTransactions {
365                            cancelled_transactions,
366                        } => Self::CancelledTransactions {
367                            cancelled_transactions,
368                        },
369                    },
370                )
371            } else {
372                BinaryConsensusDeterminedVersionAssignments::deserialize(deserializer).map(
373                    |binary| match binary {
374                        BinaryConsensusDeterminedVersionAssignments::CancelledTransactions {
375                            cancelled_transactions,
376                        } => Self::CancelledTransactions {
377                            cancelled_transactions,
378                        },
379                    },
380                )
381            }
382        }
383    }
384}
385
386mod input_argument {
387    use super::*;
388    use crate::transaction::Input;
389
390    #[derive(serde::Serialize, serde::Deserialize)]
391    #[serde(tag = "type", rename_all = "snake_case")]
392    enum ReadableInput {
393        Pure {
394            #[serde(with = "::serde_with::As::<crate::_serde::Base64Encoded>")]
395            value: Vec<u8>,
396        },
397        ImmutableOrOwned(ObjectReference),
398        Shared {
399            object_id: ObjectId,
400            #[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))]
401            initial_shared_version: u64,
402            mutable: bool,
403        },
404        Receiving(ObjectReference),
405    }
406
407    #[derive(serde::Serialize, serde::Deserialize)]
408    enum CallArg {
409        Pure(#[serde(with = "::serde_with::As::<::serde_with::Bytes>")] Vec<u8>),
410        Object(ObjectArg),
411    }
412
413    #[derive(serde::Serialize, serde::Deserialize)]
414    enum ObjectArg {
415        ImmutableOrOwned(ObjectReference),
416        Shared {
417            object_id: ObjectId,
418            initial_shared_version: u64,
419            mutable: bool,
420        },
421        Receiving(ObjectReference),
422    }
423
424    impl Serialize for Input {
425        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
426        where
427            S: Serializer,
428        {
429            if serializer.is_human_readable() {
430                let readable = match self.clone() {
431                    Input::Pure { value } => ReadableInput::Pure { value },
432                    Input::ImmutableOrOwned(object_ref) => {
433                        ReadableInput::ImmutableOrOwned(object_ref)
434                    }
435                    Input::Shared {
436                        object_id,
437                        initial_shared_version,
438                        mutable,
439                    } => ReadableInput::Shared {
440                        object_id,
441                        initial_shared_version,
442                        mutable,
443                    },
444                    Input::Receiving(object_ref) => ReadableInput::Receiving(object_ref),
445                };
446                readable.serialize(serializer)
447            } else {
448                let binary = match self.clone() {
449                    Input::Pure { value } => CallArg::Pure(value),
450                    Input::ImmutableOrOwned(object_ref) => {
451                        CallArg::Object(ObjectArg::ImmutableOrOwned(object_ref))
452                    }
453                    Input::Shared {
454                        object_id,
455                        initial_shared_version,
456                        mutable,
457                    } => CallArg::Object(ObjectArg::Shared {
458                        object_id,
459                        initial_shared_version,
460                        mutable,
461                    }),
462                    Input::Receiving(object_ref) => {
463                        CallArg::Object(ObjectArg::Receiving(object_ref))
464                    }
465                };
466                binary.serialize(serializer)
467            }
468        }
469    }
470
471    impl<'de> Deserialize<'de> for Input {
472        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
473        where
474            D: Deserializer<'de>,
475        {
476            if deserializer.is_human_readable() {
477                ReadableInput::deserialize(deserializer).map(|readable| match readable {
478                    ReadableInput::Pure { value } => Input::Pure { value },
479                    ReadableInput::ImmutableOrOwned(object_ref) => {
480                        Input::ImmutableOrOwned(object_ref)
481                    }
482                    ReadableInput::Shared {
483                        object_id,
484                        initial_shared_version,
485                        mutable,
486                    } => Input::Shared {
487                        object_id,
488                        initial_shared_version,
489                        mutable,
490                    },
491                    ReadableInput::Receiving(object_ref) => Input::Receiving(object_ref),
492                })
493            } else {
494                CallArg::deserialize(deserializer).map(|binary| match binary {
495                    CallArg::Pure(value) => Input::Pure { value },
496                    CallArg::Object(ObjectArg::ImmutableOrOwned(object_ref)) => {
497                        Input::ImmutableOrOwned(object_ref)
498                    }
499                    CallArg::Object(ObjectArg::Shared {
500                        object_id,
501                        initial_shared_version,
502                        mutable,
503                    }) => Input::Shared {
504                        object_id,
505                        initial_shared_version,
506                        mutable,
507                    },
508                    CallArg::Object(ObjectArg::Receiving(object_ref)) => {
509                        Input::Receiving(object_ref)
510                    }
511                })
512            }
513        }
514    }
515}
516
517mod argument {
518    use super::*;
519
520    #[derive(serde::Serialize, serde::Deserialize)]
521    #[serde(rename = "Argument", untagged, rename_all = "lowercase")]
522    #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
523    enum ReadableArgument {
524        /// # Gas
525        Gas(Gas),
526        /// # Input
527        Input { input: u16 },
528        /// # Result
529        Result { result: u16 },
530        /// # NestedResult
531        NestedResult { result: (u16, u16) },
532    }
533
534    #[derive(serde::Serialize, serde::Deserialize)]
535    #[serde(rename_all = "lowercase")]
536    enum Gas {
537        Gas,
538    }
539
540    #[cfg(feature = "schemars")]
541    impl schemars::JsonSchema for Gas {
542        fn schema_name() -> std::string::String {
543            "GasArgument".to_owned()
544        }
545
546        fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
547            schemars::schema::Schema::Object(schemars::schema::SchemaObject {
548                instance_type: Some(schemars::schema::InstanceType::String.into()),
549                enum_values: Some(vec!["gas".into()]),
550                ..Default::default()
551            })
552        }
553
554        fn is_referenceable() -> bool {
555            false
556        }
557    }
558
559    #[cfg(feature = "schemars")]
560    impl schemars::JsonSchema for Argument {
561        fn schema_name() -> String {
562            ReadableArgument::schema_name()
563        }
564
565        fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
566            ReadableArgument::json_schema(gen)
567        }
568    }
569
570    #[derive(serde::Serialize, serde::Deserialize)]
571    enum BinaryArgument {
572        Gas,
573        Input(u16),
574        Result(u16),
575        NestedResult(u16, u16),
576    }
577
578    impl Serialize for Argument {
579        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
580        where
581            S: Serializer,
582        {
583            if serializer.is_human_readable() {
584                let readable = match *self {
585                    Argument::Gas => ReadableArgument::Gas(Gas::Gas),
586                    Argument::Input(input) => ReadableArgument::Input { input },
587                    Argument::Result(result) => ReadableArgument::Result { result },
588                    Argument::NestedResult(result, subresult) => ReadableArgument::NestedResult {
589                        result: (result, subresult),
590                    },
591                };
592                readable.serialize(serializer)
593            } else {
594                let binary = match *self {
595                    Argument::Gas => BinaryArgument::Gas,
596                    Argument::Input(input) => BinaryArgument::Input(input),
597                    Argument::Result(result) => BinaryArgument::Result(result),
598                    Argument::NestedResult(result, subresult) => {
599                        BinaryArgument::NestedResult(result, subresult)
600                    }
601                };
602                binary.serialize(serializer)
603            }
604        }
605    }
606
607    impl<'de> Deserialize<'de> for Argument {
608        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
609        where
610            D: Deserializer<'de>,
611        {
612            if deserializer.is_human_readable() {
613                ReadableArgument::deserialize(deserializer).map(|readable| match readable {
614                    ReadableArgument::Gas(_) => Argument::Gas,
615                    ReadableArgument::Input { input } => Argument::Input(input),
616                    ReadableArgument::Result { result } => Argument::Result(result),
617                    ReadableArgument::NestedResult {
618                        result: (result, subresult),
619                    } => Argument::NestedResult(result, subresult),
620                })
621            } else {
622                BinaryArgument::deserialize(deserializer).map(|binary| match binary {
623                    BinaryArgument::Gas => Argument::Gas,
624                    BinaryArgument::Input(input) => Argument::Input(input),
625                    BinaryArgument::Result(result) => Argument::Result(result),
626                    BinaryArgument::NestedResult(result, subresult) => {
627                        Argument::NestedResult(result, subresult)
628                    }
629                })
630            }
631        }
632    }
633}
634
635mod command {
636    use super::*;
637    use crate::transaction::{
638        Command, MakeMoveVector, MergeCoins, MoveCall, Publish, SplitCoins, TransferObjects,
639        Upgrade,
640    };
641
642    #[derive(serde::Serialize)]
643    #[serde(tag = "command", rename_all = "snake_case")]
644    enum ReadableCommandRef<'a> {
645        MoveCall(&'a MoveCall),
646        TransferObjects(&'a TransferObjects),
647        SplitCoins(&'a SplitCoins),
648        MergeCoins(&'a MergeCoins),
649        Publish(&'a Publish),
650        MakeMoveVector(&'a MakeMoveVector),
651        Upgrade(&'a Upgrade),
652    }
653
654    #[derive(serde::Deserialize)]
655    #[serde(tag = "command", rename_all = "snake_case")]
656    enum ReadableCommand {
657        MoveCall(MoveCall),
658        TransferObjects(TransferObjects),
659        SplitCoins(SplitCoins),
660        MergeCoins(MergeCoins),
661        Publish(Publish),
662        MakeMoveVector(MakeMoveVector),
663        Upgrade(Upgrade),
664    }
665
666    #[derive(serde::Serialize)]
667    enum BinaryCommandRef<'a> {
668        MoveCall(&'a MoveCall),
669        TransferObjects(&'a TransferObjects),
670        SplitCoins(&'a SplitCoins),
671        MergeCoins(&'a MergeCoins),
672        Publish(&'a Publish),
673        MakeMoveVector(&'a MakeMoveVector),
674        Upgrade(&'a Upgrade),
675    }
676
677    #[derive(serde::Deserialize)]
678    enum BinaryCommand {
679        MoveCall(MoveCall),
680        TransferObjects(TransferObjects),
681        SplitCoins(SplitCoins),
682        MergeCoins(MergeCoins),
683        Publish(Publish),
684        MakeMoveVector(MakeMoveVector),
685        Upgrade(Upgrade),
686    }
687
688    impl Serialize for Command {
689        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
690        where
691            S: Serializer,
692        {
693            if serializer.is_human_readable() {
694                let readable = match self {
695                    Command::MoveCall(c) => ReadableCommandRef::MoveCall(c),
696                    Command::TransferObjects(c) => ReadableCommandRef::TransferObjects(c),
697                    Command::SplitCoins(c) => ReadableCommandRef::SplitCoins(c),
698                    Command::MergeCoins(c) => ReadableCommandRef::MergeCoins(c),
699                    Command::Publish(c) => ReadableCommandRef::Publish(c),
700                    Command::MakeMoveVector(c) => ReadableCommandRef::MakeMoveVector(c),
701                    Command::Upgrade(c) => ReadableCommandRef::Upgrade(c),
702                };
703                readable.serialize(serializer)
704            } else {
705                let binary = match self {
706                    Command::MoveCall(c) => BinaryCommandRef::MoveCall(c),
707                    Command::TransferObjects(c) => BinaryCommandRef::TransferObjects(c),
708                    Command::SplitCoins(c) => BinaryCommandRef::SplitCoins(c),
709                    Command::MergeCoins(c) => BinaryCommandRef::MergeCoins(c),
710                    Command::Publish(c) => BinaryCommandRef::Publish(c),
711                    Command::MakeMoveVector(c) => BinaryCommandRef::MakeMoveVector(c),
712                    Command::Upgrade(c) => BinaryCommandRef::Upgrade(c),
713                };
714                binary.serialize(serializer)
715            }
716        }
717    }
718
719    impl<'de> Deserialize<'de> for Command {
720        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
721        where
722            D: Deserializer<'de>,
723        {
724            if deserializer.is_human_readable() {
725                ReadableCommand::deserialize(deserializer).map(|readable| match readable {
726                    ReadableCommand::MoveCall(c) => Command::MoveCall(c),
727                    ReadableCommand::TransferObjects(c) => Command::TransferObjects(c),
728                    ReadableCommand::SplitCoins(c) => Command::SplitCoins(c),
729                    ReadableCommand::MergeCoins(c) => Command::MergeCoins(c),
730                    ReadableCommand::Publish(c) => Command::Publish(c),
731                    ReadableCommand::MakeMoveVector(c) => Command::MakeMoveVector(c),
732                    ReadableCommand::Upgrade(c) => Command::Upgrade(c),
733                })
734            } else {
735                BinaryCommand::deserialize(deserializer).map(|binary| match binary {
736                    BinaryCommand::MoveCall(c) => Command::MoveCall(c),
737                    BinaryCommand::TransferObjects(c) => Command::TransferObjects(c),
738                    BinaryCommand::SplitCoins(c) => Command::SplitCoins(c),
739                    BinaryCommand::MergeCoins(c) => Command::MergeCoins(c),
740                    BinaryCommand::Publish(c) => Command::Publish(c),
741                    BinaryCommand::MakeMoveVector(c) => Command::MakeMoveVector(c),
742                    BinaryCommand::Upgrade(c) => Command::Upgrade(c),
743                })
744            }
745        }
746    }
747}
748
749pub(crate) use signed_transaction::SignedTransactionWithIntentMessage;
750
751mod signed_transaction {
752    use serde::ser::SerializeSeq;
753
754    use super::*;
755    use crate::{
756        UserSignature,
757        transaction::{SignedTransaction, Transaction},
758    };
759
760    /// Intents are defined as:
761    ///
762    /// ```
763    /// struct Intent {
764    ///     scope: IntentScope,
765    ///     version: IntentVersion,
766    ///     app_id: AppId,
767    /// }
768    ///
769    /// enum IntentVersion {
770    ///     V0 = 0,
771    /// }
772    ///
773    /// enum AppId {
774    ///     Iota = 0,
775    ///     Narwhal = 1,
776    ///     Consensus = 2,
777    /// }
778    ///
779    /// enum IntentScope {
780    ///     TransactionData = 0,         // Used for a user signature on a transaction data.
781    ///     TransactionEffects = 1,      // Used for an authority signature on transaction effects.
782    ///     CheckpointSummary = 2,       // Used for an authority signature on a checkpoint summary.
783    ///     PersonalMessage = 3,         // Used for a user signature on a personal message.
784    ///     SenderSignedTransaction = 4, // Used for an authority signature on a user signed transaction.
785    ///     ProofOfPossession = 5, // Used as a signature representing an authority's proof of possession of its authority protocol key.
786    ///     BridgeEventDeprecated = 6, // Deprecated. Should not be reused. Introduced for bridge purposes but was never included in messages.
787    ///     ConsensusBlock = 7,    // Used for consensus authority signature on block's digest
788    /// }
789    /// ```
790    struct IntentMessageWrappedTransaction;
791
792    impl SerializeAs<Transaction> for IntentMessageWrappedTransaction {
793        fn serialize_as<S>(transaction: &Transaction, serializer: S) -> Result<S::Ok, S::Error>
794        where
795            S: Serializer,
796        {
797            use serde::ser::SerializeTuple;
798
799            let mut s = serializer.serialize_tuple(4)?;
800            s.serialize_element(&0u8)?;
801            s.serialize_element(&0u8)?;
802            s.serialize_element(&0u8)?;
803            s.serialize_element(transaction)?;
804            s.end()
805        }
806    }
807
808    impl<'de> DeserializeAs<'de, Transaction> for IntentMessageWrappedTransaction {
809        fn deserialize_as<D>(deserializer: D) -> Result<Transaction, D::Error>
810        where
811            D: Deserializer<'de>,
812        {
813            let (scope, version, app, transaction): (u8, u8, u8, Transaction) =
814                Deserialize::deserialize(deserializer)?;
815            match (scope, version, app) {
816                (0, 0, 0) => {}
817                _ => {
818                    return Err(serde::de::Error::custom(format!(
819                        "invalid intent message ({scope}, {version}, {app})"
820                    )));
821                }
822            }
823
824            Ok(transaction)
825        }
826    }
827
828    pub(crate) struct SignedTransactionWithIntentMessage;
829
830    #[derive(serde::Serialize)]
831    struct BinarySignedTransactionWithIntentMessageRef<'a> {
832        #[serde(with = "::serde_with::As::<IntentMessageWrappedTransaction>")]
833        transaction: &'a Transaction,
834        signatures: &'a Vec<UserSignature>,
835    }
836
837    #[derive(serde::Deserialize)]
838    struct BinarySignedTransactionWithIntentMessage {
839        #[serde(with = "::serde_with::As::<IntentMessageWrappedTransaction>")]
840        transaction: Transaction,
841        signatures: Vec<UserSignature>,
842    }
843
844    impl SerializeAs<SignedTransaction> for SignedTransactionWithIntentMessage {
845        fn serialize_as<S>(
846            transaction: &SignedTransaction,
847            serializer: S,
848        ) -> Result<S::Ok, S::Error>
849        where
850            S: Serializer,
851        {
852            if serializer.is_human_readable() {
853                transaction.serialize(serializer)
854            } else {
855                let SignedTransaction {
856                    transaction,
857                    signatures,
858                } = transaction;
859                let binary = BinarySignedTransactionWithIntentMessageRef {
860                    transaction,
861                    signatures,
862                };
863
864                let mut s = serializer.serialize_seq(Some(1))?;
865                s.serialize_element(&binary)?;
866                s.end()
867            }
868        }
869    }
870
871    impl<'de> DeserializeAs<'de, SignedTransaction> for SignedTransactionWithIntentMessage {
872        fn deserialize_as<D>(deserializer: D) -> Result<SignedTransaction, D::Error>
873        where
874            D: Deserializer<'de>,
875        {
876            if deserializer.is_human_readable() {
877                SignedTransaction::deserialize(deserializer)
878            } else {
879                struct V;
880                impl<'de> serde::de::Visitor<'de> for V {
881                    type Value = SignedTransaction;
882
883                    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
884                        formatter.write_str("expected a sequence with length 1")
885                    }
886
887                    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
888                    where
889                        A: serde::de::SeqAccess<'de>,
890                    {
891                        if seq.size_hint().is_some_and(|size| size != 1) {
892                            return Err(serde::de::Error::custom(
893                                "expected a sequence with length 1",
894                            ));
895                        }
896
897                        let BinarySignedTransactionWithIntentMessage {
898                            transaction,
899                            signatures,
900                        } = seq.next_element()?.ok_or_else(|| {
901                            serde::de::Error::custom("expected a sequence with length 1")
902                        })?;
903                        Ok(SignedTransaction {
904                            transaction,
905                            signatures,
906                        })
907                    }
908                }
909
910                deserializer.deserialize_seq(V)
911            }
912        }
913    }
914}
915
916mod transaction_expiration {
917    use serde::{Deserialize, Deserializer, Serialize, Serializer};
918
919    use crate::{EpochId, TransactionExpiration};
920
921    #[derive(serde::Serialize, serde::Deserialize)]
922    #[serde(rename = "TransactionExpiration")]
923    #[serde(rename_all = "lowercase")]
924    enum ReadableTransactionExpiration {
925        /// Validators wont sign a transaction unless the expiration Epoch
926        /// is greater than or equal to the current epoch
927        Epoch(
928            #[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))] EpochId,
929        ),
930    }
931
932    #[derive(serde::Serialize, serde::Deserialize)]
933    pub enum BinaryTransactionExpiration {
934        /// The transaction has no expiration
935        None,
936        /// Validators wont sign a transaction unless the expiration Epoch
937        /// is greater than or equal to the current epoch
938        Epoch(EpochId),
939    }
940
941    impl Serialize for TransactionExpiration {
942        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
943        where
944            S: Serializer,
945        {
946            if serializer.is_human_readable() {
947                match *self {
948                    Self::None => None,
949                    Self::Epoch(epoch) => Some(ReadableTransactionExpiration::Epoch(epoch)),
950                }
951                .serialize(serializer)
952            } else {
953                match *self {
954                    Self::None => BinaryTransactionExpiration::None,
955                    Self::Epoch(epoch) => BinaryTransactionExpiration::Epoch(epoch),
956                }
957                .serialize(serializer)
958            }
959        }
960    }
961
962    impl<'de> Deserialize<'de> for TransactionExpiration {
963        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
964        where
965            D: Deserializer<'de>,
966        {
967            if deserializer.is_human_readable() {
968                Option::<ReadableTransactionExpiration>::deserialize(deserializer).map(|readable| {
969                    match readable {
970                        None => Self::None,
971                        Some(ReadableTransactionExpiration::Epoch(epoch)) => Self::Epoch(epoch),
972                    }
973                })
974            } else {
975                BinaryTransactionExpiration::deserialize(deserializer).map(|binary| match binary {
976                    BinaryTransactionExpiration::None => Self::None,
977                    BinaryTransactionExpiration::Epoch(epoch) => Self::Epoch(epoch),
978                })
979            }
980        }
981    }
982
983    #[cfg(feature = "schemars")]
984    impl schemars::JsonSchema for TransactionExpiration {
985        fn schema_name() -> String {
986            "TransactionExpiration".into()
987        }
988
989        fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
990            use schemars::{
991                Map, Set,
992                schema::{
993                    InstanceType, ObjectValidation, Schema, SchemaObject, SubschemaValidation,
994                },
995            };
996            let mut object = SchemaObject {
997                instance_type: Some(InstanceType::Object.into()),
998                object: Some(Box::new(ObjectValidation {
999                    properties: {
1000                        let mut props = Map::new();
1001                        props.insert(
1002                            "epoch".to_owned(),
1003                            gen.subschema_for::<crate::_schemars::U64>(),
1004                        );
1005                        props
1006                    },
1007                    required: {
1008                        let mut required = Set::new();
1009                        required.insert("epoch".to_owned());
1010                        required
1011                    },
1012                    // Externally tagged variants must prohibit additional
1013                    // properties irrespective of the disposition of
1014                    // `deny_unknown_fields`. If additional properties were allowed
1015                    // one could easily construct an object that validated against
1016                    // multiple variants since here it's the properties rather than
1017                    // the values of a property that distinguish between variants.
1018                    additional_properties: Some(Box::new(false.into())),
1019                    ..Default::default()
1020                })),
1021                ..Default::default()
1022            };
1023            object.metadata().description = Some("Validators wont sign a transaction unless the expiration Epoch is greater than or equal to the current epoch".to_owned());
1024            let schema = Schema::Object(object);
1025            Schema::Object(SchemaObject {
1026                subschemas: Some(Box::new(SubschemaValidation {
1027                    one_of: Some(vec![
1028                        schema,
1029                        Schema::Object(SchemaObject {
1030                            instance_type: Some(InstanceType::Null.into()),
1031                            ..SchemaObject::default()
1032                        }),
1033                    ]),
1034                    ..Default::default()
1035                })),
1036                ..Default::default()
1037            })
1038        }
1039    }
1040}
1041
1042#[cfg(test)]
1043mod tests {
1044    use base64ct::{Base64, Encoding};
1045    #[cfg(target_arch = "wasm32")]
1046    use wasm_bindgen_test::wasm_bindgen_test as test;
1047
1048    use crate::{
1049        Digest, ObjectId, ObjectReference,
1050        transaction::{Argument, Input, Transaction},
1051    };
1052
1053    #[test]
1054    fn argument() {
1055        let test_cases = [
1056            (Argument::Gas, serde_json::json!("gas")),
1057            (Argument::Input(1), serde_json::json!({"input": 1})),
1058            (Argument::Result(2), serde_json::json!({"result": 2})),
1059            (
1060                Argument::NestedResult(3, 4),
1061                serde_json::json!({"result": [3, 4]}),
1062            ),
1063        ];
1064
1065        for (case, expected) in test_cases {
1066            let actual = serde_json::to_value(case).unwrap();
1067            assert_eq!(actual, expected);
1068            println!("{actual}");
1069
1070            let deser = serde_json::from_value(expected).unwrap();
1071            assert_eq!(case, deser);
1072        }
1073    }
1074
1075    #[test]
1076    fn input_argument() {
1077        let test_cases = [
1078            (
1079                Input::Pure {
1080                    value: vec![1, 2, 3, 4],
1081                },
1082                serde_json::json!({
1083                  "type": "pure",
1084                  "value": "AQIDBA=="
1085                }),
1086            ),
1087            (
1088                Input::ImmutableOrOwned(ObjectReference::new(ObjectId::ZERO, 1, Digest::ZERO)),
1089                serde_json::json!({
1090                  "type": "immutable_or_owned",
1091                  "object_id": "0x0000000000000000000000000000000000000000000000000000000000000000",
1092                  "version": "1",
1093                  "digest": "11111111111111111111111111111111"
1094                }),
1095            ),
1096            (
1097                Input::Shared {
1098                    object_id: ObjectId::ZERO,
1099                    initial_shared_version: 1,
1100                    mutable: true,
1101                },
1102                serde_json::json!({
1103                  "type": "shared",
1104                  "object_id": "0x0000000000000000000000000000000000000000000000000000000000000000",
1105                  "initial_shared_version": "1",
1106                  "mutable": true
1107                }),
1108            ),
1109            (
1110                Input::Receiving(ObjectReference::new(ObjectId::ZERO, 1, Digest::ZERO)),
1111                serde_json::json!({
1112                  "type": "receiving",
1113                  "object_id": "0x0000000000000000000000000000000000000000000000000000000000000000",
1114                  "version": "1",
1115                  "digest": "11111111111111111111111111111111"
1116                }),
1117            ),
1118        ];
1119
1120        for (case, expected) in test_cases {
1121            let actual = serde_json::to_value(&case).unwrap();
1122            assert_eq!(actual, expected);
1123            println!("{actual}");
1124
1125            let deser = serde_json::from_value(expected).unwrap();
1126            assert_eq!(case, deser);
1127        }
1128    }
1129
1130    #[test]
1131    fn transaction_fixtures() {
1132        // Look in the fixtures folder to see how to update them
1133        const GENESIS_TRANSACTION: &str = include_str!("fixtures/genesis");
1134        const CONSENSUS_PROLOGUE: &str = include_str!("fixtures/consensus-commit-prologue-v1");
1135        const EPOCH_CHANGE: &str = include_str!("fixtures/change-epoch-v2");
1136        const PTB: &str = include_str!("fixtures/ptb");
1137
1138        for fixture in [GENESIS_TRANSACTION, CONSENSUS_PROLOGUE, EPOCH_CHANGE, PTB] {
1139            let fixture = Base64::decode_vec(fixture.trim()).unwrap();
1140            let tx: Transaction = bcs::from_bytes(&fixture).unwrap();
1141            assert_eq!(bcs::to_bytes(&tx).unwrap(), fixture);
1142
1143            let json = serde_json::to_string_pretty(&tx).unwrap();
1144            println!("{json}");
1145            assert_eq!(tx, serde_json::from_str(&json).unwrap());
1146        }
1147    }
1148}