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