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