Skip to main content

iota_types/
object.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use 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
45/// Index marking the end of the object's ID + the beginning of its version
46pub 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    /// Creates a new Move object of type `tag` with BCS encoded bytes in
99    /// `contents`.
100    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    /// Creates a new Move object of type `tag` with BCS encoded bytes in
115    /// `contents`. It allows to set a `max_move_object_size` for that.
116    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        // unwrap safe because coins are always smaller than the max object size
135
136        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        // unwrap safe because coins are always smaller than the max object size
147
148        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    /// Return the `value: u64` field of a `Coin<T>` type.
158    /// Useful for reading the coin without deserializing the object into a Move
159    /// value. It is the caller's responsibility to check that `self` is a coin.
160    /// This function may panic or do something unexpected otherwise.
161    fn get_coin_value_unchecked(&self) -> u64 {
162        debug_assert!(self.object_type().is_coin());
163        // 32 bytes for object ID, 8 for balance
164        debug_assert!(self.contents().len() == 40);
165
166        // unwrap safe because we checked that it is a coin
167        u64::from_le_bytes(<[u8; 8]>::try_from(&self.contents()[ID_END_INDEX..]).unwrap())
168    }
169
170    /// Update the `value: u64` field of a `Coin<T>` type.
171    /// Useful for updating the coin without deserializing the object into a
172    /// Move value. It is the caller's responsibility to check that `self` is a
173    /// coin.
174    /// This function may panic or do something unexpected otherwise.
175    fn set_coin_value_unchecked(&mut self, value: u64) {
176        debug_assert!(self.object_type().is_coin());
177        // 32 bytes for object ID, 8 for balance
178        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    /// Update the `timestamp_ms: u64` field of the `Clock` type.
186    /// Useful for updating the clock without deserializing the object into a
187    /// Move value. It is the caller's responsibility to check that `self` is a
188    /// `Clock`.
189    /// This function may panic or do something unexpected otherwise.
190    fn set_clock_timestamp_ms_unchecked(&mut self, timestamp_ms: u64) {
191        debug_assert!(self.struct_tag().is_clock());
192        // 32 bytes for object ID, 8 for timestamp
193        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(&timestamp_ms.to_le_bytes());
197        self.set_contents(new_contents).unwrap();
198    }
199
200    /// Update the contents of this object but does not increment its version
201    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        // Update should not modify ID
230        #[cfg(debug_assertions)]
231        debug_assert_eq!(self.id(), old_id);
232
233        Ok(())
234    }
235
236    /// Sets the version of this object to a new value which is assumed to be
237    /// higher (and checked to be higher in debug).
238    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    /// Sets the version to a lower value (checked in debug).
248    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    /// Get a `MoveStructLayout` for `self`.
258    /// The `resolver` value must contain the module that declares
259    /// `self.object_type` and the (transitive) dependencies of
260    /// `self.object_type` in order for this to succeed. Failure will result
261    /// in an `ObjectSerializationError`
262    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    /// Convert `self` to the JSON representation dictated by `layout`.
284    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    /// Approximate size of the object in bytes. This is used for gas metering.
293    /// For the type tag field, we serialize it on the spot to get the accurate
294    /// size. This should not be very expensive since the type tag is
295    /// usually simple, and we only do this once per object being mutated.
296    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        // + 8 for `version`
300        self.contents().len() + serialized_type_tag_size + 8
301    }
302
303    /// Get the total amount of IOTA embedded in `self`. Intended for testing
304    /// purposes
305    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    /// Get the total balances for all `Coin<T>` embedded in `self`.
311    fn get_coin_balances(
312        &self,
313        layout_resolver: &mut dyn LayoutResolver,
314    ) -> Result<BTreeMap<TypeTag, u64>, IotaError> {
315        // Fast path without deserialization.
316        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    /// The meat of the object
341    pub data: Data,
342    /// The owner that unlocks this object
343    pub owner: Owner,
344    /// The digest of the transaction that created or last mutated this object
345    pub previous_transaction: TransactionDigest,
346    /// The amount of IOTA we would rebate if this object gets deleted.
347    /// This number is re-calculated each time the object is mutated based on
348    /// the present storage gas price.
349    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    /// Create a new Move object
393    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    // Note: this will panic if `modules` is empty
414    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    /// Create a system package which is not subject to size limits. Panics if
464    /// the object ID is not a known system package.
465    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    /// Returns true if the object is a system package.
498    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    // It's a common pattern to retrieve both the owner and object ID
523    // together, if it's owned by a single owner.
524    pub fn get_owner_and_id(&self) -> Option<(Owner, ObjectId)> {
525        Some((self.owner, self.id()))
526    }
527
528    /// Return true if this object is a Move package, false if it is a Move
529    /// value
530    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    // TODO: use `MoveObj::get_balance_unsafe` instead.
585    // context: https://github.com/iotaledger/iota/pull/10679#discussion_r1165877816
586    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    /// Return the `value: u64` field of a `Coin<T>` type.
612    /// Useful for reading the coin without deserializing the object into a Move
613    /// value It is the caller's responsibility to check that `self` is a
614    /// coin--this function may panic or do something unexpected otherwise.
615    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    /// Approximate size of the object in bytes. This is used for gas metering.
623    /// This will be slightly different from the serialized size, but
624    /// we also don't want to serialize the object just to get the size.
625    /// This approximation should be good enough for gas metering.
626    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    /// Change the owner of `self` to `new_owner`.
636    pub fn transfer(&mut self, new_owner: IotaAddress) {
637        self.owner = Owner::Address(new_owner);
638    }
639
640    /// Get a `MoveStructLayout` for `self`.
641    /// The `resolver` value must contain the module that declares
642    /// `self.object_type` and the (transitive) dependencies of
643    /// `self.object_type` in order for this to succeed. Failure will result
644    /// in an `ObjectSerializationError`
645    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    /// Treat the object type as a Move struct with one type parameter,
656    /// like this: `S<T>`.
657    /// Returns the inner parameter type `T`.
658    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        // Index access safe due to checks above.
669        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
681// Testing-related APIs.
682impl Object {
683    /// Get the total amount of IOTA embedded in `self`, including both Move
684    /// objects and the storage rebate
685    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    /// Make a new random test shared object.
723    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        // For testing, we provide sufficient gas by default.
804        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    /// Generate a new gas coin worth `value` with a random object ID and owner
834    /// For testing purposes only
835    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    /// Generate a new gas coin object with default balance and random owner.
845    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
852/// Make a few test gas objects (all with the same random owner).
853pub 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    /// Returns the object value if there is any, otherwise an Err if
877    /// the object does not exist or is deleted.
878    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    /// The object does not exist
929    ObjectNotExists(ObjectId),
930    /// The object is found to be deleted with this version
931    ObjectDeleted(ObjectRef),
932    /// The object exists and is found with this version
933    VersionFound(ObjectRef, Object, Option<MoveStructLayout>),
934    /// The object exists but not found with this version
935    VersionNotFound(ObjectId, SequenceNumber),
936    /// The asked object version is higher than the latest
937    VersionTooHigh {
938        object_id: ObjectId,
939        asked_version: SequenceNumber,
940        latest_version: SequenceNumber,
941    },
942}
943
944impl PastObjectRead {
945    /// Returns the object value if there is any, otherwise an Err
946    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    // Ensure that object digest computation and bcs serialized format are not
1014    // inadvertently changed.
1015    #[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}