1use std::{
6 collections::BTreeMap,
7 fmt::{Debug, Display, Formatter},
8 mem::size_of,
9 sync::Arc,
10};
11
12use iota_protocol_config::ProtocolConfig;
13pub use iota_sdk_types::Object as ObjectInner;
14use iota_sdk_types::{
15 Address, MoveObjectType, MoveStruct, ObjectData, ObjectId, ObjectReference, Owner, StructTag,
16 TransactionDigest, TypeTag, Version, move_package::MovePackage,
17};
18use move_binary_format::CompiledModule;
19use move_bytecode_utils::{layout::TypeLayoutBuilder, module_cache::GetModule};
20use move_core_types::annotated_value::{self, MoveStructLayout, MoveTypeLayout, MoveValue};
21use serde::{Deserialize, Serialize};
22
23use self::{balance_traversal::BalanceTraversal, bounded_visitor::BoundedVisitor};
24use crate::{
25 balance::Balance,
26 coin::{Coin, CoinMetadata, TreasuryCap},
27 crypto::deterministic_random_account_key,
28 error::{
29 ExecutionError, ExecutionErrorKind, IotaError, IotaResult, UserInputError, UserInputResult,
30 },
31 gas_coin::{GAS, GasCoin},
32 iota_sdk_types_conversions::type_tag_sdk_to_core,
33 layout_resolver::LayoutResolver,
34 move_package::MovePackageExt,
35 timelock::timelock::TimeLock,
36};
37
38mod balance_traversal;
39pub mod bounded_visitor;
40pub mod option_visitor;
41
42pub const GAS_VALUE_FOR_TESTING: u64 = 300_000_000_000_000;
43pub const OBJECT_START_VERSION: Version = Version::from_u64(1);
44
45pub const ID_END_INDEX: usize = ObjectId::LENGTH;
47
48mod move_struct_ext {
49 pub trait Sealed {}
50 impl Sealed for super::MoveStruct {}
51}
52
53pub trait MoveStructExt: Sized + move_struct_ext::Sealed {
54 fn new_from_execution(
55 tag: StructTag,
56 version: Version,
57 contents: Vec<u8>,
58 protocol_config: &ProtocolConfig,
59 ) -> Result<Self, ExecutionError>;
60 fn new_from_execution_with_limit(
61 tag: StructTag,
62 version: Version,
63 contents: Vec<u8>,
64 max_move_object_size: u64,
65 ) -> Result<Self, ExecutionError>;
66 fn new_gas_coin(version: Version, id: ObjectId, value: u64) -> Self;
67 fn new_coin(coin_type: TypeTag, version: Version, id: ObjectId, value: u64) -> Self;
68 fn get_coin_value_unchecked(&self) -> u64;
69 fn set_coin_value_unchecked(&mut self, value: u64);
70 fn set_clock_timestamp_ms_unchecked(&mut self, timestamp_ms: u64);
71 fn update_contents(
72 &mut self,
73 new_contents: Vec<u8>,
74 protocol_config: &ProtocolConfig,
75 ) -> Result<(), ExecutionError>;
76 fn update_contents_with_limit(
77 &mut self,
78 new_contents: Vec<u8>,
79 max_move_object_size: u64,
80 ) -> Result<(), ExecutionError>;
81 fn increment_version_to(&mut self, next: Version);
82 fn decrement_version_to(&mut self, prev: Version);
83 fn get_layout(&self, resolver: &impl GetModule) -> Result<MoveStructLayout, IotaError>;
84 fn get_struct_layout_from_struct_tag(
85 struct_tag: StructTag,
86 resolver: &impl GetModule,
87 ) -> Result<MoveStructLayout, IotaError>;
88 fn to_move_struct(
89 &self,
90 layout: &MoveStructLayout,
91 ) -> Result<annotated_value::MoveStruct, IotaError>;
92 fn object_size_for_gas_metering(&self) -> usize;
93 fn get_total_iota(&self, layout_resolver: &mut dyn LayoutResolver) -> Result<u64, IotaError>;
94 fn get_coin_balances(
95 &self,
96 layout_resolver: &mut dyn LayoutResolver,
97 ) -> Result<BTreeMap<TypeTag, u64>, IotaError>;
98}
99
100impl MoveStructExt for MoveStruct {
101 fn new_from_execution(
104 tag: StructTag,
105 version: Version,
106 contents: Vec<u8>,
107 protocol_config: &ProtocolConfig,
108 ) -> Result<Self, ExecutionError> {
109 Self::new_from_execution_with_limit(
110 tag,
111 version,
112 contents,
113 protocol_config.max_move_object_size(),
114 )
115 }
116
117 fn new_from_execution_with_limit(
120 tag: StructTag,
121 version: Version,
122 contents: Vec<u8>,
123 max_move_object_size: u64,
124 ) -> Result<Self, ExecutionError> {
125 if contents.len() as u64 > max_move_object_size {
126 return Err(ExecutionError::from_kind(
127 ExecutionErrorKind::ObjectTooBig {
128 object_size: contents.len() as u64,
129 max_object_size: max_move_object_size,
130 },
131 ));
132 }
133 Self::new(tag.into(), version, contents).map_err(ExecutionError::invariant_violation)
134 }
135
136 fn new_gas_coin(version: Version, id: ObjectId, value: u64) -> Self {
137 Self::new_from_execution_with_limit(
140 StructTag::new_gas_coin(),
141 version,
142 GasCoin::new(id, value).to_bcs_bytes(),
143 256,
144 )
145 .unwrap()
146 }
147
148 fn new_coin(coin_type: TypeTag, version: Version, id: ObjectId, value: u64) -> Self {
149 Self::new_from_execution_with_limit(
152 StructTag::new_coin(coin_type),
153 version,
154 Coin::new(id, value).to_bcs_bytes(),
155 256,
156 )
157 .unwrap()
158 }
159
160 fn get_coin_value_unchecked(&self) -> u64 {
165 debug_assert!(self.object_type().is_coin());
166 debug_assert!(self.contents().len() == 40);
168
169 u64::from_le_bytes(<[u8; 8]>::try_from(&self.contents()[ID_END_INDEX..]).unwrap())
171 }
172
173 fn set_coin_value_unchecked(&mut self, value: u64) {
179 debug_assert!(self.object_type().is_coin());
180 debug_assert!(self.contents().len() == 40);
182
183 let mut new_contents = self.contents().to_vec();
184 new_contents[ID_END_INDEX..].copy_from_slice(&value.to_le_bytes());
185 self.set_contents(new_contents).unwrap();
186 }
187
188 fn set_clock_timestamp_ms_unchecked(&mut self, timestamp_ms: u64) {
194 debug_assert!(self.struct_tag().is_clock());
195 debug_assert!(self.contents().len() == 40);
197
198 let mut new_contents = self.contents().to_vec();
199 new_contents[ID_END_INDEX..].copy_from_slice(×tamp_ms.to_le_bytes());
200 self.set_contents(new_contents).unwrap();
201 }
202
203 fn update_contents(
205 &mut self,
206 new_contents: Vec<u8>,
207 protocol_config: &ProtocolConfig,
208 ) -> Result<(), ExecutionError> {
209 self.update_contents_with_limit(new_contents, protocol_config.max_move_object_size())
210 }
211
212 fn update_contents_with_limit(
213 &mut self,
214 new_contents: Vec<u8>,
215 max_move_object_size: u64,
216 ) -> Result<(), ExecutionError> {
217 if new_contents.len() as u64 > max_move_object_size {
218 return Err(ExecutionError::from_kind(
219 ExecutionErrorKind::ObjectTooBig {
220 object_size: new_contents.len() as u64,
221 max_object_size: max_move_object_size,
222 },
223 ));
224 }
225
226 #[cfg(debug_assertions)]
227 let old_id = self.id();
228
229 self.set_contents(new_contents)
230 .map_err(ExecutionError::invariant_violation)?;
231
232 #[cfg(debug_assertions)]
234 debug_assert_eq!(self.id(), old_id);
235
236 Ok(())
237 }
238
239 fn increment_version_to(&mut self, next: Version) {
242 debug_assert!(
243 self.version() < next,
244 "Not an increment: {} to {next}",
245 self.version()
246 );
247 self.set_version(next);
248 }
249
250 fn decrement_version_to(&mut self, prev: Version) {
252 debug_assert!(
253 prev < self.version(),
254 "Not a decrement: {} to {prev}",
255 self.version()
256 );
257 self.set_version(prev);
258 }
259
260 fn get_layout(&self, resolver: &impl GetModule) -> Result<MoveStructLayout, IotaError> {
266 Self::get_struct_layout_from_struct_tag(self.struct_tag().clone(), resolver)
267 }
268
269 fn get_struct_layout_from_struct_tag(
270 struct_tag: StructTag,
271 resolver: &impl GetModule,
272 ) -> Result<MoveStructLayout, IotaError> {
273 let type_ = TypeTag::Struct(Box::new(struct_tag));
274 let layout = TypeLayoutBuilder::build_with_types(&type_tag_sdk_to_core(&type_), resolver)
275 .map_err(|e| IotaError::ObjectSerialization {
276 error: e.to_string(),
277 })?;
278 match layout {
279 MoveTypeLayout::Struct(l) => Ok(*l),
280 _ => unreachable!(
281 "We called build_with_types on Struct type, should get a struct layout"
282 ),
283 }
284 }
285
286 fn to_move_struct(
288 &self,
289 layout: &MoveStructLayout,
290 ) -> Result<annotated_value::MoveStruct, IotaError> {
291 BoundedVisitor::deserialize_struct(self.contents(), layout).map_err(|e| {
292 IotaError::ObjectSerialization {
293 error: e.to_string(),
294 }
295 })
296 }
297
298 fn object_size_for_gas_metering(&self) -> usize {
303 let serialized_type_tag_size =
304 bcs::serialized_size(self.object_type()).expect("Serializing type tag should not fail");
305 self.contents().len() + serialized_type_tag_size + 8
307 }
308
309 fn get_total_iota(&self, layout_resolver: &mut dyn LayoutResolver) -> Result<u64, IotaError> {
312 let balances = self.get_coin_balances(layout_resolver)?;
313 Ok(balances.get(&GAS::type_tag()).copied().unwrap_or(0))
314 }
315
316 fn get_coin_balances(
318 &self,
319 layout_resolver: &mut dyn LayoutResolver,
320 ) -> Result<BTreeMap<TypeTag, u64>, IotaError> {
321 if let Some(type_tag) = self.object_type().coin_type_opt() {
323 let balance = self.get_coin_value_unchecked();
324 Ok(if balance > 0 {
325 BTreeMap::from([(type_tag.clone(), balance)])
326 } else {
327 BTreeMap::default()
328 })
329 } else {
330 let layout = layout_resolver.get_annotated_layout(self.struct_tag())?;
331
332 let mut traversal = BalanceTraversal::default();
333 MoveValue::visit_deserialize(self.contents(), &layout.into_layout(), &mut traversal)
334 .map_err(|e| IotaError::ObjectSerialization {
335 error: e.to_string(),
336 })?;
337
338 Ok(traversal.finish())
339 }
340 }
341}
342
343#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash)]
344#[serde(from = "ObjectInner")]
345pub struct Object(Arc<ObjectInner>);
346
347impl From<ObjectInner> for Object {
348 fn from(inner: ObjectInner) -> Self {
349 Self(Arc::new(inner))
350 }
351}
352
353impl Object {
354 pub fn into_inner(self) -> ObjectInner {
355 match Arc::try_unwrap(self.0) {
356 Ok(inner) => inner,
357 Err(inner_arc) => (*inner_arc).clone(),
358 }
359 }
360
361 pub fn as_inner(&self) -> &ObjectInner {
362 &self.0
363 }
364
365 pub fn new_from_genesis(
366 data: ObjectData,
367 owner: Owner,
368 previous_transaction: TransactionDigest,
369 ) -> Self {
370 ObjectInner {
371 data,
372 owner,
373 previous_transaction,
374 storage_rebate: 0,
375 }
376 .into()
377 }
378
379 pub fn new_move(o: MoveStruct, owner: Owner, previous_transaction: TransactionDigest) -> Self {
381 ObjectInner {
382 data: ObjectData::Struct(o),
383 owner,
384 previous_transaction,
385 storage_rebate: 0,
386 }
387 .into()
388 }
389
390 pub fn new_package_from_data(
391 data: ObjectData,
392 previous_transaction: TransactionDigest,
393 ) -> Self {
394 ObjectInner {
395 data,
396 owner: Owner::Immutable,
397 previous_transaction,
398 storage_rebate: 0,
399 }
400 .into()
401 }
402
403 pub fn new_from_package(package: MovePackage, previous_transaction: TransactionDigest) -> Self {
405 Self::new_package_from_data(ObjectData::Package(package), previous_transaction)
406 }
407
408 pub fn new_package<'p>(
409 modules: &[CompiledModule],
410 previous_transaction: TransactionDigest,
411 protocol_config: &ProtocolConfig,
412 dependencies: impl IntoIterator<Item = &'p MovePackage>,
413 ) -> Result<Self, ExecutionError> {
414 Ok(Self::new_package_from_data(
415 ObjectData::Package(MovePackage::new_initial(
416 modules,
417 protocol_config,
418 dependencies,
419 )?),
420 previous_transaction,
421 ))
422 }
423
424 pub fn new_upgraded_package<'p>(
425 previous_package: &MovePackage,
426 new_package_id: ObjectId,
427 modules: &[CompiledModule],
428 previous_transaction: TransactionDigest,
429 protocol_config: &ProtocolConfig,
430 dependencies: impl IntoIterator<Item = &'p MovePackage>,
431 ) -> Result<Self, ExecutionError> {
432 Ok(Self::new_package_from_data(
433 ObjectData::Package(previous_package.new_upgraded(
434 new_package_id,
435 modules,
436 protocol_config,
437 dependencies,
438 )?),
439 previous_transaction,
440 ))
441 }
442
443 pub fn new_package_for_testing(
444 modules: &[CompiledModule],
445 previous_transaction: TransactionDigest,
446 dependencies: impl IntoIterator<Item = MovePackage>,
447 ) -> Result<Self, ExecutionError> {
448 let dependencies: Vec<_> = dependencies.into_iter().collect();
449 let config = ProtocolConfig::get_for_max_version_UNSAFE();
450 Self::new_package(modules, previous_transaction, &config, &dependencies)
451 }
452
453 pub fn new_system_package(
456 modules: &[CompiledModule],
457 version: Version,
458 dependencies: Vec<ObjectId>,
459 previous_transaction: TransactionDigest,
460 ) -> Self {
461 let ret = Self::new_package_from_data(
462 ObjectData::Package(MovePackage::new_system(version, modules, dependencies)),
463 previous_transaction,
464 );
465
466 #[cfg(not(msim))]
467 assert!(ret.is_system_package());
468
469 ret
470 }
471}
472
473impl std::ops::Deref for Object {
474 type Target = ObjectInner;
475 fn deref(&self) -> &Self::Target {
476 &self.0
477 }
478}
479
480impl std::ops::DerefMut for Object {
481 fn deref_mut(&mut self) -> &mut Self::Target {
482 Arc::make_mut(&mut self.0)
483 }
484}
485
486impl Object {
487 pub fn type_(&self) -> Option<&MoveObjectType> {
488 self.data.opt_object_type()
489 }
490
491 pub fn is_coin(&self) -> bool {
492 if let Some(move_object) = self.data.as_opt_struct() {
493 move_object.struct_tag().is_coin()
494 } else {
495 false
496 }
497 }
498
499 pub fn as_coin_maybe(&self) -> Option<Coin> {
502 if let Some(move_object) = self.data.as_opt_struct() {
503 let coin: Coin = bcs::from_bytes(move_object.contents()).ok()?;
504 Some(coin)
505 } else {
506 None
507 }
508 }
509
510 pub fn as_timelock_balance_maybe(&self) -> Option<TimeLock<Balance>> {
511 if let Some(move_object) = self.data.as_opt_struct() {
512 Some(TimeLock::from_bcs_bytes(move_object.contents()).ok()?)
513 } else {
514 None
515 }
516 }
517
518 pub fn get_coin_value_unchecked(&self) -> u64 {
523 self.data
524 .as_opt_struct()
525 .unwrap()
526 .get_coin_value_unchecked()
527 }
528
529 pub fn object_size_for_gas_metering(&self) -> usize {
534 let meta_data_size = size_of::<Owner>() + size_of::<TransactionDigest>() + size_of::<u64>();
535 let data_size = match &self.data {
536 ObjectData::Struct(m) => m.object_size_for_gas_metering(),
537 ObjectData::Package(p) => p.size(),
538 };
539 meta_data_size + data_size
540 }
541
542 pub fn get_layout(
548 &self,
549 resolver: &impl GetModule,
550 ) -> Result<Option<MoveStructLayout>, IotaError> {
551 match &self.data {
552 ObjectData::Struct(m) => Ok(Some(m.get_layout(resolver)?)),
553 ObjectData::Package(_) => Ok(None),
554 }
555 }
556
557 pub fn get_move_template_type(&self) -> IotaResult<TypeTag> {
561 let move_struct = self.data.opt_struct_tag().ok_or_else(|| IotaError::Type {
562 error: "Object must be a Move object".to_owned(),
563 })?;
564 fp_ensure!(
565 move_struct.type_params().len() == 1,
566 IotaError::Type {
567 error: "Move object struct must have one type parameter".to_owned()
568 }
569 );
570 let type_tag = move_struct.type_params()[0].clone();
572 Ok(type_tag)
573 }
574}
575
576impl Object {
578 pub fn get_total_iota(
581 &self,
582 layout_resolver: &mut dyn LayoutResolver,
583 ) -> Result<u64, IotaError> {
584 Ok(self.storage_rebate
585 + match &self.data {
586 ObjectData::Struct(m) => m.get_total_iota(layout_resolver)?,
587 ObjectData::Package(_) => 0,
588 })
589 }
590
591 pub fn immutable_with_id_for_testing(id: ObjectId) -> Self {
592 let data = ObjectData::Struct(
593 MoveStruct::new(
594 StructTag::new_gas_coin().into(),
595 OBJECT_START_VERSION,
596 GasCoin::new(id, GAS_VALUE_FOR_TESTING).to_bcs_bytes(),
597 )
598 .unwrap(),
599 );
600 ObjectInner {
601 owner: Owner::Immutable,
602 data,
603 previous_transaction: TransactionDigest::GENESIS_MARKER,
604 storage_rebate: 0,
605 }
606 .into()
607 }
608
609 pub fn immutable_for_testing() -> Self {
610 thread_local! {
611 static IMMUTABLE_OBJECT_ID: ObjectId = ObjectId::random();
612 }
613
614 Self::immutable_with_id_for_testing(IMMUTABLE_OBJECT_ID.with(|id| *id))
615 }
616
617 pub fn shared_for_testing() -> Object {
619 let id = ObjectId::random();
620 let obj = MoveStruct::new_gas_coin(OBJECT_START_VERSION, id, 10);
621 let owner = Owner::Shared(obj.version());
622 Object::new_move(obj, owner, TransactionDigest::GENESIS_MARKER)
623 }
624
625 pub fn with_id_owner_gas_for_testing(id: ObjectId, owner: Address, gas: u64) -> Self {
626 let data = ObjectData::Struct(
627 MoveStruct::new(
628 StructTag::new_gas_coin().into(),
629 OBJECT_START_VERSION,
630 GasCoin::new(id, gas).to_bcs_bytes(),
631 )
632 .unwrap(),
633 );
634 ObjectInner {
635 owner: Owner::Address(owner),
636 data,
637 previous_transaction: TransactionDigest::GENESIS_MARKER,
638 storage_rebate: 0,
639 }
640 .into()
641 }
642
643 pub fn treasury_cap_for_testing(struct_tag: StructTag, treasury_cap: TreasuryCap) -> Self {
644 let data = ObjectData::Struct(
645 MoveStruct::new(
646 StructTag::new_treasury_cap(struct_tag).into(),
647 OBJECT_START_VERSION,
648 bcs::to_bytes(&treasury_cap).expect("Failed to serialize"),
649 )
650 .unwrap(),
651 );
652 ObjectInner {
653 owner: Owner::Immutable,
654 data,
655 previous_transaction: TransactionDigest::GENESIS_MARKER,
656 storage_rebate: 0,
657 }
658 .into()
659 }
660
661 pub fn coin_metadata_for_testing(struct_tag: StructTag, metadata: CoinMetadata) -> Self {
662 let data = ObjectData::Struct(
663 MoveStruct::new(
664 StructTag::new_coin_metadata(struct_tag).into(),
665 OBJECT_START_VERSION,
666 bcs::to_bytes(&metadata).expect("Failed to serialize"),
667 )
668 .unwrap(),
669 );
670 ObjectInner {
671 owner: Owner::Immutable,
672 data,
673 previous_transaction: TransactionDigest::GENESIS_MARKER,
674 storage_rebate: 0,
675 }
676 .into()
677 }
678
679 pub fn with_object_owner_for_testing(id: ObjectId, owner: ObjectId) -> Self {
680 let data = ObjectData::Struct(
681 MoveStruct::new(
682 StructTag::new_gas_coin().into(),
683 OBJECT_START_VERSION,
684 GasCoin::new(id, GAS_VALUE_FOR_TESTING).to_bcs_bytes(),
685 )
686 .unwrap(),
687 );
688 ObjectInner {
689 owner: Owner::Object(owner),
690 data,
691 previous_transaction: TransactionDigest::GENESIS_MARKER,
692 storage_rebate: 0,
693 }
694 .into()
695 }
696
697 pub fn with_id_owner_for_testing(id: ObjectId, owner: Address) -> Self {
698 Self::with_id_owner_gas_for_testing(id, owner, GAS_VALUE_FOR_TESTING)
700 }
701
702 pub fn with_id_owner_version_for_testing(id: ObjectId, version: Version, owner: Owner) -> Self {
703 let data = ObjectData::Struct(
704 MoveStruct::new(
705 StructTag::new_gas_coin().into(),
706 version,
707 GasCoin::new(id, GAS_VALUE_FOR_TESTING).to_bcs_bytes(),
708 )
709 .unwrap(),
710 );
711 ObjectInner {
712 owner,
713 data,
714 previous_transaction: TransactionDigest::GENESIS_MARKER,
715 storage_rebate: 0,
716 }
717 .into()
718 }
719
720 pub fn with_owner_for_testing(owner: Address) -> Self {
721 Self::with_id_owner_for_testing(ObjectId::random(), owner)
722 }
723
724 pub fn new_gas_with_balance_and_owner_for_testing(value: u64, owner: Address) -> Self {
727 let obj = MoveStruct::new_gas_coin(OBJECT_START_VERSION, ObjectId::random(), value);
728 Object::new_move(
729 obj,
730 Owner::Address(owner),
731 TransactionDigest::GENESIS_MARKER,
732 )
733 }
734
735 pub fn new_gas_for_testing() -> Self {
737 let gas_object_id = ObjectId::random();
738 let (owner, _) = deterministic_random_account_key();
739 Object::with_id_owner_for_testing(gas_object_id, owner)
740 }
741}
742
743pub fn generate_test_gas_objects() -> Vec<Object> {
745 thread_local! {
746 static GAS_OBJECTS: Vec<Object> = (0..50)
747 .map(|_| {
748 let gas_object_id = ObjectId::random();
749 let (owner, _) = deterministic_random_account_key();
750 Object::with_id_owner_for_testing(gas_object_id, owner)
751 })
752 .collect();
753 }
754
755 GAS_OBJECTS.with(|v| v.clone())
756}
757
758#[derive(Serialize, Deserialize, Debug)]
759#[serde(tag = "status", content = "details")]
760pub enum ObjectRead {
761 NotExists(ObjectId),
762 Exists(ObjectReference, Object, Option<MoveStructLayout>),
763 Deleted(ObjectReference),
764}
765
766impl ObjectRead {
767 pub fn into_object(self) -> UserInputResult<Object> {
770 match self {
771 Self::Deleted(oref) => Err(UserInputError::ObjectDeleted { object_ref: oref }),
772 Self::NotExists(id) => Err(UserInputError::ObjectNotFound {
773 object_id: id,
774 version: None,
775 }),
776 Self::Exists(_, o, _) => Ok(o),
777 }
778 }
779
780 pub fn object(&self) -> UserInputResult<&Object> {
781 match self {
782 Self::Deleted(oref) => Err(UserInputError::ObjectDeleted { object_ref: *oref }),
783 Self::NotExists(id) => Err(UserInputError::ObjectNotFound {
784 object_id: *id,
785 version: None,
786 }),
787 Self::Exists(_, o, _) => Ok(o),
788 }
789 }
790
791 pub fn object_id(&self) -> ObjectId {
792 match self {
793 Self::Deleted(oref) => oref.object_id,
794 Self::NotExists(id) => *id,
795 Self::Exists(oref, _, _) => oref.object_id,
796 }
797 }
798}
799
800impl Display for ObjectRead {
801 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
802 match self {
803 Self::Deleted(oref) => {
804 write!(f, "ObjectRead::Deleted ({oref:?})")
805 }
806 Self::NotExists(id) => {
807 write!(f, "ObjectRead::NotExists ({id})")
808 }
809 Self::Exists(oref, _, _) => {
810 write!(f, "ObjectRead::Exists ({oref:?})")
811 }
812 }
813 }
814}
815
816#[derive(Serialize, Deserialize, Debug)]
817#[serde(tag = "status", content = "details")]
818pub enum PastObjectRead {
819 ObjectNotExists(ObjectId),
821 ObjectDeleted(ObjectReference),
823 VersionFound(ObjectReference, Object, Option<MoveStructLayout>),
825 VersionNotFound(ObjectId, Version),
827 VersionTooHigh {
829 object_id: ObjectId,
830 asked_version: Version,
831 latest_version: Version,
832 },
833}
834
835impl PastObjectRead {
836 pub fn into_object(self) -> UserInputResult<Object> {
838 match self {
839 Self::ObjectDeleted(oref) => Err(UserInputError::ObjectDeleted { object_ref: oref }),
840 Self::ObjectNotExists(id) => Err(UserInputError::ObjectNotFound {
841 object_id: id,
842 version: None,
843 }),
844 Self::VersionFound(_, o, _) => Ok(o),
845 Self::VersionNotFound(object_id, version) => Err(UserInputError::ObjectNotFound {
846 object_id,
847 version: Some(version),
848 }),
849 Self::VersionTooHigh {
850 object_id,
851 asked_version,
852 latest_version,
853 } => Err(UserInputError::ObjectSequenceNumberTooHigh {
854 object_id,
855 asked_version,
856 latest_version,
857 }),
858 }
859 }
860}
861
862impl Display for PastObjectRead {
863 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
864 match self {
865 Self::ObjectDeleted(oref) => {
866 write!(f, "PastObjectRead::ObjectDeleted ({oref:?})")
867 }
868 Self::ObjectNotExists(id) => {
869 write!(f, "PastObjectRead::ObjectNotExists ({id})")
870 }
871 Self::VersionFound(oref, _, _) => {
872 write!(f, "PastObjectRead::VersionFound ({oref:?})")
873 }
874 Self::VersionNotFound(object_id, version) => {
875 write!(
876 f,
877 "PastObjectRead::VersionNotFound ({object_id}, asked version {version:?})"
878 )
879 }
880 Self::VersionTooHigh {
881 object_id,
882 asked_version,
883 latest_version,
884 } => {
885 write!(
886 f,
887 "PastObjectRead::VersionTooHigh ({object_id}, asked version {asked_version:?}, latest version {latest_version:?})"
888 )
889 }
890 }
891 }
892}
893
894#[cfg(test)]
895mod tests {
896 use iota_sdk_types::{Address, ObjectId, TransactionDigest};
897
898 use crate::{
899 gas_coin::GasCoin,
900 object::{MoveStructExt, OBJECT_START_VERSION, Object, Owner},
901 };
902
903 #[test]
906 fn test_object_digest_and_serialized_format() {
907 let g =
908 GasCoin::new_for_testing_with_id(ObjectId::ZERO, 123).to_object(OBJECT_START_VERSION);
909 let o = Object::new_move(g, Owner::Address(Address::ZERO), TransactionDigest::ZERO);
910 let bytes = bcs::to_bytes(&o).unwrap();
911
912 assert_eq!(
913 bytes,
914 [
915 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
916 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
917 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,
918 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
919 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
920 ]
921 );
922 let objref = o.object_ref();
923
924 assert_eq!(objref.object_id, ObjectId::ZERO);
925 assert_eq!(objref.version, 1);
926 assert_eq!(
927 objref.digest.to_string(),
928 "Ba4YyVBcpc9jgX4PMLRoyt9dKLftYVSDvuKbtMr9f4NM"
929 );
930 }
931
932 #[test]
933 fn test_get_coin_value_unchecked() {
934 fn test_for_value(v: u64) {
935 let g = GasCoin::new_for_testing(v).to_object(OBJECT_START_VERSION);
936 assert_eq!(g.get_coin_value_unchecked(), v);
937 assert_eq!(GasCoin::try_from(&g).unwrap().value(), v);
938 }
939
940 test_for_value(0);
941 test_for_value(1);
942 test_for_value(8);
943 test_for_value(9);
944 test_for_value(u8::MAX as u64);
945 test_for_value(u8::MAX as u64 + 1);
946 test_for_value(u16::MAX as u64);
947 test_for_value(u16::MAX as u64 + 1);
948 test_for_value(u32::MAX as u64);
949 test_for_value(u32::MAX as u64 + 1);
950 test_for_value(u64::MAX);
951 }
952
953 #[test]
954 fn test_set_coin_value_unchecked() {
955 fn test_for_value(v: u64) {
956 let mut g = GasCoin::new_for_testing(u64::MAX).to_object(OBJECT_START_VERSION);
957 g.set_coin_value_unchecked(v);
958 assert_eq!(g.get_coin_value_unchecked(), v);
959 assert_eq!(GasCoin::try_from(&g).unwrap().value(), v);
960 assert_eq!(g.version(), OBJECT_START_VERSION);
961 assert_eq!(g.contents().len(), 40);
962 }
963
964 test_for_value(0);
965 test_for_value(1);
966 test_for_value(8);
967 test_for_value(9);
968 test_for_value(u8::MAX as u64);
969 test_for_value(u8::MAX as u64 + 1);
970 test_for_value(u16::MAX as u64);
971 test_for_value(u16::MAX as u64 + 1);
972 test_for_value(u32::MAX as u64);
973 test_for_value(u32::MAX as u64 + 1);
974 test_for_value(u64::MAX);
975 }
976}