sui_types/
object.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::convert::TryFrom;
5use std::fmt::{Debug, Display, Formatter};
6use std::sync::Arc;
7
8use move_binary_format::CompiledModule;
9use move_bytecode_utils::layout::TypeLayoutBuilder;
10use move_bytecode_utils::module_cache::GetModule;
11use move_core_types::annotated_value::{MoveStruct, MoveStructLayout, MoveTypeLayout, MoveValue};
12use move_core_types::language_storage::StructTag;
13use move_core_types::language_storage::TypeTag;
14use mysten_common::debug_fatal;
15use once_cell::sync::Lazy;
16use schemars::JsonSchema;
17use serde::{Deserialize, Serialize};
18use serde_with::Bytes;
19use serde_with::serde_as;
20
21use crate::accumulator_root::AccumulatorValue;
22use crate::base_types::{FullObjectID, FullObjectRef, MoveObjectType, ObjectIDParseError};
23use crate::coin::{Coin, CoinMetadata, TreasuryCap};
24use crate::crypto::{default_hash, deterministic_random_account_key};
25use crate::error::{
26    ExecutionError, ExecutionErrorKind, SuiErrorKind, UserInputError, UserInputResult,
27};
28use crate::error::{SuiError, SuiResult};
29use crate::gas_coin::GAS;
30use crate::is_system_package;
31use crate::layout_resolver::LayoutResolver;
32use crate::move_package::MovePackage;
33use crate::{
34    base_types::{
35        ObjectDigest, ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest,
36    },
37    gas_coin::GasCoin,
38};
39use sui_protocol_config::ProtocolConfig;
40
41use self::balance_traversal::BalanceTraversal;
42use self::bounded_visitor::BoundedVisitor;
43
44mod balance_traversal;
45pub mod bounded_visitor;
46pub mod option_visitor;
47
48pub const GAS_VALUE_FOR_TESTING: u64 = 300_000_000_000_000;
49pub const OBJECT_START_VERSION: SequenceNumber = SequenceNumber::from_u64(1);
50
51#[serde_as]
52#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash)]
53pub struct MoveObject {
54    /// The type of this object. Immutable
55    type_: MoveObjectType,
56    /// DEPRECATED this field is no longer used to determine whether a tx can transfer this
57    /// object. Instead, it is always calculated from the objects type when loaded in execution
58    has_public_transfer: bool,
59    /// Number that increases each time a tx takes this object as a mutable input
60    /// This is a lamport timestamp, not a sequentially increasing version
61    version: SequenceNumber,
62    /// BCS bytes of a Move struct value
63    #[serde_as(as = "Bytes")]
64    contents: Vec<u8>,
65}
66
67/// Index marking the end of the object's ID + the beginning of its version
68pub const ID_END_INDEX: usize = ObjectID::LENGTH;
69
70impl MoveObject {
71    /// Creates a new Move object of type `type_` with BCS encoded bytes in `contents`
72    /// `has_public_transfer` is determined by the abilities of the `type_`, but resolving
73    /// the abilities requires the compiled modules of the `type_: StructTag`.
74    /// In other words, `has_public_transfer` will be the same for all objects of the same `type_`.
75    ///
76    /// # Safety
77    ///
78    /// This function should ONLY be called if has_public_transfer has been determined by the type_.
79    /// Yes, this is a bit of an abuse of the `unsafe` marker, but bad things will happen if this
80    /// is inconsistent
81    pub unsafe fn new_from_execution(
82        type_: MoveObjectType,
83        has_public_transfer: bool,
84        version: SequenceNumber,
85        contents: Vec<u8>,
86        protocol_config: &ProtocolConfig,
87        system_mutation: bool,
88    ) -> Result<Self, ExecutionError> {
89        let bound = if protocol_config.allow_unbounded_system_objects() && system_mutation {
90            if contents.len() as u64 > protocol_config.max_move_object_size() {
91                debug_fatal!(
92                    "System created object (ID = {:?}) of type {:?} and size {} exceeds normal max size {}",
93                    MoveObject::id_opt(&contents).ok(),
94                    type_,
95                    contents.len(),
96                    protocol_config.max_move_object_size()
97                );
98            }
99            u64::MAX
100        } else {
101            protocol_config.max_move_object_size()
102        };
103        unsafe {
104            Self::new_from_execution_with_limit(
105                type_,
106                has_public_transfer,
107                version,
108                contents,
109                bound,
110            )
111        }
112    }
113
114    /// # Safety
115    /// This function should ONLY be called if has_public_transfer has been determined by the type_
116    pub unsafe fn new_from_execution_with_limit(
117        type_: MoveObjectType,
118        has_public_transfer: bool,
119        version: SequenceNumber,
120        contents: Vec<u8>,
121        max_move_object_size: u64,
122    ) -> Result<Self, ExecutionError> {
123        // coins should always have public transfer, as they always should have store.
124        // Thus, type_ == GasCoin::type_() ==> has_public_transfer
125        // TODO: think this can be generalized to is_coin
126        debug_assert!(!type_.is_gas_coin() || has_public_transfer);
127        if contents.len() as u64 > max_move_object_size {
128            return Err(ExecutionError::from_kind(
129                ExecutionErrorKind::MoveObjectTooBig {
130                    object_size: contents.len() as u64,
131                    max_object_size: max_move_object_size,
132                },
133            ));
134        }
135        Ok(Self {
136            type_,
137            has_public_transfer,
138            version,
139            contents,
140        })
141    }
142
143    pub fn new_gas_coin(version: SequenceNumber, id: ObjectID, value: u64) -> Self {
144        // unwrap safe because coins are always smaller than the max object size
145        unsafe {
146            Self::new_from_execution_with_limit(
147                GasCoin::type_().into(),
148                true,
149                version,
150                GasCoin::new(id, value).to_bcs_bytes(),
151                256,
152            )
153            .unwrap()
154        }
155    }
156
157    pub fn new_coin(coin_type: TypeTag, version: SequenceNumber, id: ObjectID, value: u64) -> Self {
158        // unwrap safe because coins are always smaller than the max object size
159        unsafe {
160            Self::new_from_execution_with_limit(
161                MoveObjectType::coin(coin_type),
162                true,
163                version,
164                Coin::new(id, value).to_bcs_bytes(),
165                256,
166            )
167            .unwrap()
168        }
169    }
170
171    pub fn type_(&self) -> &MoveObjectType {
172        &self.type_
173    }
174
175    pub fn is_type(&self, s: &StructTag) -> bool {
176        self.type_.is(s)
177    }
178
179    pub fn has_public_transfer(&self) -> bool {
180        self.has_public_transfer
181    }
182
183    pub fn id(&self) -> ObjectID {
184        Self::id_opt(&self.contents).unwrap()
185    }
186
187    pub fn id_opt(contents: &[u8]) -> Result<ObjectID, ObjectIDParseError> {
188        if ID_END_INDEX > contents.len() {
189            return Err(ObjectIDParseError::TryFromSliceError);
190        }
191        ObjectID::try_from(&contents[0..ID_END_INDEX])
192    }
193
194    /// Return the `value: u64` field of a `Coin<T>` type.
195    /// Useful for reading the coin without deserializing the object into a Move value
196    /// It is the caller's responsibility to check that `self` is a coin--this function
197    /// may panic or do something unexpected otherwise.
198    pub fn get_coin_value_unsafe(&self) -> u64 {
199        debug_assert!(self.type_.is_coin());
200        // 32 bytes for object ID, 8 for balance
201        debug_assert!(self.contents.len() == 40);
202
203        // unwrap safe because we checked that it is a coin
204        u64::from_le_bytes(<[u8; 8]>::try_from(&self.contents[ID_END_INDEX..]).unwrap())
205    }
206
207    /// Update the `value: u64` field of a `Coin<T>` type.
208    /// Useful for updating the coin without deserializing the object into a Move value
209    /// It is the caller's responsibility to check that `self` is a coin--this function
210    /// may panic or do something unexpected otherwise.
211    pub fn set_coin_value_unsafe(&mut self, value: u64) {
212        debug_assert!(self.type_.is_coin());
213        // 32 bytes for object ID, 8 for balance
214        debug_assert!(self.contents.len() == 40);
215
216        self.contents.splice(ID_END_INDEX.., value.to_le_bytes());
217    }
218
219    /// Update the `timestamp_ms: u64` field of the `Clock` type.
220    ///
221    /// Panics if the object isn't a `Clock`.
222    pub fn set_clock_timestamp_ms_unsafe(&mut self, timestamp_ms: u64) {
223        assert!(self.is_clock());
224        // 32 bytes for object ID, 8 for timestamp
225        assert!(self.contents.len() == 40);
226
227        self.contents
228            .splice(ID_END_INDEX.., timestamp_ms.to_le_bytes());
229    }
230
231    pub fn set_contents_unsafe(&mut self, contents: Vec<u8>) {
232        self.contents = contents;
233    }
234
235    pub fn is_coin(&self) -> bool {
236        self.type_.is_coin()
237    }
238
239    pub fn is_staked_sui(&self) -> bool {
240        self.type_.is_staked_sui()
241    }
242
243    pub fn is_clock(&self) -> bool {
244        self.type_.is(&crate::clock::Clock::type_())
245    }
246
247    pub fn version(&self) -> SequenceNumber {
248        self.version
249    }
250
251    pub fn contents_and_type_equal(&self, other: &Self) -> bool {
252        self.contents == other.contents && self.type_ == other.type_
253    }
254
255    /// Contents of the object that are specific to its type--i.e., not its ID and version, which all objects have
256    /// For example if the object was declared as `struct S has key { id: ID, f1: u64, f2: bool },
257    /// this returns the slice containing `f1` and `f2`.
258    #[cfg(test)]
259    pub fn type_specific_contents(&self) -> &[u8] {
260        &self.contents[ID_END_INDEX..]
261    }
262
263    /// Update the contents of this object but does not increment its version
264    /// This should only be used for safe mode epoch advancement.
265    pub(crate) fn update_contents_advance_epoch_safe_mode(
266        &mut self,
267        new_contents: Vec<u8>,
268        protocol_config: &ProtocolConfig,
269    ) -> Result<(), ExecutionError> {
270        if new_contents.len() as u64 > protocol_config.max_move_object_size() {
271            if protocol_config.allow_unbounded_system_objects() {
272                debug_fatal!(
273                    "Safe mode object update (ID = {}) of size {} exceeds normal max size {}",
274                    self.id(),
275                    new_contents.len(),
276                    protocol_config.max_move_object_size()
277                )
278            } else {
279                return Err(ExecutionError::from_kind(
280                    ExecutionErrorKind::MoveObjectTooBig {
281                        object_size: new_contents.len() as u64,
282                        max_object_size: protocol_config.max_move_object_size(),
283                    },
284                ));
285            }
286        }
287
288        #[cfg(debug_assertions)]
289        let old_id = self.id();
290        self.contents = new_contents;
291
292        // Update should not modify ID
293        #[cfg(debug_assertions)]
294        debug_assert_eq!(self.id(), old_id);
295
296        Ok(())
297    }
298
299    /// Sets the version of this object to a new value which is assumed to be higher (and checked to
300    /// be higher in debug).
301    pub fn increment_version_to(&mut self, next: SequenceNumber) {
302        self.version.increment_to(next);
303    }
304
305    pub fn decrement_version_to(&mut self, prev: SequenceNumber) {
306        self.version.decrement_to(prev);
307    }
308
309    pub fn contents(&self) -> &[u8] {
310        &self.contents
311    }
312
313    pub fn into_contents(self) -> Vec<u8> {
314        self.contents
315    }
316
317    pub fn into_type(self) -> MoveObjectType {
318        self.type_
319    }
320
321    pub fn into_inner(self) -> (MoveObjectType, Vec<u8>) {
322        (self.type_, self.contents)
323    }
324
325    /// Get a `MoveStructLayout` for `self`.
326    /// The `resolver` value must contain the module that declares `self.type_` and the (transitive)
327    /// dependencies of `self.type_` in order for this to succeed. Failure will result in an `ObjectSerializationError`
328    pub fn get_layout(&self, resolver: &impl GetModule) -> Result<MoveStructLayout, SuiError> {
329        Self::get_struct_layout_from_struct_tag(self.type_().clone().into(), resolver)
330    }
331
332    pub fn get_struct_layout_from_struct_tag(
333        struct_tag: StructTag,
334        resolver: &impl GetModule,
335    ) -> Result<MoveStructLayout, SuiError> {
336        let type_ = TypeTag::Struct(Box::new(struct_tag));
337        let layout = TypeLayoutBuilder::build_with_types(&type_, resolver).map_err(|e| {
338            SuiErrorKind::ObjectSerializationError {
339                error: e.to_string(),
340            }
341        })?;
342        match layout {
343            MoveTypeLayout::Struct(l) => Ok(*l),
344            _ => unreachable!(
345                "We called build_with_types on Struct type, should get a struct layout"
346            ),
347        }
348    }
349
350    /// Convert `self` to the JSON representation dictated by `layout`.
351    pub fn to_move_struct(&self, layout: &MoveStructLayout) -> Result<MoveStruct, SuiError> {
352        BoundedVisitor::deserialize_struct(&self.contents, layout).map_err(|e| {
353            SuiErrorKind::ObjectSerializationError {
354                error: e.to_string(),
355            }
356            .into()
357        })
358    }
359
360    /// Convert `self` to the JSON representation dictated by `layout`.
361    pub fn to_move_struct_with_resolver(
362        &self,
363        resolver: &impl GetModule,
364    ) -> Result<MoveStruct, SuiError> {
365        self.to_move_struct(&self.get_layout(resolver)?)
366    }
367
368    pub fn to_rust<'de, T: Deserialize<'de>>(&'de self) -> Option<T> {
369        bcs::from_bytes(self.contents()).ok()
370    }
371
372    /// Approximate size of the object in bytes. This is used for gas metering.
373    /// For the type tag field, we serialize it on the spot to get the accurate size.
374    /// This should not be very expensive since the type tag is usually simple, and
375    /// we only do this once per object being mutated.
376    pub fn object_size_for_gas_metering(&self) -> usize {
377        let serialized_type_tag_size =
378            bcs::serialized_size(&self.type_).expect("Serializing type tag should not fail");
379        // + 1 for 'has_public_transfer'
380        // + 8 for `version`
381        self.contents.len() + serialized_type_tag_size + 1 + 8
382    }
383
384    /// Get the total amount of SUI embedded in `self`. Intended for testing purposes
385    pub fn get_total_sui(&self, layout_resolver: &mut dyn LayoutResolver) -> Result<u64, SuiError> {
386        if self.type_.is_gas_coin() {
387            let balance = self.get_coin_value_unsafe();
388            Ok(balance)
389        } else if self.type_.coin_type_maybe().is_some() {
390            // It's a coin, but its not SUI
391            Ok(0)
392        } else if self.type_.is_sui_balance_accumulator_field() {
393            let value = AccumulatorValue::try_from(self)?;
394            let AccumulatorValue::U128(v) = value;
395            // Well behaved balance types can never have more than their total supply
396            // anywhere, which is 10B for SUI.
397            assert!(
398                v.value <= u64::MAX as u128,
399                "SUI balance cannot exceed u64::MAX"
400            );
401            Ok(v.value as u64)
402        } else {
403            let layout = layout_resolver.get_annotated_layout(&self.type_().clone().into())?;
404
405            let mut traversal = BalanceTraversal::default();
406            MoveValue::visit_deserialize(&self.contents, &layout.into_layout(), &mut traversal)
407                .map_err(|e| SuiErrorKind::ObjectSerializationError {
408                    error: e.to_string(),
409                })?;
410
411            Ok(traversal
412                .finish()
413                .get(&GAS::type_tag())
414                .copied()
415                .unwrap_or(0))
416        }
417    }
418}
419
420#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash)]
421#[allow(clippy::large_enum_variant)]
422pub enum Data {
423    /// An object whose governing logic lives in a published Move module
424    Move(MoveObject),
425    /// Map from each module name to raw serialized Move module bytes
426    Package(MovePackage),
427    // ... Sui "native" types go here
428}
429
430impl Data {
431    pub fn try_as_move(&self) -> Option<&MoveObject> {
432        use Data::*;
433        match self {
434            Move(m) => Some(m),
435            Package(_) => None,
436        }
437    }
438
439    pub fn try_as_move_mut(&mut self) -> Option<&mut MoveObject> {
440        use Data::*;
441        match self {
442            Move(m) => Some(m),
443            Package(_) => None,
444        }
445    }
446
447    pub fn try_as_package(&self) -> Option<&MovePackage> {
448        use Data::*;
449        match self {
450            Move(_) => None,
451            Package(p) => Some(p),
452        }
453    }
454
455    pub fn try_as_package_mut(&mut self) -> Option<&mut MovePackage> {
456        use Data::*;
457        match self {
458            Move(_) => None,
459            Package(p) => Some(p),
460        }
461    }
462
463    pub fn try_into_package(self) -> Option<MovePackage> {
464        use Data::*;
465        match self {
466            Move(_) => None,
467            Package(p) => Some(p),
468        }
469    }
470
471    pub fn type_(&self) -> Option<&MoveObjectType> {
472        use Data::*;
473        match self {
474            Move(m) => Some(m.type_()),
475            Package(_) => None,
476        }
477    }
478
479    pub fn struct_tag(&self) -> Option<StructTag> {
480        use Data::*;
481        match self {
482            Move(m) => Some(m.type_().clone().into()),
483            Package(_) => None,
484        }
485    }
486
487    pub fn id(&self) -> ObjectID {
488        match self {
489            Self::Move(v) => v.id(),
490            Self::Package(m) => m.id(),
491        }
492    }
493}
494
495#[derive(
496    Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash, JsonSchema, Ord, PartialOrd,
497)]
498#[cfg_attr(feature = "fuzzing", derive(proptest_derive::Arbitrary))]
499pub enum Owner {
500    /// Object is exclusively owned by a single address, and is mutable.
501    AddressOwner(SuiAddress),
502    /// Object is exclusively owned by a single object, and is mutable.
503    /// The object ID is converted to SuiAddress as SuiAddress is universal.
504    ObjectOwner(SuiAddress),
505    /// Object is shared, can be used by any address, and is mutable.
506    Shared {
507        /// The version at which the object became shared
508        initial_shared_version: SequenceNumber,
509    },
510    /// Object is immutable, and hence ownership doesn't matter.
511    Immutable,
512    /// Object is exclusively owned by a single address and sequenced via consensus.
513    ConsensusAddressOwner {
514        /// The version at which the object most recently became a consensus object.
515        /// This serves the same function as `initial_shared_version`, except it may change
516        /// if the object's Owner type changes.
517        start_version: SequenceNumber,
518        // The owner of the object.
519        owner: SuiAddress,
520    },
521}
522
523impl Owner {
524    // NOTE: only return address of AddressOwner, otherwise return error,
525    // ObjectOwner's address is converted from object id, thus we will skip it.
526    pub fn get_address_owner_address(&self) -> SuiResult<SuiAddress> {
527        match self {
528            Self::AddressOwner(address) => Ok(*address),
529            Self::Shared { .. }
530            | Self::Immutable
531            | Self::ObjectOwner(_)
532            | Self::ConsensusAddressOwner { .. } => Err(SuiErrorKind::UnexpectedOwnerType.into()),
533        }
534    }
535
536    // NOTE: this function will return address of AddressOwner, ConsensusAddressOwner, and
537    // ObjectOwner. The address of ObjectOwner is converted from object ID, even though the
538    // type is SuiAddress.
539    pub fn get_owner_address(&self) -> SuiResult<SuiAddress> {
540        match self {
541            Self::AddressOwner(address)
542            | Self::ObjectOwner(address)
543            | Self::ConsensusAddressOwner { owner: address, .. } => Ok(*address),
544            Self::Shared { .. } | Self::Immutable => Err(SuiErrorKind::UnexpectedOwnerType.into()),
545        }
546    }
547
548    // Returns initial_shared_version for Shared objects, and start_version
549    // for ConsensusAddressOwner objects.
550    pub fn start_version(&self) -> Option<SequenceNumber> {
551        match self {
552            Self::Shared {
553                initial_shared_version,
554            } => Some(*initial_shared_version),
555            Self::ConsensusAddressOwner { start_version, .. } => Some(*start_version),
556            Self::Immutable | Self::AddressOwner(_) | Self::ObjectOwner(_) => None,
557        }
558    }
559
560    pub fn is_immutable(&self) -> bool {
561        matches!(self, Owner::Immutable)
562    }
563
564    pub fn is_address_owned(&self) -> bool {
565        matches!(self, Owner::AddressOwner(_))
566    }
567
568    pub fn is_child_object(&self) -> bool {
569        matches!(self, Owner::ObjectOwner(_))
570    }
571
572    pub fn is_shared(&self) -> bool {
573        matches!(self, Owner::Shared { .. })
574    }
575
576    pub fn is_consensus(&self) -> bool {
577        matches!(
578            self,
579            Owner::Shared { .. } | Owner::ConsensusAddressOwner { .. }
580        )
581    }
582}
583
584impl PartialEq<ObjectID> for Owner {
585    fn eq(&self, other: &ObjectID) -> bool {
586        let other_id: SuiAddress = (*other).into();
587        match self {
588            Self::ObjectOwner(id) => id == &other_id,
589            Self::AddressOwner(_)
590            | Self::Shared { .. }
591            | Self::Immutable
592            | Self::ConsensusAddressOwner { .. } => false,
593        }
594    }
595}
596
597impl Display for Owner {
598    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
599        match self {
600            Self::AddressOwner(address) => {
601                write!(f, "Account Address ( {} )", address)
602            }
603            Self::ObjectOwner(address) => {
604                write!(f, "Object ID: ( {} )", address)
605            }
606            Self::Immutable => {
607                write!(f, "Immutable")
608            }
609            Self::Shared {
610                initial_shared_version,
611            } => {
612                write!(f, "Shared( {} )", initial_shared_version.value())
613            }
614            Self::ConsensusAddressOwner {
615                start_version,
616                owner,
617            } => {
618                write!(
619                    f,
620                    "ConsensusAddressOwner( {}, {} )",
621                    start_version.value(),
622                    owner
623                )
624            }
625        }
626    }
627}
628
629#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash)]
630#[serde(rename = "Object")]
631pub struct ObjectInner {
632    /// The meat of the object
633    pub data: Data,
634    /// The owner that unlocks this object
635    pub owner: Owner,
636    /// The digest of the transaction that created or last mutated this object
637    pub previous_transaction: TransactionDigest,
638    /// The amount of SUI we would rebate if this object gets deleted.
639    /// This number is re-calculated each time the object is mutated based on
640    /// the present storage gas price.
641    pub storage_rebate: u64,
642}
643
644#[derive(Eq, PartialEq, Clone, Deserialize, Serialize, Hash)]
645#[serde(from = "ObjectInner")]
646pub struct Object(Arc<ObjectInner>);
647
648fn is_object_debug_verbose() -> bool {
649    static SUI_OBJECT_DEBUG_VERBOSE: Lazy<bool> =
650        Lazy::new(|| std::env::var("SUI_OBJECT_DEBUG_VERBOSE").is_ok());
651    *SUI_OBJECT_DEBUG_VERBOSE
652}
653
654impl std::fmt::Debug for Object {
655    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
656        if is_object_debug_verbose() {
657            // Just call debug on ObjectInner for verbose debugging.
658            (*self.0).fmt(f)
659        } else {
660            f.debug_struct("Object")
661                .field("id", &self.id())
662                .field("version", &self.version())
663                .field("owner", &self.owner())
664                .finish()
665        }
666    }
667}
668
669impl From<ObjectInner> for Object {
670    fn from(inner: ObjectInner) -> Self {
671        Self(Arc::new(inner))
672    }
673}
674
675impl Object {
676    pub fn into_inner(self) -> ObjectInner {
677        match Arc::try_unwrap(self.0) {
678            Ok(inner) => inner,
679            Err(inner_arc) => (*inner_arc).clone(),
680        }
681    }
682
683    pub fn as_inner(&self) -> &ObjectInner {
684        &self.0
685    }
686
687    pub fn owner(&self) -> &Owner {
688        &self.0.owner
689    }
690
691    pub fn new_from_genesis(
692        data: Data,
693        owner: Owner,
694        previous_transaction: TransactionDigest,
695    ) -> Self {
696        ObjectInner {
697            data,
698            owner,
699            previous_transaction,
700            storage_rebate: 0,
701        }
702        .into()
703    }
704
705    /// Create a new Move object
706    pub fn new_move(o: MoveObject, owner: Owner, previous_transaction: TransactionDigest) -> Self {
707        ObjectInner {
708            data: Data::Move(o),
709            owner,
710            previous_transaction,
711            storage_rebate: 0,
712        }
713        .into()
714    }
715
716    pub fn new_package_from_data(data: Data, previous_transaction: TransactionDigest) -> Self {
717        ObjectInner {
718            data,
719            owner: Owner::Immutable,
720            previous_transaction,
721            storage_rebate: 0,
722        }
723        .into()
724    }
725
726    // Note: this will panic if `modules` is empty
727    pub fn new_from_package(package: MovePackage, previous_transaction: TransactionDigest) -> Self {
728        Self::new_package_from_data(Data::Package(package), previous_transaction)
729    }
730
731    pub fn new_package<'p>(
732        modules: &[CompiledModule],
733        previous_transaction: TransactionDigest,
734        protocol_config: &ProtocolConfig,
735        dependencies: impl IntoIterator<Item = &'p MovePackage>,
736    ) -> Result<Self, ExecutionError> {
737        Ok(Self::new_package_from_data(
738            Data::Package(MovePackage::new_initial(
739                modules,
740                protocol_config,
741                dependencies,
742            )?),
743            previous_transaction,
744        ))
745    }
746
747    pub fn new_upgraded_package<'p>(
748        previous_package: &MovePackage,
749        new_package_id: ObjectID,
750        modules: &[CompiledModule],
751        previous_transaction: TransactionDigest,
752        protocol_config: &ProtocolConfig,
753        dependencies: impl IntoIterator<Item = &'p MovePackage>,
754    ) -> Result<Self, ExecutionError> {
755        Ok(Self::new_package_from_data(
756            Data::Package(previous_package.new_upgraded(
757                new_package_id,
758                modules,
759                protocol_config,
760                dependencies,
761            )?),
762            previous_transaction,
763        ))
764    }
765
766    pub fn new_package_for_testing(
767        modules: &[CompiledModule],
768        previous_transaction: TransactionDigest,
769        dependencies: impl IntoIterator<Item = MovePackage>,
770    ) -> Result<Self, ExecutionError> {
771        let dependencies: Vec<_> = dependencies.into_iter().collect();
772        let config = ProtocolConfig::get_for_max_version_UNSAFE();
773        Self::new_package(modules, previous_transaction, &config, &dependencies)
774    }
775
776    /// Create a system package which is not subject to size limits. Panics if the object ID is not
777    /// a known system package.
778    pub fn new_system_package(
779        modules: &[CompiledModule],
780        version: SequenceNumber,
781        dependencies: Vec<ObjectID>,
782        previous_transaction: TransactionDigest,
783    ) -> Self {
784        let ret = Self::new_package_from_data(
785            Data::Package(MovePackage::new_system(version, modules, dependencies)),
786            previous_transaction,
787        );
788
789        #[cfg(not(msim))]
790        assert!(ret.is_system_package());
791
792        ret
793    }
794}
795
796impl std::ops::Deref for Object {
797    type Target = ObjectInner;
798    fn deref(&self) -> &Self::Target {
799        &self.0
800    }
801}
802
803impl std::ops::DerefMut for Object {
804    fn deref_mut(&mut self) -> &mut Self::Target {
805        Arc::make_mut(&mut self.0)
806    }
807}
808
809impl ObjectInner {
810    /// Returns true if the object is a system package.
811    pub fn is_system_package(&self) -> bool {
812        self.is_package() && is_system_package(self.id())
813    }
814
815    pub fn is_immutable(&self) -> bool {
816        self.owner.is_immutable()
817    }
818
819    pub fn is_address_owned(&self) -> bool {
820        self.owner.is_address_owned()
821    }
822
823    pub fn is_child_object(&self) -> bool {
824        self.owner.is_child_object()
825    }
826
827    pub fn is_shared(&self) -> bool {
828        self.owner.is_shared()
829    }
830
831    pub fn is_consensus(&self) -> bool {
832        self.owner.is_consensus()
833    }
834
835    pub fn get_single_owner(&self) -> Option<SuiAddress> {
836        self.owner.get_owner_address().ok()
837    }
838
839    // It's a common pattern to retrieve both the owner and object ID
840    // together, if it's owned by a singler owner.
841    pub fn get_owner_and_id(&self) -> Option<(Owner, ObjectID)> {
842        Some((self.owner.clone(), self.id()))
843    }
844
845    /// Return true if this object is a Move package, false if it is a Move value
846    pub fn is_package(&self) -> bool {
847        matches!(&self.data, Data::Package(_))
848    }
849
850    pub fn compute_object_reference(&self) -> ObjectRef {
851        (self.id(), self.version(), self.digest())
852    }
853
854    pub fn compute_full_object_reference(&self) -> FullObjectRef {
855        FullObjectRef(self.full_id(), self.version(), self.digest())
856    }
857
858    pub fn digest(&self) -> ObjectDigest {
859        ObjectDigest::new(default_hash(self))
860    }
861
862    pub fn id(&self) -> ObjectID {
863        use Data::*;
864
865        match &self.data {
866            Move(v) => v.id(),
867            Package(m) => m.id(),
868        }
869    }
870
871    pub fn full_id(&self) -> FullObjectID {
872        let id = self.id();
873        if let Some(start_version) = self.owner.start_version() {
874            FullObjectID::Consensus((id, start_version))
875        } else {
876            FullObjectID::Fastpath(id)
877        }
878    }
879
880    pub fn version(&self) -> SequenceNumber {
881        use Data::*;
882
883        match &self.data {
884            Move(o) => o.version(),
885            Package(p) => p.version(),
886        }
887    }
888
889    pub fn type_(&self) -> Option<&MoveObjectType> {
890        self.data.type_()
891    }
892
893    pub fn struct_tag(&self) -> Option<StructTag> {
894        self.data.struct_tag()
895    }
896
897    pub fn is_coin(&self) -> bool {
898        if let Some(move_object) = self.data.try_as_move() {
899            move_object.type_().is_coin()
900        } else {
901            false
902        }
903    }
904
905    pub fn is_gas_coin(&self) -> bool {
906        if let Some(move_object) = self.data.try_as_move() {
907            move_object.type_().is_gas_coin()
908        } else {
909            false
910        }
911    }
912
913    // TODO: use `MoveObj::get_balance_unsafe` instead.
914    // context: https://github.com/MystenLabs/sui/pull/10679#discussion_r1165877816
915    pub fn as_coin_maybe(&self) -> Option<Coin> {
916        if let Some(move_object) = self.data.try_as_move() {
917            if move_object.type_().is_coin() {
918                let coin: Coin = bcs::from_bytes(move_object.contents()).ok()?;
919                Some(coin)
920            } else {
921                None
922            }
923        } else {
924            None
925        }
926    }
927
928    pub fn coin_type_maybe(&self) -> Option<TypeTag> {
929        if let Some(move_object) = self.data.try_as_move() {
930            move_object.type_().coin_type_maybe()
931        } else {
932            None
933        }
934    }
935
936    /// Return the `value: u64` field of a `Coin<T>` type.
937    /// Useful for reading the coin without deserializing the object into a Move value
938    /// It is the caller's responsibility to check that `self` is a coin--this function
939    /// may panic or do something unexpected otherwise.
940    pub fn get_coin_value_unsafe(&self) -> u64 {
941        self.data.try_as_move().unwrap().get_coin_value_unsafe()
942    }
943
944    /// Approximate size of the object in bytes. This is used for gas metering.
945    /// This will be slightly different from the serialized size, but
946    /// we also don't want to serialize the object just to get the size.
947    /// This approximation should be good enough for gas metering.
948    pub fn object_size_for_gas_metering(&self) -> usize {
949        const DEFAULT_OWNER_SIZE: usize = 40;
950        const TRANSACTION_DIGEST_SIZE: usize = 32;
951        const STORAGE_REBATE_SIZE: usize = 8;
952
953        let meta_data_size = DEFAULT_OWNER_SIZE + TRANSACTION_DIGEST_SIZE + STORAGE_REBATE_SIZE;
954        let data_size = match &self.data {
955            Data::Move(m) => m.object_size_for_gas_metering(),
956            Data::Package(p) => p.object_size_for_gas_metering(),
957        };
958        meta_data_size + data_size
959    }
960
961    /// Change the owner of `self` to `new_owner`.
962    pub fn transfer(&mut self, new_owner: SuiAddress) {
963        self.owner = Owner::AddressOwner(new_owner);
964    }
965
966    /// Get a `MoveStructLayout` for `self`.
967    /// The `resolver` value must contain the module that declares `self.type_` and the (transitive)
968    /// dependencies of `self.type_` in order for this to succeed. Failure will result in an `ObjectSerializationError`
969    pub fn get_layout(
970        &self,
971        resolver: &impl GetModule,
972    ) -> Result<Option<MoveStructLayout>, SuiError> {
973        match &self.data {
974            Data::Move(m) => Ok(Some(m.get_layout(resolver)?)),
975            Data::Package(_) => Ok(None),
976        }
977    }
978
979    /// Treat the object type as a Move struct with one type parameter,
980    /// like this: `S<T>`.
981    /// Returns the inner parameter type `T`.
982    pub fn get_move_template_type(&self) -> SuiResult<TypeTag> {
983        let move_struct = self
984            .data
985            .struct_tag()
986            .ok_or_else(|| SuiErrorKind::TypeError {
987                error: "Object must be a Move object".to_owned(),
988            })?;
989        fp_ensure!(
990            move_struct.type_params.len() == 1,
991            SuiErrorKind::TypeError {
992                error: "Move object struct must have one type parameter".to_owned()
993            }
994            .into()
995        );
996        // Index access safe due to checks above.
997        let type_tag = move_struct.type_params[0].clone();
998        Ok(type_tag)
999    }
1000
1001    pub fn to_rust<'de, T: Deserialize<'de>>(&'de self) -> Option<T> {
1002        self.data.try_as_move().and_then(|data| data.to_rust())
1003    }
1004}
1005
1006// Testing-related APIs.
1007impl Object {
1008    /// Get the total amount of SUI embedded in `self`, including both Move objects and the storage rebate
1009    pub fn get_total_sui(&self, layout_resolver: &mut dyn LayoutResolver) -> Result<u64, SuiError> {
1010        Ok(self.storage_rebate
1011            + match &self.data {
1012                Data::Move(m) => m.get_total_sui(layout_resolver)?,
1013                Data::Package(_) => 0,
1014            })
1015    }
1016
1017    pub fn immutable_with_id_for_testing(id: ObjectID) -> Self {
1018        let data = Data::Move(MoveObject {
1019            type_: GasCoin::type_().into(),
1020            has_public_transfer: true,
1021            version: OBJECT_START_VERSION,
1022            contents: GasCoin::new(id, GAS_VALUE_FOR_TESTING).to_bcs_bytes(),
1023        });
1024        ObjectInner {
1025            owner: Owner::Immutable,
1026            data,
1027            previous_transaction: TransactionDigest::genesis_marker(),
1028            storage_rebate: 0,
1029        }
1030        .into()
1031    }
1032
1033    pub fn immutable_for_testing() -> Self {
1034        thread_local! {
1035            static IMMUTABLE_OBJECT_ID: ObjectID = ObjectID::random();
1036        }
1037
1038        Self::immutable_with_id_for_testing(IMMUTABLE_OBJECT_ID.with(|id| *id))
1039    }
1040
1041    /// Make a new random test shared object.
1042    pub fn shared_for_testing() -> Object {
1043        let id = ObjectID::random();
1044        let obj = MoveObject::new_gas_coin(OBJECT_START_VERSION, id, 10);
1045        let owner = Owner::Shared {
1046            initial_shared_version: obj.version(),
1047        };
1048        Object::new_move(obj, owner, TransactionDigest::genesis_marker())
1049    }
1050
1051    pub fn with_id_owner_gas_for_testing(id: ObjectID, owner: SuiAddress, gas: u64) -> Self {
1052        let data = Data::Move(MoveObject {
1053            type_: GasCoin::type_().into(),
1054            has_public_transfer: true,
1055            version: OBJECT_START_VERSION,
1056            contents: GasCoin::new(id, gas).to_bcs_bytes(),
1057        });
1058        ObjectInner {
1059            owner: Owner::AddressOwner(owner),
1060            data,
1061            previous_transaction: TransactionDigest::genesis_marker(),
1062            storage_rebate: 0,
1063        }
1064        .into()
1065    }
1066
1067    pub fn treasury_cap_for_testing(struct_tag: StructTag, treasury_cap: TreasuryCap) -> Self {
1068        let data = Data::Move(MoveObject {
1069            type_: TreasuryCap::type_(struct_tag).into(),
1070            has_public_transfer: true,
1071            version: OBJECT_START_VERSION,
1072            contents: bcs::to_bytes(&treasury_cap).expect("Failed to serialize"),
1073        });
1074        ObjectInner {
1075            owner: Owner::Immutable,
1076            data,
1077            previous_transaction: TransactionDigest::genesis_marker(),
1078            storage_rebate: 0,
1079        }
1080        .into()
1081    }
1082
1083    pub fn coin_metadata_for_testing(struct_tag: StructTag, metadata: CoinMetadata) -> Self {
1084        let data = Data::Move(MoveObject {
1085            type_: CoinMetadata::type_(struct_tag).into(),
1086            has_public_transfer: true,
1087            version: OBJECT_START_VERSION,
1088            contents: bcs::to_bytes(&metadata).expect("Failed to serialize"),
1089        });
1090        ObjectInner {
1091            owner: Owner::Immutable,
1092            data,
1093            previous_transaction: TransactionDigest::genesis_marker(),
1094            storage_rebate: 0,
1095        }
1096        .into()
1097    }
1098
1099    pub fn with_object_owner_for_testing(id: ObjectID, owner: ObjectID) -> Self {
1100        let data = Data::Move(MoveObject {
1101            type_: GasCoin::type_().into(),
1102            has_public_transfer: true,
1103            version: OBJECT_START_VERSION,
1104            contents: GasCoin::new(id, GAS_VALUE_FOR_TESTING).to_bcs_bytes(),
1105        });
1106        ObjectInner {
1107            owner: Owner::ObjectOwner(owner.into()),
1108            data,
1109            previous_transaction: TransactionDigest::genesis_marker(),
1110            storage_rebate: 0,
1111        }
1112        .into()
1113    }
1114
1115    pub fn with_id_owner_for_testing(id: ObjectID, owner: SuiAddress) -> Self {
1116        // For testing, we provide sufficient gas by default.
1117        Self::with_id_owner_gas_for_testing(id, owner, GAS_VALUE_FOR_TESTING)
1118    }
1119
1120    pub fn with_id_owner_version_for_testing(
1121        id: ObjectID,
1122        version: SequenceNumber,
1123        owner: Owner,
1124    ) -> Self {
1125        let data = Data::Move(MoveObject {
1126            type_: GasCoin::type_().into(),
1127            has_public_transfer: true,
1128            version,
1129            contents: GasCoin::new(id, GAS_VALUE_FOR_TESTING).to_bcs_bytes(),
1130        });
1131        ObjectInner {
1132            owner,
1133            data,
1134            previous_transaction: TransactionDigest::genesis_marker(),
1135            storage_rebate: 0,
1136        }
1137        .into()
1138    }
1139
1140    pub fn with_owner_for_testing(owner: SuiAddress) -> Self {
1141        Self::with_id_owner_for_testing(ObjectID::random(), owner)
1142    }
1143
1144    /// Generate a new gas coin worth `value` with a random object ID and owner
1145    /// For testing purposes only
1146    pub fn new_gas_with_balance_and_owner_for_testing(value: u64, owner: SuiAddress) -> Self {
1147        let obj = MoveObject::new_gas_coin(OBJECT_START_VERSION, ObjectID::random(), value);
1148        Object::new_move(
1149            obj,
1150            Owner::AddressOwner(owner),
1151            TransactionDigest::genesis_marker(),
1152        )
1153    }
1154
1155    /// Generate a new gas coin object with default balance and random owner.
1156    pub fn new_gas_for_testing() -> Self {
1157        let gas_object_id = ObjectID::random();
1158        let (owner, _) = deterministic_random_account_key();
1159        Object::with_id_owner_for_testing(gas_object_id, owner)
1160    }
1161}
1162
1163/// Make a few test gas objects (all with the same random owner).
1164pub fn generate_test_gas_objects() -> Vec<Object> {
1165    thread_local! {
1166        static GAS_OBJECTS: Vec<Object> = (0..50)
1167            .map(|_| {
1168                let gas_object_id = ObjectID::random();
1169                let (owner, _) = deterministic_random_account_key();
1170                Object::with_id_owner_for_testing(gas_object_id, owner)
1171            })
1172            .collect();
1173    }
1174
1175    GAS_OBJECTS.with(|v| v.clone())
1176}
1177
1178#[allow(clippy::large_enum_variant)]
1179#[derive(Serialize, Deserialize, Debug)]
1180#[serde(tag = "status", content = "details")]
1181pub enum ObjectRead {
1182    NotExists(ObjectID),
1183    Exists(ObjectRef, Object, Option<MoveStructLayout>),
1184    Deleted(ObjectRef),
1185}
1186
1187impl ObjectRead {
1188    /// Returns the object value if there is any, otherwise an Err if
1189    /// the object does not exist or is deleted.
1190    pub fn into_object(self) -> UserInputResult<Object> {
1191        match self {
1192            Self::Deleted(oref) => Err(UserInputError::ObjectDeleted { object_ref: oref }),
1193            Self::NotExists(id) => Err(UserInputError::ObjectNotFound {
1194                object_id: id,
1195                version: None,
1196            }),
1197            Self::Exists(_, o, _) => Ok(o),
1198        }
1199    }
1200
1201    pub fn object(&self) -> UserInputResult<&Object> {
1202        match self {
1203            Self::Deleted(oref) => Err(UserInputError::ObjectDeleted { object_ref: *oref }),
1204            Self::NotExists(id) => Err(UserInputError::ObjectNotFound {
1205                object_id: *id,
1206                version: None,
1207            }),
1208            Self::Exists(_, o, _) => Ok(o),
1209        }
1210    }
1211
1212    pub fn object_id(&self) -> ObjectID {
1213        match self {
1214            Self::Deleted(oref) => oref.0,
1215            Self::NotExists(id) => *id,
1216            Self::Exists(oref, _, _) => oref.0,
1217        }
1218    }
1219}
1220
1221impl Display for ObjectRead {
1222    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1223        match self {
1224            Self::Deleted(oref) => {
1225                write!(f, "ObjectRead::Deleted ({:?})", oref)
1226            }
1227            Self::NotExists(id) => {
1228                write!(f, "ObjectRead::NotExists ({:?})", id)
1229            }
1230            Self::Exists(oref, _, _) => {
1231                write!(f, "ObjectRead::Exists ({:?})", oref)
1232            }
1233        }
1234    }
1235}
1236
1237#[allow(clippy::large_enum_variant)]
1238#[derive(Serialize, Deserialize, Debug)]
1239#[serde(tag = "status", content = "details")]
1240pub enum PastObjectRead {
1241    /// The object does not exist
1242    ObjectNotExists(ObjectID),
1243    /// The object is found to be deleted with this version
1244    ObjectDeleted(ObjectRef),
1245    /// The object exists and is found with this version
1246    VersionFound(ObjectRef, Object, Option<MoveStructLayout>),
1247    /// The object exists but not found with this version
1248    VersionNotFound(ObjectID, SequenceNumber),
1249    /// The asked object version is higher than the latest
1250    VersionTooHigh {
1251        object_id: ObjectID,
1252        asked_version: SequenceNumber,
1253        latest_version: SequenceNumber,
1254    },
1255}
1256
1257impl PastObjectRead {
1258    /// Returns the object value if there is any, otherwise an Err
1259    pub fn into_object(self) -> UserInputResult<Object> {
1260        match self {
1261            Self::ObjectDeleted(oref) => Err(UserInputError::ObjectDeleted { object_ref: oref }),
1262            Self::ObjectNotExists(id) => Err(UserInputError::ObjectNotFound {
1263                object_id: id,
1264                version: None,
1265            }),
1266            Self::VersionFound(_, o, _) => Ok(o),
1267            Self::VersionNotFound(object_id, version) => Err(UserInputError::ObjectNotFound {
1268                object_id,
1269                version: Some(version),
1270            }),
1271            Self::VersionTooHigh {
1272                object_id,
1273                asked_version,
1274                latest_version,
1275            } => Err(UserInputError::ObjectSequenceNumberTooHigh {
1276                object_id,
1277                asked_version,
1278                latest_version,
1279            }),
1280        }
1281    }
1282}
1283
1284impl Display for PastObjectRead {
1285    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1286        match self {
1287            Self::ObjectDeleted(oref) => {
1288                write!(f, "PastObjectRead::ObjectDeleted ({:?})", oref)
1289            }
1290            Self::ObjectNotExists(id) => {
1291                write!(f, "PastObjectRead::ObjectNotExists ({:?})", id)
1292            }
1293            Self::VersionFound(oref, _, _) => {
1294                write!(f, "PastObjectRead::VersionFound ({:?})", oref)
1295            }
1296            Self::VersionNotFound(object_id, version) => {
1297                write!(
1298                    f,
1299                    "PastObjectRead::VersionNotFound ({:?}, asked sequence number {:?})",
1300                    object_id, version
1301                )
1302            }
1303            Self::VersionTooHigh {
1304                object_id,
1305                asked_version,
1306                latest_version,
1307            } => {
1308                write!(
1309                    f,
1310                    "PastObjectRead::VersionTooHigh ({:?}, asked sequence number {:?}, latest sequence number {:?})",
1311                    object_id, asked_version, latest_version
1312                )
1313            }
1314        }
1315    }
1316}
1317
1318#[cfg(test)]
1319mod tests {
1320    use crate::object::{OBJECT_START_VERSION, Object, Owner};
1321    use crate::{
1322        base_types::{ObjectID, SuiAddress, TransactionDigest},
1323        gas_coin::GasCoin,
1324    };
1325
1326    // Ensure that object digest computation and bcs serialized format are not inadvertently changed.
1327    #[test]
1328    fn test_object_digest_and_serialized_format() {
1329        let g =
1330            GasCoin::new_for_testing_with_id(ObjectID::ZERO, 123).to_object(OBJECT_START_VERSION);
1331        let o = Object::new_move(
1332            g,
1333            Owner::AddressOwner(SuiAddress::ZERO),
1334            TransactionDigest::ZERO,
1335        );
1336        let bytes = bcs::to_bytes(&o).unwrap();
1337
1338        assert_eq!(
1339            bytes,
1340            [
1341                0, 1, 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,
1342                0, 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,
1343                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,
1344                0, 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,
1345                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1346            ]
1347        );
1348
1349        let objref = format!("{:?}", o.compute_object_reference());
1350        assert_eq!(
1351            objref,
1352            "(0x0000000000000000000000000000000000000000000000000000000000000000, SequenceNumber(1), o#59tZq65HVqZjUyNtD7BCGLTD87N5cpayYwEFrtwR4aMz)"
1353        );
1354    }
1355
1356    #[test]
1357    fn test_get_coin_value_unsafe() {
1358        fn test_for_value(v: u64) {
1359            let g = GasCoin::new_for_testing(v).to_object(OBJECT_START_VERSION);
1360            assert_eq!(g.get_coin_value_unsafe(), v);
1361            assert_eq!(GasCoin::try_from(&g).unwrap().value(), v);
1362        }
1363
1364        test_for_value(0);
1365        test_for_value(1);
1366        test_for_value(8);
1367        test_for_value(9);
1368        test_for_value(u8::MAX as u64);
1369        test_for_value(u8::MAX as u64 + 1);
1370        test_for_value(u16::MAX as u64);
1371        test_for_value(u16::MAX as u64 + 1);
1372        test_for_value(u32::MAX as u64);
1373        test_for_value(u32::MAX as u64 + 1);
1374        test_for_value(u64::MAX);
1375    }
1376
1377    #[test]
1378    fn test_set_coin_value_unsafe() {
1379        fn test_for_value(v: u64) {
1380            let mut g = GasCoin::new_for_testing(u64::MAX).to_object(OBJECT_START_VERSION);
1381            g.set_coin_value_unsafe(v);
1382            assert_eq!(g.get_coin_value_unsafe(), v);
1383            assert_eq!(GasCoin::try_from(&g).unwrap().value(), v);
1384            assert_eq!(g.version(), OBJECT_START_VERSION);
1385            assert_eq!(g.contents().len(), 40);
1386        }
1387
1388        test_for_value(0);
1389        test_for_value(1);
1390        test_for_value(8);
1391        test_for_value(9);
1392        test_for_value(u8::MAX as u64);
1393        test_for_value(u8::MAX as u64 + 1);
1394        test_for_value(u16::MAX as u64);
1395        test_for_value(u16::MAX as u64 + 1);
1396        test_for_value(u32::MAX as u64);
1397        test_for_value(u32::MAX as u64 + 1);
1398        test_for_value(u64::MAX);
1399    }
1400}