sui_sdk_types/transaction/
serialization.rs

1use serde::Deserialize;
2use serde::Deserializer;
3use serde::Serialize;
4use serde::Serializer;
5use serde_with::DeserializeAs;
6use serde_with::SerializeAs;
7
8use crate::ObjectReference;
9
10mod transaction {
11    use super::*;
12    use crate::Address;
13    use crate::transaction::GasPayment;
14    use crate::transaction::Transaction;
15    use crate::transaction::TransactionExpiration;
16    use crate::transaction::TransactionKind;
17
18    #[derive(serde_derive::Serialize)]
19    #[serde(rename = "Transaction")]
20    enum TransactionDataRef<'a> {
21        V1(TransactionV1Ref<'a>),
22    }
23
24    #[derive(serde_derive::Deserialize)]
25    #[serde(rename = "Transaction")]
26    enum TransactionData {
27        V1(TransactionV1),
28    }
29
30    #[derive(serde_derive::Serialize)]
31    #[serde(rename = "TransactionV1")]
32    struct TransactionV1Ref<'a> {
33        kind: &'a TransactionKind,
34        sender: &'a Address,
35        gas_payment: &'a GasPayment,
36        expiration: &'a TransactionExpiration,
37    }
38
39    #[derive(serde_derive::Deserialize)]
40    #[serde(rename = "TransactionV1")]
41    struct TransactionV1 {
42        kind: TransactionKind,
43        sender: Address,
44        gas_payment: GasPayment,
45        expiration: TransactionExpiration,
46    }
47
48    impl Serialize for Transaction {
49        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
50        where
51            S: Serializer,
52        {
53            let transaction = TransactionV1Ref {
54                kind: &self.kind,
55                sender: &self.sender,
56                gas_payment: &self.gas_payment,
57                expiration: &self.expiration,
58            };
59
60            TransactionDataRef::V1(transaction).serialize(serializer)
61        }
62    }
63
64    impl<'de> Deserialize<'de> for Transaction {
65        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
66        where
67            D: Deserializer<'de>,
68        {
69            let TransactionData::V1(TransactionV1 {
70                kind,
71                sender,
72                gas_payment,
73                expiration,
74            }) = Deserialize::deserialize(deserializer)?;
75
76            Ok(Transaction {
77                kind,
78                sender,
79                gas_payment,
80                expiration,
81            })
82        }
83    }
84}
85
86mod input_argument {
87    use crate::transaction::FundsWithdrawal;
88    use crate::transaction::Input;
89    use crate::transaction::SharedInput;
90
91    use super::*;
92
93    #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
94    enum CallArg {
95        Pure(#[serde(with = "::serde_with::As::<::serde_with::Bytes>")] Vec<u8>),
96        Object(ObjectArg),
97        FundsWithdrawal(FundsWithdrawal),
98    }
99
100    #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
101    enum ObjectArg {
102        ImmutableOrOwned(ObjectReference),
103        Shared(SharedInput),
104        Receiving(ObjectReference),
105    }
106
107    impl Serialize for Input {
108        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
109        where
110            S: Serializer,
111        {
112            let binary = match self.clone() {
113                Input::Pure(value) => CallArg::Pure(value),
114                Input::ImmutableOrOwned(object_ref) => {
115                    CallArg::Object(ObjectArg::ImmutableOrOwned(object_ref))
116                }
117                Input::Shared(shared_input) => CallArg::Object(ObjectArg::Shared(shared_input)),
118                Input::Receiving(object_ref) => CallArg::Object(ObjectArg::Receiving(object_ref)),
119                Input::FundsWithdrawal(funds_withdrawal) => {
120                    CallArg::FundsWithdrawal(funds_withdrawal)
121                }
122            };
123            binary.serialize(serializer)
124        }
125    }
126
127    impl<'de> Deserialize<'de> for Input {
128        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
129        where
130            D: Deserializer<'de>,
131        {
132            CallArg::deserialize(deserializer).map(|binary| match binary {
133                CallArg::Pure(value) => Input::Pure(value),
134                CallArg::Object(ObjectArg::ImmutableOrOwned(object_ref)) => {
135                    Input::ImmutableOrOwned(object_ref)
136                }
137                CallArg::Object(ObjectArg::Shared(shared_input)) => Input::Shared(shared_input),
138                CallArg::Object(ObjectArg::Receiving(object_ref)) => Input::Receiving(object_ref),
139                CallArg::FundsWithdrawal(funds_withdrawal) => {
140                    Input::FundsWithdrawal(funds_withdrawal)
141                }
142            })
143        }
144    }
145}
146
147pub(crate) use signed_transaction::SignedTransactionWithIntentMessage;
148
149mod signed_transaction {
150    use serde::ser::SerializeSeq;
151
152    use super::*;
153    use crate::UserSignature;
154    use crate::transaction::SignedTransaction;
155    use crate::transaction::Transaction;
156
157    /// serde implementation that serializes a transaction prefixed with the signing intent. See
158    /// [struct Intent] for more info.
159    ///
160    /// So we need to serialize Transaction as (0, 0, 0, Transaction)
161    struct IntentMessageWrappedTransaction;
162
163    impl SerializeAs<Transaction> for IntentMessageWrappedTransaction {
164        fn serialize_as<S>(transaction: &Transaction, serializer: S) -> Result<S::Ok, S::Error>
165        where
166            S: Serializer,
167        {
168            use serde::ser::SerializeTuple;
169
170            let mut s = serializer.serialize_tuple(4)?;
171            s.serialize_element(&0u8)?;
172            s.serialize_element(&0u8)?;
173            s.serialize_element(&0u8)?;
174            s.serialize_element(transaction)?;
175            s.end()
176        }
177    }
178
179    impl<'de> DeserializeAs<'de, Transaction> for IntentMessageWrappedTransaction {
180        fn deserialize_as<D>(deserializer: D) -> Result<Transaction, D::Error>
181        where
182            D: Deserializer<'de>,
183        {
184            let (scope, version, app, transaction): (u8, u8, u8, Transaction) =
185                Deserialize::deserialize(deserializer)?;
186            match (scope, version, app) {
187                (0, 0, 0) => {}
188                _ => {
189                    return Err(serde::de::Error::custom(format!(
190                        "invalid intent message ({scope}, {version}, {app})"
191                    )));
192                }
193            }
194
195            Ok(transaction)
196        }
197    }
198
199    pub(crate) struct SignedTransactionWithIntentMessage;
200
201    #[derive(serde_derive::Serialize)]
202    struct BinarySignedTransactionWithIntentMessageRef<'a> {
203        #[serde(with = "::serde_with::As::<IntentMessageWrappedTransaction>")]
204        transaction: &'a Transaction,
205        signatures: &'a Vec<UserSignature>,
206    }
207
208    #[derive(serde_derive::Deserialize)]
209    struct BinarySignedTransactionWithIntentMessage {
210        #[serde(with = "::serde_with::As::<IntentMessageWrappedTransaction>")]
211        transaction: Transaction,
212        signatures: Vec<UserSignature>,
213    }
214
215    impl SerializeAs<SignedTransaction> for SignedTransactionWithIntentMessage {
216        fn serialize_as<S>(
217            transaction: &SignedTransaction,
218            serializer: S,
219        ) -> Result<S::Ok, S::Error>
220        where
221            S: Serializer,
222        {
223            if serializer.is_human_readable() {
224                transaction.serialize(serializer)
225            } else {
226                let SignedTransaction {
227                    transaction,
228                    signatures,
229                } = transaction;
230                let binary = BinarySignedTransactionWithIntentMessageRef {
231                    transaction,
232                    signatures,
233                };
234
235                let mut s = serializer.serialize_seq(Some(1))?;
236                s.serialize_element(&binary)?;
237                s.end()
238            }
239        }
240    }
241
242    impl<'de> DeserializeAs<'de, SignedTransaction> for SignedTransactionWithIntentMessage {
243        fn deserialize_as<D>(deserializer: D) -> Result<SignedTransaction, D::Error>
244        where
245            D: Deserializer<'de>,
246        {
247            if deserializer.is_human_readable() {
248                SignedTransaction::deserialize(deserializer)
249            } else {
250                struct V;
251                impl<'de> serde::de::Visitor<'de> for V {
252                    type Value = SignedTransaction;
253
254                    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
255                        formatter.write_str("expected a sequence with length 1")
256                    }
257
258                    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
259                    where
260                        A: serde::de::SeqAccess<'de>,
261                    {
262                        if seq.size_hint().is_some_and(|size| size != 1) {
263                            return Err(serde::de::Error::custom(
264                                "expected a sequence with length 1",
265                            ));
266                        }
267
268                        let BinarySignedTransactionWithIntentMessage {
269                            transaction,
270                            signatures,
271                        } = seq.next_element()?.ok_or_else(|| {
272                            serde::de::Error::custom("expected a sequence with length 1")
273                        })?;
274                        Ok(SignedTransaction {
275                            transaction,
276                            signatures,
277                        })
278                    }
279                }
280
281                deserializer.deserialize_seq(V)
282            }
283        }
284    }
285}
286
287#[cfg(test)]
288mod test {
289    use base64ct::Base64;
290    use base64ct::Encoding;
291
292    use crate::transaction::Transaction;
293
294    #[cfg(target_arch = "wasm32")]
295    use wasm_bindgen_test::wasm_bindgen_test as test;
296
297    #[test]
298    fn transaction_fixtures() {
299        const GENESIS_TRANSACTION: &str = include_str!("fixtures/genesis-transaction");
300        const CONSENSUS_PROLOGUE: &str = "AAMAAAAAAAAAAAIAAAAAAAAAtkjHeocBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA==";
301        const EPOCH_CHANGE: &str = "AAUCAmkBAAAAAAAAmSrgAQAAAAAAagEAAAAAAAApAAAAAAAAALAQCoNLLwAAnNn0sywGAABsVBEfSC0AAKQnlhd1AAAAzve+vo4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAA=";
302        const AUTHENTICATOR_STATE_UPDATE: &str =
303            include_str!("fixtures/authenticator_state_update");
304        const PTB: &str = "AAADAQFEBbUNeR/TNGdU6Bcaqra8LtJsLEbv3QM8FLMK5QesMyx96QEAAAAAAQAIVsakAAAAAAABALyyokbZ/8ynfWQer6UyP1DpeCnPU1NC7AyFNJSaTztnQF40BQAAAAAgffPXh5XuG6TWjHk6qC5w9k2a+41oTWfm0sC1FOYRqsEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN7pB2Nsb2JfdjIMY2FuY2VsX29yZGVyAgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgNzdWkDU1VJAAddSzAlBmRcN/8TO5jEtQpa4UhBZZc41tcz1Z0NIXqTvwRjb2luBENPSU4AAwEAAAEBAAECAPgh00g/x3Jeuvqlo9Ejc9SZAb384UhPIZ2qcGajDfd9ASXQjpFOD6mfycbzwD1wc+IOkCXQ8rHQo/Vi5SDOGMR/Jl40BQAAAAAgV7P1E0IMKon5uI82R/0arWLt+dc1ng/4VwKDqpTCxHT4IdNIP8dyXrr6paPRI3PUmQG9/OFITyGdqnBmow33fe4CAAAAAAAAAMqaOwAAAAAA";
305        const WORMHOLE_PYTH_TRANSACTION: &str = include_str!("fixtures/wormhole-pyth-transaction");
306
307        for fixture in [
308            GENESIS_TRANSACTION,
309            CONSENSUS_PROLOGUE,
310            EPOCH_CHANGE,
311            AUTHENTICATOR_STATE_UPDATE,
312            PTB,
313            WORMHOLE_PYTH_TRANSACTION,
314        ] {
315            let fixture = Base64::decode_vec(fixture.trim()).unwrap();
316            let tx: Transaction = bcs::from_bytes(&fixture).unwrap();
317            assert_eq!(bcs::to_bytes(&tx).unwrap(), fixture);
318
319            let json = serde_json::to_string_pretty(&tx).unwrap();
320            println!("{json}");
321            assert_eq!(tx, serde_json::from_str(&json).unwrap());
322        }
323    }
324}