Skip to main content

iota_types/
object.rs

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