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, MovePackage, MoveStruct, ObjectData, ObjectId, ObjectReference, Owner, StructTag,
17 TransactionDigest, TypeTag, Version,
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 _ => unimplemented!("a new ObjectData enum variant was added and needs to be handled"),
551 };
552 meta_data_size + data_size
553 }
554
555 pub fn get_layout(
561 &self,
562 resolver: &impl GetModule,
563 ) -> Result<Option<MoveStructLayout>, IotaError> {
564 match &self.data {
565 ObjectData::Struct(m) => Ok(Some(m.get_layout(resolver)?)),
566 ObjectData::Package(_) => Ok(None),
567 _ => unimplemented!("a new ObjectData enum variant was added and needs to be handled"),
568 }
569 }
570
571 pub fn get_move_template_type(&self) -> IotaResult<TypeTag> {
575 let move_struct = self.data.opt_struct_tag().ok_or_else(|| IotaError::Type {
576 error: "Object must be a Move object".to_owned(),
577 })?;
578 fp_ensure!(
579 move_struct.type_params().len() == 1,
580 IotaError::Type {
581 error: "Move object struct must have one type parameter".to_owned()
582 }
583 );
584 let type_tag = move_struct.type_params()[0].clone();
586 Ok(type_tag)
587 }
588}
589
590impl Object {
592 pub fn get_total_iota(
595 &self,
596 layout_resolver: &mut dyn LayoutResolver,
597 ) -> Result<u64, IotaError> {
598 Ok(self.storage_rebate
599 + match &self.data {
600 ObjectData::Struct(m) => m.get_total_iota(layout_resolver)?,
601 ObjectData::Package(_) => 0,
602 _ => unimplemented!(
603 "a new ObjectData enum variant was added and needs to be handled"
604 ),
605 })
606 }
607
608 pub fn immutable_with_id_for_testing(id: ObjectId) -> Self {
609 let data = ObjectData::Struct(
610 MoveStruct::new(
611 StructTag::new_gas_coin().into(),
612 OBJECT_START_VERSION,
613 GasCoin::new(id, GAS_VALUE_FOR_TESTING).to_bcs_bytes(),
614 )
615 .unwrap(),
616 );
617 ObjectInner {
618 owner: Owner::Immutable,
619 data,
620 previous_transaction: TransactionDigest::GENESIS_MARKER,
621 storage_rebate: 0,
622 }
623 .into()
624 }
625
626 pub fn immutable_for_testing() -> Self {
627 thread_local! {
628 static IMMUTABLE_OBJECT_ID: ObjectId = ObjectId::random();
629 }
630
631 Self::immutable_with_id_for_testing(IMMUTABLE_OBJECT_ID.with(|id| *id))
632 }
633
634 pub fn shared_for_testing() -> Object {
636 let id = ObjectId::random();
637 let move_struct = MoveStruct::new_gas_coin(OBJECT_START_VERSION, id, 10);
638 let owner = Owner::Shared(move_struct.version());
639 Object::new_move(move_struct, owner, TransactionDigest::GENESIS_MARKER)
640 }
641
642 pub fn with_id_owner_gas_for_testing(id: ObjectId, owner: Address, gas: u64) -> Self {
643 let data = ObjectData::Struct(
644 MoveStruct::new(
645 StructTag::new_gas_coin().into(),
646 OBJECT_START_VERSION,
647 GasCoin::new(id, gas).to_bcs_bytes(),
648 )
649 .unwrap(),
650 );
651 ObjectInner {
652 owner: Owner::Address(owner),
653 data,
654 previous_transaction: TransactionDigest::GENESIS_MARKER,
655 storage_rebate: 0,
656 }
657 .into()
658 }
659
660 pub fn treasury_cap_for_testing(struct_tag: StructTag, treasury_cap: TreasuryCap) -> Self {
661 let data = ObjectData::Struct(
662 MoveStruct::new(
663 StructTag::new_treasury_cap(struct_tag).into(),
664 OBJECT_START_VERSION,
665 bcs::to_bytes(&treasury_cap).expect("Failed to serialize"),
666 )
667 .unwrap(),
668 );
669 ObjectInner {
670 owner: Owner::Immutable,
671 data,
672 previous_transaction: TransactionDigest::GENESIS_MARKER,
673 storage_rebate: 0,
674 }
675 .into()
676 }
677
678 pub fn coin_metadata_for_testing(struct_tag: StructTag, metadata: CoinMetadata) -> Self {
679 let data = ObjectData::Struct(
680 MoveStruct::new(
681 StructTag::new_coin_metadata(struct_tag).into(),
682 OBJECT_START_VERSION,
683 bcs::to_bytes(&metadata).expect("Failed to serialize"),
684 )
685 .unwrap(),
686 );
687 ObjectInner {
688 owner: Owner::Immutable,
689 data,
690 previous_transaction: TransactionDigest::GENESIS_MARKER,
691 storage_rebate: 0,
692 }
693 .into()
694 }
695
696 pub fn with_object_owner_for_testing(id: ObjectId, owner: ObjectId) -> Self {
697 let data = ObjectData::Struct(
698 MoveStruct::new(
699 StructTag::new_gas_coin().into(),
700 OBJECT_START_VERSION,
701 GasCoin::new(id, GAS_VALUE_FOR_TESTING).to_bcs_bytes(),
702 )
703 .unwrap(),
704 );
705 ObjectInner {
706 owner: Owner::Object(owner),
707 data,
708 previous_transaction: TransactionDigest::GENESIS_MARKER,
709 storage_rebate: 0,
710 }
711 .into()
712 }
713
714 pub fn with_id_owner_for_testing(id: ObjectId, owner: Address) -> Self {
715 Self::with_id_owner_gas_for_testing(id, owner, GAS_VALUE_FOR_TESTING)
717 }
718
719 pub fn with_id_owner_version_for_testing(id: ObjectId, version: Version, owner: Owner) -> Self {
720 let data = ObjectData::Struct(
721 MoveStruct::new(
722 StructTag::new_gas_coin().into(),
723 version,
724 GasCoin::new(id, GAS_VALUE_FOR_TESTING).to_bcs_bytes(),
725 )
726 .unwrap(),
727 );
728 ObjectInner {
729 owner,
730 data,
731 previous_transaction: TransactionDigest::GENESIS_MARKER,
732 storage_rebate: 0,
733 }
734 .into()
735 }
736
737 pub fn with_owner_for_testing(owner: Address) -> Self {
738 Self::with_id_owner_for_testing(ObjectId::random(), owner)
739 }
740
741 pub fn new_gas_with_balance_and_owner_for_testing(value: u64, owner: Address) -> Self {
744 let move_struct = MoveStruct::new_gas_coin(OBJECT_START_VERSION, ObjectId::random(), value);
745 Object::new_move(
746 move_struct,
747 Owner::Address(owner),
748 TransactionDigest::GENESIS_MARKER,
749 )
750 }
751
752 pub fn new_gas_for_testing() -> Self {
754 let gas_object_id = ObjectId::random();
755 let (owner, _) = deterministic_random_account_private_key();
756 Object::with_id_owner_for_testing(gas_object_id, owner)
757 }
758}
759
760pub fn generate_test_gas_objects() -> Vec<Object> {
762 thread_local! {
763 static GAS_OBJECTS: Vec<Object> = (0..50)
764 .map(|_| {
765 let gas_object_id = ObjectId::random();
766 let (owner, _) = deterministic_random_account_private_key();
767 Object::with_id_owner_for_testing(gas_object_id, owner)
768 })
769 .collect();
770 }
771
772 GAS_OBJECTS.with(|v| v.clone())
773}
774
775#[derive(Serialize, Deserialize, Debug)]
776#[serde(tag = "status", content = "details")]
777pub enum ObjectRead {
778 NotExists(ObjectId),
779 Exists(ObjectReference, Object, Option<MoveStructLayout>),
780 Deleted(ObjectReference),
781}
782
783impl ObjectRead {
784 pub fn into_object(self) -> UserInputResult<Object> {
787 match self {
788 Self::Deleted(oref) => Err(UserInputError::ObjectDeleted { object_ref: oref }),
789 Self::NotExists(id) => Err(UserInputError::ObjectNotFound {
790 object_id: id,
791 version: None,
792 }),
793 Self::Exists(_, o, _) => Ok(o),
794 }
795 }
796
797 pub fn object(&self) -> UserInputResult<&Object> {
798 match self {
799 Self::Deleted(oref) => Err(UserInputError::ObjectDeleted { object_ref: *oref }),
800 Self::NotExists(id) => Err(UserInputError::ObjectNotFound {
801 object_id: *id,
802 version: None,
803 }),
804 Self::Exists(_, o, _) => Ok(o),
805 }
806 }
807
808 pub fn object_id(&self) -> ObjectId {
809 match self {
810 Self::Deleted(oref) => oref.object_id,
811 Self::NotExists(id) => *id,
812 Self::Exists(oref, _, _) => oref.object_id,
813 }
814 }
815}
816
817impl Display for ObjectRead {
818 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
819 match self {
820 Self::Deleted(oref) => {
821 write!(f, "ObjectRead::Deleted ({oref:?})")
822 }
823 Self::NotExists(id) => {
824 write!(f, "ObjectRead::NotExists ({id})")
825 }
826 Self::Exists(oref, _, _) => {
827 write!(f, "ObjectRead::Exists ({oref:?})")
828 }
829 }
830 }
831}
832
833#[derive(Serialize, Deserialize, Debug)]
834#[serde(tag = "status", content = "details")]
835pub enum PastObjectRead {
836 ObjectNotExists(ObjectId),
838 ObjectDeleted(ObjectReference),
840 VersionFound(ObjectReference, Object, Option<MoveStructLayout>),
842 VersionNotFound(ObjectId, Version),
844 VersionTooHigh {
846 object_id: ObjectId,
847 asked_version: Version,
848 latest_version: Version,
849 },
850}
851
852impl PastObjectRead {
853 pub fn into_object(self) -> UserInputResult<Object> {
855 match self {
856 Self::ObjectDeleted(oref) => Err(UserInputError::ObjectDeleted { object_ref: oref }),
857 Self::ObjectNotExists(id) => Err(UserInputError::ObjectNotFound {
858 object_id: id,
859 version: None,
860 }),
861 Self::VersionFound(_, o, _) => Ok(o),
862 Self::VersionNotFound(object_id, version) => Err(UserInputError::ObjectNotFound {
863 object_id,
864 version: Some(version),
865 }),
866 Self::VersionTooHigh {
867 object_id,
868 asked_version,
869 latest_version,
870 } => Err(UserInputError::ObjectSequenceNumberTooHigh {
871 object_id,
872 asked_version,
873 latest_version,
874 }),
875 }
876 }
877}
878
879impl Display for PastObjectRead {
880 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
881 match self {
882 Self::ObjectDeleted(oref) => {
883 write!(f, "PastObjectRead::ObjectDeleted ({oref:?})")
884 }
885 Self::ObjectNotExists(id) => {
886 write!(f, "PastObjectRead::ObjectNotExists ({id})")
887 }
888 Self::VersionFound(oref, _, _) => {
889 write!(f, "PastObjectRead::VersionFound ({oref:?})")
890 }
891 Self::VersionNotFound(object_id, version) => {
892 write!(
893 f,
894 "PastObjectRead::VersionNotFound ({object_id}, asked version {version:?})"
895 )
896 }
897 Self::VersionTooHigh {
898 object_id,
899 asked_version,
900 latest_version,
901 } => {
902 write!(
903 f,
904 "PastObjectRead::VersionTooHigh ({object_id}, asked version {asked_version:?}, latest version {latest_version:?})"
905 )
906 }
907 }
908 }
909}
910
911#[cfg(test)]
912mod tests {
913 use iota_sdk_types::{Address, ObjectId, TransactionDigest};
914
915 use crate::{
916 gas_coin::GasCoin,
917 object::{MoveStructExt, OBJECT_START_VERSION, Object, Owner},
918 };
919
920 #[test]
923 fn test_object_digest_and_serialized_format() {
924 let g = GasCoin::new_for_testing_with_id(ObjectId::ZERO, 123)
925 .to_move_struct(OBJECT_START_VERSION);
926 let o = Object::new_move(g, Owner::Address(Address::ZERO), TransactionDigest::ZERO);
927 let bytes = bcs::to_bytes(&o).unwrap();
928
929 assert_eq!(
930 bytes,
931 [
932 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,
933 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,
934 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,
935 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,
936 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
937 ]
938 );
939 let objref = o.object_ref();
940
941 assert_eq!(objref.object_id, ObjectId::ZERO);
942 assert_eq!(objref.version, 1);
943 assert_eq!(
944 objref.digest.to_string(),
945 "Ba4YyVBcpc9jgX4PMLRoyt9dKLftYVSDvuKbtMr9f4NM"
946 );
947 }
948
949 #[test]
950 fn test_get_coin_value_unchecked() {
951 fn test_for_value(v: u64) {
952 let g = GasCoin::new_for_testing(v).to_move_struct(OBJECT_START_VERSION);
953 assert_eq!(g.get_coin_value_unchecked(), v);
954 assert_eq!(GasCoin::try_from(&g).unwrap().value(), v);
955 }
956
957 test_for_value(0);
958 test_for_value(1);
959 test_for_value(8);
960 test_for_value(9);
961 test_for_value(u8::MAX as u64);
962 test_for_value(u8::MAX as u64 + 1);
963 test_for_value(u16::MAX as u64);
964 test_for_value(u16::MAX as u64 + 1);
965 test_for_value(u32::MAX as u64);
966 test_for_value(u32::MAX as u64 + 1);
967 test_for_value(u64::MAX);
968 }
969
970 #[test]
971 fn test_set_coin_value_unchecked() {
972 fn test_for_value(v: u64) {
973 let mut g = GasCoin::new_for_testing(u64::MAX).to_move_struct(OBJECT_START_VERSION);
974 g.set_coin_value_unchecked(v);
975 assert_eq!(g.get_coin_value_unchecked(), v);
976 assert_eq!(GasCoin::try_from(&g).unwrap().value(), v);
977 assert_eq!(g.version(), OBJECT_START_VERSION);
978 assert_eq!(g.contents().len(), 40);
979 }
980
981 test_for_value(0);
982 test_for_value(1);
983 test_for_value(8);
984 test_for_value(9);
985 test_for_value(u8::MAX as u64);
986 test_for_value(u8::MAX as u64 + 1);
987 test_for_value(u16::MAX as u64);
988 test_for_value(u16::MAX as u64 + 1);
989 test_for_value(u32::MAX as u64);
990 test_for_value(u32::MAX as u64 + 1);
991 test_for_value(u64::MAX);
992 }
993}