sui_sdk_types/
object.rs

1use std::collections::BTreeMap;
2
3use super::Address;
4use super::Digest;
5use super::Identifier;
6use super::StructTag;
7
8pub type Version = u64;
9
10/// Reference to an object
11///
12/// Contains sufficient information to uniquely identify a specific object.
13///
14/// # BCS
15///
16/// The BCS serialized form for this type is defined by the following ABNF:
17///
18/// ```text
19/// object-ref = address u64 digest
20/// ```
21#[derive(Clone, Debug, PartialEq, Eq)]
22#[cfg_attr(
23    feature = "serde",
24    derive(serde_derive::Serialize, serde_derive::Deserialize)
25)]
26#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
27pub struct ObjectReference {
28    /// The object id of this object.
29    object_id: Address,
30    /// The version of this object.
31    version: Version,
32    /// The digest of this object.
33    digest: Digest,
34}
35
36impl ObjectReference {
37    /// Creates a new object reference from the object's id, version, and digest.
38    pub fn new(object_id: Address, version: Version, digest: Digest) -> Self {
39        Self {
40            object_id,
41            version,
42            digest,
43        }
44    }
45
46    /// Returns a reference to the object id that this ObjectReference is referring to.
47    pub fn object_id(&self) -> &Address {
48        &self.object_id
49    }
50
51    /// Returns the version of the object that this ObjectReference is referring to.
52    pub fn version(&self) -> Version {
53        self.version
54    }
55
56    /// Returns the digest of the object that this ObjectReference is referring to.
57    pub fn digest(&self) -> &Digest {
58        &self.digest
59    }
60
61    /// Construct a synthetic coin reservation reference.
62    ///
63    /// The reservation ref encodes an address-balance reservation for coin
64    /// usage so that the server can reserve the specified `balance` of the
65    /// sender's address balance and transparently create a `Coin<T>` without
66    /// requiring actual coin objects.
67    ///
68    /// See the [coin reservation protocol][cr] for the full specification.
69    ///
70    /// [cr]: https://github.com/mystenlabs/sui/blob/main/crates/sui-types/src/coin_reservation.rs
71    #[cfg(all(feature = "hash", feature = "serde"))]
72    #[cfg_attr(doc_cfg, doc(cfg(all(feature = "hash", feature = "serde"))))]
73    pub fn coin_reservation(
74        coin_type: &crate::StructTag,
75        balance: u64,
76        epoch: u64,
77        chain_id: Digest,
78        owner: Address,
79    ) -> Self {
80        use super::Identifier;
81
82        // Derive the accumulator object ID for (owner, Balance<SUI>).
83        //
84        // The parent is the accumulator root object (0xacc). The key is
85        // the BCS-serialized owner address. The key type tag is
86        // `accumulator::Key<balance::Balance<SUI>>`.
87        let accumulator_root = const { Address::from_static("0xacc") };
88
89        let balance_sui_type = StructTag::new(
90            Address::TWO,
91            Identifier::from_static("balance"),
92            Identifier::from_static("Balance"),
93            vec![coin_type.clone().into()],
94        );
95        let key_type_tag: super::TypeTag = StructTag::new(
96            Address::TWO,
97            Identifier::from_static("accumulator"),
98            Identifier::from_static("Key"),
99            vec![balance_sui_type.into()],
100        )
101        .into();
102
103        let object_id = accumulator_root.derive_dynamic_child_id(&key_type_tag, owner.as_ref());
104
105        // XOR-mask the object ID with the chain identifier to prevent
106        // cross-chain replay.
107        let masked_id = {
108            let id_bytes = object_id.into_inner();
109            let mask_bytes = chain_id.into_inner();
110            let mut masked = [0u8; 32];
111            for i in 0..32 {
112                masked[i] = id_bytes[i] ^ mask_bytes[i];
113            }
114            Address::new(masked)
115        };
116
117        // Construct the magic digest:
118        //   bytes  0-7:  reservation balance (LE u64)
119        //   bytes  8-11: epoch ID (LE u32)
120        //   bytes 12-31: magic constant [0xac; 20]
121        let digest = {
122            let mut bytes = [0u8; 32];
123            bytes[0..8].copy_from_slice(&balance.to_le_bytes());
124            bytes[8..12].copy_from_slice(&(epoch as u32).to_le_bytes());
125            bytes[12..32].copy_from_slice(&[0xac; 20]);
126            Digest::new(bytes)
127        };
128
129        Self::new(masked_id, 0, digest)
130    }
131
132    /// Returns a 3-tuple containing the object id, version, and digest.
133    pub fn into_parts(self) -> (Address, Version, Digest) {
134        let Self {
135            object_id,
136            version,
137            digest,
138        } = self;
139
140        (object_id, version, digest)
141    }
142}
143
144/// Enum of different types of ownership for an object.
145///
146/// # BCS
147///
148/// The BCS serialized form for this type is defined by the following ABNF:
149///
150/// ```text
151/// owner = owner-address / owner-object / owner-shared / owner-immutable
152///
153/// owner-address   = %x00 address
154/// owner-object    = %x01 address
155/// owner-shared    = %x02 u64
156/// owner-immutable = %x03
157/// ```
158#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
159#[cfg_attr(
160    feature = "serde",
161    derive(serde_derive::Serialize, serde_derive::Deserialize),
162    serde(rename_all = "lowercase")
163)]
164#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
165#[non_exhaustive]
166pub enum Owner {
167    /// Object is exclusively owned by a single address, and is mutable.
168    Address(Address),
169    /// Object is exclusively owned by a single object, and is mutable.
170    Object(Address),
171    /// Object is shared, can be used by any address, and is mutable.
172    Shared(
173        /// The version at which the object became shared
174        Version,
175    ),
176    /// Object is immutable, and hence ownership doesn't matter.
177    Immutable,
178
179    /// Object is exclusively owned by a single address and sequenced via consensus.
180    ConsensusAddress {
181        /// The version at which the object most recently became a consensus object.
182        /// This serves the same function as `initial_shared_version`, except it may change
183        /// if the object's Owner type changes.
184        start_version: Version,
185
186        /// The owner of the object.
187        owner: Address,
188    },
189}
190
191/// Object data, either a package or struct
192///
193/// # BCS
194///
195/// The BCS serialized form for this type is defined by the following ABNF:
196///
197/// ```text
198/// object-data = object-data-struct / object-data-package
199///
200/// object-data-struct  = %x00 object-move-struct
201/// object-data-package = %x01 object-move-package
202/// ```
203#[derive(Clone, Debug, PartialEq, Eq, Hash)]
204#[cfg_attr(
205    feature = "serde",
206    derive(serde_derive::Serialize, serde_derive::Deserialize)
207)]
208#[allow(clippy::large_enum_variant)]
209#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
210//TODO think about hiding this type and not exposing it
211pub enum ObjectData {
212    /// An object whose governing logic lives in a published Move module
213    Struct(MoveStruct),
214    /// Map from each module name to raw serialized Move module bytes
215    Package(MovePackage),
216    // ... Sui "native" types go here
217}
218
219/// A move package
220///
221/// # BCS
222///
223/// The BCS serialized form for this type is defined by the following ABNF:
224///
225/// ```text
226/// object-move-package = address u64 move-modules type-origin-table linkage-table
227///
228/// move-modules = map (identifier bytes)
229/// type-origin-table = vector type-origin
230/// linkage-table = map (address upgrade-info)
231/// ```
232#[derive(Eq, PartialEq, Debug, Clone, Hash)]
233#[cfg_attr(
234    feature = "serde",
235    derive(serde_derive::Serialize, serde_derive::Deserialize)
236)]
237#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
238pub struct MovePackage {
239    /// Address or Id of this package
240    pub id: Address,
241
242    /// Most move packages are uniquely identified by their ID (i.e. there is only one version per
243    /// ID), but the version is still stored because one package may be an upgrade of another (at a
244    /// different ID), in which case its version will be one greater than the version of the
245    /// upgraded package.
246    ///
247    /// Framework packages are an exception to this rule -- all versions of the framework packages
248    /// exist at the same ID, at increasing versions.
249    ///
250    /// In all cases, packages are referred to by move calls using just their ID, and they are
251    /// always loaded at their latest version.
252    pub version: Version,
253
254    /// Set of modules defined by this package
255    #[cfg_attr(
256        feature = "serde",
257        serde(with = "::serde_with::As::<BTreeMap<::serde_with::Same, ::serde_with::Bytes>>")
258    )]
259    #[cfg_attr(
260        feature = "proptest",
261        strategy(
262            proptest::collection::btree_map(proptest::arbitrary::any::<Identifier>(), proptest::collection::vec(proptest::arbitrary::any::<u8>(), 0..=1024), 0..=5)
263        )
264    )]
265    pub modules: BTreeMap<Identifier, Vec<u8>>,
266
267    /// Maps struct/module to a package version where it was first defined, stored as a vector for
268    /// simple serialization and deserialization.
269    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
270    pub type_origin_table: Vec<TypeOrigin>,
271
272    /// For each dependency, maps original package ID to the info about the (upgraded) dependency
273    /// version that this package is using
274    #[cfg_attr(
275        feature = "proptest",
276        strategy(
277            proptest::collection::btree_map(proptest::arbitrary::any::<Address>(), proptest::arbitrary::any::<UpgradeInfo>(), 0..=5)
278        )
279    )]
280    pub linkage_table: BTreeMap<Address, UpgradeInfo>,
281}
282
283/// Identifies a struct and the module it was defined in
284///
285/// # BCS
286///
287/// The BCS serialized form for this type is defined by the following ABNF:
288///
289/// ```text
290/// type-origin = identifier identifier address
291/// ```
292#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
293#[cfg_attr(
294    feature = "serde",
295    derive(serde_derive::Serialize, serde_derive::Deserialize)
296)]
297#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
298pub struct TypeOrigin {
299    pub module_name: Identifier,
300    pub struct_name: Identifier,
301    pub package: Address,
302}
303
304/// Upgraded package info for the linkage table
305///
306/// # BCS
307///
308/// The BCS serialized form for this type is defined by the following ABNF:
309///
310/// ```text
311/// upgrade-info = address u64
312/// ```
313#[derive(Eq, PartialEq, Debug, Clone, Hash)]
314#[cfg_attr(
315    feature = "serde",
316    derive(serde_derive::Serialize, serde_derive::Deserialize)
317)]
318#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
319pub struct UpgradeInfo {
320    /// Id of the upgraded packages
321    pub upgraded_id: Address,
322    /// Version of the upgraded package
323    pub upgraded_version: Version,
324}
325
326/// A move struct
327///
328/// # BCS
329///
330/// The BCS serialized form for this type is defined by the following ABNF:
331///
332/// ```text
333/// object-move-struct = compressed-struct-tag bool u64 object-contents
334///
335/// compressed-struct-tag = other-struct-type / gas-coin-type / staked-sui-type / coin-type
336/// other-struct-type     = %x00 struct-tag
337/// gas-coin-type         = %x01
338/// staked-sui-type       = %x02
339/// coin-type             = %x03 type-tag
340///
341/// ; first 32 bytes of the contents are the object's address
342/// object-contents = uleb128 (address *OCTET) ; length followed by contents
343/// ```
344#[derive(Eq, PartialEq, Debug, Clone, Hash)]
345//TODO hand-roll a Deserialize impl to enforce that an objectid is present
346#[cfg_attr(
347    feature = "serde",
348    derive(serde_derive::Serialize, serde_derive::Deserialize)
349)]
350#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
351pub struct MoveStruct {
352    /// The type of this object
353    #[cfg_attr(
354        feature = "serde",
355        serde(with = "::serde_with::As::<serialization::BinaryMoveStructType>")
356    )]
357    pub(crate) type_: StructTag,
358
359    /// DEPRECATED this field is no longer used to determine whether a tx can transfer this
360    /// object. Instead, it is always calculated from the objects type when loaded in execution
361    has_public_transfer: bool,
362
363    /// Number that increases each time a tx takes this object as a mutable input
364    /// This is a lamport timestamp, not a sequentially increasing version
365    version: Version,
366
367    /// BCS bytes of a Move struct value
368    #[cfg_attr(
369        feature = "serde",
370        serde(with = "crate::_serde::ReadableBase64Encoded")
371    )]
372    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(32..=1024).lift()))]
373    pub(crate) contents: Vec<u8>,
374}
375
376impl MoveStruct {
377    /// Construct a move struct
378    pub fn new(
379        type_: StructTag,
380        has_public_transfer: bool,
381        version: Version,
382        contents: Vec<u8>,
383    ) -> Option<Self> {
384        id_opt(&contents).map(|_| Self {
385            type_,
386            has_public_transfer,
387            version,
388            contents,
389        })
390    }
391
392    /// Return the type of the struct
393    pub fn object_type(&self) -> &StructTag {
394        &self.type_
395    }
396
397    /// Return if this object can be publicly transferred
398    ///
399    /// DEPRECATED
400    ///
401    /// This field is no longer used to determine whether a tx can transfer this object. Instead,
402    /// it is always calculated from the objects type when loaded in execution.
403    #[doc(hidden)]
404    pub fn has_public_transfer(&self) -> bool {
405        self.has_public_transfer
406    }
407
408    /// Return the version of this object
409    pub fn version(&self) -> Version {
410        self.version
411    }
412
413    /// Return the raw contents of this struct
414    pub fn contents(&self) -> &[u8] {
415        &self.contents
416    }
417
418    /// Return the ObjectId of this object
419    pub fn object_id(&self) -> Address {
420        id_opt(self.contents()).unwrap()
421    }
422}
423
424/// Type of a Sui object
425#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
426pub enum ObjectType {
427    /// Move package containing one or more bytecode modules
428    Package,
429    /// A Move struct of the given type
430    Struct(StructTag),
431}
432
433/// An object on the sui blockchain
434///
435/// # BCS
436///
437/// The BCS serialized form for this type is defined by the following ABNF:
438///
439/// ```text
440/// object = object-data owner digest u64
441/// ```
442#[derive(Clone, Debug, PartialEq, Eq)]
443#[cfg_attr(
444    feature = "serde",
445    derive(serde_derive::Serialize, serde_derive::Deserialize)
446)]
447#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
448pub struct Object {
449    /// The meat of the object
450    pub(crate) data: ObjectData,
451
452    /// The owner that unlocks this object
453    owner: Owner,
454
455    /// The digest of the transaction that created or last mutated this object
456    previous_transaction: Digest,
457
458    /// The amount of SUI we would rebate if this object gets deleted.
459    /// This number is re-calculated each time the object is mutated based on
460    /// the present storage gas price.
461    storage_rebate: u64,
462}
463
464impl Object {
465    /// Build an object
466    pub fn new(
467        data: ObjectData,
468        owner: Owner,
469        previous_transaction: Digest,
470        storage_rebate: u64,
471    ) -> Self {
472        Self {
473            data,
474            owner,
475            previous_transaction,
476            storage_rebate,
477        }
478    }
479
480    /// Return this object's id
481    pub fn object_id(&self) -> Address {
482        match &self.data {
483            ObjectData::Struct(struct_) => id_opt(&struct_.contents).unwrap(),
484            ObjectData::Package(package) => package.id,
485        }
486    }
487
488    /// Return this object's version
489    pub fn version(&self) -> Version {
490        match &self.data {
491            ObjectData::Struct(struct_) => struct_.version,
492            ObjectData::Package(package) => package.version,
493        }
494    }
495
496    /// Return this object's type
497    pub fn object_type(&self) -> ObjectType {
498        match &self.data {
499            ObjectData::Struct(struct_) => ObjectType::Struct(struct_.type_.clone()),
500            ObjectData::Package(_) => ObjectType::Package,
501        }
502    }
503
504    /// Try to interpret this object as a move struct
505    pub fn as_struct(&self) -> Option<&MoveStruct> {
506        match &self.data {
507            ObjectData::Struct(struct_) => Some(struct_),
508            _ => None,
509        }
510    }
511
512    /// Return this object's owner
513    pub fn owner(&self) -> &Owner {
514        &self.owner
515    }
516
517    /// Return this object's data
518    pub fn data(&self) -> &ObjectData {
519        &self.data
520    }
521
522    /// Return the digest of the transaction that last modified this object
523    pub fn previous_transaction(&self) -> Digest {
524        self.previous_transaction
525    }
526
527    /// Return the storage rebate locked in this object
528    ///
529    /// Storage rebates are credited to the gas coin used in a transaction that deletes this
530    /// object.
531    pub fn storage_rebate(&self) -> u64 {
532        self.storage_rebate
533    }
534}
535
536fn id_opt(contents: &[u8]) -> Option<Address> {
537    if Address::LENGTH > contents.len() {
538        return None;
539    }
540
541    Some(Address::from_bytes(&contents[..Address::LENGTH]).unwrap())
542}
543
544/// An object part of the initial chain state
545///
546/// `GenesisObject`'s are included as a part of genesis, the initial checkpoint/transaction, that
547/// initializes the state of the blockchain.
548///
549/// # BCS
550///
551/// The BCS serialized form for this type is defined by the following ABNF:
552///
553/// ```text
554/// genesis-object = object-data owner
555/// ```
556#[derive(Clone, Debug, PartialEq, Eq)]
557#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
558pub struct GenesisObject {
559    data: ObjectData,
560    owner: Owner,
561}
562
563impl GenesisObject {
564    pub fn new(data: ObjectData, owner: Owner) -> Self {
565        Self { data, owner }
566    }
567
568    pub fn object_id(&self) -> Address {
569        match &self.data {
570            ObjectData::Struct(struct_) => id_opt(&struct_.contents).unwrap(),
571            ObjectData::Package(package) => package.id,
572        }
573    }
574
575    pub fn version(&self) -> Version {
576        match &self.data {
577            ObjectData::Struct(struct_) => struct_.version,
578            ObjectData::Package(package) => package.version,
579        }
580    }
581
582    pub fn object_type(&self) -> ObjectType {
583        match &self.data {
584            ObjectData::Struct(struct_) => ObjectType::Struct(struct_.type_.clone()),
585            ObjectData::Package(_) => ObjectType::Package,
586        }
587    }
588
589    pub fn owner(&self) -> &Owner {
590        &self.owner
591    }
592
593    pub fn data(&self) -> &ObjectData {
594        &self.data
595    }
596}
597
598//TODO improve ser/de to do borrowing to avoid clones where possible
599#[cfg(feature = "serde")]
600#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
601mod serialization {
602    use serde::Deserialize;
603    use serde::Deserializer;
604    use serde::Serialize;
605    use serde::Serializer;
606    use serde_with::DeserializeAs;
607    use serde_with::SerializeAs;
608
609    use super::*;
610    use crate::TypeTag;
611
612    /// Wrapper around StructTag with a space-efficient representation for common types like coins
613    /// The StructTag for a gas coin is 84 bytes, so using 1 byte instead is a win.
614    /// The inner representation is private to prevent incorrectly constructing an `Other` instead of
615    /// one of the specialized variants, e.g. `Other(GasCoin::type_())` instead of `GasCoin`
616    #[derive(serde_derive::Deserialize)]
617    enum MoveStructType {
618        /// A type that is not `0x2::coin::Coin<T>`
619        Other(StructTag),
620        /// A SUI coin (i.e., `0x2::coin::Coin<0x2::sui::SUI>`)
621        GasCoin,
622        /// A record of a staked SUI coin (i.e., `0x3::staking_pool::StakedSui`)
623        StakedSui,
624        /// A non-SUI coin type (i.e., `0x2::coin::Coin<T> where T != 0x2::sui::SUI`)
625        Coin(TypeTag),
626        /// A SUI balance accumulator field
627        /// (i.e., `0x2::dynamic_field::Field<0x2::accumulator::Key<0x2::balance::Balance<0x2::sui::SUI>>, 0x2::accumulator::U128>`)
628        SuiBalanceAccumulatorField,
629        /// A non-SUI balance accumulator field
630        /// (i.e., `0x2::dynamic_field::Field<0x2::accumulator::Key<0x2::balance::Balance<T>>, 0x2::accumulator::U128>`
631        /// where T != 0x2::sui::SUI)
632        BalanceAccumulatorField(TypeTag),
633        // NOTE: if adding a new type here, and there are existing on-chain objects of that
634        // type with Other(_), that is ok, but you must hand-roll PartialEq/Eq/Ord/maybe Hash
635        // to make sure the new type and Other(_) are interpreted consistently.
636    }
637
638    /// See `MoveStructType`
639    #[derive(serde_derive::Serialize)]
640    enum MoveStructTypeRef<'a> {
641        /// A type that is not `0x2::coin::Coin<T>`
642        Other(&'a StructTag),
643        /// A SUI coin (i.e., `0x2::coin::Coin<0x2::sui::SUI>`)
644        GasCoin,
645        /// A record of a staked SUI coin (i.e., `0x3::staking_pool::StakedSui`)
646        StakedSui,
647        /// A non-SUI coin type (i.e., `0x2::coin::Coin<T> where T != 0x2::sui::SUI`)
648        Coin(&'a TypeTag),
649        /// A SUI balance accumulator field
650        /// (i.e., `0x2::dynamic_field::Field<0x2::accumulator::Key<0x2::balance::Balance<0x2::sui::SUI>>, 0x2::accumulator::U128>`)
651        SuiBalanceAccumulatorField,
652        /// A non-SUI balance accumulator field
653        /// (i.e., `0x2::dynamic_field::Field<0x2::accumulator::Key<0x2::balance::Balance<T>>, 0x2::accumulator::U128>`
654        /// where T != 0x2::sui::SUI)
655        BalanceAccumulatorField(&'a TypeTag),
656        // NOTE: if adding a new type here, and there are existing on-chain objects of that
657        // type with Other(_), that is ok, but you must hand-roll PartialEq/Eq/Ord/maybe Hash
658        // to make sure the new type and Other(_) are interpreted consistently.
659    }
660
661    impl MoveStructType {
662        fn into_struct_tag(self) -> StructTag {
663            match self {
664                MoveStructType::Other(tag) => tag,
665                MoveStructType::GasCoin => StructTag::gas_coin(),
666                MoveStructType::StakedSui => StructTag::staked_sui(),
667                MoveStructType::Coin(type_tag) => StructTag::coin(type_tag),
668                MoveStructType::SuiBalanceAccumulatorField => {
669                    StructTag::balance_accumulator_field(StructTag::sui().into())
670                }
671                MoveStructType::BalanceAccumulatorField(type_tag) => {
672                    StructTag::balance_accumulator_field(type_tag)
673                }
674            }
675        }
676    }
677
678    impl<'a> MoveStructTypeRef<'a> {
679        fn from_struct_tag(s: &'a StructTag) -> Self {
680            let address = s.address();
681            let module = s.module();
682            let name = s.name();
683            let type_params = s.type_params();
684
685            if let Some(coin_type) = s.is_coin() {
686                if let TypeTag::Struct(s_inner) = coin_type
687                    && s_inner.is_gas()
688                {
689                    Self::GasCoin
690                } else {
691                    Self::Coin(coin_type)
692                }
693            } else if address == &Address::THREE
694                && module == "staking_pool"
695                && name == "StakedSui"
696                && type_params.is_empty()
697            {
698                Self::StakedSui
699            } else if let Some(coin_type) = s.is_balance_accumulator_field() {
700                if let TypeTag::Struct(s_inner) = coin_type
701                    && s_inner.is_gas()
702                {
703                    Self::SuiBalanceAccumulatorField
704                } else {
705                    Self::BalanceAccumulatorField(coin_type)
706                }
707            } else {
708                Self::Other(s)
709            }
710        }
711    }
712
713    pub(super) struct BinaryMoveStructType;
714
715    impl SerializeAs<StructTag> for BinaryMoveStructType {
716        fn serialize_as<S>(source: &StructTag, serializer: S) -> Result<S::Ok, S::Error>
717        where
718            S: Serializer,
719        {
720            let move_object_type = MoveStructTypeRef::from_struct_tag(source);
721            move_object_type.serialize(serializer)
722        }
723    }
724
725    impl<'de> DeserializeAs<'de, StructTag> for BinaryMoveStructType {
726        fn deserialize_as<D>(deserializer: D) -> Result<StructTag, D::Error>
727        where
728            D: Deserializer<'de>,
729        {
730            let struct_type = MoveStructType::deserialize(deserializer)?;
731            Ok(struct_type.into_struct_tag())
732        }
733    }
734
735    #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
736    enum BinaryGenesisObject {
737        RawObject { data: ObjectData, owner: Owner },
738    }
739
740    impl Serialize for GenesisObject {
741        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
742        where
743            S: Serializer,
744        {
745            let binary = BinaryGenesisObject::RawObject {
746                data: self.data.clone(),
747                owner: self.owner,
748            };
749            binary.serialize(serializer)
750        }
751    }
752
753    impl<'de> Deserialize<'de> for GenesisObject {
754        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
755        where
756            D: Deserializer<'de>,
757        {
758            let BinaryGenesisObject::RawObject { data, owner } =
759                Deserialize::deserialize(deserializer)?;
760
761            Ok(GenesisObject { data, owner })
762        }
763    }
764
765    #[cfg(test)]
766    mod test {
767        use crate::bcs::FromBcs;
768        use crate::bcs::ToBcs;
769        use crate::object::Object;
770
771        #[cfg(target_arch = "wasm32")]
772        use wasm_bindgen_test::wasm_bindgen_test as test;
773
774        #[test]
775        fn object_fixture() {
776            const SUI_COIN: &[u8] = &[
777                0, 1, 1, 32, 79, 43, 0, 0, 0, 0, 0, 40, 35, 95, 175, 213, 151, 87, 206, 190, 35,
778                131, 79, 35, 254, 22, 15, 181, 40, 108, 28, 77, 68, 229, 107, 254, 191, 160, 196,
779                186, 42, 2, 122, 53, 52, 133, 199, 58, 0, 0, 0, 0, 0, 79, 255, 208, 0, 85, 34, 190,
780                75, 192, 41, 114, 76, 127, 15, 110, 215, 9, 58, 107, 243, 160, 155, 144, 230, 47,
781                97, 220, 21, 24, 30, 26, 62, 32, 17, 197, 192, 38, 64, 173, 142, 143, 49, 111, 15,
782                211, 92, 84, 48, 160, 243, 102, 229, 253, 251, 137, 210, 101, 119, 173, 228, 51,
783                141, 20, 15, 85, 96, 19, 15, 0, 0, 0, 0, 0,
784            ];
785
786            const SUI_STAKE: &[u8] = &[
787                0, 2, 1, 154, 1, 52, 5, 0, 0, 0, 0, 80, 3, 112, 71, 231, 166, 234, 205, 164, 99,
788                237, 29, 56, 97, 170, 21, 96, 105, 158, 227, 122, 22, 251, 60, 162, 12, 97, 151,
789                218, 71, 253, 231, 239, 116, 138, 12, 233, 128, 195, 128, 77, 33, 38, 122, 77, 53,
790                154, 197, 198, 75, 212, 12, 182, 163, 224, 42, 82, 123, 69, 248, 40, 207, 143, 211,
791                13, 106, 1, 0, 0, 0, 0, 0, 0, 59, 81, 183, 246, 112, 0, 0, 0, 0, 79, 255, 208, 0,
792                85, 34, 190, 75, 192, 41, 114, 76, 127, 15, 110, 215, 9, 58, 107, 243, 160, 155,
793                144, 230, 47, 97, 220, 21, 24, 30, 26, 62, 32, 247, 239, 248, 71, 247, 102, 190,
794                149, 232, 153, 138, 67, 169, 209, 203, 29, 255, 215, 223, 57, 159, 44, 40, 218,
795                166, 13, 80, 71, 14, 188, 232, 68, 0, 0, 0, 0, 0, 0, 0, 0,
796            ];
797
798            const NFT: &[u8] = &[
799                0, 0, 97, 201, 195, 159, 216, 97, 133, 173, 96, 215, 56, 212, 229, 43, 208, 139,
800                218, 7, 29, 54, 106, 205, 224, 126, 7, 195, 145, 106, 45, 117, 168, 22, 12, 100,
801                105, 115, 116, 114, 105, 98, 117, 116, 105, 111, 110, 11, 68, 69, 69, 80, 87, 114,
802                97, 112, 112, 101, 114, 0, 0, 124, 24, 223, 4, 0, 0, 0, 0, 40, 31, 8, 18, 84, 38,
803                164, 252, 84, 115, 250, 246, 137, 132, 128, 186, 156, 36, 62, 18, 140, 21, 4, 90,
804                209, 105, 85, 84, 92, 214, 97, 81, 207, 64, 194, 198, 208, 21, 0, 0, 0, 0, 79, 255,
805                208, 0, 85, 34, 190, 75, 192, 41, 114, 76, 127, 15, 110, 215, 9, 58, 107, 243, 160,
806                155, 144, 230, 47, 97, 220, 21, 24, 30, 26, 62, 32, 170, 4, 94, 114, 207, 155, 31,
807                80, 62, 254, 220, 206, 240, 218, 83, 54, 204, 197, 255, 239, 41, 66, 199, 150, 56,
808                189, 86, 217, 166, 216, 128, 241, 64, 205, 21, 0, 0, 0, 0, 0,
809            ];
810
811            const FUD_COIN: &[u8] = &[
812                0, 3, 7, 118, 203, 129, 155, 1, 171, 237, 80, 43, 238, 138, 112, 43, 76, 45, 84,
813                117, 50, 193, 47, 37, 0, 28, 157, 234, 121, 90, 94, 99, 28, 38, 241, 3, 102, 117,
814                100, 3, 70, 85, 68, 0, 1, 193, 89, 252, 3, 0, 0, 0, 0, 40, 33, 214, 90, 11, 56,
815                243, 115, 10, 250, 121, 250, 28, 34, 237, 104, 130, 148, 40, 130, 29, 248, 137,
816                244, 27, 138, 94, 150, 28, 182, 104, 162, 185, 0, 152, 247, 62, 93, 1, 0, 0, 0, 42,
817                95, 32, 226, 13, 31, 128, 91, 188, 127, 235, 12, 75, 73, 116, 112, 3, 227, 244,
818                126, 59, 81, 214, 118, 144, 243, 195, 17, 82, 216, 119, 170, 32, 239, 247, 71, 249,
819                241, 98, 133, 53, 46, 37, 100, 242, 94, 231, 241, 184, 8, 69, 192, 69, 67, 1, 116,
820                251, 229, 226, 99, 119, 79, 255, 71, 43, 64, 242, 19, 0, 0, 0, 0, 0,
821            ];
822
823            const BULLSHARK_PACKAGE: &[u8] = &[
824                1, 135, 35, 29, 28, 138, 126, 114, 145, 204, 122, 145, 8, 244, 199, 188, 26, 10,
825                28, 14, 182, 55, 91, 91, 97, 10, 245, 202, 35, 223, 14, 140, 86, 1, 0, 0, 0, 0, 0,
826                0, 0, 1, 9, 98, 117, 108, 108, 115, 104, 97, 114, 107, 162, 6, 161, 28, 235, 11, 6,
827                0, 0, 0, 10, 1, 0, 12, 2, 12, 36, 3, 48, 61, 4, 109, 12, 5, 121, 137, 1, 7, 130, 2,
828                239, 1, 8, 241, 3, 96, 6, 209, 4, 82, 10, 163, 5, 5, 12, 168, 5, 75, 0, 7, 1, 16,
829                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,
830                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,
831                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,
832                14, 17, 1, 1, 0, 3, 17, 7, 1, 1, 12, 3, 18, 16, 1, 1, 12, 4, 19, 13, 14, 0, 5, 15,
833                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,
834                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,
835                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,
836                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,
837                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,
838                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,
839                72, 65, 82, 75, 4, 67, 111, 105, 110, 12, 67, 111, 105, 110, 77, 101, 116, 97, 100,
840                97, 116, 97, 6, 79, 112, 116, 105, 111, 110, 11, 84, 114, 101, 97, 115, 117, 114,
841                121, 67, 97, 112, 9, 84, 120, 67, 111, 110, 116, 101, 120, 116, 3, 85, 114, 108, 9,
842                98, 117, 108, 108, 115, 104, 97, 114, 107, 4, 98, 117, 114, 110, 4, 99, 111, 105,
843                110, 15, 99, 114, 101, 97, 116, 101, 95, 99, 117, 114, 114, 101, 110, 99, 121, 11,
844                100, 117, 109, 109, 121, 95, 102, 105, 101, 108, 100, 4, 105, 110, 105, 116, 4,
845                109, 105, 110, 116, 17, 109, 105, 110, 116, 95, 97, 110, 100, 95, 116, 114, 97,
846                110, 115, 102, 101, 114, 21, 110, 101, 119, 95, 117, 110, 115, 97, 102, 101, 95,
847                102, 114, 111, 109, 95, 98, 121, 116, 101, 115, 6, 111, 112, 116, 105, 111, 110,
848                20, 112, 117, 98, 108, 105, 99, 95, 102, 114, 101, 101, 122, 101, 95, 111, 98, 106,
849                101, 99, 116, 15, 112, 117, 98, 108, 105, 99, 95, 116, 114, 97, 110, 115, 102, 101,
850                114, 6, 115, 101, 110, 100, 101, 114, 4, 115, 111, 109, 101, 8, 116, 114, 97, 110,
851                115, 102, 101, 114, 10, 116, 120, 95, 99, 111, 110, 116, 101, 120, 116, 3, 117,
852                114, 108, 135, 35, 29, 28, 138, 126, 114, 145, 204, 122, 145, 8, 244, 199, 188, 26,
853                10, 28, 14, 182, 55, 91, 91, 97, 10, 245, 202, 35, 223, 14, 140, 86, 0, 0, 0, 0, 0,
854                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,
855                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,
856                0, 0, 2, 10, 2, 10, 9, 66, 85, 76, 76, 83, 72, 65, 82, 75, 10, 2, 20, 19, 66, 117,
857                108, 108, 32, 83, 104, 97, 114, 107, 32, 83, 117, 105, 70, 114, 101, 110, 115, 10,
858                2, 1, 0, 10, 2, 39, 38, 104, 116, 116, 112, 115, 58, 47, 47, 105, 46, 105, 98, 98,
859                46, 99, 111, 47, 104, 87, 89, 50, 87, 53, 120, 47, 98, 117, 108, 108, 115, 104, 97,
860                114, 107, 46, 112, 110, 103, 0, 2, 1, 11, 1, 0, 0, 0, 0, 4, 20, 11, 0, 49, 6, 7, 0,
861                7, 1, 7, 2, 7, 3, 17, 10, 56, 0, 10, 1, 56, 1, 12, 2, 12, 3, 11, 2, 56, 2, 11, 3,
862                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,
863                2, 1, 4, 0, 1, 5, 11, 0, 11, 1, 56, 5, 1, 2, 0, 1, 9, 98, 117, 108, 108, 115, 104,
864                97, 114, 107, 9, 66, 85, 76, 76, 83, 72, 65, 82, 75, 135, 35, 29, 28, 138, 126,
865                114, 145, 204, 122, 145, 8, 244, 199, 188, 26, 10, 28, 14, 182, 55, 91, 91, 97, 10,
866                245, 202, 35, 223, 14, 140, 86, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
867                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,
868                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,
869                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,
870                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,
871                0, 0, 0, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 0, 0, 0, 3, 32, 87, 145, 191, 231, 147, 185,
872                46, 159, 240, 181, 95, 126, 236, 65, 154, 55, 16, 196, 229, 218, 47, 59, 99, 197,
873                13, 89, 18, 159, 205, 129, 112, 131, 112, 192, 126, 0, 0, 0, 0, 0,
874            ];
875
876            for fixture in [SUI_COIN, SUI_STAKE, NFT, FUD_COIN, BULLSHARK_PACKAGE] {
877                let object: Object = bcs::from_bytes(fixture).unwrap();
878                assert_eq!(bcs::to_bytes(&object).unwrap(), fixture);
879
880                let json = serde_json::to_string_pretty(&object).unwrap();
881                println!("{json}");
882                assert_eq!(object, serde_json::from_str(&json).unwrap());
883            }
884        }
885
886        // Test to ensure we properly serialize and deserialize the new MoveStructType variants for
887        // address balances
888        #[test]
889        fn address_balance_objects() {
890            let non_sui_address_balance_type = "AAUHIRSU0QWQjjOQW/wFv4O24+PddAa8JQ45YwhB+i7EfzgGY29pbl9hBkNPSU5fQQAAEAAAAAAAAABQCSgWThHEQ1NqPKQQsXVKd/yFD0FSDYUtrvXx1xt+jIk0Bsyk3bbd4hLE1MDxwok6jzp0k3365HVXhJgmi+4vjcQJAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACswgmPj1SN1ZkPLtiVtVs0XD3QCgS/YYUFBh9Q6p4b+zoJAAAAAAAAAAAA==";
891
892            let non_sui = Object::from_bcs_base64(non_sui_address_balance_type).unwrap();
893            assert_eq!(
894                non_sui.to_bcs_base64().unwrap(),
895                non_sui_address_balance_type
896            );
897
898            let sui_address_balance_type = "AAQAAgAAAAAAAABQlJ321C1hKFc15SQmGZUdTDrwVh7xQ46GoV2zEnFK88b/JOPl1wGyhHd/R1itnNXhAzGoyXuDHuOL3V34auvxf+gDAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACswgjRbaObiwu6bn07xewfd3V9iFJfbhcaWy7K6YgNsZdKkAAAAAAAAAAA==";
899
900            let sui = Object::from_bcs_base64(sui_address_balance_type).unwrap();
901            assert_eq!(sui.to_bcs_base64().unwrap(), sui_address_balance_type);
902        }
903    }
904}