1use std::collections::BTreeMap;
2
3use super::Address;
4use super::Digest;
5use super::Identifier;
6use super::StructTag;
7
8pub type Version = u64;
9
10#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
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 object_id: Address,
30 version: Version,
32 digest: Digest,
34}
35
36impl ObjectReference {
37 pub fn new(object_id: Address, version: Version, digest: Digest) -> Self {
39 Self {
40 object_id,
41 version,
42 digest,
43 }
44 }
45
46 pub fn object_id(&self) -> &Address {
48 &self.object_id
49 }
50
51 pub fn version(&self) -> Version {
53 self.version
54 }
55
56 pub fn digest(&self) -> &Digest {
58 &self.digest
59 }
60
61 #[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 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 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 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 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#[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 Address(Address),
169 Object(Address),
171 Shared(
173 Version,
175 ),
176 Immutable,
178
179 ConsensusAddress {
181 start_version: Version,
185
186 owner: Address,
188 },
189}
190
191#[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))]
210pub enum ObjectData {
212 Struct(MoveStruct),
214 Package(MovePackage),
216 }
218
219#[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 pub id: Address,
241
242 pub version: Version,
253
254 #[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 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
270 pub type_origin_table: Vec<TypeOrigin>,
271
272 #[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#[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#[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 pub upgraded_id: Address,
322 pub upgraded_version: Version,
324}
325
326#[derive(Eq, PartialEq, Debug, Clone, Hash)]
345#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
346#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
347pub struct MoveStruct {
348 #[cfg_attr(
350 feature = "serde",
351 serde(with = "::serde_with::As::<serialization::BinaryMoveStructType>")
352 )]
353 pub(crate) type_: StructTag,
354
355 has_public_transfer: bool,
358
359 version: Version,
362
363 #[cfg_attr(
365 feature = "serde",
366 serde(with = "crate::_serde::ReadableBase64Encoded")
367 )]
368 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(32..=1024).lift()))]
369 pub(crate) contents: Vec<u8>,
370}
371
372impl MoveStruct {
373 pub fn new(
375 type_: StructTag,
376 has_public_transfer: bool,
377 version: Version,
378 contents: Vec<u8>,
379 ) -> Option<Self> {
380 id_opt(&contents).map(|_| Self {
381 type_,
382 has_public_transfer,
383 version,
384 contents,
385 })
386 }
387
388 pub fn object_type(&self) -> &StructTag {
390 &self.type_
391 }
392
393 #[doc(hidden)]
400 pub fn has_public_transfer(&self) -> bool {
401 self.has_public_transfer
402 }
403
404 pub fn version(&self) -> Version {
406 self.version
407 }
408
409 pub fn contents(&self) -> &[u8] {
411 &self.contents
412 }
413
414 pub fn object_id(&self) -> Address {
416 id_opt(self.contents()).unwrap()
417 }
418}
419
420#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
422pub enum ObjectType {
423 Package,
425 Struct(StructTag),
427}
428
429#[derive(Clone, Debug, PartialEq, Eq)]
439#[cfg_attr(
440 feature = "serde",
441 derive(serde_derive::Serialize, serde_derive::Deserialize)
442)]
443#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
444pub struct Object {
445 pub(crate) data: ObjectData,
447
448 owner: Owner,
450
451 previous_transaction: Digest,
453
454 storage_rebate: u64,
458}
459
460impl Object {
461 pub fn new(
463 data: ObjectData,
464 owner: Owner,
465 previous_transaction: Digest,
466 storage_rebate: u64,
467 ) -> Self {
468 Self {
469 data,
470 owner,
471 previous_transaction,
472 storage_rebate,
473 }
474 }
475
476 pub fn object_id(&self) -> Address {
478 match &self.data {
479 ObjectData::Struct(struct_) => id_opt(&struct_.contents).unwrap(),
480 ObjectData::Package(package) => package.id,
481 }
482 }
483
484 pub fn version(&self) -> Version {
486 match &self.data {
487 ObjectData::Struct(struct_) => struct_.version,
488 ObjectData::Package(package) => package.version,
489 }
490 }
491
492 pub fn object_type(&self) -> ObjectType {
494 match &self.data {
495 ObjectData::Struct(struct_) => ObjectType::Struct(struct_.type_.clone()),
496 ObjectData::Package(_) => ObjectType::Package,
497 }
498 }
499
500 pub fn as_struct(&self) -> Option<&MoveStruct> {
502 match &self.data {
503 ObjectData::Struct(struct_) => Some(struct_),
504 _ => None,
505 }
506 }
507
508 pub fn owner(&self) -> &Owner {
510 &self.owner
511 }
512
513 pub fn data(&self) -> &ObjectData {
515 &self.data
516 }
517
518 pub fn previous_transaction(&self) -> Digest {
520 self.previous_transaction
521 }
522
523 pub fn storage_rebate(&self) -> u64 {
528 self.storage_rebate
529 }
530}
531
532fn id_opt(contents: &[u8]) -> Option<Address> {
533 if Address::LENGTH > contents.len() {
534 return None;
535 }
536
537 Some(Address::from_bytes(&contents[..Address::LENGTH]).unwrap())
538}
539
540#[derive(Clone, Debug, PartialEq, Eq)]
553#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
554pub struct GenesisObject {
555 data: ObjectData,
556 owner: Owner,
557}
558
559impl GenesisObject {
560 pub fn new(data: ObjectData, owner: Owner) -> Self {
561 Self { data, owner }
562 }
563
564 pub fn object_id(&self) -> Address {
565 match &self.data {
566 ObjectData::Struct(struct_) => id_opt(&struct_.contents).unwrap(),
567 ObjectData::Package(package) => package.id,
568 }
569 }
570
571 pub fn version(&self) -> Version {
572 match &self.data {
573 ObjectData::Struct(struct_) => struct_.version,
574 ObjectData::Package(package) => package.version,
575 }
576 }
577
578 pub fn object_type(&self) -> ObjectType {
579 match &self.data {
580 ObjectData::Struct(struct_) => ObjectType::Struct(struct_.type_.clone()),
581 ObjectData::Package(_) => ObjectType::Package,
582 }
583 }
584
585 pub fn owner(&self) -> &Owner {
586 &self.owner
587 }
588
589 pub fn data(&self) -> &ObjectData {
590 &self.data
591 }
592}
593
594#[cfg(feature = "serde")]
596#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
597mod serialization {
598 use serde::Deserialize;
599 use serde::Deserializer;
600 use serde::Serialize;
601 use serde::Serializer;
602 use serde_with::DeserializeAs;
603 use serde_with::SerializeAs;
604
605 use super::*;
606 use crate::TypeTag;
607
608 #[derive(serde_derive::Deserialize)]
613 enum MoveStructType {
614 Other(StructTag),
616 GasCoin,
618 StakedSui,
620 Coin(TypeTag),
622 SuiBalanceAccumulatorField,
625 BalanceAccumulatorField(TypeTag),
629 }
630
631 #[derive(serde_derive::Serialize)]
633 enum MoveStructTypeRef<'a> {
634 Other(&'a StructTag),
636 GasCoin,
638 StakedSui,
640 Coin(&'a TypeTag),
642 SuiBalanceAccumulatorField,
645 BalanceAccumulatorField(&'a TypeTag),
649 }
650
651 impl MoveStructType {
652 fn into_struct_tag(self) -> StructTag {
653 match self {
654 MoveStructType::Other(tag) => tag,
655 MoveStructType::GasCoin => StructTag::gas_coin(),
656 MoveStructType::StakedSui => StructTag::staked_sui(),
657 MoveStructType::Coin(type_tag) => StructTag::coin(type_tag),
658 MoveStructType::SuiBalanceAccumulatorField => {
659 StructTag::balance_accumulator_field(StructTag::sui().into())
660 }
661 MoveStructType::BalanceAccumulatorField(type_tag) => {
662 StructTag::balance_accumulator_field(type_tag)
663 }
664 }
665 }
666
667 fn variant_index(&self) -> u8 {
671 match self {
672 Self::Other(_) => 0,
673 Self::GasCoin => 1,
674 Self::StakedSui => 2,
675 Self::Coin(_) => 3,
676 Self::SuiBalanceAccumulatorField => 4,
677 Self::BalanceAccumulatorField(_) => 5,
678 }
679 }
680 }
681
682 impl<'a> MoveStructTypeRef<'a> {
683 fn from_struct_tag(s: &'a StructTag) -> Self {
684 let address = s.address();
685 let module = s.module();
686 let name = s.name();
687 let type_params = s.type_params();
688
689 if let Some(coin_type) = s.is_coin() {
690 if let TypeTag::Struct(s_inner) = coin_type
691 && s_inner.is_gas()
692 {
693 Self::GasCoin
694 } else {
695 Self::Coin(coin_type)
696 }
697 } else if address == &Address::THREE
698 && module == "staking_pool"
699 && name == "StakedSui"
700 && type_params.is_empty()
701 {
702 Self::StakedSui
703 } else if let Some(coin_type) = s.is_balance_accumulator_field() {
704 if let TypeTag::Struct(s_inner) = coin_type
705 && s_inner.is_gas()
706 {
707 Self::SuiBalanceAccumulatorField
708 } else {
709 Self::BalanceAccumulatorField(coin_type)
710 }
711 } else {
712 Self::Other(s)
713 }
714 }
715
716 fn variant_index(&self) -> u8 {
717 match self {
718 Self::Other(_) => 0,
719 Self::GasCoin => 1,
720 Self::StakedSui => 2,
721 Self::Coin(_) => 3,
722 Self::SuiBalanceAccumulatorField => 4,
723 Self::BalanceAccumulatorField(_) => 5,
724 }
725 }
726 }
727
728 pub(super) struct BinaryMoveStructType;
729
730 impl SerializeAs<StructTag> for BinaryMoveStructType {
731 fn serialize_as<S>(source: &StructTag, serializer: S) -> Result<S::Ok, S::Error>
732 where
733 S: Serializer,
734 {
735 let move_object_type = MoveStructTypeRef::from_struct_tag(source);
736 move_object_type.serialize(serializer)
737 }
738 }
739
740 impl<'de> DeserializeAs<'de, StructTag> for BinaryMoveStructType {
741 fn deserialize_as<D>(deserializer: D) -> Result<StructTag, D::Error>
742 where
743 D: Deserializer<'de>,
744 {
745 let parsed = MoveStructType::deserialize(deserializer)?;
753 let parsed_idx = parsed.variant_index();
754 let tag = parsed.into_struct_tag();
755 let canonical_idx = MoveStructTypeRef::from_struct_tag(&tag).variant_index();
756 if parsed_idx != canonical_idx {
757 return Err(serde::de::Error::custom(format!(
758 "non-canonical MoveStructType encoding: variant {parsed_idx} \
759 would be re-encoded as variant {canonical_idx}",
760 )));
761 }
762 Ok(tag)
763 }
764 }
765
766 #[derive(serde_derive::Deserialize)]
771 struct RawMoveStruct {
772 #[serde(with = "::serde_with::As::<BinaryMoveStructType>")]
773 type_: StructTag,
774 has_public_transfer: bool,
775 version: Version,
776 #[serde(with = "crate::_serde::ReadableBase64Encoded")]
777 contents: Vec<u8>,
778 }
779
780 impl<'de> Deserialize<'de> for MoveStruct {
781 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
782 where
783 D: Deserializer<'de>,
784 {
785 let RawMoveStruct {
786 type_,
787 has_public_transfer,
788 version,
789 contents,
790 } = RawMoveStruct::deserialize(deserializer)?;
791
792 if contents.len() < Address::LENGTH {
793 return Err(serde::de::Error::custom(format!(
794 "MoveStruct contents must be at least {} bytes (object id), got {}",
795 Address::LENGTH,
796 contents.len(),
797 )));
798 }
799
800 Ok(MoveStruct {
801 type_,
802 has_public_transfer,
803 version,
804 contents,
805 })
806 }
807 }
808
809 #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
810 enum BinaryGenesisObject {
811 RawObject { data: ObjectData, owner: Owner },
812 }
813
814 impl Serialize for GenesisObject {
815 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
816 where
817 S: Serializer,
818 {
819 let binary = BinaryGenesisObject::RawObject {
820 data: self.data.clone(),
821 owner: self.owner,
822 };
823 binary.serialize(serializer)
824 }
825 }
826
827 impl<'de> Deserialize<'de> for GenesisObject {
828 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
829 where
830 D: Deserializer<'de>,
831 {
832 let BinaryGenesisObject::RawObject { data, owner } =
833 Deserialize::deserialize(deserializer)?;
834
835 Ok(GenesisObject { data, owner })
836 }
837 }
838
839 #[cfg(test)]
840 mod test {
841 use crate::bcs::FromBcs;
842 use crate::bcs::ToBcs;
843 use crate::object::Object;
844
845 #[cfg(target_arch = "wasm32")]
846 use wasm_bindgen_test::wasm_bindgen_test as test;
847
848 #[test]
849 fn object_fixture() {
850 const SUI_COIN: &[u8] = &[
851 0, 1, 1, 32, 79, 43, 0, 0, 0, 0, 0, 40, 35, 95, 175, 213, 151, 87, 206, 190, 35,
852 131, 79, 35, 254, 22, 15, 181, 40, 108, 28, 77, 68, 229, 107, 254, 191, 160, 196,
853 186, 42, 2, 122, 53, 52, 133, 199, 58, 0, 0, 0, 0, 0, 79, 255, 208, 0, 85, 34, 190,
854 75, 192, 41, 114, 76, 127, 15, 110, 215, 9, 58, 107, 243, 160, 155, 144, 230, 47,
855 97, 220, 21, 24, 30, 26, 62, 32, 17, 197, 192, 38, 64, 173, 142, 143, 49, 111, 15,
856 211, 92, 84, 48, 160, 243, 102, 229, 253, 251, 137, 210, 101, 119, 173, 228, 51,
857 141, 20, 15, 85, 96, 19, 15, 0, 0, 0, 0, 0,
858 ];
859
860 const SUI_STAKE: &[u8] = &[
861 0, 2, 1, 154, 1, 52, 5, 0, 0, 0, 0, 80, 3, 112, 71, 231, 166, 234, 205, 164, 99,
862 237, 29, 56, 97, 170, 21, 96, 105, 158, 227, 122, 22, 251, 60, 162, 12, 97, 151,
863 218, 71, 253, 231, 239, 116, 138, 12, 233, 128, 195, 128, 77, 33, 38, 122, 77, 53,
864 154, 197, 198, 75, 212, 12, 182, 163, 224, 42, 82, 123, 69, 248, 40, 207, 143, 211,
865 13, 106, 1, 0, 0, 0, 0, 0, 0, 59, 81, 183, 246, 112, 0, 0, 0, 0, 79, 255, 208, 0,
866 85, 34, 190, 75, 192, 41, 114, 76, 127, 15, 110, 215, 9, 58, 107, 243, 160, 155,
867 144, 230, 47, 97, 220, 21, 24, 30, 26, 62, 32, 247, 239, 248, 71, 247, 102, 190,
868 149, 232, 153, 138, 67, 169, 209, 203, 29, 255, 215, 223, 57, 159, 44, 40, 218,
869 166, 13, 80, 71, 14, 188, 232, 68, 0, 0, 0, 0, 0, 0, 0, 0,
870 ];
871
872 const NFT: &[u8] = &[
873 0, 0, 97, 201, 195, 159, 216, 97, 133, 173, 96, 215, 56, 212, 229, 43, 208, 139,
874 218, 7, 29, 54, 106, 205, 224, 126, 7, 195, 145, 106, 45, 117, 168, 22, 12, 100,
875 105, 115, 116, 114, 105, 98, 117, 116, 105, 111, 110, 11, 68, 69, 69, 80, 87, 114,
876 97, 112, 112, 101, 114, 0, 0, 124, 24, 223, 4, 0, 0, 0, 0, 40, 31, 8, 18, 84, 38,
877 164, 252, 84, 115, 250, 246, 137, 132, 128, 186, 156, 36, 62, 18, 140, 21, 4, 90,
878 209, 105, 85, 84, 92, 214, 97, 81, 207, 64, 194, 198, 208, 21, 0, 0, 0, 0, 79, 255,
879 208, 0, 85, 34, 190, 75, 192, 41, 114, 76, 127, 15, 110, 215, 9, 58, 107, 243, 160,
880 155, 144, 230, 47, 97, 220, 21, 24, 30, 26, 62, 32, 170, 4, 94, 114, 207, 155, 31,
881 80, 62, 254, 220, 206, 240, 218, 83, 54, 204, 197, 255, 239, 41, 66, 199, 150, 56,
882 189, 86, 217, 166, 216, 128, 241, 64, 205, 21, 0, 0, 0, 0, 0,
883 ];
884
885 const FUD_COIN: &[u8] = &[
886 0, 3, 7, 118, 203, 129, 155, 1, 171, 237, 80, 43, 238, 138, 112, 43, 76, 45, 84,
887 117, 50, 193, 47, 37, 0, 28, 157, 234, 121, 90, 94, 99, 28, 38, 241, 3, 102, 117,
888 100, 3, 70, 85, 68, 0, 1, 193, 89, 252, 3, 0, 0, 0, 0, 40, 33, 214, 90, 11, 56,
889 243, 115, 10, 250, 121, 250, 28, 34, 237, 104, 130, 148, 40, 130, 29, 248, 137,
890 244, 27, 138, 94, 150, 28, 182, 104, 162, 185, 0, 152, 247, 62, 93, 1, 0, 0, 0, 42,
891 95, 32, 226, 13, 31, 128, 91, 188, 127, 235, 12, 75, 73, 116, 112, 3, 227, 244,
892 126, 59, 81, 214, 118, 144, 243, 195, 17, 82, 216, 119, 170, 32, 239, 247, 71, 249,
893 241, 98, 133, 53, 46, 37, 100, 242, 94, 231, 241, 184, 8, 69, 192, 69, 67, 1, 116,
894 251, 229, 226, 99, 119, 79, 255, 71, 43, 64, 242, 19, 0, 0, 0, 0, 0,
895 ];
896
897 const BULLSHARK_PACKAGE: &[u8] = &[
898 1, 135, 35, 29, 28, 138, 126, 114, 145, 204, 122, 145, 8, 244, 199, 188, 26, 10,
899 28, 14, 182, 55, 91, 91, 97, 10, 245, 202, 35, 223, 14, 140, 86, 1, 0, 0, 0, 0, 0,
900 0, 0, 1, 9, 98, 117, 108, 108, 115, 104, 97, 114, 107, 162, 6, 161, 28, 235, 11, 6,
901 0, 0, 0, 10, 1, 0, 12, 2, 12, 36, 3, 48, 61, 4, 109, 12, 5, 121, 137, 1, 7, 130, 2,
902 239, 1, 8, 241, 3, 96, 6, 209, 4, 82, 10, 163, 5, 5, 12, 168, 5, 75, 0, 7, 1, 16,
903 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,
904 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,
905 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,
906 14, 17, 1, 1, 0, 3, 17, 7, 1, 1, 12, 3, 18, 16, 1, 1, 12, 4, 19, 13, 14, 0, 5, 15,
907 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,
908 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,
909 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,
910 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,
911 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,
912 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,
913 72, 65, 82, 75, 4, 67, 111, 105, 110, 12, 67, 111, 105, 110, 77, 101, 116, 97, 100,
914 97, 116, 97, 6, 79, 112, 116, 105, 111, 110, 11, 84, 114, 101, 97, 115, 117, 114,
915 121, 67, 97, 112, 9, 84, 120, 67, 111, 110, 116, 101, 120, 116, 3, 85, 114, 108, 9,
916 98, 117, 108, 108, 115, 104, 97, 114, 107, 4, 98, 117, 114, 110, 4, 99, 111, 105,
917 110, 15, 99, 114, 101, 97, 116, 101, 95, 99, 117, 114, 114, 101, 110, 99, 121, 11,
918 100, 117, 109, 109, 121, 95, 102, 105, 101, 108, 100, 4, 105, 110, 105, 116, 4,
919 109, 105, 110, 116, 17, 109, 105, 110, 116, 95, 97, 110, 100, 95, 116, 114, 97,
920 110, 115, 102, 101, 114, 21, 110, 101, 119, 95, 117, 110, 115, 97, 102, 101, 95,
921 102, 114, 111, 109, 95, 98, 121, 116, 101, 115, 6, 111, 112, 116, 105, 111, 110,
922 20, 112, 117, 98, 108, 105, 99, 95, 102, 114, 101, 101, 122, 101, 95, 111, 98, 106,
923 101, 99, 116, 15, 112, 117, 98, 108, 105, 99, 95, 116, 114, 97, 110, 115, 102, 101,
924 114, 6, 115, 101, 110, 100, 101, 114, 4, 115, 111, 109, 101, 8, 116, 114, 97, 110,
925 115, 102, 101, 114, 10, 116, 120, 95, 99, 111, 110, 116, 101, 120, 116, 3, 117,
926 114, 108, 135, 35, 29, 28, 138, 126, 114, 145, 204, 122, 145, 8, 244, 199, 188, 26,
927 10, 28, 14, 182, 55, 91, 91, 97, 10, 245, 202, 35, 223, 14, 140, 86, 0, 0, 0, 0, 0,
928 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,
929 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,
930 0, 0, 2, 10, 2, 10, 9, 66, 85, 76, 76, 83, 72, 65, 82, 75, 10, 2, 20, 19, 66, 117,
931 108, 108, 32, 83, 104, 97, 114, 107, 32, 83, 117, 105, 70, 114, 101, 110, 115, 10,
932 2, 1, 0, 10, 2, 39, 38, 104, 116, 116, 112, 115, 58, 47, 47, 105, 46, 105, 98, 98,
933 46, 99, 111, 47, 104, 87, 89, 50, 87, 53, 120, 47, 98, 117, 108, 108, 115, 104, 97,
934 114, 107, 46, 112, 110, 103, 0, 2, 1, 11, 1, 0, 0, 0, 0, 4, 20, 11, 0, 49, 6, 7, 0,
935 7, 1, 7, 2, 7, 3, 17, 10, 56, 0, 10, 1, 56, 1, 12, 2, 12, 3, 11, 2, 56, 2, 11, 3,
936 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,
937 2, 1, 4, 0, 1, 5, 11, 0, 11, 1, 56, 5, 1, 2, 0, 1, 9, 98, 117, 108, 108, 115, 104,
938 97, 114, 107, 9, 66, 85, 76, 76, 83, 72, 65, 82, 75, 135, 35, 29, 28, 138, 126,
939 114, 145, 204, 122, 145, 8, 244, 199, 188, 26, 10, 28, 14, 182, 55, 91, 91, 97, 10,
940 245, 202, 35, 223, 14, 140, 86, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
941 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,
942 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,
943 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,
944 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,
945 0, 0, 0, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 0, 0, 0, 3, 32, 87, 145, 191, 231, 147, 185,
946 46, 159, 240, 181, 95, 126, 236, 65, 154, 55, 16, 196, 229, 218, 47, 59, 99, 197,
947 13, 89, 18, 159, 205, 129, 112, 131, 112, 192, 126, 0, 0, 0, 0, 0,
948 ];
949
950 for fixture in [SUI_COIN, SUI_STAKE, NFT, FUD_COIN, BULLSHARK_PACKAGE] {
951 let object: Object = bcs::from_bytes(fixture).unwrap();
952 assert_eq!(bcs::to_bytes(&object).unwrap(), fixture);
953
954 let json = serde_json::to_string_pretty(&object).unwrap();
955 println!("{json}");
956 assert_eq!(object, serde_json::from_str(&json).unwrap());
957 }
958 }
959
960 #[test]
963 fn address_balance_objects() {
964 let non_sui_address_balance_type = "AAUHIRSU0QWQjjOQW/wFv4O24+PddAa8JQ45YwhB+i7EfzgGY29pbl9hBkNPSU5fQQAAEAAAAAAAAABQCSgWThHEQ1NqPKQQsXVKd/yFD0FSDYUtrvXx1xt+jIk0Bsyk3bbd4hLE1MDxwok6jzp0k3365HVXhJgmi+4vjcQJAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACswgmPj1SN1ZkPLtiVtVs0XD3QCgS/YYUFBh9Q6p4b+zoJAAAAAAAAAAAA==";
965
966 let non_sui = Object::from_bcs_base64(non_sui_address_balance_type).unwrap();
967 assert_eq!(
968 non_sui.to_bcs_base64().unwrap(),
969 non_sui_address_balance_type
970 );
971
972 let sui_address_balance_type = "AAQAAgAAAAAAAABQlJ321C1hKFc15SQmGZUdTDrwVh7xQ46GoV2zEnFK88b/JOPl1wGyhHd/R1itnNXhAzGoyXuDHuOL3V34auvxf+gDAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACswgjRbaObiwu6bn07xewfd3V9iFJfbhcaWy7K6YgNsZdKkAAAAAAAAAAA==";
973
974 let sui = Object::from_bcs_base64(sui_address_balance_type).unwrap();
975 assert_eq!(sui.to_bcs_base64().unwrap(), sui_address_balance_type);
976 }
977
978 #[test]
984 fn truncated_move_struct_contents_is_rejected() {
985 let mut bytes = Vec::new();
991 bytes.push(0x00);
992 bytes.push(0x01);
993 bytes.push(0x00);
994 bytes.extend_from_slice(&[0u8; 8]);
995 bytes.push(0x0a);
996 bytes.extend_from_slice(&[0u8; 10]);
997 bytes.push(0x03);
998 bytes.push(0x20);
999 bytes.extend_from_slice(&[0u8; 32]);
1000 bytes.extend_from_slice(&[0u8; 8]);
1001
1002 let bcs_err = bcs::from_bytes::<Object>(&bytes).unwrap_err();
1003 assert!(
1004 bcs_err.to_string().contains("MoveStruct contents"),
1005 "unexpected BCS error: {bcs_err}"
1006 );
1007
1008 let json = serde_json::json!({
1009 "data": {
1010 "Struct": {
1011 "type_": "GasCoin",
1012 "has_public_transfer": false,
1013 "version": 0,
1014 "contents": "AAAAAAAAAAAAAA=="
1015 }
1016 },
1017 "owner": "immutable",
1018 "previous_transaction":
1019 "11111111111111111111111111111111",
1020 "storage_rebate": 0,
1021 });
1022 let json_err = serde_json::from_value::<Object>(json).unwrap_err();
1023 assert!(
1024 json_err.to_string().contains("MoveStruct contents"),
1025 "unexpected JSON error: {json_err}"
1026 );
1027 }
1028
1029 #[test]
1035 fn non_canonical_move_struct_type_is_rejected() {
1036 use crate::StructTag;
1037 use crate::TypeTag;
1038
1039 fn object_bytes_with_type(type_bytes: &[u8]) -> Vec<u8> {
1045 let mut bytes = Vec::new();
1046 bytes.push(0x00); bytes.extend_from_slice(type_bytes);
1048 bytes.push(0x00); bytes.extend_from_slice(&[0u8; 8]); bytes.push(0x20); bytes.extend_from_slice(&[0u8; 32]); bytes.push(0x03); bytes.push(0x20); bytes.extend_from_slice(&[0u8; 32]); bytes.extend_from_slice(&[0u8; 8]); bytes
1057 }
1058
1059 let cases: Vec<(&str, Vec<u8>)> = vec![
1065 (
1066 "Other(GasCoin tag)",
1067 [&[0u8][..], &bcs::to_bytes(&StructTag::gas_coin()).unwrap()].concat(),
1068 ),
1069 (
1070 "Other(StakedSui tag)",
1071 [
1072 &[0u8][..],
1073 &bcs::to_bytes(&StructTag::staked_sui()).unwrap(),
1074 ]
1075 .concat(),
1076 ),
1077 (
1078 "Other(Coin<non-SUI> tag)",
1079 [
1080 &[0u8][..],
1081 &bcs::to_bytes(&StructTag::coin(TypeTag::U64)).unwrap(),
1082 ]
1083 .concat(),
1084 ),
1085 (
1086 "Other(SuiBalanceAccumulatorField tag)",
1087 [
1088 &[0u8][..],
1089 &bcs::to_bytes(&StructTag::balance_accumulator_field(
1090 StructTag::sui().into(),
1091 ))
1092 .unwrap(),
1093 ]
1094 .concat(),
1095 ),
1096 (
1097 "Coin(SUI's TypeTag)",
1098 [
1099 &[3u8][..],
1100 &bcs::to_bytes(&TypeTag::from(StructTag::sui())).unwrap(),
1101 ]
1102 .concat(),
1103 ),
1104 (
1105 "BalanceAccumulatorField(SUI's TypeTag)",
1106 [
1107 &[5u8][..],
1108 &bcs::to_bytes(&TypeTag::from(StructTag::sui())).unwrap(),
1109 ]
1110 .concat(),
1111 ),
1112 ];
1113
1114 for (label, type_bytes) in cases {
1115 let bytes = object_bytes_with_type(&type_bytes);
1116 let err = bcs::from_bytes::<Object>(&bytes).unwrap_err();
1117 assert!(
1118 err.to_string().contains("non-canonical MoveStructType"),
1119 "{label}: unexpected error: {err}"
1120 );
1121 }
1122 }
1123 }
1124}