iota_sdk_types/transaction/
serialization.rs1use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use serde_with::{DeserializeAs, SerializeAs};
7
8use crate::{Identifier, ObjectId, ObjectReference};
9
10mod input_argument {
11 use super::*;
12 use crate::{
13 Version,
14 transaction::{Input, SharedObjectReference},
15 };
16
17 #[derive(serde::Deserialize, serde::Serialize)]
21 #[serde(rename = "Input")]
22 enum ReadableInput {
23 Pure(#[serde(with = "crate::_serde::ReadableBase64Encoded")] Vec<u8>),
28 ImmutableOrOwned(ObjectReference),
30 Shared(SharedObjectReference),
32 Receiving(ObjectReference),
34 }
35
36 #[derive(serde::Deserialize, serde::Serialize)]
37 #[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
38 enum CallArg {
39 Pure(#[serde(with = "::serde_with::As::<::serde_with::Bytes>")] Vec<u8>),
40 Object(ObjectArg),
41 }
42
43 #[derive(serde::Deserialize, serde::Serialize)]
44 #[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
45 enum ObjectArg {
46 ImmutableOrOwned(ObjectReference),
47 Shared {
48 object_id: ObjectId,
49 initial_shared_version: Version,
50 mutable: bool,
51 },
52 Receiving(ObjectReference),
53 }
54
55 impl Serialize for Input {
56 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
57 where
58 S: Serializer,
59 {
60 if serializer.is_human_readable() {
61 let readable = match self.clone() {
62 Input::Pure(value) => ReadableInput::Pure(value),
63 Input::ImmutableOrOwned(object_ref) => {
64 ReadableInput::ImmutableOrOwned(object_ref)
65 }
66 Input::Shared(shared) => ReadableInput::Shared(shared),
67 Input::Receiving(object_ref) => ReadableInput::Receiving(object_ref),
68 };
69 readable.serialize(serializer)
70 } else {
71 let binary = match self.clone() {
72 Input::Pure(value) => CallArg::Pure(value),
73 Input::ImmutableOrOwned(object_ref) => {
74 CallArg::Object(ObjectArg::ImmutableOrOwned(object_ref))
75 }
76 Input::Shared(SharedObjectReference {
77 object_id,
78 initial_shared_version,
79 mutable,
80 }) => CallArg::Object(ObjectArg::Shared {
81 object_id,
82 initial_shared_version,
83 mutable,
84 }),
85 Input::Receiving(object_ref) => {
86 CallArg::Object(ObjectArg::Receiving(object_ref))
87 }
88 };
89 binary.serialize(serializer)
90 }
91 }
92 }
93
94 impl<'de> Deserialize<'de> for Input {
95 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
96 where
97 D: Deserializer<'de>,
98 {
99 if deserializer.is_human_readable() {
100 ReadableInput::deserialize(deserializer).map(|readable| match readable {
101 ReadableInput::Pure(value) => Input::Pure(value),
102 ReadableInput::ImmutableOrOwned(object_ref) => {
103 Input::ImmutableOrOwned(object_ref)
104 }
105 ReadableInput::Shared(shared) => Input::Shared(shared),
106 ReadableInput::Receiving(object_ref) => Input::Receiving(object_ref),
107 })
108 } else {
109 CallArg::deserialize(deserializer).map(|binary| match binary {
110 CallArg::Pure(value) => Input::Pure(value),
111 CallArg::Object(ObjectArg::ImmutableOrOwned(object_ref)) => {
112 Input::ImmutableOrOwned(object_ref)
113 }
114 CallArg::Object(ObjectArg::Shared {
115 object_id,
116 initial_shared_version,
117 mutable,
118 }) => Input::Shared(SharedObjectReference {
119 object_id,
120 initial_shared_version,
121 mutable,
122 }),
123 CallArg::Object(ObjectArg::Receiving(object_ref)) => {
124 Input::Receiving(object_ref)
125 }
126 })
127 }
128 }
129 }
130}
131
132pub(crate) use signed_transaction::SignedTransactionWithIntentMessage;
133
134mod signed_transaction {
135 use serde::ser::SerializeSeq;
136
137 use super::*;
138 use crate::{
139 Intent, UserSignature,
140 transaction::{SignedTransaction, Transaction},
141 };
142
143 pub(crate) struct SignedTransactionWithIntentMessage;
144
145 #[derive(serde::Serialize)]
146 #[serde(rename = "SignedTransaction")]
147 struct BinarySignedTransactionWithIntentMessageRef<'a> {
148 intent: &'a Intent,
149 transaction: &'a Transaction,
150 signatures: &'a Vec<UserSignature>,
151 }
152
153 #[derive(serde::Deserialize)]
154 #[cfg_attr(
155 feature = "bcs-schema",
156 derive(iota_bcs_schema::BcsSchema),
157 bcs_schema(name = "intent-signed-transaction")
158 )]
159 #[serde(rename = "SignedTransaction")]
160 struct BinarySignedTransactionWithIntentMessage {
161 intent: Intent,
162 transaction: Transaction,
163 signatures: Vec<UserSignature>,
164 }
165
166 impl SerializeAs<SignedTransaction> for SignedTransactionWithIntentMessage {
167 fn serialize_as<S>(
168 transaction: &SignedTransaction,
169 serializer: S,
170 ) -> Result<S::Ok, S::Error>
171 where
172 S: Serializer,
173 {
174 if serializer.is_human_readable() {
175 transaction.serialize(serializer)
176 } else {
177 let SignedTransaction {
178 transaction,
179 signatures,
180 } = transaction;
181 let intent = Intent {
182 scope: crate::IntentScope::TransactionData,
183 version: crate::IntentVersion::V0,
184 app_id: crate::IntentAppId::Iota,
185 };
186 let binary = BinarySignedTransactionWithIntentMessageRef {
187 intent: &intent,
188 transaction,
189 signatures,
190 };
191
192 let mut s = serializer.serialize_seq(Some(1))?;
193 s.serialize_element(&binary)?;
194 s.end()
195 }
196 }
197 }
198
199 impl<'de> DeserializeAs<'de, SignedTransaction> for SignedTransactionWithIntentMessage {
200 fn deserialize_as<D>(deserializer: D) -> Result<SignedTransaction, D::Error>
201 where
202 D: Deserializer<'de>,
203 {
204 if deserializer.is_human_readable() {
205 SignedTransaction::deserialize(deserializer)
206 } else {
207 struct V;
208 impl<'de> serde::de::Visitor<'de> for V {
209 type Value = SignedTransaction;
210
211 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
212 formatter.write_str("expected a sequence with length 1")
213 }
214
215 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
216 where
217 A: serde::de::SeqAccess<'de>,
218 {
219 if seq.size_hint().is_some_and(|size| size != 1) {
220 return Err(serde::de::Error::custom(
221 "expected a sequence with length 1",
222 ));
223 }
224
225 let BinarySignedTransactionWithIntentMessage {
226 intent:
227 Intent {
228 scope: crate::IntentScope::TransactionData,
229 version: crate::IntentVersion::V0,
230 app_id: crate::IntentAppId::Iota,
231 },
232 transaction,
233 signatures,
234 } = seq.next_element()?.ok_or_else(|| {
235 serde::de::Error::custom("expected a sequence with length 1")
236 })?
237 else {
238 return Err(serde::de::Error::custom("invalid intent"));
239 };
240 Ok(SignedTransaction {
241 transaction,
242 signatures,
243 })
244 }
245 }
246
247 deserializer.deserialize_seq(V)
248 }
249 }
250 }
251}
252
253pub(super) fn deserialize_ident_unchecked<'de, D>(d: D) -> Result<Identifier, D::Error>
259where
260 D: serde::Deserializer<'de>,
261{
262 if d.is_human_readable() {
263 serde::Deserialize::deserialize(d)
264 } else {
265 let s: String = serde::Deserialize::deserialize(d)?;
266 Ok(Identifier::new_unchecked(s))
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use base64ct::{Base64, Encoding};
273 #[cfg(target_arch = "wasm32")]
274 use wasm_bindgen_test::wasm_bindgen_test as test;
275
276 use crate::{
277 Address, ObjectDigest, ObjectId, ObjectReference, Version,
278 transaction::{
279 Argument, EndOfEpochTransactionKind, Input, SharedObjectReference, Transaction,
280 TransactionDenyRulesUpdate, TransactionKind,
281 },
282 };
283
284 #[test]
285 fn argument() {
286 let test_cases = [
287 (Argument::Gas, serde_json::json!("Gas")),
288 (Argument::Input(1), serde_json::json!({"Input": 1})),
289 (Argument::Result(2), serde_json::json!({"Result": 2})),
290 (
291 Argument::NestedResult(3, 4),
292 serde_json::json!({"NestedResult": [3, 4]}),
293 ),
294 ];
295
296 for (case, expected) in test_cases {
297 let actual = serde_json::to_value(case).unwrap();
298 assert_eq!(actual, expected);
299 println!("{actual}");
300
301 let deser = serde_json::from_value(expected).unwrap();
302 assert_eq!(case, deser);
303 }
304 }
305
306 #[test]
307 fn input_argument() {
308 let object_ref = serde_json::json!({
309 "object_id": "0x0000000000000000000000000000000000000000000000000000000000000000",
310 "version": "1",
311 "digest": "11111111111111111111111111111111"
312 });
313 let test_cases = [
314 (
315 Input::Pure(vec![1, 2, 3, 4]),
316 serde_json::json!({ "Pure": "AQIDBA==" }),
317 ),
318 (
319 Input::ImmutableOrOwned(ObjectReference::new(
320 ObjectId::ZERO,
321 Version::from_u64(1),
322 ObjectDigest::ZERO,
323 )),
324 serde_json::json!({ "ImmutableOrOwned": object_ref }),
325 ),
326 (
327 Input::Shared(SharedObjectReference {
328 object_id: ObjectId::ZERO,
329 initial_shared_version: Version::from_u64(1),
330 mutable: true,
331 }),
332 serde_json::json!({
333 "Shared": {
334 "object_id": "0x0000000000000000000000000000000000000000000000000000000000000000",
335 "initial_shared_version": "1",
336 "mutable": true
337 }
338 }),
339 ),
340 (
341 Input::Receiving(ObjectReference::new(
342 ObjectId::ZERO,
343 Version::from_u64(1),
344 ObjectDigest::ZERO,
345 )),
346 serde_json::json!({ "Receiving": object_ref }),
347 ),
348 ];
349
350 for (case, expected) in test_cases {
351 let actual = serde_json::to_value(&case).unwrap();
352 assert_eq!(actual, expected);
353 println!("{actual}");
354
355 let deser = serde_json::from_value(expected).unwrap();
356 assert_eq!(case, deser);
357 }
358 }
359
360 #[test]
361 fn transaction_fixtures() {
362 const GENESIS_TRANSACTION: &str = include_str!("fixtures/genesis");
364 const CONSENSUS_PROLOGUE: &str = include_str!("fixtures/consensus-commit-prologue-v1");
365 const EPOCH_CHANGE: &str = include_str!("fixtures/change-epoch-v2");
366 const PTB: &str = include_str!("fixtures/ptb");
367
368 for fixture in [GENESIS_TRANSACTION, CONSENSUS_PROLOGUE, EPOCH_CHANGE, PTB] {
369 let fixture = Base64::decode_vec(fixture.trim()).unwrap();
370 let tx: Transaction = bcs::from_bytes(&fixture).unwrap();
371 assert_eq!(bcs::to_bytes(&tx).unwrap(), fixture);
372
373 let json = serde_json::to_string_pretty(&tx).unwrap();
374 println!("{json}");
375 assert_eq!(tx, serde_json::from_str(&json).unwrap());
376 }
377 }
378
379 #[test]
386 fn transaction_deny_rules_update_bcs_pin() {
387 let mut samples: Vec<[bool; 6]> = (0..6)
390 .map(|hot| std::array::from_fn(|i| i == hot))
391 .collect();
392 samples.push([false; 6]);
393 samples.push([true; 6]);
394
395 for switches in samples {
396 let [
397 package_publish_disabled,
398 package_upgrade_disabled,
399 shared_object_disabled,
400 user_transaction_disabled,
401 receiving_objects_disabled,
402 move_authenticator_disabled,
403 ] = switches;
404 let kind = TransactionKind::TransactionDenyRulesUpdate(TransactionDenyRulesUpdate {
405 epoch: 7,
406 round: 11,
407 added_addresses: [Address::new([1; 32]), Address::new([2; 32])].into(),
408 removed_addresses: [Address::new([3; 32])].into(),
409 added_objects: [ObjectId::new([4; 32])].into(),
410 removed_objects: [ObjectId::new([5; 32]), ObjectId::new([6; 32])].into(),
411 added_packages: [
412 ObjectId::new([7; 32]),
413 ObjectId::new([8; 32]),
414 ObjectId::new([9; 32]),
415 ]
416 .into(),
417 removed_packages: [].into(),
418 package_publish_disabled,
419 package_upgrade_disabled,
420 shared_object_disabled,
421 user_transaction_disabled,
422 receiving_objects_disabled,
423 move_authenticator_disabled,
424 deny_rules_obj_initial_shared_version: Version::from_u64(42),
425 });
426
427 let mut expected = vec![6]; expected.extend(7u64.to_le_bytes()); expected.extend(11u64.to_le_bytes()); expected.push(2); expected.extend([1; 32]);
432 expected.extend([2; 32]);
433 expected.push(1); expected.extend([3; 32]);
435 expected.push(1); expected.extend([4; 32]);
437 expected.push(2); expected.extend([5; 32]);
439 expected.extend([6; 32]);
440 expected.push(3); expected.extend([7; 32]);
442 expected.extend([8; 32]);
443 expected.extend([9; 32]);
444 expected.push(0); expected.extend(switches.map(u8::from));
446 expected.extend(42u64.to_le_bytes()); assert_eq!(bcs::to_bytes(&kind).unwrap(), expected);
449 assert_eq!(bcs::from_bytes::<TransactionKind>(&expected).unwrap(), kind);
450 }
451 }
452
453 #[test]
456 fn transaction_deny_rules_create_bcs_pin() {
457 let kind = EndOfEpochTransactionKind::TransactionDenyRulesCreate;
458 assert_eq!(bcs::to_bytes(&kind).unwrap(), [4]);
459 assert_eq!(
460 bcs::from_bytes::<EndOfEpochTransactionKind>(&[4]).unwrap(),
461 kind
462 );
463
464 let tx_kind = TransactionKind::EndOfEpoch(vec![kind]);
465 let expected = [4, 1, 4]; assert_eq!(bcs::to_bytes(&tx_kind).unwrap(), expected);
467 assert_eq!(
468 bcs::from_bytes::<TransactionKind>(&expected).unwrap(),
469 tx_kind
470 );
471 }
472}