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