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::{MoveStruct as MoveObject, ObjectData as Data};
14use iota_sdk_types::{ObjectId, Owner, StructTag, TypeTag, move_package::MovePackage};
15use move_binary_format::CompiledModule;
16use move_bytecode_utils::{layout::TypeLayoutBuilder, module_cache::GetModule};
17use move_core_types::annotated_value::{MoveStruct, MoveStructLayout, MoveTypeLayout, MoveValue};
18use serde::{Deserialize, Serialize};
19
20use self::{balance_traversal::BalanceTraversal, bounded_visitor::BoundedVisitor};
21use crate::{
22 balance::Balance,
23 base_types::{
24 IotaAddress, MoveObjectType, ObjectDigest, ObjectRef, SequenceNumber, TransactionDigest,
25 },
26 coin::{Coin, CoinMetadata, TreasuryCap},
27 crypto::{default_hash, 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: SequenceNumber = SequenceNumber::from_u64(1);
44
45pub const ID_END_INDEX: usize = ObjectId::LENGTH;
47
48mod move_object_ext {
49 pub trait Sealed {}
50 impl Sealed for super::MoveObject {}
51}
52
53pub trait MoveObjectExt: Sized + move_object_ext::Sealed {
54 fn new_from_execution(
55 tag: StructTag,
56 version: SequenceNumber,
57 contents: Vec<u8>,
58 protocol_config: &ProtocolConfig,
59 ) -> Result<Self, ExecutionError>;
60 fn new_from_execution_with_limit(
61 tag: StructTag,
62 version: SequenceNumber,
63 contents: Vec<u8>,
64 max_move_object_size: u64,
65 ) -> Result<Self, ExecutionError>;
66 fn new_gas_coin(version: SequenceNumber, id: ObjectId, value: u64) -> Self;
67 fn new_coin(coin_type: TypeTag, version: SequenceNumber, 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: SequenceNumber);
82 fn decrement_version_to(&mut self, prev: SequenceNumber);
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(&self, layout: &MoveStructLayout) -> Result<MoveStruct, IotaError>;
89 fn object_size_for_gas_metering(&self) -> usize;
90 fn get_total_iota(&self, layout_resolver: &mut dyn LayoutResolver) -> Result<u64, IotaError>;
91 fn get_coin_balances(
92 &self,
93 layout_resolver: &mut dyn LayoutResolver,
94 ) -> Result<BTreeMap<TypeTag, u64>, IotaError>;
95}
96
97impl MoveObjectExt for MoveObject {
98 fn new_from_execution(
101 tag: StructTag,
102 version: SequenceNumber,
103 contents: Vec<u8>,
104 protocol_config: &ProtocolConfig,
105 ) -> Result<Self, ExecutionError> {
106 Self::new_from_execution_with_limit(
107 tag,
108 version,
109 contents,
110 protocol_config.max_move_object_size(),
111 )
112 }
113
114 fn new_from_execution_with_limit(
117 tag: StructTag,
118 version: SequenceNumber,
119 contents: Vec<u8>,
120 max_move_object_size: u64,
121 ) -> Result<Self, ExecutionError> {
122 if contents.len() as u64 > max_move_object_size {
123 return Err(ExecutionError::from_kind(
124 ExecutionErrorKind::ObjectTooBig {
125 object_size: contents.len() as u64,
126 max_object_size: max_move_object_size,
127 },
128 ));
129 }
130 Self::new(tag.into(), version, contents).map_err(ExecutionError::invariant_violation)
131 }
132
133 fn new_gas_coin(version: SequenceNumber, id: ObjectId, value: u64) -> Self {
134 Self::new_from_execution_with_limit(
137 StructTag::new_gas_coin(),
138 version,
139 GasCoin::new(id, value).to_bcs_bytes(),
140 256,
141 )
142 .unwrap()
143 }
144
145 fn new_coin(coin_type: TypeTag, version: SequenceNumber, id: ObjectId, value: u64) -> Self {
146 Self::new_from_execution_with_limit(
149 StructTag::new_coin(coin_type),
150 version,
151 Coin::new(id, value).to_bcs_bytes(),
152 256,
153 )
154 .unwrap()
155 }
156
157 fn get_coin_value_unchecked(&self) -> u64 {
162 debug_assert!(self.object_type().is_coin());
163 debug_assert!(self.contents().len() == 40);
165
166 u64::from_le_bytes(<[u8; 8]>::try_from(&self.contents()[ID_END_INDEX..]).unwrap())
168 }
169
170 fn set_coin_value_unchecked(&mut self, value: u64) {
176 debug_assert!(self.object_type().is_coin());
177 debug_assert!(self.contents().len() == 40);
179
180 let mut new_contents = self.contents().to_vec();
181 new_contents[ID_END_INDEX..].copy_from_slice(&value.to_le_bytes());
182 self.set_contents(new_contents).unwrap();
183 }
184
185 fn set_clock_timestamp_ms_unchecked(&mut self, timestamp_ms: u64) {
191 debug_assert!(self.struct_tag().is_clock());
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(×tamp_ms.to_le_bytes());
197 self.set_contents(new_contents).unwrap();
198 }
199
200 fn update_contents(
202 &mut self,
203 new_contents: Vec<u8>,
204 protocol_config: &ProtocolConfig,
205 ) -> Result<(), ExecutionError> {
206 self.update_contents_with_limit(new_contents, protocol_config.max_move_object_size())
207 }
208
209 fn update_contents_with_limit(
210 &mut self,
211 new_contents: Vec<u8>,
212 max_move_object_size: u64,
213 ) -> Result<(), ExecutionError> {
214 if new_contents.len() as u64 > max_move_object_size {
215 return Err(ExecutionError::from_kind(
216 ExecutionErrorKind::ObjectTooBig {
217 object_size: new_contents.len() as u64,
218 max_object_size: max_move_object_size,
219 },
220 ));
221 }
222
223 #[cfg(debug_assertions)]
224 let old_id = self.id();
225
226 self.set_contents(new_contents)
227 .map_err(ExecutionError::invariant_violation)?;
228
229 #[cfg(debug_assertions)]
231 debug_assert_eq!(self.id(), old_id);
232
233 Ok(())
234 }
235
236 fn increment_version_to(&mut self, next: SequenceNumber) {
239 debug_assert!(
240 self.version() < next,
241 "Not an increment: {} to {next}",
242 self.version()
243 );
244 self.set_version(next);
245 }
246
247 fn decrement_version_to(&mut self, prev: SequenceNumber) {
249 debug_assert!(
250 prev < self.version(),
251 "Not a decrement: {} to {prev}",
252 self.version()
253 );
254 self.set_version(prev);
255 }
256
257 fn get_layout(&self, resolver: &impl GetModule) -> Result<MoveStructLayout, IotaError> {
263 Self::get_struct_layout_from_struct_tag(self.struct_tag().clone(), resolver)
264 }
265
266 fn get_struct_layout_from_struct_tag(
267 struct_tag: StructTag,
268 resolver: &impl GetModule,
269 ) -> Result<MoveStructLayout, IotaError> {
270 let type_ = TypeTag::Struct(Box::new(struct_tag));
271 let layout = TypeLayoutBuilder::build_with_types(&type_tag_sdk_to_core(&type_), resolver)
272 .map_err(|e| IotaError::ObjectSerialization {
273 error: e.to_string(),
274 })?;
275 match layout {
276 MoveTypeLayout::Struct(l) => Ok(*l),
277 _ => unreachable!(
278 "We called build_with_types on Struct type, should get a struct layout"
279 ),
280 }
281 }
282
283 fn to_move_struct(&self, layout: &MoveStructLayout) -> Result<MoveStruct, IotaError> {
285 BoundedVisitor::deserialize_struct(self.contents(), layout).map_err(|e| {
286 IotaError::ObjectSerialization {
287 error: e.to_string(),
288 }
289 })
290 }
291
292 fn object_size_for_gas_metering(&self) -> usize {
297 let serialized_type_tag_size =
298 bcs::serialized_size(self.object_type()).expect("Serializing type tag should not fail");
299 self.contents().len() + serialized_type_tag_size + 8
301 }
302
303 fn get_total_iota(&self, layout_resolver: &mut dyn LayoutResolver) -> Result<u64, IotaError> {
306 let balances = self.get_coin_balances(layout_resolver)?;
307 Ok(balances.get(&GAS::type_tag()).copied().unwrap_or(0))
308 }
309
310 fn get_coin_balances(
312 &self,
313 layout_resolver: &mut dyn LayoutResolver,
314 ) -> Result<BTreeMap<TypeTag, u64>, IotaError> {
315 if let Some(type_tag) = self.object_type().coin_type_opt() {
317 let balance = self.get_coin_value_unchecked();
318 Ok(if balance > 0 {
319 BTreeMap::from([(type_tag.clone(), balance)])
320 } else {
321 BTreeMap::default()
322 })
323 } else {
324 let layout = layout_resolver.get_annotated_layout(self.struct_tag())?;
325
326 let mut traversal = BalanceTraversal::default();
327 MoveValue::visit_deserialize(self.contents(), &layout.into_layout(), &mut traversal)
328 .map_err(|e| IotaError::ObjectSerialization {
329 error: e.to_string(),
330 })?;
331
332 Ok(traversal.finish())
333 }
334 }
335}
336
337#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash)]
338#[serde(rename = "Object")]
339pub struct ObjectInner {
340 pub data: Data,
342 pub owner: Owner,
344 pub previous_transaction: TransactionDigest,
346 pub storage_rebate: u64,
350}
351
352#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash)]
353#[serde(from = "ObjectInner")]
354pub struct Object(Arc<ObjectInner>);
355
356impl From<ObjectInner> for Object {
357 fn from(inner: ObjectInner) -> Self {
358 Self(Arc::new(inner))
359 }
360}
361
362impl Object {
363 pub fn into_inner(self) -> ObjectInner {
364 match Arc::try_unwrap(self.0) {
365 Ok(inner) => inner,
366 Err(inner_arc) => (*inner_arc).clone(),
367 }
368 }
369
370 pub fn as_inner(&self) -> &ObjectInner {
371 &self.0
372 }
373
374 pub fn owner(&self) -> &Owner {
375 &self.0.owner
376 }
377
378 pub fn new_from_genesis(
379 data: Data,
380 owner: Owner,
381 previous_transaction: TransactionDigest,
382 ) -> Self {
383 ObjectInner {
384 data,
385 owner,
386 previous_transaction,
387 storage_rebate: 0,
388 }
389 .into()
390 }
391
392 pub fn new_move(o: MoveObject, owner: Owner, previous_transaction: TransactionDigest) -> Self {
394 ObjectInner {
395 data: Data::Struct(o),
396 owner,
397 previous_transaction,
398 storage_rebate: 0,
399 }
400 .into()
401 }
402
403 pub fn new_package_from_data(data: Data, previous_transaction: TransactionDigest) -> Self {
404 ObjectInner {
405 data,
406 owner: Owner::Immutable,
407 previous_transaction,
408 storage_rebate: 0,
409 }
410 .into()
411 }
412
413 pub fn new_from_package(package: MovePackage, previous_transaction: TransactionDigest) -> Self {
415 Self::new_package_from_data(Data::Package(package), previous_transaction)
416 }
417
418 pub fn new_package<'p>(
419 modules: &[CompiledModule],
420 previous_transaction: TransactionDigest,
421 protocol_config: &ProtocolConfig,
422 dependencies: impl IntoIterator<Item = &'p MovePackage>,
423 ) -> Result<Self, ExecutionError> {
424 Ok(Self::new_package_from_data(
425 Data::Package(MovePackage::new_initial(
426 modules,
427 protocol_config,
428 dependencies,
429 )?),
430 previous_transaction,
431 ))
432 }
433
434 pub fn new_upgraded_package<'p>(
435 previous_package: &MovePackage,
436 new_package_id: ObjectId,
437 modules: &[CompiledModule],
438 previous_transaction: TransactionDigest,
439 protocol_config: &ProtocolConfig,
440 dependencies: impl IntoIterator<Item = &'p MovePackage>,
441 ) -> Result<Self, ExecutionError> {
442 Ok(Self::new_package_from_data(
443 Data::Package(previous_package.new_upgraded(
444 new_package_id,
445 modules,
446 protocol_config,
447 dependencies,
448 )?),
449 previous_transaction,
450 ))
451 }
452
453 pub fn new_package_for_testing(
454 modules: &[CompiledModule],
455 previous_transaction: TransactionDigest,
456 dependencies: impl IntoIterator<Item = MovePackage>,
457 ) -> Result<Self, ExecutionError> {
458 let dependencies: Vec<_> = dependencies.into_iter().collect();
459 let config = ProtocolConfig::get_for_max_version_UNSAFE();
460 Self::new_package(modules, previous_transaction, &config, &dependencies)
461 }
462
463 pub fn new_system_package(
466 modules: &[CompiledModule],
467 version: SequenceNumber,
468 dependencies: Vec<ObjectId>,
469 previous_transaction: TransactionDigest,
470 ) -> Self {
471 let ret = Self::new_package_from_data(
472 Data::Package(MovePackage::new_system(version, modules, dependencies)),
473 previous_transaction,
474 );
475
476 #[cfg(not(msim))]
477 assert!(ret.is_system_package());
478
479 ret
480 }
481}
482
483impl std::ops::Deref for Object {
484 type Target = ObjectInner;
485 fn deref(&self) -> &Self::Target {
486 &self.0
487 }
488}
489
490impl std::ops::DerefMut for Object {
491 fn deref_mut(&mut self) -> &mut Self::Target {
492 Arc::make_mut(&mut self.0)
493 }
494}
495
496impl ObjectInner {
497 pub fn is_system_package(&self) -> bool {
499 self.is_package() && self.id().is_system_package()
500 }
501
502 pub fn is_immutable(&self) -> bool {
503 self.owner.is_immutable()
504 }
505
506 pub fn is_address_owned(&self) -> bool {
507 self.owner.is_address()
508 }
509
510 pub fn is_child_object(&self) -> bool {
511 self.owner.is_object()
512 }
513
514 pub fn is_shared(&self) -> bool {
515 self.owner.is_shared()
516 }
517
518 pub fn get_single_owner(&self) -> Option<IotaAddress> {
519 self.owner.address_or_object().copied()
520 }
521
522 pub fn get_owner_and_id(&self) -> Option<(Owner, ObjectId)> {
525 Some((self.owner, self.id()))
526 }
527
528 pub fn is_package(&self) -> bool {
531 matches!(&self.data, Data::Package(_))
532 }
533
534 pub fn compute_object_reference(&self) -> ObjectRef {
535 ObjectRef::new(self.id(), self.version(), self.digest())
536 }
537
538 pub fn digest(&self) -> ObjectDigest {
539 ObjectDigest::new(default_hash(self))
540 }
541
542 pub fn id(&self) -> ObjectId {
543 use Data::*;
544
545 match &self.data {
546 Struct(v) => v.id(),
547 Package(m) => m.id(),
548 }
549 }
550
551 pub fn version(&self) -> SequenceNumber {
552 use Data::*;
553
554 match &self.data {
555 Struct(o) => o.version(),
556 Package(p) => p.version(),
557 }
558 }
559
560 pub fn type_(&self) -> Option<&MoveObjectType> {
561 self.data.object_type()
562 }
563
564 pub fn struct_tag(&self) -> Option<StructTag> {
565 self.data.struct_tag()
566 }
567
568 pub fn is_coin(&self) -> bool {
569 if let Some(move_object) = self.data.as_struct_opt() {
570 move_object.struct_tag().is_coin()
571 } else {
572 false
573 }
574 }
575
576 pub fn is_gas_coin(&self) -> bool {
577 if let Some(move_object) = self.data.as_struct_opt() {
578 move_object.struct_tag().is_gas_coin()
579 } else {
580 false
581 }
582 }
583
584 pub fn as_coin_maybe(&self) -> Option<Coin> {
587 if let Some(move_object) = self.data.as_struct_opt() {
588 let coin: Coin = bcs::from_bytes(move_object.contents()).ok()?;
589 Some(coin)
590 } else {
591 None
592 }
593 }
594
595 pub fn as_timelock_balance_maybe(&self) -> Option<TimeLock<Balance>> {
596 if let Some(move_object) = self.data.as_struct_opt() {
597 Some(TimeLock::from_bcs_bytes(move_object.contents()).ok()?)
598 } else {
599 None
600 }
601 }
602
603 pub fn coin_type_opt(&self) -> Option<&TypeTag> {
604 if let Some(move_object) = self.data.as_struct_opt() {
605 move_object.struct_tag().coin_type_opt()
606 } else {
607 None
608 }
609 }
610
611 pub fn get_coin_value_unchecked(&self) -> u64 {
616 self.data
617 .as_struct_opt()
618 .unwrap()
619 .get_coin_value_unchecked()
620 }
621
622 pub fn object_size_for_gas_metering(&self) -> usize {
627 let meta_data_size = size_of::<Owner>() + size_of::<TransactionDigest>() + size_of::<u64>();
628 let data_size = match &self.data {
629 Data::Struct(m) => m.object_size_for_gas_metering(),
630 Data::Package(p) => p.size(),
631 };
632 meta_data_size + data_size
633 }
634
635 pub fn transfer(&mut self, new_owner: IotaAddress) {
637 self.owner = Owner::Address(new_owner);
638 }
639
640 pub fn get_layout(
646 &self,
647 resolver: &impl GetModule,
648 ) -> Result<Option<MoveStructLayout>, IotaError> {
649 match &self.data {
650 Data::Struct(m) => Ok(Some(m.get_layout(resolver)?)),
651 Data::Package(_) => Ok(None),
652 }
653 }
654
655 pub fn get_move_template_type(&self) -> IotaResult<TypeTag> {
659 let move_struct = self.data.struct_tag().ok_or_else(|| IotaError::Type {
660 error: "Object must be a Move object".to_owned(),
661 })?;
662 fp_ensure!(
663 move_struct.type_params().len() == 1,
664 IotaError::Type {
665 error: "Move object struct must have one type parameter".to_owned()
666 }
667 );
668 let type_tag = move_struct.type_params()[0].clone();
670 Ok(type_tag)
671 }
672
673 pub fn to_rust<'de, T: Deserialize<'de>>(&'de self) -> Result<T, bcs::Error> {
674 self.data
675 .as_struct_opt()
676 .ok_or_else(|| bcs::Error::Custom("Object is not a struct".to_string()))?
677 .to_rust()
678 }
679}
680
681impl Object {
683 pub fn get_total_iota(
686 &self,
687 layout_resolver: &mut dyn LayoutResolver,
688 ) -> Result<u64, IotaError> {
689 Ok(self.storage_rebate
690 + match &self.data {
691 Data::Struct(m) => m.get_total_iota(layout_resolver)?,
692 Data::Package(_) => 0,
693 })
694 }
695
696 pub fn immutable_with_id_for_testing(id: ObjectId) -> Self {
697 let data = Data::Struct(
698 MoveObject::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::Immutable,
707 data,
708 previous_transaction: TransactionDigest::GENESIS_MARKER,
709 storage_rebate: 0,
710 }
711 .into()
712 }
713
714 pub fn immutable_for_testing() -> Self {
715 thread_local! {
716 static IMMUTABLE_OBJECT_ID: ObjectId = ObjectId::random();
717 }
718
719 Self::immutable_with_id_for_testing(IMMUTABLE_OBJECT_ID.with(|id| *id))
720 }
721
722 pub fn shared_for_testing() -> Object {
724 let id = ObjectId::random();
725 let obj = MoveObject::new_gas_coin(OBJECT_START_VERSION, id, 10);
726 let owner = Owner::Shared(obj.version());
727 Object::new_move(obj, owner, TransactionDigest::GENESIS_MARKER)
728 }
729
730 pub fn with_id_owner_gas_for_testing(id: ObjectId, owner: IotaAddress, gas: u64) -> Self {
731 let data = Data::Struct(
732 MoveObject::new(
733 StructTag::new_gas_coin().into(),
734 OBJECT_START_VERSION,
735 GasCoin::new(id, gas).to_bcs_bytes(),
736 )
737 .unwrap(),
738 );
739 ObjectInner {
740 owner: Owner::Address(owner),
741 data,
742 previous_transaction: TransactionDigest::GENESIS_MARKER,
743 storage_rebate: 0,
744 }
745 .into()
746 }
747
748 pub fn treasury_cap_for_testing(struct_tag: StructTag, treasury_cap: TreasuryCap) -> Self {
749 let data = Data::Struct(
750 MoveObject::new(
751 StructTag::new_treasury_cap(struct_tag).into(),
752 OBJECT_START_VERSION,
753 bcs::to_bytes(&treasury_cap).expect("Failed to serialize"),
754 )
755 .unwrap(),
756 );
757 ObjectInner {
758 owner: Owner::Immutable,
759 data,
760 previous_transaction: TransactionDigest::GENESIS_MARKER,
761 storage_rebate: 0,
762 }
763 .into()
764 }
765
766 pub fn coin_metadata_for_testing(struct_tag: StructTag, metadata: CoinMetadata) -> Self {
767 let data = Data::Struct(
768 MoveObject::new(
769 StructTag::new_coin_metadata(struct_tag).into(),
770 OBJECT_START_VERSION,
771 bcs::to_bytes(&metadata).expect("Failed to serialize"),
772 )
773 .unwrap(),
774 );
775 ObjectInner {
776 owner: Owner::Immutable,
777 data,
778 previous_transaction: TransactionDigest::GENESIS_MARKER,
779 storage_rebate: 0,
780 }
781 .into()
782 }
783
784 pub fn with_object_owner_for_testing(id: ObjectId, owner: ObjectId) -> Self {
785 let data = Data::Struct(
786 MoveObject::new(
787 StructTag::new_gas_coin().into(),
788 OBJECT_START_VERSION,
789 GasCoin::new(id, GAS_VALUE_FOR_TESTING).to_bcs_bytes(),
790 )
791 .unwrap(),
792 );
793 ObjectInner {
794 owner: Owner::Object(owner),
795 data,
796 previous_transaction: TransactionDigest::GENESIS_MARKER,
797 storage_rebate: 0,
798 }
799 .into()
800 }
801
802 pub fn with_id_owner_for_testing(id: ObjectId, owner: IotaAddress) -> Self {
803 Self::with_id_owner_gas_for_testing(id, owner, GAS_VALUE_FOR_TESTING)
805 }
806
807 pub fn with_id_owner_version_for_testing(
808 id: ObjectId,
809 version: SequenceNumber,
810 owner: Owner,
811 ) -> Self {
812 let data = Data::Struct(
813 MoveObject::new(
814 StructTag::new_gas_coin().into(),
815 version,
816 GasCoin::new(id, GAS_VALUE_FOR_TESTING).to_bcs_bytes(),
817 )
818 .unwrap(),
819 );
820 ObjectInner {
821 owner,
822 data,
823 previous_transaction: TransactionDigest::GENESIS_MARKER,
824 storage_rebate: 0,
825 }
826 .into()
827 }
828
829 pub fn with_owner_for_testing(owner: IotaAddress) -> Self {
830 Self::with_id_owner_for_testing(ObjectId::random(), owner)
831 }
832
833 pub fn new_gas_with_balance_and_owner_for_testing(value: u64, owner: IotaAddress) -> Self {
836 let obj = MoveObject::new_gas_coin(OBJECT_START_VERSION, ObjectId::random(), value);
837 Object::new_move(
838 obj,
839 Owner::Address(owner),
840 TransactionDigest::GENESIS_MARKER,
841 )
842 }
843
844 pub fn new_gas_for_testing() -> Self {
846 let gas_object_id = ObjectId::random();
847 let (owner, _) = deterministic_random_account_key();
848 Object::with_id_owner_for_testing(gas_object_id, owner)
849 }
850}
851
852pub fn generate_test_gas_objects() -> Vec<Object> {
854 thread_local! {
855 static GAS_OBJECTS: Vec<Object> = (0..50)
856 .map(|_| {
857 let gas_object_id = ObjectId::random();
858 let (owner, _) = deterministic_random_account_key();
859 Object::with_id_owner_for_testing(gas_object_id, owner)
860 })
861 .collect();
862 }
863
864 GAS_OBJECTS.with(|v| v.clone())
865}
866
867#[derive(Serialize, Deserialize, Debug)]
868#[serde(tag = "status", content = "details")]
869pub enum ObjectRead {
870 NotExists(ObjectId),
871 Exists(ObjectRef, Object, Option<MoveStructLayout>),
872 Deleted(ObjectRef),
873}
874
875impl ObjectRead {
876 pub fn into_object(self) -> UserInputResult<Object> {
879 match self {
880 Self::Deleted(oref) => Err(UserInputError::ObjectDeleted { object_ref: oref }),
881 Self::NotExists(id) => Err(UserInputError::ObjectNotFound {
882 object_id: id,
883 version: None,
884 }),
885 Self::Exists(_, o, _) => Ok(o),
886 }
887 }
888
889 pub fn object(&self) -> UserInputResult<&Object> {
890 match self {
891 Self::Deleted(oref) => Err(UserInputError::ObjectDeleted { object_ref: *oref }),
892 Self::NotExists(id) => Err(UserInputError::ObjectNotFound {
893 object_id: *id,
894 version: None,
895 }),
896 Self::Exists(_, o, _) => Ok(o),
897 }
898 }
899
900 pub fn object_id(&self) -> ObjectId {
901 match self {
902 Self::Deleted(oref) => oref.object_id,
903 Self::NotExists(id) => *id,
904 Self::Exists(oref, _, _) => oref.object_id,
905 }
906 }
907}
908
909impl Display for ObjectRead {
910 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
911 match self {
912 Self::Deleted(oref) => {
913 write!(f, "ObjectRead::Deleted ({oref:?})")
914 }
915 Self::NotExists(id) => {
916 write!(f, "ObjectRead::NotExists ({id})")
917 }
918 Self::Exists(oref, _, _) => {
919 write!(f, "ObjectRead::Exists ({oref:?})")
920 }
921 }
922 }
923}
924
925#[derive(Serialize, Deserialize, Debug)]
926#[serde(tag = "status", content = "details")]
927pub enum PastObjectRead {
928 ObjectNotExists(ObjectId),
930 ObjectDeleted(ObjectRef),
932 VersionFound(ObjectRef, Object, Option<MoveStructLayout>),
934 VersionNotFound(ObjectId, SequenceNumber),
936 VersionTooHigh {
938 object_id: ObjectId,
939 asked_version: SequenceNumber,
940 latest_version: SequenceNumber,
941 },
942}
943
944impl PastObjectRead {
945 pub fn into_object(self) -> UserInputResult<Object> {
947 match self {
948 Self::ObjectDeleted(oref) => Err(UserInputError::ObjectDeleted { object_ref: oref }),
949 Self::ObjectNotExists(id) => Err(UserInputError::ObjectNotFound {
950 object_id: id,
951 version: None,
952 }),
953 Self::VersionFound(_, o, _) => Ok(o),
954 Self::VersionNotFound(object_id, version) => Err(UserInputError::ObjectNotFound {
955 object_id,
956 version: Some(version),
957 }),
958 Self::VersionTooHigh {
959 object_id,
960 asked_version,
961 latest_version,
962 } => Err(UserInputError::ObjectSequenceNumberTooHigh {
963 object_id,
964 asked_version,
965 latest_version,
966 }),
967 }
968 }
969}
970
971impl Display for PastObjectRead {
972 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
973 match self {
974 Self::ObjectDeleted(oref) => {
975 write!(f, "PastObjectRead::ObjectDeleted ({oref:?})")
976 }
977 Self::ObjectNotExists(id) => {
978 write!(f, "PastObjectRead::ObjectNotExists ({id})")
979 }
980 Self::VersionFound(oref, _, _) => {
981 write!(f, "PastObjectRead::VersionFound ({oref:?})")
982 }
983 Self::VersionNotFound(object_id, version) => {
984 write!(
985 f,
986 "PastObjectRead::VersionNotFound ({object_id}, asked sequence number {version:?})"
987 )
988 }
989 Self::VersionTooHigh {
990 object_id,
991 asked_version,
992 latest_version,
993 } => {
994 write!(
995 f,
996 "PastObjectRead::VersionTooHigh ({object_id}, asked sequence number {asked_version:?}, latest sequence number {latest_version:?})"
997 )
998 }
999 }
1000 }
1001}
1002
1003#[cfg(test)]
1004mod tests {
1005 use iota_sdk_types::ObjectId;
1006
1007 use crate::{
1008 base_types::{IotaAddress, TransactionDigest},
1009 gas_coin::GasCoin,
1010 object::{MoveObjectExt, OBJECT_START_VERSION, Object, Owner},
1011 };
1012
1013 #[test]
1016 fn test_object_digest_and_serialized_format() {
1017 let g =
1018 GasCoin::new_for_testing_with_id(ObjectId::ZERO, 123).to_object(OBJECT_START_VERSION);
1019 let o = Object::new_move(
1020 g,
1021 Owner::Address(IotaAddress::ZERO),
1022 TransactionDigest::ZERO,
1023 );
1024 let bytes = bcs::to_bytes(&o).unwrap();
1025
1026 assert_eq!(
1027 bytes,
1028 [
1029 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,
1030 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,
1031 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,
1032 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,
1033 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1034 ]
1035 );
1036 let objref = o.compute_object_reference();
1037
1038 assert_eq!(objref.object_id, ObjectId::ZERO);
1039 assert_eq!(objref.version, 1);
1040 assert_eq!(
1041 objref.digest.to_string(),
1042 "Ba4YyVBcpc9jgX4PMLRoyt9dKLftYVSDvuKbtMr9f4NM"
1043 );
1044 }
1045
1046 #[test]
1047 fn test_get_coin_value_unchecked() {
1048 fn test_for_value(v: u64) {
1049 let g = GasCoin::new_for_testing(v).to_object(OBJECT_START_VERSION);
1050 assert_eq!(g.get_coin_value_unchecked(), v);
1051 assert_eq!(GasCoin::try_from(&g).unwrap().value(), v);
1052 }
1053
1054 test_for_value(0);
1055 test_for_value(1);
1056 test_for_value(8);
1057 test_for_value(9);
1058 test_for_value(u8::MAX as u64);
1059 test_for_value(u8::MAX as u64 + 1);
1060 test_for_value(u16::MAX as u64);
1061 test_for_value(u16::MAX as u64 + 1);
1062 test_for_value(u32::MAX as u64);
1063 test_for_value(u32::MAX as u64 + 1);
1064 test_for_value(u64::MAX);
1065 }
1066
1067 #[test]
1068 fn test_set_coin_value_unchecked() {
1069 fn test_for_value(v: u64) {
1070 let mut g = GasCoin::new_for_testing(u64::MAX).to_object(OBJECT_START_VERSION);
1071 g.set_coin_value_unchecked(v);
1072 assert_eq!(g.get_coin_value_unchecked(), v);
1073 assert_eq!(GasCoin::try_from(&g).unwrap().value(), v);
1074 assert_eq!(g.version(), OBJECT_START_VERSION);
1075 assert_eq!(g.contents().len(), 40);
1076 }
1077
1078 test_for_value(0);
1079 test_for_value(1);
1080 test_for_value(8);
1081 test_for_value(9);
1082 test_for_value(u8::MAX as u64);
1083 test_for_value(u8::MAX as u64 + 1);
1084 test_for_value(u16::MAX as u64);
1085 test_for_value(u16::MAX as u64 + 1);
1086 test_for_value(u32::MAX as u64);
1087 test_for_value(u32::MAX as u64 + 1);
1088 test_for_value(u64::MAX);
1089 }
1090}