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