Skip to main content

iota_types/
object.rs

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