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