sui_sdk_types/
object.rs

1use std::collections::BTreeMap;
2
3use super::Address;
4use super::Identifier;
5use super::ObjectDigest;
6use super::ObjectId;
7use super::StructTag;
8use super::TransactionDigest;
9
10pub type Version = u64;
11
12/// Reference to an object
13///
14/// Contains sufficient information to uniquely identify a specific object.
15///
16/// # BCS
17///
18/// The BCS serialized form for this type is defined by the following ABNF:
19///
20/// ```text
21/// object-ref = object-id u64 digest
22/// ```
23#[derive(Clone, Debug, PartialEq, Eq)]
24#[cfg_attr(
25    feature = "serde",
26    derive(serde_derive::Serialize, serde_derive::Deserialize)
27)]
28#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
29pub struct ObjectReference {
30    /// The object id of this object.
31    object_id: ObjectId,
32    /// The version of this object.
33    #[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))]
34    version: Version,
35    /// The digest of this object.
36    digest: ObjectDigest,
37}
38
39impl ObjectReference {
40    /// Creates a new object reference from the object's id, version, and digest.
41    pub fn new(object_id: ObjectId, version: Version, digest: ObjectDigest) -> Self {
42        Self {
43            object_id,
44            version,
45            digest,
46        }
47    }
48
49    /// Returns a reference to the object id that this ObjectReference is referring to.
50    pub fn object_id(&self) -> &ObjectId {
51        &self.object_id
52    }
53
54    /// Returns the version of the object that this ObjectReference is referring to.
55    pub fn version(&self) -> Version {
56        self.version
57    }
58
59    /// Returns the digest of the object that this ObjectReference is referring to.
60    pub fn digest(&self) -> &ObjectDigest {
61        &self.digest
62    }
63
64    /// Returns a 3-tuple containing the object id, version, and digest.
65    pub fn into_parts(self) -> (ObjectId, Version, ObjectDigest) {
66        let Self {
67            object_id,
68            version,
69            digest,
70        } = self;
71
72        (object_id, version, digest)
73    }
74}
75
76/// Enum of different types of ownership for an object.
77///
78/// # BCS
79///
80/// The BCS serialized form for this type is defined by the following ABNF:
81///
82/// ```text
83/// owner = owner-address / owner-object / owner-shared / owner-immutable
84///
85/// owner-address   = %x00 address
86/// owner-object    = %x01 object-id
87/// owner-shared    = %x02 u64
88/// owner-immutable = %x03
89/// ```
90#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
91#[cfg_attr(
92    feature = "serde",
93    derive(serde_derive::Serialize, serde_derive::Deserialize),
94    serde(rename_all = "lowercase")
95)]
96#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
97pub enum Owner {
98    /// Object is exclusively owned by a single address, and is mutable.
99    Address(Address),
100    /// Object is exclusively owned by a single object, and is mutable.
101    Object(ObjectId),
102    /// Object is shared, can be used by any address, and is mutable.
103    Shared(
104        /// The version at which the object became shared
105        #[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))]
106        Version,
107    ),
108    /// Object is immutable, and hence ownership doesn't matter.
109    Immutable,
110
111    /// Object is exclusively owned by a single address and sequenced via consensus.
112    ConsensusAddress {
113        /// The version at which the object most recently became a consensus object.
114        /// This serves the same function as `initial_shared_version`, except it may change
115        /// if the object's Owner type changes.
116        #[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))]
117        start_version: Version,
118
119        /// The owner of the object.
120        owner: Address,
121    },
122}
123
124/// Object data, either a package or struct
125///
126/// # BCS
127///
128/// The BCS serialized form for this type is defined by the following ABNF:
129///
130/// ```text
131/// object-data = object-data-struct / object-data-package
132///
133/// object-data-struct  = %x00 object-move-struct
134/// object-data-package = %x01 object-move-package
135/// ```
136#[derive(Clone, Debug, PartialEq, Eq, Hash)]
137#[cfg_attr(
138    feature = "serde",
139    derive(serde_derive::Serialize, serde_derive::Deserialize)
140)]
141#[allow(clippy::large_enum_variant)]
142#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
143//TODO think about hiding this type and not exposing it
144pub enum ObjectData {
145    /// An object whose governing logic lives in a published Move module
146    Struct(MoveStruct),
147    /// Map from each module name to raw serialized Move module bytes
148    Package(MovePackage),
149    // ... Sui "native" types go here
150}
151
152/// A move package
153///
154/// # BCS
155///
156/// The BCS serialized form for this type is defined by the following ABNF:
157///
158/// ```text
159/// object-move-package = object-id u64 move-modules type-origin-table linkage-table
160///
161/// move-modules = map (identifier bytes)
162/// type-origin-table = vector type-origin
163/// linkage-table = map (object-id upgrade-info)
164/// ```
165#[derive(Eq, PartialEq, Debug, Clone, Hash)]
166#[cfg_attr(
167    feature = "serde",
168    derive(serde_derive::Serialize, serde_derive::Deserialize)
169)]
170#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
171pub struct MovePackage {
172    /// Address or Id of this package
173    pub id: ObjectId,
174
175    /// Most move packages are uniquely identified by their ID (i.e. there is only one version per
176    /// ID), but the version is still stored because one package may be an upgrade of another (at a
177    /// different ID), in which case its version will be one greater than the version of the
178    /// upgraded package.
179    ///
180    /// Framework packages are an exception to this rule -- all versions of the framework packages
181    /// exist at the same ID, at increasing versions.
182    ///
183    /// In all cases, packages are referred to by move calls using just their ID, and they are
184    /// always loaded at their latest version.
185    #[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))]
186    pub version: Version,
187
188    /// Set of modules defined by this package
189    #[cfg_attr(
190        feature = "serde",
191        serde(with = "::serde_with::As::<BTreeMap<::serde_with::Same, ::serde_with::Bytes>>")
192    )]
193    #[cfg_attr(
194        feature = "proptest",
195        strategy(
196            proptest::collection::btree_map(proptest::arbitrary::any::<Identifier>(), proptest::collection::vec(proptest::arbitrary::any::<u8>(), 0..=1024), 0..=5)
197        )
198    )]
199    pub modules: BTreeMap<Identifier, Vec<u8>>,
200
201    /// Maps struct/module to a package version where it was first defined, stored as a vector for
202    /// simple serialization and deserialization.
203    pub type_origin_table: Vec<TypeOrigin>,
204
205    /// For each dependency, maps original package ID to the info about the (upgraded) dependency
206    /// version that this package is using
207    #[cfg_attr(
208        feature = "proptest",
209        strategy(
210            proptest::collection::btree_map(proptest::arbitrary::any::<ObjectId>(), proptest::arbitrary::any::<UpgradeInfo>(), 0..=5)
211        )
212    )]
213    pub linkage_table: BTreeMap<ObjectId, UpgradeInfo>,
214}
215
216/// Identifies a struct and the module it was defined in
217///
218/// # BCS
219///
220/// The BCS serialized form for this type is defined by the following ABNF:
221///
222/// ```text
223/// type-origin = identifier identifier object-id
224/// ```
225#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
226#[cfg_attr(
227    feature = "serde",
228    derive(serde_derive::Serialize, serde_derive::Deserialize)
229)]
230#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
231pub struct TypeOrigin {
232    pub module_name: Identifier,
233    pub struct_name: Identifier,
234    pub package: ObjectId,
235}
236
237/// Upgraded package info for the linkage table
238///
239/// # BCS
240///
241/// The BCS serialized form for this type is defined by the following ABNF:
242///
243/// ```text
244/// upgrade-info = object-id u64
245/// ```
246#[derive(Eq, PartialEq, Debug, Clone, Hash)]
247#[cfg_attr(
248    feature = "serde",
249    derive(serde_derive::Serialize, serde_derive::Deserialize)
250)]
251#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
252pub struct UpgradeInfo {
253    /// Id of the upgraded packages
254    pub upgraded_id: ObjectId,
255    /// Version of the upgraded package
256    #[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))]
257    pub upgraded_version: Version,
258}
259
260/// A move struct
261///
262/// # BCS
263///
264/// The BCS serialized form for this type is defined by the following ABNF:
265///
266/// ```text
267/// object-move-struct = compressed-struct-tag bool u64 object-contents
268///
269/// compressed-struct-tag = other-struct-type / gas-coin-type / staked-sui-type / coin-type
270/// other-struct-type     = %x00 struct-tag
271/// gas-coin-type         = %x01
272/// staked-sui-type       = %x02
273/// coin-type             = %x03 type-tag
274///
275/// ; first 32 bytes of the contents are the object's object-id
276/// object-contents = uleb128 (object-id *OCTET) ; length followed by contents
277/// ```
278#[derive(Eq, PartialEq, Debug, Clone, Hash)]
279//TODO hand-roll a Deserialize impl to enforce that an objectid is present
280#[cfg_attr(
281    feature = "serde",
282    derive(serde_derive::Serialize, serde_derive::Deserialize)
283)]
284#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
285pub struct MoveStruct {
286    /// The type of this object
287    #[cfg_attr(
288        feature = "serde",
289        serde(with = "::serde_with::As::<serialization::BinaryMoveStructType>")
290    )]
291    pub(crate) type_: StructTag,
292
293    /// DEPRECATED this field is no longer used to determine whether a tx can transfer this
294    /// object. Instead, it is always calculated from the objects type when loaded in execution
295    has_public_transfer: bool,
296
297    /// Number that increases each time a tx takes this object as a mutable input
298    /// This is a lamport timestamp, not a sequentially increasing version
299    #[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))]
300    version: Version,
301
302    /// BCS bytes of a Move struct value
303    #[cfg_attr(
304        feature = "serde",
305        serde(with = "::serde_with::As::<::serde_with::Bytes>")
306    )]
307    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(32..=1024).lift()))]
308    pub(crate) contents: Vec<u8>,
309}
310
311impl MoveStruct {
312    /// Construct a move struct
313    pub fn new(
314        type_: StructTag,
315        has_public_transfer: bool,
316        version: Version,
317        contents: Vec<u8>,
318    ) -> Option<Self> {
319        id_opt(&contents).map(|_| Self {
320            type_,
321            has_public_transfer,
322            version,
323            contents,
324        })
325    }
326
327    /// Return the type of the struct
328    pub fn object_type(&self) -> &StructTag {
329        &self.type_
330    }
331
332    /// Return if this object can be publicly transfered
333    ///
334    /// DEPRECATED
335    ///
336    /// This field is no longer used to determine whether a tx can transfer this object. Instead,
337    /// it is always calculated from the objects type when loaded in execution.
338    #[doc(hidden)]
339    pub fn has_public_transfer(&self) -> bool {
340        self.has_public_transfer
341    }
342
343    /// Return the version of this object
344    pub fn version(&self) -> Version {
345        self.version
346    }
347
348    /// Return the raw contents of this struct
349    pub fn contents(&self) -> &[u8] {
350        &self.contents
351    }
352
353    /// Return the ObjectId of this object
354    pub fn object_id(&self) -> ObjectId {
355        id_opt(self.contents()).unwrap()
356    }
357}
358
359/// Type of a Sui object
360#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
361pub enum ObjectType {
362    /// Move package containing one or more bytecode modules
363    Package,
364    /// A Move struct of the given type
365    Struct(StructTag),
366}
367
368/// An object on the sui blockchain
369///
370/// # BCS
371///
372/// The BCS serialized form for this type is defined by the following ABNF:
373///
374/// ```text
375/// object = object-data owner digest u64
376/// ```
377#[derive(Clone, Debug, PartialEq, Eq)]
378#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
379pub struct Object {
380    /// The meat of the object
381    pub(crate) data: ObjectData,
382
383    /// The owner that unlocks this object
384    owner: Owner,
385
386    /// The digest of the transaction that created or last mutated this object
387    previous_transaction: TransactionDigest,
388
389    /// The amount of SUI we would rebate if this object gets deleted.
390    /// This number is re-calculated each time the object is mutated based on
391    /// the present storage gas price.
392    storage_rebate: u64,
393}
394
395impl Object {
396    /// Build an object
397    pub fn new(
398        data: ObjectData,
399        owner: Owner,
400        previous_transaction: TransactionDigest,
401        storage_rebate: u64,
402    ) -> Self {
403        Self {
404            data,
405            owner,
406            previous_transaction,
407            storage_rebate,
408        }
409    }
410
411    /// Return this object's id
412    pub fn object_id(&self) -> ObjectId {
413        match &self.data {
414            ObjectData::Struct(struct_) => id_opt(&struct_.contents).unwrap(),
415            ObjectData::Package(package) => package.id,
416        }
417    }
418
419    /// Return this object's version
420    pub fn version(&self) -> Version {
421        match &self.data {
422            ObjectData::Struct(struct_) => struct_.version,
423            ObjectData::Package(package) => package.version,
424        }
425    }
426
427    /// Return this object's type
428    pub fn object_type(&self) -> ObjectType {
429        match &self.data {
430            ObjectData::Struct(struct_) => ObjectType::Struct(struct_.type_.clone()),
431            ObjectData::Package(_) => ObjectType::Package,
432        }
433    }
434
435    /// Try to interpret this object as a move struct
436    pub fn as_struct(&self) -> Option<&MoveStruct> {
437        match &self.data {
438            ObjectData::Struct(struct_) => Some(struct_),
439            _ => None,
440        }
441    }
442
443    /// Return this object's owner
444    pub fn owner(&self) -> &Owner {
445        &self.owner
446    }
447
448    /// Return this object's data
449    pub fn data(&self) -> &ObjectData {
450        &self.data
451    }
452
453    /// Return the digest of the transaction that last modified this object
454    pub fn previous_transaction(&self) -> TransactionDigest {
455        self.previous_transaction
456    }
457
458    /// Return the storage rebate locked in this object
459    ///
460    /// Storage rebates are credited to the gas coin used in a transaction that deletes this
461    /// object.
462    pub fn storage_rebate(&self) -> u64 {
463        self.storage_rebate
464    }
465}
466
467fn id_opt(contents: &[u8]) -> Option<ObjectId> {
468    if ObjectId::LENGTH > contents.len() {
469        return None;
470    }
471
472    Some(ObjectId::from(
473        Address::from_bytes(&contents[..ObjectId::LENGTH]).unwrap(),
474    ))
475}
476
477/// An object part of the initial chain state
478///
479/// `GenesisObject`'s are included as a part of genesis, the initial checkpoint/transaction, that
480/// initializes the state of the blockchain.
481///
482/// # BCS
483///
484/// The BCS serialized form for this type is defined by the following ABNF:
485///
486/// ```text
487/// genesis-object = object-data owner
488/// ```
489#[derive(Clone, Debug, PartialEq, Eq)]
490#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
491pub struct GenesisObject {
492    data: ObjectData,
493    owner: Owner,
494}
495
496impl GenesisObject {
497    pub fn new(data: ObjectData, owner: Owner) -> Self {
498        Self { data, owner }
499    }
500
501    pub fn object_id(&self) -> ObjectId {
502        match &self.data {
503            ObjectData::Struct(struct_) => id_opt(&struct_.contents).unwrap(),
504            ObjectData::Package(package) => package.id,
505        }
506    }
507
508    pub fn version(&self) -> Version {
509        match &self.data {
510            ObjectData::Struct(struct_) => struct_.version,
511            ObjectData::Package(package) => package.version,
512        }
513    }
514
515    pub fn object_type(&self) -> ObjectType {
516        match &self.data {
517            ObjectData::Struct(struct_) => ObjectType::Struct(struct_.type_.clone()),
518            ObjectData::Package(_) => ObjectType::Package,
519        }
520    }
521
522    pub fn owner(&self) -> &Owner {
523        &self.owner
524    }
525
526    pub fn data(&self) -> &ObjectData {
527        &self.data
528    }
529}
530
531//TODO improve ser/de to do borrowing to avoid clones where possible
532#[cfg(feature = "serde")]
533#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
534mod serialization {
535    use std::borrow::Cow;
536    use std::str::FromStr;
537
538    use serde::Deserialize;
539    use serde::Deserializer;
540    use serde::Serialize;
541    use serde::Serializer;
542    use serde_with::DeserializeAs;
543    use serde_with::SerializeAs;
544
545    use super::*;
546    use crate::TypeTag;
547
548    #[test]
549    fn obj() {
550        let o = Object {
551            data: ObjectData::Struct(MoveStruct {
552                type_: StructTag {
553                    address: Address::TWO,
554                    module: Identifier::new("bar").unwrap(),
555                    name: Identifier::new("foo").unwrap(),
556                    type_params: Vec::new(),
557                },
558                has_public_transfer: true,
559                version: 12,
560                contents: ObjectId::ZERO.into(),
561            }),
562            // owner: Owner::Address(Address::ZERO),
563            owner: Owner::Object(ObjectId::ZERO),
564            // owner: Owner::Immutable,
565            // owner: Owner::Shared {
566            //     initial_shared_version: 14,
567            // },
568            previous_transaction: TransactionDigest::ZERO,
569            storage_rebate: 100,
570        };
571
572        println!("{}", serde_json::to_string_pretty(&o).unwrap());
573        println!(
574            "{}",
575            serde_json::to_string_pretty(&ObjectReference {
576                object_id: ObjectId::ZERO,
577                version: 1,
578                digest: ObjectDigest::ZERO,
579            })
580            .unwrap()
581        );
582    }
583
584    /// Wrapper around StructTag with a space-efficient representation for common types like coins
585    /// The StructTag for a gas coin is 84 bytes, so using 1 byte instead is a win.
586    /// The inner representation is private to prevent incorrectly constructing an `Other` instead of
587    /// one of the specialized variants, e.g. `Other(GasCoin::type_())` instead of `GasCoin`
588    #[derive(serde_derive::Deserialize)]
589    enum MoveStructType {
590        /// A type that is not `0x2::coin::Coin<T>`
591        Other(StructTag),
592        /// A SUI coin (i.e., `0x2::coin::Coin<0x2::sui::SUI>`)
593        GasCoin,
594        /// A record of a staked SUI coin (i.e., `0x3::staking_pool::StakedSui`)
595        StakedSui,
596        /// A non-SUI coin type (i.e., `0x2::coin::Coin<T> where T != 0x2::sui::SUI`)
597        Coin(TypeTag),
598        // NOTE: if adding a new type here, and there are existing on-chain objects of that
599        // type with Other(_), that is ok, but you must hand-roll PartialEq/Eq/Ord/maybe Hash
600        // to make sure the new type and Other(_) are interpreted consistently.
601    }
602
603    /// See `MoveStructType`
604    #[derive(serde_derive::Serialize)]
605    enum MoveStructTypeRef<'a> {
606        /// A type that is not `0x2::coin::Coin<T>`
607        Other(&'a StructTag),
608        /// A SUI coin (i.e., `0x2::coin::Coin<0x2::sui::SUI>`)
609        GasCoin,
610        /// A record of a staked SUI coin (i.e., `0x3::staking_pool::StakedSui`)
611        StakedSui,
612        /// A non-SUI coin type (i.e., `0x2::coin::Coin<T> where T != 0x2::sui::SUI`)
613        Coin(&'a TypeTag),
614        // NOTE: if adding a new type here, and there are existing on-chain objects of that
615        // type with Other(_), that is ok, but you must hand-roll PartialEq/Eq/Ord/maybe Hash
616        // to make sure the new type and Other(_) are interpreted consistently.
617    }
618
619    impl MoveStructType {
620        fn into_struct_tag(self) -> StructTag {
621            match self {
622                MoveStructType::Other(tag) => tag,
623                MoveStructType::GasCoin => StructTag::gas_coin(),
624                MoveStructType::StakedSui => StructTag::staked_sui(),
625                MoveStructType::Coin(type_tag) => StructTag::coin(type_tag),
626            }
627        }
628    }
629
630    impl<'a> MoveStructTypeRef<'a> {
631        fn from_struct_tag(s: &'a StructTag) -> Self {
632            let StructTag {
633                address,
634                module,
635                name,
636                type_params,
637            } = s;
638
639            if let Some(coin_type) = s.is_coin() {
640                if let TypeTag::Struct(s_inner) = coin_type {
641                    let StructTag {
642                        address,
643                        module,
644                        name,
645                        type_params,
646                    } = s_inner.as_ref();
647
648                    if address == &Address::TWO
649                        && module == "sui"
650                        && name == "SUI"
651                        && type_params.is_empty()
652                    {
653                        return Self::GasCoin;
654                    }
655                }
656
657                Self::Coin(coin_type)
658            } else if address == &Address::THREE
659                && module == "staking_pool"
660                && name == "StakedSui"
661                && type_params.is_empty()
662            {
663                Self::StakedSui
664            } else {
665                Self::Other(s)
666            }
667        }
668    }
669
670    pub(super) struct BinaryMoveStructType;
671
672    impl SerializeAs<StructTag> for BinaryMoveStructType {
673        fn serialize_as<S>(source: &StructTag, serializer: S) -> Result<S::Ok, S::Error>
674        where
675            S: Serializer,
676        {
677            let move_object_type = MoveStructTypeRef::from_struct_tag(source);
678            move_object_type.serialize(serializer)
679        }
680    }
681
682    impl<'de> DeserializeAs<'de, StructTag> for BinaryMoveStructType {
683        fn deserialize_as<D>(deserializer: D) -> Result<StructTag, D::Error>
684        where
685            D: Deserializer<'de>,
686        {
687            let struct_type = MoveStructType::deserialize(deserializer)?;
688            Ok(struct_type.into_struct_tag())
689        }
690    }
691
692    struct ReadableObjectType;
693
694    impl SerializeAs<ObjectType> for ReadableObjectType {
695        fn serialize_as<S>(source: &ObjectType, serializer: S) -> Result<S::Ok, S::Error>
696        where
697            S: Serializer,
698        {
699            match source {
700                ObjectType::Package => "package".serialize(serializer),
701                ObjectType::Struct(s) => s.serialize(serializer),
702            }
703        }
704    }
705
706    impl<'de> DeserializeAs<'de, ObjectType> for ReadableObjectType {
707        fn deserialize_as<D>(deserializer: D) -> Result<ObjectType, D::Error>
708        where
709            D: Deserializer<'de>,
710        {
711            let s: Cow<'de, str> = Deserialize::deserialize(deserializer)?;
712            if s == "package" {
713                Ok(ObjectType::Package)
714            } else {
715                let struct_tag = StructTag::from_str(&s)
716                    .map_err(|_| serde::de::Error::custom("invalid object type"))?;
717                Ok(ObjectType::Struct(struct_tag))
718            }
719        }
720    }
721
722    #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
723    #[serde(rename = "Object")]
724    struct ReadableObject {
725        object_id: ObjectId,
726        #[serde(with = "crate::_serde::ReadableDisplay")]
727        version: Version,
728        owner: Owner,
729
730        #[serde(with = "::serde_with::As::<ReadableObjectType>")]
731        #[serde(rename = "type")]
732        type_: ObjectType,
733
734        #[serde(flatten)]
735        data: ReadableObjectData,
736
737        previous_transaction: TransactionDigest,
738        #[serde(with = "crate::_serde::ReadableDisplay")]
739        storage_rebate: u64,
740    }
741
742    #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
743    #[serde(untagged)]
744    enum ReadableObjectData {
745        Move(ReadableMoveStruct),
746        Package(ReadablePackage),
747    }
748
749    #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
750    struct ReadablePackage {
751        #[serde(
752            with = "::serde_with::As::<BTreeMap<::serde_with::Same, crate::_serde::Base64Encoded>>"
753        )]
754        modules: BTreeMap<Identifier, Vec<u8>>,
755        type_origin_table: Vec<TypeOrigin>,
756        linkage_table: BTreeMap<ObjectId, UpgradeInfo>,
757    }
758
759    #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
760    struct ReadableMoveStruct {
761        has_public_transfer: bool,
762        #[serde(with = "::serde_with::As::<crate::_serde::Base64Encoded>")]
763        contents: Vec<u8>,
764    }
765
766    impl Object {
767        fn readable_object_data(&self) -> ReadableObjectData {
768            match &self.data {
769                ObjectData::Struct(struct_) => ReadableObjectData::Move(ReadableMoveStruct {
770                    has_public_transfer: struct_.has_public_transfer,
771                    contents: struct_.contents.clone(),
772                }),
773                ObjectData::Package(package) => ReadableObjectData::Package(ReadablePackage {
774                    modules: package.modules.clone(),
775                    type_origin_table: package.type_origin_table.clone(),
776                    linkage_table: package.linkage_table.clone(),
777                }),
778            }
779        }
780    }
781
782    impl Serialize for Object {
783        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
784        where
785            S: Serializer,
786        {
787            if serializer.is_human_readable() {
788                let readable = ReadableObject {
789                    object_id: self.object_id(),
790                    version: self.version(),
791                    // digest: todo!(),
792                    owner: self.owner,
793                    previous_transaction: self.previous_transaction,
794                    storage_rebate: self.storage_rebate,
795                    type_: self.object_type(),
796                    data: self.readable_object_data(),
797                };
798                readable.serialize(serializer)
799            } else {
800                let binary = BinaryObject {
801                    data: self.data.clone(),
802                    owner: self.owner,
803                    previous_transaction: self.previous_transaction,
804                    storage_rebate: self.storage_rebate,
805                };
806                binary.serialize(serializer)
807            }
808        }
809    }
810
811    impl<'de> Deserialize<'de> for Object {
812        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
813        where
814            D: Deserializer<'de>,
815        {
816            if deserializer.is_human_readable() {
817                let ReadableObject {
818                    object_id,
819                    version,
820                    owner,
821                    previous_transaction,
822                    storage_rebate,
823                    type_,
824                    data,
825                } = Deserialize::deserialize(deserializer)?;
826
827                // check if package or struct
828                let data = match (type_, data) {
829                    (
830                        ObjectType::Package,
831                        ReadableObjectData::Package(ReadablePackage {
832                            modules,
833                            type_origin_table,
834                            linkage_table,
835                        }),
836                    ) => ObjectData::Package(MovePackage {
837                        id: object_id,
838                        version,
839                        modules,
840                        type_origin_table,
841                        linkage_table,
842                    }),
843                    (
844                        ObjectType::Struct(type_),
845                        ReadableObjectData::Move(ReadableMoveStruct {
846                            has_public_transfer,
847                            contents,
848                        }),
849                    ) => {
850                        // check id matches in contents
851                        // switch to if id_opt(&contents).is_none_or(|id| id != object_id) when the
852                        // API of is_none_or is stabilized as now this would fail in wasm tests
853                        #[allow(clippy::nonminimal_bool)]
854                        if !id_opt(&contents).is_some_and(|id| id == object_id) {
855                            return Err(serde::de::Error::custom("id from contents doesn't match"));
856                        }
857
858                        ObjectData::Struct(MoveStruct {
859                            type_,
860                            has_public_transfer,
861                            version,
862                            contents,
863                        })
864                    }
865                    _ => return Err(serde::de::Error::custom("type and data don't match")),
866                };
867
868                Ok(Object {
869                    data,
870                    owner,
871                    previous_transaction,
872                    storage_rebate,
873                })
874            } else {
875                let BinaryObject {
876                    data,
877                    owner,
878                    previous_transaction,
879                    storage_rebate,
880                } = Deserialize::deserialize(deserializer)?;
881
882                Ok(Object {
883                    data,
884                    owner,
885                    previous_transaction,
886                    storage_rebate,
887                })
888            }
889        }
890    }
891
892    #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
893    struct BinaryObject {
894        data: ObjectData,
895        owner: Owner,
896        previous_transaction: TransactionDigest,
897        storage_rebate: u64,
898    }
899
900    #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
901    #[serde(rename = "GenesisObject")]
902    struct ReadableGenesisObject {
903        object_id: ObjectId,
904        #[serde(with = "crate::_serde::ReadableDisplay")]
905        version: Version,
906        owner: Owner,
907
908        #[serde(with = "::serde_with::As::<ReadableObjectType>")]
909        #[serde(rename = "type")]
910        type_: ObjectType,
911
912        #[serde(flatten)]
913        data: ReadableObjectData,
914    }
915
916    #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
917    enum BinaryGenesisObject {
918        RawObject { data: ObjectData, owner: Owner },
919    }
920
921    impl GenesisObject {
922        fn readable_object_data(&self) -> ReadableObjectData {
923            match &self.data {
924                ObjectData::Struct(struct_) => ReadableObjectData::Move(ReadableMoveStruct {
925                    has_public_transfer: struct_.has_public_transfer,
926                    contents: struct_.contents.clone(),
927                }),
928                ObjectData::Package(package) => ReadableObjectData::Package(ReadablePackage {
929                    modules: package.modules.clone(),
930                    type_origin_table: package.type_origin_table.clone(),
931                    linkage_table: package.linkage_table.clone(),
932                }),
933            }
934        }
935    }
936
937    impl Serialize for GenesisObject {
938        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
939        where
940            S: Serializer,
941        {
942            if serializer.is_human_readable() {
943                let readable = ReadableGenesisObject {
944                    object_id: self.object_id(),
945                    version: self.version(),
946                    owner: self.owner,
947                    type_: self.object_type(),
948                    data: self.readable_object_data(),
949                };
950                readable.serialize(serializer)
951            } else {
952                let binary = BinaryGenesisObject::RawObject {
953                    data: self.data.clone(),
954                    owner: self.owner,
955                };
956                binary.serialize(serializer)
957            }
958        }
959    }
960
961    impl<'de> Deserialize<'de> for GenesisObject {
962        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
963        where
964            D: Deserializer<'de>,
965        {
966            if deserializer.is_human_readable() {
967                let ReadableGenesisObject {
968                    object_id,
969                    version,
970                    owner,
971                    type_,
972                    data,
973                } = Deserialize::deserialize(deserializer)?;
974
975                // check if package or struct
976                let data = match (type_, data) {
977                    (
978                        ObjectType::Package,
979                        ReadableObjectData::Package(ReadablePackage {
980                            modules,
981                            type_origin_table,
982                            linkage_table,
983                        }),
984                    ) => ObjectData::Package(MovePackage {
985                        id: object_id,
986                        version,
987                        modules,
988                        type_origin_table,
989                        linkage_table,
990                    }),
991                    (
992                        ObjectType::Struct(type_),
993                        ReadableObjectData::Move(ReadableMoveStruct {
994                            has_public_transfer,
995                            contents,
996                        }),
997                    ) => {
998                        // check id matches in contents
999                        // switch to if id_opt(&contents).is_none_or(|id| id != object_id) when the
1000                        // API of is_none_or is stabilized as now this would fail in wasm tests
1001                        #[allow(clippy::nonminimal_bool)]
1002                        if !id_opt(&contents).is_some_and(|id| id == object_id) {
1003                            return Err(serde::de::Error::custom("id from contents doesn't match"));
1004                        }
1005
1006                        ObjectData::Struct(MoveStruct {
1007                            type_,
1008                            has_public_transfer,
1009                            version,
1010                            contents,
1011                        })
1012                    }
1013                    _ => return Err(serde::de::Error::custom("type and data don't match")),
1014                };
1015
1016                Ok(GenesisObject { data, owner })
1017            } else {
1018                let BinaryGenesisObject::RawObject { data, owner } =
1019                    Deserialize::deserialize(deserializer)?;
1020
1021                Ok(GenesisObject { data, owner })
1022            }
1023        }
1024    }
1025
1026    #[cfg(test)]
1027    mod test {
1028        use crate::object::Object;
1029
1030        #[cfg(target_arch = "wasm32")]
1031        use wasm_bindgen_test::wasm_bindgen_test as test;
1032
1033        #[test]
1034        fn object_fixture() {
1035            const SUI_COIN: &[u8] = &[
1036                0, 1, 1, 32, 79, 43, 0, 0, 0, 0, 0, 40, 35, 95, 175, 213, 151, 87, 206, 190, 35,
1037                131, 79, 35, 254, 22, 15, 181, 40, 108, 28, 77, 68, 229, 107, 254, 191, 160, 196,
1038                186, 42, 2, 122, 53, 52, 133, 199, 58, 0, 0, 0, 0, 0, 79, 255, 208, 0, 85, 34, 190,
1039                75, 192, 41, 114, 76, 127, 15, 110, 215, 9, 58, 107, 243, 160, 155, 144, 230, 47,
1040                97, 220, 21, 24, 30, 26, 62, 32, 17, 197, 192, 38, 64, 173, 142, 143, 49, 111, 15,
1041                211, 92, 84, 48, 160, 243, 102, 229, 253, 251, 137, 210, 101, 119, 173, 228, 51,
1042                141, 20, 15, 85, 96, 19, 15, 0, 0, 0, 0, 0,
1043            ];
1044
1045            const SUI_STAKE: &[u8] = &[
1046                0, 2, 1, 154, 1, 52, 5, 0, 0, 0, 0, 80, 3, 112, 71, 231, 166, 234, 205, 164, 99,
1047                237, 29, 56, 97, 170, 21, 96, 105, 158, 227, 122, 22, 251, 60, 162, 12, 97, 151,
1048                218, 71, 253, 231, 239, 116, 138, 12, 233, 128, 195, 128, 77, 33, 38, 122, 77, 53,
1049                154, 197, 198, 75, 212, 12, 182, 163, 224, 42, 82, 123, 69, 248, 40, 207, 143, 211,
1050                13, 106, 1, 0, 0, 0, 0, 0, 0, 59, 81, 183, 246, 112, 0, 0, 0, 0, 79, 255, 208, 0,
1051                85, 34, 190, 75, 192, 41, 114, 76, 127, 15, 110, 215, 9, 58, 107, 243, 160, 155,
1052                144, 230, 47, 97, 220, 21, 24, 30, 26, 62, 32, 247, 239, 248, 71, 247, 102, 190,
1053                149, 232, 153, 138, 67, 169, 209, 203, 29, 255, 215, 223, 57, 159, 44, 40, 218,
1054                166, 13, 80, 71, 14, 188, 232, 68, 0, 0, 0, 0, 0, 0, 0, 0,
1055            ];
1056
1057            const NFT: &[u8] = &[
1058                0, 0, 97, 201, 195, 159, 216, 97, 133, 173, 96, 215, 56, 212, 229, 43, 208, 139,
1059                218, 7, 29, 54, 106, 205, 224, 126, 7, 195, 145, 106, 45, 117, 168, 22, 12, 100,
1060                105, 115, 116, 114, 105, 98, 117, 116, 105, 111, 110, 11, 68, 69, 69, 80, 87, 114,
1061                97, 112, 112, 101, 114, 0, 0, 124, 24, 223, 4, 0, 0, 0, 0, 40, 31, 8, 18, 84, 38,
1062                164, 252, 84, 115, 250, 246, 137, 132, 128, 186, 156, 36, 62, 18, 140, 21, 4, 90,
1063                209, 105, 85, 84, 92, 214, 97, 81, 207, 64, 194, 198, 208, 21, 0, 0, 0, 0, 79, 255,
1064                208, 0, 85, 34, 190, 75, 192, 41, 114, 76, 127, 15, 110, 215, 9, 58, 107, 243, 160,
1065                155, 144, 230, 47, 97, 220, 21, 24, 30, 26, 62, 32, 170, 4, 94, 114, 207, 155, 31,
1066                80, 62, 254, 220, 206, 240, 218, 83, 54, 204, 197, 255, 239, 41, 66, 199, 150, 56,
1067                189, 86, 217, 166, 216, 128, 241, 64, 205, 21, 0, 0, 0, 0, 0,
1068            ];
1069
1070            const FUD_COIN: &[u8] = &[
1071                0, 3, 7, 118, 203, 129, 155, 1, 171, 237, 80, 43, 238, 138, 112, 43, 76, 45, 84,
1072                117, 50, 193, 47, 37, 0, 28, 157, 234, 121, 90, 94, 99, 28, 38, 241, 3, 102, 117,
1073                100, 3, 70, 85, 68, 0, 1, 193, 89, 252, 3, 0, 0, 0, 0, 40, 33, 214, 90, 11, 56,
1074                243, 115, 10, 250, 121, 250, 28, 34, 237, 104, 130, 148, 40, 130, 29, 248, 137,
1075                244, 27, 138, 94, 150, 28, 182, 104, 162, 185, 0, 152, 247, 62, 93, 1, 0, 0, 0, 42,
1076                95, 32, 226, 13, 31, 128, 91, 188, 127, 235, 12, 75, 73, 116, 112, 3, 227, 244,
1077                126, 59, 81, 214, 118, 144, 243, 195, 17, 82, 216, 119, 170, 32, 239, 247, 71, 249,
1078                241, 98, 133, 53, 46, 37, 100, 242, 94, 231, 241, 184, 8, 69, 192, 69, 67, 1, 116,
1079                251, 229, 226, 99, 119, 79, 255, 71, 43, 64, 242, 19, 0, 0, 0, 0, 0,
1080            ];
1081
1082            const BULLSHARK_PACKAGE: &[u8] = &[
1083                1, 135, 35, 29, 28, 138, 126, 114, 145, 204, 122, 145, 8, 244, 199, 188, 26, 10,
1084                28, 14, 182, 55, 91, 91, 97, 10, 245, 202, 35, 223, 14, 140, 86, 1, 0, 0, 0, 0, 0,
1085                0, 0, 1, 9, 98, 117, 108, 108, 115, 104, 97, 114, 107, 162, 6, 161, 28, 235, 11, 6,
1086                0, 0, 0, 10, 1, 0, 12, 2, 12, 36, 3, 48, 61, 4, 109, 12, 5, 121, 137, 1, 7, 130, 2,
1087                239, 1, 8, 241, 3, 96, 6, 209, 4, 82, 10, 163, 5, 5, 12, 168, 5, 75, 0, 7, 1, 16,
1088                2, 9, 2, 21, 2, 22, 2, 23, 0, 0, 2, 0, 1, 3, 7, 1, 0, 0, 2, 1, 12, 1, 0, 1, 2, 2,
1089                12, 1, 0, 1, 2, 4, 12, 1, 0, 1, 4, 5, 2, 0, 5, 6, 7, 0, 0, 12, 0, 1, 0, 0, 13, 2,
1090                1, 0, 0, 8, 3, 1, 0, 1, 20, 7, 8, 1, 0, 2, 8, 18, 19, 1, 0, 2, 10, 10, 11, 1, 2, 2,
1091                14, 17, 1, 1, 0, 3, 17, 7, 1, 1, 12, 3, 18, 16, 1, 1, 12, 4, 19, 13, 14, 0, 5, 15,
1092                5, 6, 0, 3, 6, 5, 9, 7, 12, 8, 15, 6, 9, 4, 9, 2, 8, 0, 7, 8, 5, 0, 4, 7, 11, 4, 1,
1093                8, 0, 3, 5, 7, 8, 5, 2, 7, 11, 4, 1, 8, 0, 11, 2, 1, 8, 0, 2, 11, 3, 1, 8, 0, 11,
1094                4, 1, 8, 0, 1, 10, 2, 1, 8, 6, 1, 9, 0, 1, 11, 1, 1, 9, 0, 1, 8, 0, 7, 9, 0, 2, 10,
1095                2, 10, 2, 10, 2, 11, 1, 1, 8, 6, 7, 8, 5, 2, 11, 4, 1, 9, 0, 11, 3, 1, 9, 0, 1, 11,
1096                3, 1, 8, 0, 1, 6, 8, 5, 1, 5, 1, 11, 4, 1, 8, 0, 2, 9, 0, 5, 4, 7, 11, 4, 1, 9, 0,
1097                3, 5, 7, 8, 5, 2, 7, 11, 4, 1, 9, 0, 11, 2, 1, 9, 0, 1, 3, 9, 66, 85, 76, 76, 83,
1098                72, 65, 82, 75, 4, 67, 111, 105, 110, 12, 67, 111, 105, 110, 77, 101, 116, 97, 100,
1099                97, 116, 97, 6, 79, 112, 116, 105, 111, 110, 11, 84, 114, 101, 97, 115, 117, 114,
1100                121, 67, 97, 112, 9, 84, 120, 67, 111, 110, 116, 101, 120, 116, 3, 85, 114, 108, 9,
1101                98, 117, 108, 108, 115, 104, 97, 114, 107, 4, 98, 117, 114, 110, 4, 99, 111, 105,
1102                110, 15, 99, 114, 101, 97, 116, 101, 95, 99, 117, 114, 114, 101, 110, 99, 121, 11,
1103                100, 117, 109, 109, 121, 95, 102, 105, 101, 108, 100, 4, 105, 110, 105, 116, 4,
1104                109, 105, 110, 116, 17, 109, 105, 110, 116, 95, 97, 110, 100, 95, 116, 114, 97,
1105                110, 115, 102, 101, 114, 21, 110, 101, 119, 95, 117, 110, 115, 97, 102, 101, 95,
1106                102, 114, 111, 109, 95, 98, 121, 116, 101, 115, 6, 111, 112, 116, 105, 111, 110,
1107                20, 112, 117, 98, 108, 105, 99, 95, 102, 114, 101, 101, 122, 101, 95, 111, 98, 106,
1108                101, 99, 116, 15, 112, 117, 98, 108, 105, 99, 95, 116, 114, 97, 110, 115, 102, 101,
1109                114, 6, 115, 101, 110, 100, 101, 114, 4, 115, 111, 109, 101, 8, 116, 114, 97, 110,
1110                115, 102, 101, 114, 10, 116, 120, 95, 99, 111, 110, 116, 101, 120, 116, 3, 117,
1111                114, 108, 135, 35, 29, 28, 138, 126, 114, 145, 204, 122, 145, 8, 244, 199, 188, 26,
1112                10, 28, 14, 182, 55, 91, 91, 97, 10, 245, 202, 35, 223, 14, 140, 86, 0, 0, 0, 0, 0,
1113                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
1114                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1115                0, 0, 2, 10, 2, 10, 9, 66, 85, 76, 76, 83, 72, 65, 82, 75, 10, 2, 20, 19, 66, 117,
1116                108, 108, 32, 83, 104, 97, 114, 107, 32, 83, 117, 105, 70, 114, 101, 110, 115, 10,
1117                2, 1, 0, 10, 2, 39, 38, 104, 116, 116, 112, 115, 58, 47, 47, 105, 46, 105, 98, 98,
1118                46, 99, 111, 47, 104, 87, 89, 50, 87, 53, 120, 47, 98, 117, 108, 108, 115, 104, 97,
1119                114, 107, 46, 112, 110, 103, 0, 2, 1, 11, 1, 0, 0, 0, 0, 4, 20, 11, 0, 49, 6, 7, 0,
1120                7, 1, 7, 2, 7, 3, 17, 10, 56, 0, 10, 1, 56, 1, 12, 2, 12, 3, 11, 2, 56, 2, 11, 3,
1121                11, 1, 46, 17, 9, 56, 3, 2, 1, 1, 4, 0, 1, 6, 11, 0, 11, 1, 11, 2, 11, 3, 56, 4, 2,
1122                2, 1, 4, 0, 1, 5, 11, 0, 11, 1, 56, 5, 1, 2, 0, 1, 9, 98, 117, 108, 108, 115, 104,
1123                97, 114, 107, 9, 66, 85, 76, 76, 83, 72, 65, 82, 75, 135, 35, 29, 28, 138, 126,
1124                114, 145, 204, 122, 145, 8, 244, 199, 188, 26, 10, 28, 14, 182, 55, 91, 91, 97, 10,
1125                245, 202, 35, 223, 14, 140, 86, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1126                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1127                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0,
1128                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1129                0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1130                0, 0, 0, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 0, 0, 0, 3, 32, 87, 145, 191, 231, 147, 185,
1131                46, 159, 240, 181, 95, 126, 236, 65, 154, 55, 16, 196, 229, 218, 47, 59, 99, 197,
1132                13, 89, 18, 159, 205, 129, 112, 131, 112, 192, 126, 0, 0, 0, 0, 0,
1133            ];
1134
1135            for fixture in [SUI_COIN, SUI_STAKE, NFT, FUD_COIN, BULLSHARK_PACKAGE] {
1136                let object: Object = bcs::from_bytes(fixture).unwrap();
1137                assert_eq!(bcs::to_bytes(&object).unwrap(), fixture);
1138
1139                let json = serde_json::to_string_pretty(&object).unwrap();
1140                println!("{json}");
1141                assert_eq!(object, serde_json::from_str(&json).unwrap());
1142            }
1143        }
1144    }
1145}