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