Skip to main content

sui_types/
base_types.rs

1// Copyright (c) 2021, Facebook, Inc. and its affiliates
2// Copyright (c) Mysten Labs, Inc.
3// SPDX-License-Identifier: Apache-2.0
4
5use crate::MOVE_STDLIB_ADDRESS;
6use crate::MoveTypeTagTrait;
7use crate::MoveTypeTagTraitGeneric;
8use crate::SUI_CLOCK_OBJECT_ID;
9use crate::SUI_FRAMEWORK_ADDRESS;
10use crate::SUI_SYSTEM_ADDRESS;
11use crate::accumulator_root::accumulator_value_balance_type_maybe;
12use crate::balance::Balance;
13use crate::coin::COIN_MODULE_NAME;
14use crate::coin::COIN_STRUCT_NAME;
15use crate::coin::Coin;
16use crate::coin::CoinMetadata;
17use crate::coin::TreasuryCap;
18use crate::coin_registry::Currency;
19pub use crate::committee::EpochId;
20use crate::crypto::{
21    AuthorityPublicKeyBytes, DefaultHash, PublicKey, SignatureScheme, SuiPublicKey, SuiSignature,
22};
23pub use crate::digests::{ObjectDigest, TransactionDigest, TransactionEffectsDigest};
24use crate::dynamic_field::DynamicFieldInfo;
25use crate::dynamic_field::DynamicFieldType;
26use crate::dynamic_field::{DYNAMIC_FIELD_FIELD_STRUCT_NAME, DYNAMIC_FIELD_MODULE_NAME};
27use crate::effects::TransactionEffects;
28use crate::effects::TransactionEffectsAPI;
29use crate::epoch_data::EpochData;
30use crate::error::SuiError;
31use crate::error::SuiErrorKind;
32use crate::error::{ExecutionError, SuiResult};
33use crate::execution_status::ExecutionErrorKind;
34use crate::gas_coin::GAS;
35use crate::gas_coin::GasCoin;
36use crate::governance::STAKED_SUI_STRUCT_NAME;
37use crate::governance::STAKING_POOL_MODULE_NAME;
38use crate::governance::StakedSui;
39use crate::id::RESOLVED_SUI_ID;
40use crate::messages_checkpoint::CheckpointTimestamp;
41use crate::multisig::MultiSigPublicKey;
42use crate::object::{Object, Owner};
43use crate::parse_sui_struct_tag;
44use crate::signature::GenericSignature;
45use crate::sui_serde::Readable;
46use crate::sui_serde::to_custom_deser_error;
47use crate::sui_serde::to_sui_struct_tag_string;
48use crate::transaction::Transaction;
49use crate::transaction::VerifiedTransaction;
50use crate::zk_login_authenticator::ZkLoginAuthenticator;
51use anyhow::anyhow;
52use fastcrypto::encoding::decode_bytes_hex;
53use fastcrypto::encoding::{Encoding, Hex};
54use fastcrypto::hash::HashFunction;
55use fastcrypto::traits::AllowedRng;
56use fastcrypto_zkp::bn254::zk_login::ZkLoginInputs;
57use move_binary_format::CompiledModule;
58use move_binary_format::file_format::SignatureToken;
59use move_bytecode_utils::resolve_struct;
60use move_core_types::account_address::AccountAddress;
61use move_core_types::annotated_value as A;
62use move_core_types::ident_str;
63use move_core_types::identifier::IdentStr;
64use move_core_types::language_storage::ModuleId;
65use move_core_types::language_storage::StructTag;
66use move_core_types::language_storage::TypeTag;
67use rand::Rng;
68use schemars::JsonSchema;
69use serde::Deserializer;
70use serde::Serializer;
71use serde::ser::Error;
72use serde::ser::SerializeSeq;
73use serde::{Deserialize, Serialize};
74use serde_with::DeserializeAs;
75use serde_with::SerializeAs;
76use serde_with::serde_as;
77use shared_crypto::intent::HashingIntentScope;
78use std::borrow::Cow;
79use std::cmp::max;
80use std::convert::{TryFrom, TryInto};
81use std::fmt;
82use std::str::FromStr;
83use sui_protocol_config::ProtocolConfig;
84
85#[cfg(test)]
86#[path = "unit_tests/base_types_tests.rs"]
87mod base_types_tests;
88
89#[cfg(test)]
90#[path = "unit_tests/accumulator_types_tests.rs"]
91mod accumulator_types_tests;
92
93#[derive(
94    Eq,
95    PartialEq,
96    Ord,
97    PartialOrd,
98    Copy,
99    Clone,
100    Hash,
101    Default,
102    Debug,
103    Serialize,
104    Deserialize,
105    JsonSchema,
106)]
107#[cfg_attr(feature = "fuzzing", derive(proptest_derive::Arbitrary))]
108pub struct SequenceNumber(u64);
109
110impl SequenceNumber {
111    pub fn one_before(&self) -> Option<SequenceNumber> {
112        if self.0 == 0 {
113            None
114        } else {
115            Some(SequenceNumber(self.0 - 1))
116        }
117    }
118
119    pub fn next(&self) -> SequenceNumber {
120        SequenceNumber(self.0 + 1)
121    }
122}
123
124pub type TxSequenceNumber = u64;
125
126impl fmt::Display for SequenceNumber {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        write!(f, "{:#x}", self.0)
129    }
130}
131
132pub type VersionNumber = SequenceNumber;
133
134#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Hash, Default, Debug, Serialize, Deserialize)]
135pub struct UserData(pub Option<[u8; 32]>);
136
137pub type AuthorityName = AuthorityPublicKeyBytes;
138
139pub trait ConciseableName<'a> {
140    type ConciseTypeRef: std::fmt::Debug;
141    type ConciseType: std::fmt::Debug;
142
143    fn concise(&'a self) -> Self::ConciseTypeRef;
144    fn concise_owned(&self) -> Self::ConciseType;
145}
146
147#[serde_as]
148#[derive(Eq, PartialEq, Clone, Copy, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)]
149pub struct ObjectID(
150    #[schemars(with = "Hex")]
151    #[serde_as(as = "Readable<HexAccountAddress, _>")]
152    AccountAddress,
153);
154
155#[serde_as]
156#[derive(Debug, Eq, PartialEq, Clone, Copy, PartialOrd, Ord, Hash, Serialize, Deserialize)]
157pub enum FullObjectID {
158    Fastpath(ObjectID),
159    Consensus(ConsensusObjectSequenceKey),
160}
161
162impl FullObjectID {
163    pub fn new(object_id: ObjectID, start_version: Option<SequenceNumber>) -> Self {
164        if let Some(start_version) = start_version {
165            Self::Consensus((object_id, start_version))
166        } else {
167            Self::Fastpath(object_id)
168        }
169    }
170
171    pub fn id(&self) -> ObjectID {
172        match &self {
173            FullObjectID::Fastpath(object_id) => *object_id,
174            FullObjectID::Consensus(consensus_object_sequence_key) => {
175                consensus_object_sequence_key.0
176            }
177        }
178    }
179}
180
181pub type VersionDigest = (SequenceNumber, ObjectDigest);
182
183pub type ObjectRef = (ObjectID, SequenceNumber, ObjectDigest);
184
185pub fn random_object_ref() -> ObjectRef {
186    (
187        ObjectID::random(),
188        SequenceNumber::new(),
189        ObjectDigest::new([0; 32]),
190    )
191}
192
193pub fn update_object_ref_for_testing(object_ref: ObjectRef) -> ObjectRef {
194    (
195        object_ref.0,
196        object_ref.1.next(),
197        ObjectDigest::new([0; 32]),
198    )
199}
200
201#[derive(Debug, Eq, PartialEq, Clone, Copy, PartialOrd, Ord, Hash, Serialize, Deserialize)]
202pub struct FullObjectRef(pub FullObjectID, pub SequenceNumber, pub ObjectDigest);
203
204impl FullObjectRef {
205    pub fn from_fastpath_ref(object_ref: ObjectRef) -> Self {
206        Self(
207            FullObjectID::Fastpath(object_ref.0),
208            object_ref.1,
209            object_ref.2,
210        )
211    }
212
213    pub fn from_object_ref_and_owner(object_ref: ObjectRef, owner: &Owner) -> Self {
214        let full_id = if let Some(start_version) = owner.start_version() {
215            FullObjectID::Consensus((object_ref.0, start_version))
216        } else {
217            FullObjectID::Fastpath(object_ref.0)
218        };
219        Self(full_id, object_ref.1, object_ref.2)
220    }
221
222    pub fn as_object_ref(&self) -> ObjectRef {
223        (self.0.id(), self.1, self.2)
224    }
225}
226/// Represents an distinct stream of object versions for a consensus object,
227/// based on the object ID and start version.
228pub type ConsensusObjectSequenceKey = (ObjectID, SequenceNumber);
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq)]
231pub struct ConsensusObjectVersion {
232    pub initial_shared_version: SequenceNumber,
233    pub version: SequenceNumber,
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
237pub struct SystemObjectVersions {
238    accumulator_version: Option<ConsensusObjectVersion>,
239}
240
241impl SystemObjectVersions {
242    pub fn new(accumulator_version: Option<ConsensusObjectVersion>) -> Self {
243        Self {
244            accumulator_version,
245        }
246    }
247
248    pub fn empty() -> Self {
249        Self::new(None)
250    }
251
252    pub fn get(&self, object_id: &ObjectID) -> Option<ConsensusObjectVersion> {
253        if *object_id == crate::SUI_ACCUMULATOR_ROOT_OBJECT_ID {
254            self.accumulator_version
255        } else {
256            panic!("{object_id} is not an implicitly read system object")
257        }
258    }
259}
260
261/// Wrapper around StructTag with a space-efficient representation for common types like coins
262/// The StructTag for a gas coin is 84 bytes, so using 1 byte instead is a win.
263/// The inner representation is private to prevent incorrectly constructing an `Other` instead of
264/// one of the specialized variants, e.g. `Other(GasCoin::type_())` instead of `GasCoin`
265#[derive(Eq, PartialEq, PartialOrd, Ord, Debug, Clone, Deserialize, Serialize, Hash)]
266pub struct MoveObjectType(MoveObjectType_);
267
268/// Even though it is declared public, it is the "private", internal representation for
269/// `MoveObjectType`
270#[derive(Eq, PartialEq, PartialOrd, Ord, Debug, Clone, Deserialize, Serialize, Hash)]
271pub enum MoveObjectType_ {
272    /// A type that is not `0x2::coin::Coin<T>`
273    Other(StructTag),
274    /// A SUI coin (i.e., `0x2::coin::Coin<0x2::sui::SUI>`)
275    GasCoin,
276    /// A record of a staked SUI coin (i.e., `0x3::staking_pool::StakedSui`)
277    StakedSui,
278    /// A non-SUI coin type (i.e., `0x2::coin::Coin<T> where T != 0x2::sui::SUI`)
279    Coin(TypeTag),
280    /// A SUI balance accumulator field
281    /// (i.e., `0x2::dynamic_field::Field<0x2::accumulator::Key<0x2::balance::Balance<0x2::sui::SUI>>, 0x2::accumulator::U128>`)
282    SuiBalanceAccumulatorField,
283    /// A non-SUI balance accumulator field
284    /// (i.e., `0x2::dynamic_field::Field<0x2::accumulator::Key<0x2::balance::Balance<T>>, 0x2::accumulator::U128>`
285    /// where T != 0x2::sui::SUI)
286    BalanceAccumulatorField(TypeTag),
287    // NOTE: if adding a new type here, and there are existing on-chain objects of that
288    // type with Other(_), that is ok, but you must hand-roll PartialEq/Eq/Ord/maybe Hash
289    // to make sure the new type and Other(_) are interpreted consistently.
290}
291
292impl MoveObjectType {
293    pub fn gas_coin() -> Self {
294        Self(MoveObjectType_::GasCoin)
295    }
296
297    pub fn is_efficient_representation(&self) -> bool {
298        !matches!(self.0, MoveObjectType_::Other(_))
299    }
300
301    pub fn coin(coin_type: TypeTag) -> Self {
302        Self(if GAS::is_gas_type(&coin_type) {
303            MoveObjectType_::GasCoin
304        } else {
305            MoveObjectType_::Coin(coin_type)
306        })
307    }
308
309    pub fn staked_sui() -> Self {
310        Self(MoveObjectType_::StakedSui)
311    }
312
313    pub fn address(&self) -> AccountAddress {
314        match &self.0 {
315            MoveObjectType_::GasCoin | MoveObjectType_::Coin(_) => SUI_FRAMEWORK_ADDRESS,
316            MoveObjectType_::StakedSui => SUI_SYSTEM_ADDRESS,
317            MoveObjectType_::SuiBalanceAccumulatorField
318            | MoveObjectType_::BalanceAccumulatorField(_) => SUI_FRAMEWORK_ADDRESS,
319            MoveObjectType_::Other(s) => s.address,
320        }
321    }
322
323    pub fn module(&self) -> &IdentStr {
324        match &self.0 {
325            MoveObjectType_::GasCoin | MoveObjectType_::Coin(_) => COIN_MODULE_NAME,
326            MoveObjectType_::StakedSui => STAKING_POOL_MODULE_NAME,
327            MoveObjectType_::SuiBalanceAccumulatorField
328            | MoveObjectType_::BalanceAccumulatorField(_) => DYNAMIC_FIELD_MODULE_NAME,
329            MoveObjectType_::Other(s) => &s.module,
330        }
331    }
332
333    pub fn name(&self) -> &IdentStr {
334        match &self.0 {
335            MoveObjectType_::GasCoin | MoveObjectType_::Coin(_) => COIN_STRUCT_NAME,
336            MoveObjectType_::StakedSui => STAKED_SUI_STRUCT_NAME,
337            MoveObjectType_::SuiBalanceAccumulatorField
338            | MoveObjectType_::BalanceAccumulatorField(_) => DYNAMIC_FIELD_FIELD_STRUCT_NAME,
339            MoveObjectType_::Other(s) => &s.name,
340        }
341    }
342
343    pub fn type_params(&self) -> Vec<Cow<'_, TypeTag>> {
344        match &self.0 {
345            MoveObjectType_::GasCoin => vec![Cow::Owned(GAS::type_tag())],
346            MoveObjectType_::StakedSui => vec![],
347            MoveObjectType_::Coin(inner) => vec![Cow::Borrowed(inner)],
348            MoveObjectType_::SuiBalanceAccumulatorField => {
349                Self::balance_accumulator_field_type_params(GAS::type_tag())
350                    .into_iter()
351                    .map(Cow::Owned)
352                    .collect()
353            }
354            MoveObjectType_::BalanceAccumulatorField(inner) => {
355                Self::balance_accumulator_field_type_params(inner.clone())
356                    .into_iter()
357                    .map(Cow::Owned)
358                    .collect()
359            }
360            MoveObjectType_::Other(s) => s.type_params.iter().map(Cow::Borrowed).collect(),
361        }
362    }
363
364    pub fn into_type_params(self) -> Vec<TypeTag> {
365        match self.0 {
366            MoveObjectType_::GasCoin => vec![GAS::type_tag()],
367            MoveObjectType_::StakedSui => vec![],
368            MoveObjectType_::Coin(inner) => vec![inner],
369            MoveObjectType_::SuiBalanceAccumulatorField => {
370                Self::balance_accumulator_field_type_params(GAS::type_tag())
371            }
372            MoveObjectType_::BalanceAccumulatorField(inner) => {
373                Self::balance_accumulator_field_type_params(inner)
374            }
375            MoveObjectType_::Other(s) => s.type_params,
376        }
377    }
378
379    pub fn coin_type_maybe(&self) -> Option<TypeTag> {
380        match &self.0 {
381            MoveObjectType_::GasCoin => Some(GAS::type_tag()),
382            MoveObjectType_::Coin(inner) => Some(inner.clone()),
383            MoveObjectType_::StakedSui => None,
384            MoveObjectType_::SuiBalanceAccumulatorField => None,
385            MoveObjectType_::BalanceAccumulatorField(_) => None,
386            MoveObjectType_::Other(_) => None,
387        }
388    }
389
390    pub fn balance_accumulator_field_type_maybe(&self) -> Option<TypeTag> {
391        match &self.0 {
392            MoveObjectType_::SuiBalanceAccumulatorField => Some(GAS::type_tag()),
393            MoveObjectType_::BalanceAccumulatorField(inner) => Some(inner.clone()),
394            _ => None,
395        }
396    }
397
398    pub fn is_balance_accumulator_field(&self) -> bool {
399        matches!(
400            self.0,
401            MoveObjectType_::SuiBalanceAccumulatorField
402                | MoveObjectType_::BalanceAccumulatorField(_)
403        )
404    }
405
406    pub fn is_sui_balance_accumulator_field(&self) -> bool {
407        matches!(self.0, MoveObjectType_::SuiBalanceAccumulatorField)
408    }
409
410    pub fn module_id(&self) -> ModuleId {
411        ModuleId::new(self.address(), self.module().to_owned())
412    }
413
414    pub fn size_for_gas_metering(&self) -> usize {
415        // unwraps safe because a `StructTag` cannot fail to serialize
416        match &self.0 {
417            MoveObjectType_::GasCoin => 1,
418            MoveObjectType_::StakedSui => 1,
419            MoveObjectType_::Coin(inner) => bcs::serialized_size(inner).unwrap() + 1,
420            MoveObjectType_::SuiBalanceAccumulatorField => 1,
421            MoveObjectType_::BalanceAccumulatorField(inner) => {
422                bcs::serialized_size(inner).unwrap() + 1
423            }
424            MoveObjectType_::Other(s) => bcs::serialized_size(s).unwrap() + 1,
425        }
426    }
427
428    /// Return true if `self` is `0x2::coin::Coin<T>` for some T (note: T can be SUI)
429    pub fn is_coin(&self) -> bool {
430        match &self.0 {
431            MoveObjectType_::GasCoin | MoveObjectType_::Coin(_) => true,
432            MoveObjectType_::StakedSui
433            | MoveObjectType_::SuiBalanceAccumulatorField
434            | MoveObjectType_::BalanceAccumulatorField(_)
435            | MoveObjectType_::Other(_) => false,
436        }
437    }
438
439    /// Return true if `self` is 0x2::coin::Coin<0x2::sui::SUI>
440    pub fn is_gas_coin(&self) -> bool {
441        match &self.0 {
442            MoveObjectType_::GasCoin => true,
443            MoveObjectType_::StakedSui
444            | MoveObjectType_::Coin(_)
445            | MoveObjectType_::SuiBalanceAccumulatorField
446            | MoveObjectType_::BalanceAccumulatorField(_)
447            | MoveObjectType_::Other(_) => false,
448        }
449    }
450
451    /// Return true if `self` is `0x2::coin::Coin<t>`
452    pub fn is_coin_t(&self, t: &TypeTag) -> bool {
453        match &self.0 {
454            MoveObjectType_::GasCoin => GAS::is_gas_type(t),
455            MoveObjectType_::Coin(c) => t == c,
456            MoveObjectType_::StakedSui
457            | MoveObjectType_::SuiBalanceAccumulatorField
458            | MoveObjectType_::BalanceAccumulatorField(_)
459            | MoveObjectType_::Other(_) => false,
460        }
461    }
462
463    pub fn is_staked_sui(&self) -> bool {
464        match &self.0 {
465            MoveObjectType_::StakedSui => true,
466            MoveObjectType_::GasCoin
467            | MoveObjectType_::Coin(_)
468            | MoveObjectType_::SuiBalanceAccumulatorField
469            | MoveObjectType_::BalanceAccumulatorField(_)
470            | MoveObjectType_::Other(_) => false,
471        }
472    }
473
474    pub fn is_coin_metadata(&self) -> bool {
475        match &self.0 {
476            MoveObjectType_::GasCoin
477            | MoveObjectType_::StakedSui
478            | MoveObjectType_::Coin(_)
479            | MoveObjectType_::SuiBalanceAccumulatorField
480            | MoveObjectType_::BalanceAccumulatorField(_) => false,
481            MoveObjectType_::Other(s) => CoinMetadata::is_coin_metadata(s),
482        }
483    }
484
485    pub fn is_currency(&self) -> bool {
486        match &self.0 {
487            MoveObjectType_::GasCoin
488            | MoveObjectType_::StakedSui
489            | MoveObjectType_::Coin(_)
490            | MoveObjectType_::SuiBalanceAccumulatorField
491            | MoveObjectType_::BalanceAccumulatorField(_) => false,
492            MoveObjectType_::Other(s) => Currency::is_currency(s),
493        }
494    }
495
496    pub fn is_treasury_cap(&self) -> bool {
497        match &self.0 {
498            MoveObjectType_::GasCoin
499            | MoveObjectType_::StakedSui
500            | MoveObjectType_::Coin(_)
501            | MoveObjectType_::SuiBalanceAccumulatorField
502            | MoveObjectType_::BalanceAccumulatorField(_) => false,
503            MoveObjectType_::Other(s) => TreasuryCap::is_treasury_type(s),
504        }
505    }
506
507    pub fn is_upgrade_cap(&self) -> bool {
508        self.address() == SUI_FRAMEWORK_ADDRESS
509            && self.module().as_str() == "package"
510            && self.name().as_str() == "UpgradeCap"
511    }
512
513    pub fn is_regulated_coin_metadata(&self) -> bool {
514        self.address() == SUI_FRAMEWORK_ADDRESS
515            && self.module().as_str() == "coin"
516            && self.name().as_str() == "RegulatedCoinMetadata"
517    }
518
519    pub fn is_coin_deny_cap(&self) -> bool {
520        self.address() == SUI_FRAMEWORK_ADDRESS
521            && self.module().as_str() == "coin"
522            && self.name().as_str() == "DenyCap"
523    }
524
525    pub fn is_coin_deny_cap_v2(&self) -> bool {
526        self.address() == SUI_FRAMEWORK_ADDRESS
527            && self.module().as_str() == "coin"
528            && self.name().as_str() == "DenyCapV2"
529    }
530
531    pub fn is_dynamic_field(&self) -> bool {
532        match &self.0 {
533            MoveObjectType_::GasCoin | MoveObjectType_::StakedSui | MoveObjectType_::Coin(_) => {
534                false
535            }
536            MoveObjectType_::SuiBalanceAccumulatorField
537            | MoveObjectType_::BalanceAccumulatorField(_) => true, // These are dynamic fields
538            MoveObjectType_::Other(s) => DynamicFieldInfo::is_dynamic_field(s),
539        }
540    }
541
542    pub fn try_extract_field_name(&self, type_: &DynamicFieldType) -> SuiResult<TypeTag> {
543        match &self.0 {
544            MoveObjectType_::GasCoin | MoveObjectType_::StakedSui | MoveObjectType_::Coin(_) => {
545                Err(SuiErrorKind::ObjectDeserializationError {
546                    error: "Error extracting dynamic object name from specialized object type"
547                        .to_string(),
548                }
549                .into())
550            }
551            MoveObjectType_::SuiBalanceAccumulatorField
552            | MoveObjectType_::BalanceAccumulatorField(_) => {
553                let struct_tag: StructTag = self.clone().into();
554                DynamicFieldInfo::try_extract_field_name(&struct_tag, type_)
555            }
556            MoveObjectType_::Other(s) => DynamicFieldInfo::try_extract_field_name(s, type_),
557        }
558    }
559
560    pub fn try_extract_field_value(&self) -> SuiResult<TypeTag> {
561        match &self.0 {
562            MoveObjectType_::GasCoin | MoveObjectType_::StakedSui | MoveObjectType_::Coin(_) => {
563                Err(SuiErrorKind::ObjectDeserializationError {
564                    error: "Error extracting dynamic object value from specialized object type"
565                        .to_string(),
566                }
567                .into())
568            }
569            MoveObjectType_::SuiBalanceAccumulatorField
570            | MoveObjectType_::BalanceAccumulatorField(_) => {
571                let struct_tag: StructTag = self.clone().into();
572                DynamicFieldInfo::try_extract_field_value(&struct_tag)
573            }
574            MoveObjectType_::Other(s) => DynamicFieldInfo::try_extract_field_value(s),
575        }
576    }
577
578    pub fn is(&self, s: &StructTag) -> bool {
579        match &self.0 {
580            MoveObjectType_::GasCoin => GasCoin::is_gas_coin(s),
581            MoveObjectType_::StakedSui => StakedSui::is_staked_sui(s),
582            MoveObjectType_::Coin(inner) => {
583                Coin::is_coin(s) && s.type_params.len() == 1 && inner == &s.type_params[0]
584            }
585            MoveObjectType_::SuiBalanceAccumulatorField => accumulator_value_balance_type_maybe(s)
586                .map(|t| GAS::is_gas_type(&t))
587                .unwrap_or(false),
588            MoveObjectType_::BalanceAccumulatorField(inner) => {
589                accumulator_value_balance_type_maybe(s)
590                    .map(|t| &t == inner)
591                    .unwrap_or(false)
592            }
593            MoveObjectType_::Other(o) => s == o,
594        }
595    }
596
597    pub fn other(&self) -> Option<&StructTag> {
598        if let MoveObjectType_::Other(s) = &self.0 {
599            Some(s)
600        } else {
601            None
602        }
603    }
604
605    /// Returns the string representation of this object's type using the canonical display.
606    pub fn to_canonical_string(&self, with_prefix: bool) -> String {
607        StructTag::from(self.clone()).to_canonical_string(with_prefix)
608    }
609
610    /// Helper function to construct type parameters for balance accumulator fields
611    /// Field<Key<Balance<T>>, U128> has two type params
612    fn balance_accumulator_field_type_params(inner_type: TypeTag) -> Vec<TypeTag> {
613        use crate::accumulator_root::{AccumulatorKey, U128};
614        let balance_type = Balance::type_tag(inner_type);
615        let key_type = AccumulatorKey::get_type_tag(&[balance_type]);
616        let u128_type = U128::get_type_tag();
617        vec![key_type, u128_type]
618    }
619
620    /// Map from T to Field<AccumulatorKey<Balance<T>>, U128>
621    fn balance_accumulator_field_struct_tag(inner_type: TypeTag) -> StructTag {
622        use crate::accumulator_root::{AccumulatorKey, U128};
623        let balance_type = Balance::type_tag(inner_type);
624        let key_type = AccumulatorKey::get_type_tag(&[balance_type]);
625        let u128_type = U128::get_type_tag();
626        DynamicFieldInfo::dynamic_field_type(key_type, u128_type)
627    }
628}
629
630impl From<StructTag> for MoveObjectType {
631    fn from(mut s: StructTag) -> Self {
632        Self(if GasCoin::is_gas_coin(&s) {
633            MoveObjectType_::GasCoin
634        } else if Coin::is_coin(&s) {
635            // unwrap safe because a coin has exactly one type parameter
636            MoveObjectType_::Coin(s.type_params.pop().unwrap())
637        } else if StakedSui::is_staked_sui(&s) {
638            MoveObjectType_::StakedSui
639        } else if let Some(balance_type) = accumulator_value_balance_type_maybe(&s) {
640            if GAS::is_gas_type(&balance_type) {
641                MoveObjectType_::SuiBalanceAccumulatorField
642            } else {
643                MoveObjectType_::BalanceAccumulatorField(balance_type)
644            }
645        } else {
646            MoveObjectType_::Other(s)
647        })
648    }
649}
650
651impl From<MoveObjectType> for StructTag {
652    fn from(t: MoveObjectType) -> Self {
653        match t.0 {
654            MoveObjectType_::GasCoin => GasCoin::type_(),
655            MoveObjectType_::StakedSui => StakedSui::type_(),
656            MoveObjectType_::Coin(inner) => Coin::type_(inner),
657            MoveObjectType_::SuiBalanceAccumulatorField => {
658                MoveObjectType::balance_accumulator_field_struct_tag(GAS::type_tag())
659            }
660            MoveObjectType_::BalanceAccumulatorField(inner) => {
661                MoveObjectType::balance_accumulator_field_struct_tag(inner)
662            }
663            MoveObjectType_::Other(s) => s,
664        }
665    }
666}
667
668impl From<MoveObjectType> for TypeTag {
669    fn from(o: MoveObjectType) -> TypeTag {
670        let s: StructTag = o.into();
671        TypeTag::Struct(Box::new(s))
672    }
673}
674
675/// Whether this type is valid as a primitive (pure) transaction input.
676pub fn is_primitive_type_tag(t: &TypeTag) -> bool {
677    use TypeTag as T;
678
679    match t {
680        T::Bool | T::U8 | T::U16 | T::U32 | T::U64 | T::U128 | T::U256 | T::Address => true,
681        T::Vector(inner) => is_primitive_type_tag(inner),
682        T::Struct(st) => {
683            let StructTag {
684                address,
685                module,
686                name,
687                type_params: type_args,
688            } = &**st;
689            let resolved_struct = (address, module.as_ident_str(), name.as_ident_str());
690            // is id or..
691            if resolved_struct == RESOLVED_SUI_ID {
692                return true;
693            }
694            // is utf8 string
695            if resolved_struct == RESOLVED_UTF8_STR {
696                return true;
697            }
698            // is ascii string
699            if resolved_struct == RESOLVED_ASCII_STR {
700                return true;
701            }
702            // is option of a primitive
703            resolved_struct == RESOLVED_STD_OPTION
704                && type_args.len() == 1
705                && is_primitive_type_tag(&type_args[0])
706        }
707        T::Signer => false,
708    }
709}
710
711/// Type of a Sui object
712#[derive(Clone, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Debug)]
713pub enum ObjectType {
714    /// Move package containing one or more bytecode modules
715    Package,
716    /// A Move struct of the given type
717    Struct(MoveObjectType),
718}
719
720impl From<&Object> for ObjectType {
721    fn from(o: &Object) -> Self {
722        o.data
723            .type_()
724            .map(|t| ObjectType::Struct(t.clone()))
725            .unwrap_or(ObjectType::Package)
726    }
727}
728
729impl TryFrom<ObjectType> for StructTag {
730    type Error = anyhow::Error;
731
732    fn try_from(o: ObjectType) -> Result<Self, anyhow::Error> {
733        match o {
734            ObjectType::Package => Err(anyhow!("Cannot create StructTag from Package")),
735            ObjectType::Struct(move_object_type) => Ok(move_object_type.into()),
736        }
737    }
738}
739
740impl FromStr for ObjectType {
741    type Err = anyhow::Error;
742
743    fn from_str(s: &str) -> Result<Self, Self::Err> {
744        if s.to_lowercase() == PACKAGE {
745            Ok(ObjectType::Package)
746        } else {
747            let tag = parse_sui_struct_tag(s)?;
748            Ok(ObjectType::Struct(MoveObjectType::from(tag)))
749        }
750    }
751}
752
753#[derive(Clone, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Debug)]
754pub struct ObjectInfo {
755    pub object_id: ObjectID,
756    pub version: SequenceNumber,
757    pub digest: ObjectDigest,
758    pub type_: ObjectType,
759    pub owner: Owner,
760    pub previous_transaction: TransactionDigest,
761}
762
763impl ObjectInfo {
764    pub fn new(oref: &ObjectRef, o: &Object) -> Self {
765        let (object_id, version, digest) = *oref;
766        Self {
767            object_id,
768            version,
769            digest,
770            type_: o.into(),
771            owner: o.owner.clone(),
772            previous_transaction: o.previous_transaction,
773        }
774    }
775
776    pub fn from_object(object: &Object) -> Self {
777        Self {
778            object_id: object.id(),
779            version: object.version(),
780            digest: object.digest(),
781            type_: object.into(),
782            owner: object.owner.clone(),
783            previous_transaction: object.previous_transaction,
784        }
785    }
786}
787const PACKAGE: &str = "package";
788impl ObjectType {
789    pub fn is_gas_coin(&self) -> bool {
790        matches!(self, ObjectType::Struct(s) if s.is_gas_coin())
791    }
792
793    pub fn is_coin(&self) -> bool {
794        matches!(self, ObjectType::Struct(s) if s.is_coin())
795    }
796
797    /// Return true if `self` is `0x2::coin::Coin<t>`
798    pub fn is_coin_t(&self, t: &TypeTag) -> bool {
799        matches!(self, ObjectType::Struct(s) if s.is_coin_t(t))
800    }
801
802    pub fn is_package(&self) -> bool {
803        matches!(self, ObjectType::Package)
804    }
805}
806
807impl From<ObjectInfo> for ObjectRef {
808    fn from(info: ObjectInfo) -> Self {
809        (info.object_id, info.version, info.digest)
810    }
811}
812
813impl From<&ObjectInfo> for ObjectRef {
814    fn from(info: &ObjectInfo) -> Self {
815        (info.object_id, info.version, info.digest)
816    }
817}
818
819pub const SUI_ADDRESS_LENGTH: usize = ObjectID::LENGTH;
820
821#[serde_as]
822#[derive(
823    Eq, Default, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema,
824)]
825#[cfg_attr(feature = "fuzzing", derive(proptest_derive::Arbitrary))]
826pub struct SuiAddress(
827    #[schemars(with = "Hex")]
828    #[serde_as(as = "Readable<Hex, _>")]
829    [u8; SUI_ADDRESS_LENGTH],
830);
831
832impl SuiAddress {
833    pub const ZERO: Self = Self([0u8; SUI_ADDRESS_LENGTH]);
834
835    /// Convert the address to a byte buffer.
836    pub fn to_vec(&self) -> Vec<u8> {
837        self.0.to_vec()
838    }
839
840    /// Return a random SuiAddress.
841    pub fn random_for_testing_only() -> Self {
842        AccountAddress::random().into()
843    }
844
845    pub fn generate<R: rand::RngCore + rand::CryptoRng>(mut rng: R) -> Self {
846        let buf: [u8; SUI_ADDRESS_LENGTH] = rng.r#gen();
847        Self(buf)
848    }
849
850    /// Serialize an `Option<SuiAddress>` in Hex.
851    pub fn optional_address_as_hex<S>(
852        key: &Option<SuiAddress>,
853        serializer: S,
854    ) -> Result<S::Ok, S::Error>
855    where
856        S: serde::ser::Serializer,
857    {
858        serializer.serialize_str(&key.map(Hex::encode).unwrap_or_default())
859    }
860
861    /// Deserialize into an `Option<SuiAddress>`.
862    pub fn optional_address_from_hex<'de, D>(
863        deserializer: D,
864    ) -> Result<Option<SuiAddress>, D::Error>
865    where
866        D: serde::de::Deserializer<'de>,
867    {
868        let s = String::deserialize(deserializer)?;
869        let value = decode_bytes_hex(&s).map_err(serde::de::Error::custom)?;
870        Ok(Some(value))
871    }
872
873    /// Return the underlying byte array of a SuiAddress.
874    pub fn to_inner(self) -> [u8; SUI_ADDRESS_LENGTH] {
875        self.0
876    }
877
878    /// Parse a SuiAddress from a byte array or buffer.
879    pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Self, SuiError> {
880        <[u8; SUI_ADDRESS_LENGTH]>::try_from(bytes.as_ref())
881            .map_err(|_| SuiErrorKind::InvalidAddress.into())
882            .map(SuiAddress)
883    }
884
885    /// This derives a zkLogin address by parsing the iss and address_seed from [struct ZkLoginAuthenticator].
886    /// Define as iss_bytes_len || iss_bytes || padded_32_byte_address_seed. This is to be differentiated with
887    /// try_from_unpadded defined below.
888    pub fn try_from_padded(inputs: &ZkLoginInputs) -> SuiResult<Self> {
889        Ok((&PublicKey::from_zklogin_inputs(inputs)?).into())
890    }
891
892    /// Define as iss_bytes_len || iss_bytes || unpadded_32_byte_address_seed.
893    pub fn try_from_unpadded(inputs: &ZkLoginInputs) -> SuiResult<Self> {
894        let mut hasher = DefaultHash::default();
895        hasher.update([SignatureScheme::ZkLoginAuthenticator.flag()]);
896        let iss_bytes = inputs.get_iss().as_bytes();
897        hasher.update([iss_bytes.len() as u8]);
898        hasher.update(iss_bytes);
899        hasher.update(inputs.get_address_seed().unpadded());
900        Ok(SuiAddress(hasher.finalize().digest))
901    }
902}
903
904impl From<ObjectID> for SuiAddress {
905    fn from(object_id: ObjectID) -> SuiAddress {
906        Self(object_id.into_bytes())
907    }
908}
909
910impl From<AccountAddress> for SuiAddress {
911    fn from(address: AccountAddress) -> SuiAddress {
912        Self(address.into_bytes())
913    }
914}
915
916impl TryFrom<&[u8]> for SuiAddress {
917    type Error = SuiError;
918
919    /// Tries to convert the provided byte array into a SuiAddress.
920    fn try_from(bytes: &[u8]) -> Result<Self, SuiError> {
921        Self::from_bytes(bytes)
922    }
923}
924
925impl TryFrom<Vec<u8>> for SuiAddress {
926    type Error = SuiError;
927
928    /// Tries to convert the provided byte buffer into a SuiAddress.
929    fn try_from(bytes: Vec<u8>) -> Result<Self, SuiError> {
930        Self::from_bytes(bytes)
931    }
932}
933
934impl AsRef<[u8]> for SuiAddress {
935    fn as_ref(&self) -> &[u8] {
936        &self.0[..]
937    }
938}
939
940impl FromStr for SuiAddress {
941    type Err = anyhow::Error;
942    fn from_str(s: &str) -> Result<Self, Self::Err> {
943        decode_bytes_hex(s).map_err(|e| anyhow!(e))
944    }
945}
946
947impl<T: SuiPublicKey> From<&T> for SuiAddress {
948    fn from(pk: &T) -> Self {
949        let mut hasher = DefaultHash::default();
950        hasher.update([T::SIGNATURE_SCHEME.flag()]);
951        hasher.update(pk);
952        let g_arr = hasher.finalize();
953        SuiAddress(g_arr.digest)
954    }
955}
956
957impl From<&PublicKey> for SuiAddress {
958    fn from(pk: &PublicKey) -> Self {
959        let mut hasher = DefaultHash::default();
960        hasher.update([pk.flag()]);
961        hasher.update(pk);
962        let g_arr = hasher.finalize();
963        SuiAddress(g_arr.digest)
964    }
965}
966
967impl From<&MultiSigPublicKey> for SuiAddress {
968    /// Derive a SuiAddress from [struct MultiSigPublicKey]. A MultiSig address
969    /// is defined as the 32-byte Blake2b hash of serializing the flag, the
970    /// threshold, concatenation of all n flag, public keys and
971    /// its weight. `flag_MultiSig || threshold || flag_1 || pk_1 || weight_1
972    /// || ... || flag_n || pk_n || weight_n`.
973    ///
974    /// When flag_i is ZkLogin, pk_i refers to [struct ZkLoginPublicIdentifier]
975    /// derived from padded address seed in bytes and iss.
976    fn from(multisig_pk: &MultiSigPublicKey) -> Self {
977        let mut hasher = DefaultHash::default();
978        hasher.update([SignatureScheme::MultiSig.flag()]);
979        hasher.update(multisig_pk.threshold().to_le_bytes());
980        multisig_pk.pubkeys().iter().for_each(|(pk, w)| {
981            hasher.update([pk.flag()]);
982            hasher.update(pk.as_ref());
983            hasher.update(w.to_le_bytes());
984        });
985        SuiAddress(hasher.finalize().digest)
986    }
987}
988
989/// Sui address for [struct ZkLoginAuthenticator] is defined as the black2b hash of
990/// [zklogin_flag || iss_bytes_length || iss_bytes || unpadded_address_seed_in_bytes].
991impl TryFrom<&ZkLoginAuthenticator> for SuiAddress {
992    type Error = SuiError;
993    fn try_from(authenticator: &ZkLoginAuthenticator) -> SuiResult<Self> {
994        SuiAddress::try_from_unpadded(&authenticator.inputs)
995    }
996}
997
998impl TryFrom<&GenericSignature> for SuiAddress {
999    type Error = SuiError;
1000    /// Derive a SuiAddress from a serialized signature in Sui [GenericSignature].
1001    fn try_from(sig: &GenericSignature) -> SuiResult<Self> {
1002        match sig {
1003            GenericSignature::Signature(sig) => {
1004                let scheme = sig.scheme();
1005                let pub_key_bytes = sig.public_key_bytes();
1006                let pub_key = PublicKey::try_from_bytes(scheme, pub_key_bytes).map_err(|_| {
1007                    SuiErrorKind::InvalidSignature {
1008                        error: "Cannot parse pubkey".to_string(),
1009                    }
1010                })?;
1011                Ok(SuiAddress::from(&pub_key))
1012            }
1013            GenericSignature::MultiSig(ms) => Ok(ms.get_pk().into()),
1014            GenericSignature::MultiSigLegacy(ms) => {
1015                Ok(crate::multisig::MultiSig::try_from(ms.clone())
1016                    .map_err(|_| SuiErrorKind::InvalidSignature {
1017                        error: "Invalid legacy multisig".to_string(),
1018                    })?
1019                    .get_pk()
1020                    .into())
1021            }
1022            GenericSignature::ZkLoginAuthenticator(zklogin) => {
1023                SuiAddress::try_from_unpadded(&zklogin.inputs)
1024            }
1025            GenericSignature::PasskeyAuthenticator(s) => Ok(SuiAddress::from(&s.get_pk()?)),
1026        }
1027    }
1028}
1029
1030impl fmt::Display for SuiAddress {
1031    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1032        write!(f, "0x{}", Hex::encode(self.0))
1033    }
1034}
1035
1036impl fmt::Debug for SuiAddress {
1037    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
1038        write!(f, "0x{}", Hex::encode(self.0))
1039    }
1040}
1041
1042/// Generate a fake SuiAddress with repeated one byte.
1043pub fn dbg_addr(name: u8) -> SuiAddress {
1044    let addr = [name; SUI_ADDRESS_LENGTH];
1045    SuiAddress(addr)
1046}
1047
1048#[derive(
1049    Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema, Debug,
1050)]
1051pub struct ExecutionDigests {
1052    pub transaction: TransactionDigest,
1053    pub effects: TransactionEffectsDigest,
1054}
1055
1056impl ExecutionDigests {
1057    pub fn new(transaction: TransactionDigest, effects: TransactionEffectsDigest) -> Self {
1058        Self {
1059            transaction,
1060            effects,
1061        }
1062    }
1063
1064    pub fn random() -> Self {
1065        Self {
1066            transaction: TransactionDigest::random(),
1067            effects: TransactionEffectsDigest::random(),
1068        }
1069    }
1070}
1071
1072#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
1073pub struct ExecutionData {
1074    pub transaction: Transaction,
1075    pub effects: TransactionEffects,
1076}
1077
1078impl ExecutionData {
1079    pub fn new(transaction: Transaction, effects: TransactionEffects) -> ExecutionData {
1080        debug_assert_eq!(transaction.digest(), effects.transaction_digest());
1081        Self {
1082            transaction,
1083            effects,
1084        }
1085    }
1086
1087    pub fn digests(&self) -> ExecutionDigests {
1088        self.effects.execution_digests()
1089    }
1090}
1091
1092#[derive(Clone, Eq, PartialEq, Debug)]
1093pub struct VerifiedExecutionData {
1094    pub transaction: VerifiedTransaction,
1095    pub effects: TransactionEffects,
1096}
1097
1098impl VerifiedExecutionData {
1099    pub fn new(transaction: VerifiedTransaction, effects: TransactionEffects) -> Self {
1100        debug_assert_eq!(transaction.digest(), effects.transaction_digest());
1101        Self {
1102            transaction,
1103            effects,
1104        }
1105    }
1106
1107    pub fn new_unchecked(data: ExecutionData) -> Self {
1108        Self {
1109            transaction: VerifiedTransaction::new_unchecked(data.transaction),
1110            effects: data.effects,
1111        }
1112    }
1113
1114    pub fn into_inner(self) -> ExecutionData {
1115        ExecutionData {
1116            transaction: self.transaction.into_inner(),
1117            effects: self.effects,
1118        }
1119    }
1120
1121    pub fn digests(&self) -> ExecutionDigests {
1122        self.effects.execution_digests()
1123    }
1124}
1125
1126pub const STD_OPTION_MODULE_NAME: &IdentStr = ident_str!("option");
1127pub const STD_OPTION_STRUCT_NAME: &IdentStr = ident_str!("Option");
1128pub const RESOLVED_STD_OPTION: (&AccountAddress, &IdentStr, &IdentStr) = (
1129    &MOVE_STDLIB_ADDRESS,
1130    STD_OPTION_MODULE_NAME,
1131    STD_OPTION_STRUCT_NAME,
1132);
1133
1134pub const STD_ASCII_MODULE_NAME: &IdentStr = ident_str!("ascii");
1135pub const STD_ASCII_STRUCT_NAME: &IdentStr = ident_str!("String");
1136pub const RESOLVED_ASCII_STR: (&AccountAddress, &IdentStr, &IdentStr) = (
1137    &MOVE_STDLIB_ADDRESS,
1138    STD_ASCII_MODULE_NAME,
1139    STD_ASCII_STRUCT_NAME,
1140);
1141
1142pub const STD_UTF8_MODULE_NAME: &IdentStr = ident_str!("string");
1143pub const STD_UTF8_STRUCT_NAME: &IdentStr = ident_str!("String");
1144pub const RESOLVED_UTF8_STR: (&AccountAddress, &IdentStr, &IdentStr) = (
1145    &MOVE_STDLIB_ADDRESS,
1146    STD_UTF8_MODULE_NAME,
1147    STD_UTF8_STRUCT_NAME,
1148);
1149
1150pub const STD_TYPE_NAME_MODULE_NAME: &IdentStr = ident_str!("type_name");
1151pub const STD_TYPE_NAME_STRUCT_NAME: &IdentStr = ident_str!("TypeName");
1152pub const RESOLVED_STD_TYPE_NAME: (&AccountAddress, &IdentStr, &IdentStr) = (
1153    &MOVE_STDLIB_ADDRESS,
1154    STD_TYPE_NAME_MODULE_NAME,
1155    STD_TYPE_NAME_STRUCT_NAME,
1156);
1157
1158pub const TX_CONTEXT_MODULE_NAME: &IdentStr = ident_str!("tx_context");
1159pub const TX_CONTEXT_STRUCT_NAME: &IdentStr = ident_str!("TxContext");
1160pub const RESOLVED_TX_CONTEXT: (&AccountAddress, &IdentStr, &IdentStr) = (
1161    &SUI_FRAMEWORK_ADDRESS,
1162    TX_CONTEXT_MODULE_NAME,
1163    TX_CONTEXT_STRUCT_NAME,
1164);
1165
1166pub const URL_MODULE_NAME: &IdentStr = ident_str!("url");
1167pub const URL_STRUCT_NAME: &IdentStr = ident_str!("Url");
1168
1169pub const VEC_MAP_MODULE_NAME: &IdentStr = ident_str!("vec_map");
1170pub const VEC_MAP_STRUCT_NAME: &IdentStr = ident_str!("VecMap");
1171pub const VEC_MAP_ENTRY_STRUCT_NAME: &IdentStr = ident_str!("Entry");
1172
1173pub fn move_ascii_str_layout() -> A::MoveStructLayout {
1174    A::MoveStructLayout {
1175        type_: StructTag {
1176            address: MOVE_STDLIB_ADDRESS,
1177            module: STD_ASCII_MODULE_NAME.to_owned(),
1178            name: STD_ASCII_STRUCT_NAME.to_owned(),
1179            type_params: vec![],
1180        },
1181        fields: vec![A::MoveFieldLayout::new(
1182            ident_str!("bytes").into(),
1183            A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
1184        )],
1185    }
1186}
1187
1188pub fn move_utf8_str_layout() -> A::MoveStructLayout {
1189    A::MoveStructLayout {
1190        type_: StructTag {
1191            address: MOVE_STDLIB_ADDRESS,
1192            module: STD_UTF8_MODULE_NAME.to_owned(),
1193            name: STD_UTF8_STRUCT_NAME.to_owned(),
1194            type_params: vec![],
1195        },
1196        fields: vec![A::MoveFieldLayout::new(
1197            ident_str!("bytes").into(),
1198            A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
1199        )],
1200    }
1201}
1202
1203pub fn url_layout() -> A::MoveStructLayout {
1204    A::MoveStructLayout {
1205        type_: StructTag {
1206            address: SUI_FRAMEWORK_ADDRESS,
1207            module: URL_MODULE_NAME.to_owned(),
1208            name: URL_STRUCT_NAME.to_owned(),
1209            type_params: vec![],
1210        },
1211        fields: vec![A::MoveFieldLayout::new(
1212            ident_str!("url").to_owned(),
1213            A::MoveTypeLayout::Struct(Box::new(move_ascii_str_layout())),
1214        )],
1215    }
1216}
1217
1218pub fn type_name_layout() -> A::MoveStructLayout {
1219    A::MoveStructLayout {
1220        type_: StructTag {
1221            address: MOVE_STDLIB_ADDRESS,
1222            module: STD_TYPE_NAME_MODULE_NAME.to_owned(),
1223            name: STD_TYPE_NAME_STRUCT_NAME.to_owned(),
1224            type_params: vec![],
1225        },
1226        fields: vec![A::MoveFieldLayout::new(
1227            ident_str!("name").into(),
1228            A::MoveTypeLayout::Struct(Box::new(move_ascii_str_layout())),
1229        )],
1230    }
1231}
1232
1233// The Rust representation of the Move `TxContext`.
1234// This struct must be kept in sync with the Move `TxContext` definition.
1235// Moving forward we are going to zero all fields of the Move `TxContext`
1236// and use native functions to retrieve info about the transaction.
1237// However we cannot remove the Move type and so this struct is going to
1238// be the Rust equivalent to the Move `TxContext` for legacy usages.
1239//
1240// `TxContext` in Rust (see below) is going to be purely used in Rust and can
1241// evolve as needed without worrying any compatibility with Move.
1242#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
1243pub struct MoveLegacyTxContext {
1244    // Signer/sender of the transaction
1245    sender: AccountAddress,
1246    // Digest of the current transaction
1247    digest: Vec<u8>,
1248    // The current epoch number
1249    epoch: EpochId,
1250    // Timestamp that the epoch started at
1251    epoch_timestamp_ms: CheckpointTimestamp,
1252    // Number of `ObjectID`'s generated during execution of the current transaction
1253    ids_created: u64,
1254}
1255
1256impl From<&TxContext> for MoveLegacyTxContext {
1257    fn from(tx_context: &TxContext) -> Self {
1258        Self {
1259            sender: tx_context.sender,
1260            digest: tx_context.digest.clone(),
1261            epoch: tx_context.epoch,
1262            epoch_timestamp_ms: tx_context.epoch_timestamp_ms,
1263            ids_created: tx_context.ids_created,
1264        }
1265    }
1266}
1267
1268// Information about the transaction context.
1269// This struct is not related to Move and can evolve as needed/required.
1270#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
1271pub struct TxContext {
1272    /// Sender of the transaction
1273    sender: AccountAddress,
1274    /// Digest of the current transaction
1275    digest: Vec<u8>,
1276    /// The current epoch number
1277    epoch: EpochId,
1278    /// Timestamp that the epoch started at
1279    epoch_timestamp_ms: CheckpointTimestamp,
1280    /// Number of `ObjectID`'s generated during execution of the current transaction
1281    ids_created: u64,
1282    // Reference gas price
1283    rgp: u64,
1284    // gas price passed to transaction as input
1285    gas_price: u64,
1286    // gas budget passed to transaction as input
1287    gas_budget: u64,
1288    // address of the sponsor if any
1289    sponsor: Option<AccountAddress>,
1290    // whether the `TxContext` is native or not
1291    // (TODO: once we version execution we could drop this field)
1292    is_native: bool,
1293}
1294
1295#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1296pub enum TxContextKind {
1297    // No TxContext
1298    None,
1299    // &mut TxContext
1300    Mutable,
1301    // &TxContext
1302    Immutable,
1303}
1304
1305impl TxContext {
1306    pub fn new(
1307        sender: &SuiAddress,
1308        digest: &TransactionDigest,
1309        epoch_data: &EpochData,
1310        rgp: u64,
1311        gas_price: u64,
1312        gas_budget: u64,
1313        sponsor: Option<SuiAddress>,
1314        protocol_config: &ProtocolConfig,
1315    ) -> Self {
1316        Self::new_from_components(
1317            sender,
1318            digest,
1319            &epoch_data.epoch_id(),
1320            epoch_data.epoch_start_timestamp(),
1321            rgp,
1322            gas_price,
1323            gas_budget,
1324            sponsor,
1325            protocol_config,
1326        )
1327    }
1328
1329    pub fn new_from_components(
1330        sender: &SuiAddress,
1331        digest: &TransactionDigest,
1332        epoch_id: &EpochId,
1333        epoch_timestamp_ms: u64,
1334        rgp: u64,
1335        gas_price: u64,
1336        gas_budget: u64,
1337        sponsor: Option<SuiAddress>,
1338        protocol_config: &ProtocolConfig,
1339    ) -> Self {
1340        Self {
1341            sender: AccountAddress::new(sender.0),
1342            digest: digest.into_inner().to_vec(),
1343            epoch: *epoch_id,
1344            epoch_timestamp_ms,
1345            ids_created: 0,
1346            rgp,
1347            gas_price,
1348            gas_budget,
1349            sponsor: sponsor.map(|s| s.into()),
1350            is_native: protocol_config.move_native_context(),
1351        }
1352    }
1353
1354    /// Returns whether the type signature is &mut TxContext, &TxContext, or none of the above.
1355    pub fn kind(view: &CompiledModule, s: &SignatureToken) -> TxContextKind {
1356        use SignatureToken as S;
1357        let (kind, s) = match s {
1358            S::MutableReference(s) => (TxContextKind::Mutable, s),
1359            S::Reference(s) => (TxContextKind::Immutable, s),
1360            _ => return TxContextKind::None,
1361        };
1362
1363        let S::Datatype(idx) = &**s else {
1364            return TxContextKind::None;
1365        };
1366
1367        if resolve_struct(view, *idx) == RESOLVED_TX_CONTEXT {
1368            kind
1369        } else {
1370            TxContextKind::None
1371        }
1372    }
1373
1374    pub fn type_() -> StructTag {
1375        StructTag {
1376            address: SUI_FRAMEWORK_ADDRESS,
1377            module: TX_CONTEXT_MODULE_NAME.to_owned(),
1378            name: TX_CONTEXT_STRUCT_NAME.to_owned(),
1379            type_params: vec![],
1380        }
1381    }
1382
1383    pub fn epoch(&self) -> EpochId {
1384        self.epoch
1385    }
1386
1387    pub fn sender(&self) -> SuiAddress {
1388        self.sender.into()
1389    }
1390
1391    pub fn epoch_timestamp_ms(&self) -> u64 {
1392        self.epoch_timestamp_ms
1393    }
1394
1395    /// Return the transaction digest, to include in new objects
1396    pub fn digest(&self) -> TransactionDigest {
1397        TransactionDigest::new(self.digest.clone().try_into().unwrap())
1398    }
1399
1400    pub fn sponsor(&self) -> Option<SuiAddress> {
1401        self.sponsor.map(SuiAddress::from)
1402    }
1403
1404    pub fn rgp(&self) -> u64 {
1405        self.rgp
1406    }
1407
1408    pub fn gas_price(&self) -> u64 {
1409        self.gas_price
1410    }
1411
1412    pub fn gas_budget(&self) -> u64 {
1413        self.gas_budget
1414    }
1415
1416    pub fn ids_created(&self) -> u64 {
1417        self.ids_created
1418    }
1419
1420    /// Derive a globally unique object ID by hashing self.digest | self.ids_created
1421    pub fn fresh_id(&mut self) -> ObjectID {
1422        let id = ObjectID::derive_id(self.digest(), self.ids_created);
1423
1424        self.ids_created += 1;
1425        id
1426    }
1427
1428    pub fn to_bcs_legacy_context(&self) -> Vec<u8> {
1429        let move_context: MoveLegacyTxContext = if self.is_native {
1430            let tx_context = &TxContext {
1431                sender: AccountAddress::ZERO,
1432                digest: self.digest.clone(),
1433                epoch: 0,
1434                epoch_timestamp_ms: 0,
1435                ids_created: 0,
1436                rgp: 0,
1437                gas_price: 0,
1438                gas_budget: 0,
1439                sponsor: None,
1440                is_native: true,
1441            };
1442            tx_context.into()
1443        } else {
1444            self.into()
1445        };
1446        bcs::to_bytes(&move_context).unwrap()
1447    }
1448
1449    pub fn to_vec(&self) -> Vec<u8> {
1450        bcs::to_bytes(&self).unwrap()
1451    }
1452
1453    /// Updates state of the context instance. It's intended to use
1454    /// when mutable context is passed over some boundary via
1455    /// serialize/deserialize and this is the reason why this method
1456    /// consumes the other context..
1457    pub fn update_state(&mut self, other: MoveLegacyTxContext) -> Result<(), ExecutionError> {
1458        if !self.is_native {
1459            if self.sender != other.sender
1460                || self.digest != other.digest
1461                || other.ids_created < self.ids_created
1462            {
1463                return Err(ExecutionError::new_with_source(
1464                    ExecutionErrorKind::InvariantViolation,
1465                    "Immutable fields for TxContext changed",
1466                ));
1467            }
1468            self.ids_created = other.ids_created;
1469        }
1470        Ok(())
1471    }
1472
1473    //
1474    // Move test only API
1475    //
1476    pub fn replace(
1477        &mut self,
1478        sender: AccountAddress,
1479        tx_hash: Vec<u8>,
1480        epoch: u64,
1481        epoch_timestamp_ms: u64,
1482        ids_created: u64,
1483        rgp: u64,
1484        gas_price: u64,
1485        gas_budget: u64,
1486        sponsor: Option<AccountAddress>,
1487    ) {
1488        self.sender = sender;
1489        self.digest = tx_hash;
1490        self.epoch = epoch;
1491        self.epoch_timestamp_ms = epoch_timestamp_ms;
1492        self.ids_created = ids_created;
1493        self.rgp = rgp;
1494        self.gas_price = gas_price;
1495        self.gas_budget = gas_budget;
1496        self.sponsor = sponsor;
1497    }
1498}
1499
1500// TODO: rename to version
1501impl SequenceNumber {
1502    pub const MIN: SequenceNumber = SequenceNumber(u64::MIN);
1503    pub const MAX: SequenceNumber = SequenceNumber(0x7fff_ffff_ffff_ffff);
1504    pub const CANCELLED_READ: SequenceNumber = SequenceNumber(SequenceNumber::MAX.value() + 1);
1505    pub const CONGESTED: SequenceNumber = SequenceNumber(SequenceNumber::MAX.value() + 2);
1506    pub const RANDOMNESS_UNAVAILABLE: SequenceNumber =
1507        SequenceNumber(SequenceNumber::MAX.value() + 3);
1508    // Used to represent a sequence number whose value is unknown.
1509    // For internal use only. This should never appear on chain.
1510    pub const UNKNOWN: SequenceNumber = SequenceNumber(SequenceNumber::MAX.value() + 4);
1511
1512    pub const fn new() -> Self {
1513        SequenceNumber(0)
1514    }
1515
1516    pub const fn value(&self) -> u64 {
1517        self.0
1518    }
1519
1520    pub const fn from_u64(u: u64) -> Self {
1521        SequenceNumber(u)
1522    }
1523
1524    pub fn increment(&mut self) {
1525        assert_ne!(self.0, u64::MAX);
1526        self.0 += 1;
1527    }
1528
1529    pub fn increment_to(&mut self, next: SequenceNumber) {
1530        debug_assert!(*self < next, "Not an increment: {} to {}", self, next);
1531        *self = next;
1532    }
1533
1534    pub fn decrement(&mut self) {
1535        assert_ne!(self.0, 0);
1536        self.0 -= 1;
1537    }
1538
1539    pub fn decrement_to(&mut self, prev: SequenceNumber) {
1540        debug_assert!(prev < *self, "Not a decrement: {} to {}", self, prev);
1541        *self = prev;
1542    }
1543
1544    /// Returns a new sequence number that is greater than all `SequenceNumber`s in `inputs`,
1545    /// assuming this operation will not overflow.
1546    #[must_use]
1547    pub fn lamport_increment(inputs: impl IntoIterator<Item = SequenceNumber>) -> SequenceNumber {
1548        let max_input = inputs.into_iter().fold(SequenceNumber::new(), max);
1549
1550        // TODO: Ensure this never overflows.
1551        // Option 1: Freeze the object when sequence number reaches MAX.
1552        // Option 2: Reject tx with MAX sequence number.
1553        // Issue #182.
1554        assert_ne!(max_input.0, u64::MAX);
1555
1556        SequenceNumber(max_input.0 + 1)
1557    }
1558
1559    pub fn is_cancelled(&self) -> bool {
1560        self == &SequenceNumber::CANCELLED_READ
1561            || self == &SequenceNumber::CONGESTED
1562            || self == &SequenceNumber::RANDOMNESS_UNAVAILABLE
1563    }
1564
1565    pub fn is_valid(&self) -> bool {
1566        self < &SequenceNumber::MAX
1567    }
1568}
1569
1570impl From<SequenceNumber> for u64 {
1571    fn from(val: SequenceNumber) -> Self {
1572        val.0
1573    }
1574}
1575
1576impl From<u64> for SequenceNumber {
1577    fn from(value: u64) -> Self {
1578        SequenceNumber(value)
1579    }
1580}
1581
1582impl From<SequenceNumber> for usize {
1583    fn from(value: SequenceNumber) -> Self {
1584        value.0 as usize
1585    }
1586}
1587
1588impl ObjectID {
1589    /// The number of bytes in an address.
1590    pub const LENGTH: usize = AccountAddress::LENGTH;
1591    /// Hex address: 0x0
1592    pub const ZERO: Self = Self::new([0u8; Self::LENGTH]);
1593    pub const MAX: Self = Self::new([0xff; Self::LENGTH]);
1594    /// Create a new ObjectID
1595    pub const fn new(obj_id: [u8; Self::LENGTH]) -> Self {
1596        Self(AccountAddress::new(obj_id))
1597    }
1598
1599    /// Const fn variant of `<ObjectID as From<AccountAddress>>::from`
1600    pub const fn from_address(addr: AccountAddress) -> Self {
1601        Self(addr)
1602    }
1603
1604    /// Return a random ObjectID.
1605    pub fn random() -> Self {
1606        Self::from(AccountAddress::random())
1607    }
1608
1609    /// Return a random ObjectID from a given RNG.
1610    pub fn random_from_rng<R>(rng: &mut R) -> Self
1611    where
1612        R: AllowedRng,
1613    {
1614        let buf: [u8; Self::LENGTH] = rng.r#gen();
1615        ObjectID::new(buf)
1616    }
1617
1618    /// Return the underlying bytes buffer of the ObjectID.
1619    pub fn to_vec(&self) -> Vec<u8> {
1620        self.0.to_vec()
1621    }
1622
1623    /// Parse the ObjectID from byte array or buffer.
1624    pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Self, ObjectIDParseError> {
1625        <[u8; Self::LENGTH]>::try_from(bytes.as_ref())
1626            .map_err(|_| ObjectIDParseError::TryFromSliceError)
1627            .map(ObjectID::new)
1628    }
1629
1630    /// Return the underlying bytes array of the ObjectID.
1631    pub fn into_bytes(self) -> [u8; Self::LENGTH] {
1632        self.0.into_bytes()
1633    }
1634
1635    /// Make an ObjectID with padding 0s before the single byte.
1636    pub const fn from_single_byte(byte: u8) -> ObjectID {
1637        let mut bytes = [0u8; Self::LENGTH];
1638        bytes[Self::LENGTH - 1] = byte;
1639        ObjectID::new(bytes)
1640    }
1641
1642    /// System objects have IDs that fit in the last 8 bytes (24 leading zero bytes).
1643    pub fn is_system_object(&self) -> bool {
1644        self.0.as_ref()[..24] == [0u8; 24]
1645    }
1646
1647    /// Convert from hex string to ObjectID where the string is prefixed with 0x
1648    /// Padding 0s if the string is too short.
1649    pub fn from_hex_literal(literal: &str) -> Result<Self, ObjectIDParseError> {
1650        if !literal.starts_with("0x") {
1651            return Err(ObjectIDParseError::HexLiteralPrefixMissing);
1652        }
1653
1654        let hex_len = literal.len() - 2;
1655
1656        // If the string is too short, pad it
1657        if hex_len < Self::LENGTH * 2 {
1658            let mut hex_str = String::with_capacity(Self::LENGTH * 2);
1659            for _ in 0..Self::LENGTH * 2 - hex_len {
1660                hex_str.push('0');
1661            }
1662            hex_str.push_str(&literal[2..]);
1663            Self::from_str(&hex_str)
1664        } else {
1665            Self::from_str(&literal[2..])
1666        }
1667    }
1668
1669    /// Create an ObjectID from `TransactionDigest` and `creation_num`.
1670    /// Caller is responsible for ensuring that `creation_num` is fresh
1671    pub fn derive_id(digest: TransactionDigest, creation_num: u64) -> Self {
1672        let mut hasher = DefaultHash::default();
1673        hasher.update([HashingIntentScope::RegularObjectId as u8]);
1674        hasher.update(digest);
1675        hasher.update(creation_num.to_le_bytes());
1676        let hash = hasher.finalize();
1677
1678        // truncate into an ObjectID.
1679        // OK to access slice because digest should never be shorter than ObjectID::LENGTH.
1680        ObjectID::try_from(&hash.as_ref()[0..ObjectID::LENGTH]).unwrap()
1681    }
1682
1683    /// Incremenent the ObjectID by usize IDs, assuming the ObjectID hex is a number represented as an array of bytes
1684    pub fn advance(&self, step: usize) -> Result<ObjectID, anyhow::Error> {
1685        let mut curr_vec = self.to_vec();
1686        let mut step_copy = step;
1687
1688        let mut carry = 0;
1689        for idx in (0..Self::LENGTH).rev() {
1690            if step_copy == 0 {
1691                // Nothing else to do
1692                break;
1693            }
1694            // Extract the relevant part
1695            let g = (step_copy % 0x100) as u16;
1696            // Shift to next group
1697            step_copy >>= 8;
1698            let mut val = curr_vec[idx] as u16;
1699            (carry, val) = ((val + carry + g) / 0x100, (val + carry + g) % 0x100);
1700            curr_vec[idx] = val as u8;
1701        }
1702
1703        if carry > 0 {
1704            return Err(anyhow!("Increment will cause overflow"));
1705        }
1706        ObjectID::try_from(curr_vec).map_err(|w| w.into())
1707    }
1708
1709    /// Increment the ObjectID by one, assuming the ObjectID hex is a number represented as an array of bytes
1710    pub fn next_increment(&self) -> Result<ObjectID, anyhow::Error> {
1711        let mut prev_val = self.to_vec();
1712        let mx = [0xFF; Self::LENGTH];
1713
1714        if prev_val == mx {
1715            return Err(anyhow!("Increment will cause overflow"));
1716        }
1717
1718        // This logic increments the integer representation of an ObjectID u8 array
1719        for idx in (0..Self::LENGTH).rev() {
1720            if prev_val[idx] == 0xFF {
1721                prev_val[idx] = 0;
1722            } else {
1723                prev_val[idx] += 1;
1724                break;
1725            };
1726        }
1727        ObjectID::try_from(prev_val.clone()).map_err(|w| w.into())
1728    }
1729
1730    /// Create `count` object IDs starting with one at `offset`
1731    pub fn in_range(offset: ObjectID, count: u64) -> Result<Vec<ObjectID>, anyhow::Error> {
1732        let mut ret = Vec::new();
1733        let mut prev = offset;
1734        for o in 0..count {
1735            if o != 0 {
1736                prev = prev.next_increment()?;
1737            }
1738            ret.push(prev);
1739        }
1740        Ok(ret)
1741    }
1742
1743    /// Return the full hex string with 0x prefix without removing trailing 0s. Prefer this
1744    /// over [fn to_hex_literal] if the string needs to be fully preserved.
1745    pub fn to_hex_uncompressed(&self) -> String {
1746        format!("{self}")
1747    }
1748
1749    pub fn is_clock(&self) -> bool {
1750        *self == SUI_CLOCK_OBJECT_ID
1751    }
1752
1753    pub fn is_implicitly_read_system_object(&self) -> bool {
1754        crate::IMPLICITLY_READ_SYSTEM_OBJECTS.contains(self)
1755    }
1756}
1757
1758impl From<SuiAddress> for ObjectID {
1759    fn from(address: SuiAddress) -> ObjectID {
1760        let tmp: AccountAddress = address.into();
1761        tmp.into()
1762    }
1763}
1764
1765impl From<AccountAddress> for ObjectID {
1766    fn from(address: AccountAddress) -> Self {
1767        Self(address)
1768    }
1769}
1770
1771impl fmt::Display for ObjectID {
1772    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
1773        write!(f, "0x{}", Hex::encode(self.0))
1774    }
1775}
1776
1777impl fmt::Debug for ObjectID {
1778    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
1779        write!(f, "0x{}", Hex::encode(self.0))
1780    }
1781}
1782
1783impl AsRef<[u8]> for ObjectID {
1784    fn as_ref(&self) -> &[u8] {
1785        self.0.as_slice()
1786    }
1787}
1788
1789impl TryFrom<&[u8]> for ObjectID {
1790    type Error = ObjectIDParseError;
1791
1792    /// Tries to convert the provided byte array into ObjectID.
1793    fn try_from(bytes: &[u8]) -> Result<ObjectID, ObjectIDParseError> {
1794        Self::from_bytes(bytes)
1795    }
1796}
1797
1798impl TryFrom<Vec<u8>> for ObjectID {
1799    type Error = ObjectIDParseError;
1800
1801    /// Tries to convert the provided byte buffer into ObjectID.
1802    fn try_from(bytes: Vec<u8>) -> Result<ObjectID, ObjectIDParseError> {
1803        Self::from_bytes(bytes)
1804    }
1805}
1806
1807impl FromStr for ObjectID {
1808    type Err = ObjectIDParseError;
1809
1810    /// Parse ObjectID from hex string with or without 0x prefix, pad with 0s if needed.
1811    fn from_str(s: &str) -> Result<Self, ObjectIDParseError> {
1812        decode_bytes_hex(s).or_else(|_| Self::from_hex_literal(s))
1813    }
1814}
1815
1816impl std::ops::Deref for ObjectID {
1817    type Target = AccountAddress;
1818
1819    fn deref(&self) -> &Self::Target {
1820        &self.0
1821    }
1822}
1823
1824/// Generate a fake ObjectID with repeated one byte.
1825pub fn dbg_object_id(name: u8) -> ObjectID {
1826    ObjectID::new([name; ObjectID::LENGTH])
1827}
1828
1829#[derive(PartialEq, Eq, Clone, Debug, thiserror::Error)]
1830pub enum ObjectIDParseError {
1831    #[error("ObjectID hex literal must start with 0x")]
1832    HexLiteralPrefixMissing,
1833
1834    #[error("Could not convert from bytes slice")]
1835    TryFromSliceError,
1836}
1837
1838impl From<ObjectID> for AccountAddress {
1839    fn from(obj_id: ObjectID) -> Self {
1840        obj_id.0
1841    }
1842}
1843
1844impl From<SuiAddress> for AccountAddress {
1845    fn from(address: SuiAddress) -> Self {
1846        Self::new(address.0)
1847    }
1848}
1849
1850/// Hex serde for AccountAddress
1851struct HexAccountAddress;
1852
1853impl SerializeAs<AccountAddress> for HexAccountAddress {
1854    fn serialize_as<S>(value: &AccountAddress, serializer: S) -> Result<S::Ok, S::Error>
1855    where
1856        S: Serializer,
1857    {
1858        Hex::serialize_as(value, serializer)
1859    }
1860}
1861
1862impl<'de> DeserializeAs<'de, AccountAddress> for HexAccountAddress {
1863    fn deserialize_as<D>(deserializer: D) -> Result<AccountAddress, D::Error>
1864    where
1865        D: Deserializer<'de>,
1866    {
1867        let s = String::deserialize(deserializer)?;
1868        if s.starts_with("0x") {
1869            AccountAddress::from_hex_literal(&s)
1870        } else {
1871            AccountAddress::from_hex(&s)
1872        }
1873        .map_err(to_custom_deser_error::<'de, D, _>)
1874    }
1875}
1876
1877impl fmt::Display for MoveObjectType {
1878    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
1879        let s: StructTag = self.clone().into();
1880        write!(
1881            f,
1882            "{}",
1883            to_sui_struct_tag_string(&s).map_err(fmt::Error::custom)?
1884        )
1885    }
1886}
1887
1888impl fmt::Display for ObjectType {
1889    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
1890        match self {
1891            ObjectType::Package => write!(f, "{}", PACKAGE),
1892            ObjectType::Struct(t) => write!(f, "{}", t),
1893        }
1894    }
1895}
1896
1897// SizeOneVec is a wrapper around Vec<T> that enforces the size of the vec to be 1.
1898// This seems pointless, but it allows us to have fields in protocol messages that are
1899// current enforced to be of size 1, but might later allow other sizes, and to have
1900// that constraint enforced in the serialization/deserialization layer, instead of
1901// requiring manual input validation.
1902#[derive(Debug, Deserialize, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
1903#[serde(try_from = "Vec<T>")]
1904pub struct SizeOneVec<T> {
1905    e: T,
1906}
1907
1908impl<T> SizeOneVec<T> {
1909    pub fn new(e: T) -> Self {
1910        Self { e }
1911    }
1912
1913    pub fn element(&self) -> &T {
1914        &self.e
1915    }
1916
1917    pub fn element_mut(&mut self) -> &mut T {
1918        &mut self.e
1919    }
1920
1921    pub fn into_inner(self) -> T {
1922        self.e
1923    }
1924
1925    pub fn iter(&self) -> std::iter::Once<&T> {
1926        std::iter::once(&self.e)
1927    }
1928}
1929
1930impl<T> Serialize for SizeOneVec<T>
1931where
1932    T: Serialize,
1933{
1934    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1935    where
1936        S: Serializer,
1937    {
1938        let mut seq = serializer.serialize_seq(Some(1))?;
1939        seq.serialize_element(&self.e)?;
1940        seq.end()
1941    }
1942}
1943
1944impl<T> TryFrom<Vec<T>> for SizeOneVec<T> {
1945    type Error = anyhow::Error;
1946
1947    fn try_from(mut v: Vec<T>) -> Result<Self, Self::Error> {
1948        if v.len() != 1 {
1949            Err(anyhow!("Expected a vec of size 1"))
1950        } else {
1951            Ok(SizeOneVec {
1952                e: v.pop().unwrap(),
1953            })
1954        }
1955    }
1956}
1957
1958#[test]
1959fn test_size_one_vec_is_transparent() {
1960    let regular = vec![42u8];
1961    let size_one = SizeOneVec::new(42u8);
1962
1963    // Vec -> SizeOneVec serialization is transparent
1964    let regular_ser = bcs::to_bytes(&regular).unwrap();
1965    let size_one_deser = bcs::from_bytes::<SizeOneVec<u8>>(&regular_ser).unwrap();
1966    assert_eq!(size_one, size_one_deser);
1967
1968    // other direction works too
1969    let size_one_ser = bcs::to_bytes(&SizeOneVec::new(43u8)).unwrap();
1970    let regular_deser = bcs::from_bytes::<Vec<u8>>(&size_one_ser).unwrap();
1971    assert_eq!(regular_deser, vec![43u8]);
1972
1973    // we get a deserialize error when deserializing a vec with size != 1
1974    let empty_ser = bcs::to_bytes(&Vec::<u8>::new()).unwrap();
1975    bcs::from_bytes::<SizeOneVec<u8>>(&empty_ser).unwrap_err();
1976
1977    let size_greater_than_one_ser = bcs::to_bytes(&vec![1u8, 2u8]).unwrap();
1978    bcs::from_bytes::<SizeOneVec<u8>>(&size_greater_than_one_ser).unwrap_err();
1979}