Skip to main content

iota_types/
base_types.rs

1// Copyright (c) 2021, Facebook, Inc. and its affiliates
2// Copyright (c) Mysten Labs, Inc.
3// Modifications Copyright (c) 2024 IOTA Stiftung
4// SPDX-License-Identifier: Apache-2.0
5
6use std::{
7    convert::{TryFrom, TryInto},
8    fmt,
9    str::FromStr,
10};
11
12use anyhow::anyhow;
13use fastcrypto::hash::HashFunction;
14use iota_protocol_config::ProtocolConfig;
15pub use iota_sdk_types::{
16    Address as IotaAddress, MoveObjectType, ObjectReference as ObjectRef, Version as SequenceNumber,
17};
18use iota_sdk_types::{Identifier, ObjectId, Owner, StructTag, TypeTag};
19use move_binary_format::{CompiledModule, file_format::SignatureToken};
20use move_bytecode_utils::resolve_struct;
21use move_core_types::{
22    account_address::AccountAddress, annotated_value as A, ident_str, identifier::IdentStr,
23};
24use serde::{
25    Deserialize, Serialize, Serializer,
26    ser::{Error, SerializeSeq},
27};
28
29use crate::{
30    MOVE_STDLIB_ADDRESS,
31    crypto::{AuthorityPublicKeyBytes, DefaultHash, IotaPublicKey, IotaSignature, PublicKey},
32    effects::{TransactionEffects, TransactionEffectsAPI, TransactionEffectsExt},
33    epoch_data::EpochData,
34    error::{ExecutionError, ExecutionErrorKind, IotaError, IotaResult},
35    id::RESOLVED_IOTA_ID,
36    iota_sdk_types_conversions::struct_tag_sdk_to_core,
37    iota_serde::to_iota_struct_tag_string,
38    messages_checkpoint::CheckpointTimestamp,
39    object::Object,
40    parse_iota_struct_tag,
41    signature::GenericSignature,
42    transaction::{Transaction, VerifiedTransaction},
43};
44pub use crate::{
45    committee::EpochId,
46    digests::{ObjectDigest, TransactionDigest, TransactionEffectsDigest},
47};
48
49#[cfg(test)]
50#[path = "unit_tests/base_types_tests.rs"]
51mod base_types_tests;
52
53pub type TxSequenceNumber = u64;
54
55pub type VersionNumber = SequenceNumber;
56
57/// The round number.
58pub type CommitRound = u64;
59
60pub type AuthorityName = AuthorityPublicKeyBytes;
61
62pub trait ConciseableName<'a> {
63    type ConciseTypeRef: std::fmt::Debug;
64    type ConciseType: std::fmt::Debug;
65
66    fn concise(&'a self) -> Self::ConciseTypeRef;
67    fn concise_owned(&self) -> Self::ConciseType;
68}
69
70pub type VersionDigest = (SequenceNumber, ObjectDigest);
71
72pub fn random_object_ref() -> ObjectRef {
73    ObjectRef::new(
74        ObjectId::random(),
75        SequenceNumber::default(),
76        ObjectDigest::new([0; 32]),
77    )
78}
79
80/// Whether this type is valid as a primitive (pure) transaction input.
81pub fn is_primitive_type_tag(t: &TypeTag) -> bool {
82    use TypeTag as T;
83
84    match t {
85        T::Bool | T::U8 | T::U16 | T::U32 | T::U64 | T::U128 | T::U256 | T::Address => true,
86        T::Vector(inner) => is_primitive_type_tag(inner),
87        T::Struct(st) => {
88            let resolved_struct = (
89                &AccountAddress::new(st.address().into_bytes()),
90                move_core_types::identifier::IdentStr::new(st.module().as_str()).unwrap(),
91                move_core_types::identifier::IdentStr::new(st.name().as_str()).unwrap(),
92            );
93            // is id or..
94            if resolved_struct == RESOLVED_IOTA_ID {
95                return true;
96            }
97            // is utf8 string
98            if resolved_struct == RESOLVED_UTF8_STR {
99                return true;
100            }
101            // is ascii string
102            if resolved_struct == RESOLVED_ASCII_STR {
103                return true;
104            }
105            // is option of a primitive
106            resolved_struct == RESOLVED_STD_OPTION
107                && st.type_params().len() == 1
108                && is_primitive_type_tag(&st.type_params()[0])
109        }
110        T::Signer => false,
111    }
112}
113
114/// Type of an IOTA object
115#[derive(Clone, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Debug)]
116pub enum ObjectType {
117    /// Move package containing one or more bytecode modules
118    Package,
119    /// A Move struct of the given type
120    Struct(MoveObjectType),
121}
122
123const PACKAGE: &str = "package";
124
125impl ObjectType {
126    pub fn is_gas_coin(&self) -> bool {
127        matches!(self, ObjectType::Struct(s) if s.is_gas_coin())
128    }
129
130    pub fn is_coin(&self) -> bool {
131        matches!(self, ObjectType::Struct(s) if s.is_coin())
132    }
133
134    pub fn is_package(&self) -> bool {
135        matches!(self, ObjectType::Package)
136    }
137}
138
139impl From<&Object> for ObjectType {
140    fn from(o: &Object) -> Self {
141        o.data
142            .object_type()
143            .map(|t| ObjectType::Struct(t.clone()))
144            .unwrap_or(ObjectType::Package)
145    }
146}
147
148impl TryFrom<ObjectType> for StructTag {
149    type Error = anyhow::Error;
150
151    fn try_from(o: ObjectType) -> Result<Self, anyhow::Error> {
152        match o {
153            ObjectType::Package => Err(anyhow!("Cannot create StructTag from Package")),
154            ObjectType::Struct(s) => Ok(s.into()),
155        }
156    }
157}
158
159impl FromStr for ObjectType {
160    type Err = anyhow::Error;
161
162    fn from_str(s: &str) -> Result<Self, Self::Err> {
163        if s.to_lowercase() == PACKAGE {
164            Ok(ObjectType::Package)
165        } else {
166            let tag = parse_iota_struct_tag(s)?;
167            Ok(ObjectType::Struct(tag.into()))
168        }
169    }
170}
171
172#[derive(Clone, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Debug)]
173pub struct ObjectInfo {
174    pub object_id: ObjectId,
175    pub version: SequenceNumber,
176    pub digest: ObjectDigest,
177    pub type_: ObjectType,
178    pub owner: Owner,
179    pub previous_transaction: TransactionDigest,
180}
181
182impl ObjectInfo {
183    pub fn new(oref: &ObjectRef, o: &Object) -> Self {
184        Self {
185            object_id: oref.object_id,
186            version: oref.version,
187            digest: oref.digest,
188            type_: o.into(),
189            owner: o.owner,
190            previous_transaction: o.previous_transaction,
191        }
192    }
193
194    pub fn from_object(object: &Object) -> Self {
195        Self {
196            object_id: object.id(),
197            version: object.version(),
198            digest: object.digest(),
199            type_: object.into(),
200            owner: object.owner,
201            previous_transaction: object.previous_transaction,
202        }
203    }
204}
205
206impl From<ObjectInfo> for ObjectRef {
207    fn from(info: ObjectInfo) -> Self {
208        ObjectRef::new(info.object_id, info.version, info.digest)
209    }
210}
211
212impl From<&ObjectInfo> for ObjectRef {
213    fn from(info: &ObjectInfo) -> Self {
214        ObjectRef::new(info.object_id, info.version, info.digest)
215    }
216}
217
218pub const IOTA_ADDRESS_LENGTH: usize = ObjectId::LENGTH;
219
220pub fn address_from_iota_pub_key<T: IotaPublicKey>(pk: &T) -> IotaAddress {
221    let mut hasher = DefaultHash::default();
222    T::SIGNATURE_SCHEME.update_hasher_with_flag(&mut hasher);
223    hasher.update(pk);
224    let g_arr = hasher.finalize();
225    IotaAddress::new(g_arr.digest)
226}
227
228impl From<&PublicKey> for IotaAddress {
229    fn from(pk: &PublicKey) -> Self {
230        let mut hasher = DefaultHash::default();
231        pk.scheme().update_hasher_with_flag(&mut hasher);
232        hasher.update(pk);
233        let g_arr = hasher.finalize();
234        IotaAddress::new(g_arr.digest)
235    }
236}
237
238impl TryFrom<&GenericSignature> for IotaAddress {
239    type Error = IotaError;
240    /// Derive a IotaAddress from a serialized signature in IOTA
241    /// [GenericSignature].
242    fn try_from(sig: &GenericSignature) -> IotaResult<Self> {
243        match sig {
244            GenericSignature::Signature(sig) => {
245                let scheme = sig.scheme();
246                let pub_key_bytes = sig.public_key_bytes();
247                let pub_key = PublicKey::try_from_bytes(scheme, pub_key_bytes).map_err(|_| {
248                    IotaError::InvalidSignature {
249                        error: "Cannot parse pubkey".to_string(),
250                    }
251                })?;
252                Ok(IotaAddress::from(&pub_key))
253            }
254            GenericSignature::MultiSig(ms) => Ok(ms.committee().into()),
255            #[allow(deprecated)]
256            GenericSignature::ZkLoginAuthenticatorDeprecated(_) => {
257                Err(IotaError::UnsupportedFeature {
258                    error: "zkLogin is not supported".to_string(),
259                })
260            }
261            GenericSignature::PasskeyAuthenticator(s) => Ok(IotaAddress::from(&s.get_pk()?)),
262            GenericSignature::MoveAuthenticator(move_authenticator) => move_authenticator.address(),
263        }
264    }
265}
266
267/// Generate a fake IotaAddress with repeated one byte.
268pub fn dbg_addr(name: u8) -> IotaAddress {
269    let addr = [name; IOTA_ADDRESS_LENGTH];
270    IotaAddress::new(addr)
271}
272
273#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize, Debug)]
274pub struct ExecutionDigests {
275    pub transaction: TransactionDigest,
276    pub effects: TransactionEffectsDigest,
277}
278
279impl ExecutionDigests {
280    pub fn new(transaction: TransactionDigest, effects: TransactionEffectsDigest) -> Self {
281        Self {
282            transaction,
283            effects,
284        }
285    }
286
287    pub fn random() -> Self {
288        Self {
289            transaction: TransactionDigest::random(),
290            effects: TransactionEffectsDigest::random(),
291        }
292    }
293}
294
295#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
296pub struct ExecutionData {
297    pub transaction: Transaction,
298    pub effects: TransactionEffects,
299}
300
301impl ExecutionData {
302    pub fn new(transaction: Transaction, effects: TransactionEffects) -> ExecutionData {
303        debug_assert_eq!(transaction.digest(), effects.transaction_digest());
304        Self {
305            transaction,
306            effects,
307        }
308    }
309
310    pub fn digests(&self) -> ExecutionDigests {
311        self.effects.execution_digests()
312    }
313}
314
315#[derive(Clone, Eq, PartialEq, Debug)]
316pub struct VerifiedExecutionData {
317    pub transaction: VerifiedTransaction,
318    pub effects: TransactionEffects,
319}
320
321impl VerifiedExecutionData {
322    pub fn new(transaction: VerifiedTransaction, effects: TransactionEffects) -> Self {
323        debug_assert_eq!(transaction.digest(), effects.transaction_digest());
324        Self {
325            transaction,
326            effects,
327        }
328    }
329
330    pub fn new_unchecked(data: ExecutionData) -> Self {
331        Self {
332            transaction: VerifiedTransaction::new_unchecked(data.transaction),
333            effects: data.effects,
334        }
335    }
336
337    pub fn into_inner(self) -> ExecutionData {
338        ExecutionData {
339            transaction: self.transaction.into_inner(),
340            effects: self.effects,
341        }
342    }
343
344    pub fn digests(&self) -> ExecutionDigests {
345        self.effects.execution_digests()
346    }
347}
348
349pub const RESOLVED_STD_OPTION: (&AccountAddress, &IdentStr, &IdentStr) = (
350    &MOVE_STDLIB_ADDRESS,
351    ident_str!("option"),
352    ident_str!("Option"),
353);
354
355pub const RESOLVED_ASCII_STR: (&AccountAddress, &IdentStr, &IdentStr) = (
356    &MOVE_STDLIB_ADDRESS,
357    ident_str!("ascii"),
358    ident_str!("String"),
359);
360
361pub const RESOLVED_UTF8_STR: (&AccountAddress, &IdentStr, &IdentStr) = (
362    &MOVE_STDLIB_ADDRESS,
363    ident_str!("string"),
364    ident_str!("String"),
365);
366
367pub fn move_ascii_str_layout() -> A::MoveStructLayout {
368    A::MoveStructLayout {
369        type_: struct_tag_sdk_to_core(&StructTag::new_ascii_string()),
370        fields: vec![A::MoveFieldLayout::new(
371            ident_str!("bytes").into(),
372            A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
373        )],
374    }
375}
376
377pub fn move_utf8_str_layout() -> A::MoveStructLayout {
378    A::MoveStructLayout {
379        type_: struct_tag_sdk_to_core(&StructTag::new_string()),
380        fields: vec![A::MoveFieldLayout::new(
381            ident_str!("bytes").into(),
382            A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
383        )],
384    }
385}
386
387pub fn url_layout() -> A::MoveStructLayout {
388    A::MoveStructLayout {
389        type_: struct_tag_sdk_to_core(&StructTag::new_url()),
390        fields: vec![A::MoveFieldLayout::new(
391            ident_str!("url").to_owned(),
392            A::MoveTypeLayout::Struct(Box::new(move_ascii_str_layout())),
393        )],
394    }
395}
396
397// The Rust representation of the Move `TxContext`.
398// This struct must be kept in sync with the Move `TxContext` definition.
399// Moving forward we are going to zero all fields of the Move `TxContext`
400// and use native functions to retrieve info about the transaction.
401// However we cannot remove the Move type and so this struct is going to
402// be the Rust equivalent to the Move `TxContext` for legacy usages.
403//
404// `TxContext` in Rust (see below) is going to be purely used in Rust and can
405// evolve as needed without worrying about any compatibility with Move.
406#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
407pub struct MoveLegacyTxContext {
408    // Signer/sender of the transaction
409    sender: AccountAddress,
410    // Digest of the current transaction
411    digest: Vec<u8>,
412    // The current epoch number
413    epoch: EpochId,
414    // Timestamp that the epoch started at
415    epoch_timestamp_ms: CheckpointTimestamp,
416    // Number of `ObjectId`'s generated during execution of the current transaction
417    ids_created: u64,
418}
419
420impl From<&TxContext> for MoveLegacyTxContext {
421    fn from(tx_context: &TxContext) -> Self {
422        Self {
423            sender: tx_context.sender,
424            digest: tx_context.digest.clone(),
425            epoch: tx_context.epoch,
426            epoch_timestamp_ms: tx_context.epoch_timestamp_ms,
427            ids_created: tx_context.ids_created,
428        }
429    }
430}
431
432// Information about the transaction context.
433// This struct is not related to Move and can evolve as needed/required.
434#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
435pub struct TxContext {
436    /// Signer/sender of the transaction
437    sender: AccountAddress,
438    /// Digest of the current transaction
439    digest: Vec<u8>,
440    /// The current epoch number
441    epoch: EpochId,
442    /// Timestamp that the epoch started at
443    epoch_timestamp_ms: CheckpointTimestamp,
444    /// Number of `ObjectId`'s generated during execution of the current
445    /// transaction
446    ids_created: u64,
447    // Reference gas price
448    rgp: u64,
449    /// Gas price passed to transaction as input
450    gas_price: u64,
451    /// Gas budget passed to transaction as input
452    gas_budget: u64,
453    /// Address of the sponsor if any (gas owner != sender)
454    sponsor: Option<AccountAddress>,
455    /// Whether the `TxContext` is native or not (i.e., Move reads values via
456    /// native functions instead of struct fields).
457    is_native: bool,
458}
459
460#[derive(PartialEq, Eq, Clone, Copy)]
461pub enum TxContextKind {
462    // No TxContext
463    None,
464    // &mut TxContext
465    Mutable,
466    // &TxContext
467    Immutable,
468}
469
470impl TxContext {
471    pub fn new(
472        sender: &IotaAddress,
473        digest: &TransactionDigest,
474        epoch_data: &EpochData,
475        rgp: u64,
476        gas_price: u64,
477        gas_budget: u64,
478        sponsor: Option<IotaAddress>,
479        protocol_config: &ProtocolConfig,
480    ) -> Self {
481        Self::new_from_components(
482            sender,
483            digest,
484            &epoch_data.epoch_id(),
485            epoch_data.epoch_start_timestamp(),
486            rgp,
487            gas_price,
488            gas_budget,
489            sponsor,
490            protocol_config,
491        )
492    }
493
494    pub fn new_from_components(
495        sender: &IotaAddress,
496        digest: &TransactionDigest,
497        epoch_id: &EpochId,
498        epoch_timestamp_ms: u64,
499        rgp: u64,
500        gas_price: u64,
501        gas_budget: u64,
502        sponsor: Option<IotaAddress>,
503        protocol_config: &ProtocolConfig,
504    ) -> Self {
505        Self {
506            sender: AccountAddress::new(sender.into_bytes()),
507            digest: digest.into_inner().to_vec(),
508            epoch: *epoch_id,
509            epoch_timestamp_ms,
510            ids_created: 0,
511            rgp,
512            gas_price,
513            gas_budget,
514            sponsor: sponsor.map(|s| AccountAddress::new(s.into_bytes())),
515            is_native: protocol_config.move_native_tx_context(),
516        }
517    }
518
519    /// Returns whether the type signature is &mut TxContext, &TxContext, or
520    /// none of the above.
521    pub fn kind(view: &CompiledModule, s: &SignatureToken) -> TxContextKind {
522        use SignatureToken as S;
523        let (kind, s) = match s {
524            S::MutableReference(s) => (TxContextKind::Mutable, s),
525            S::Reference(s) => (TxContextKind::Immutable, s),
526            _ => return TxContextKind::None,
527        };
528
529        let S::Datatype(idx) = &**s else {
530            return TxContextKind::None;
531        };
532
533        let (module_addr, module_name, struct_name) = resolve_struct(view, *idx);
534        let is_tx_context_type = module_name.as_str() == Identifier::TX_CONTEXT_MODULE.as_str()
535            && module_addr.as_ref() == IotaAddress::FRAMEWORK.as_bytes()
536            && struct_name.as_str() == Identifier::TX_CONTEXT.as_str();
537
538        if is_tx_context_type {
539            kind
540        } else {
541            TxContextKind::None
542        }
543    }
544
545    pub fn epoch(&self) -> EpochId {
546        self.epoch
547    }
548
549    pub fn epoch_timestamp_ms(&self) -> u64 {
550        self.epoch_timestamp_ms
551    }
552
553    /// Return the transaction digest, to include in new objects
554    pub fn digest(&self) -> TransactionDigest {
555        TransactionDigest::new(self.digest.clone().try_into().unwrap())
556    }
557
558    pub fn sponsor(&self) -> Option<IotaAddress> {
559        self.sponsor.map(|a| IotaAddress::from(a.into_bytes()))
560    }
561
562    pub fn rgp(&self) -> u64 {
563        self.rgp
564    }
565
566    pub fn gas_price(&self) -> u64 {
567        self.gas_price
568    }
569
570    pub fn gas_budget(&self) -> u64 {
571        self.gas_budget
572    }
573
574    pub fn ids_created(&self) -> u64 {
575        self.ids_created
576    }
577
578    /// Derive a globally unique object ID by hashing self.digest |
579    /// self.ids_created
580    pub fn fresh_id(&mut self) -> ObjectId {
581        let id = ObjectId::derive_id(self.digest(), self.ids_created);
582        self.ids_created += 1;
583        id
584    }
585
586    pub fn sender(&self) -> IotaAddress {
587        IotaAddress::new(self.sender.into_bytes())
588    }
589
590    pub fn to_vec(&self) -> Vec<u8> {
591        bcs::to_bytes(&self).unwrap()
592    }
593
594    /// Serialize this context as a `MoveLegacyTxContext`. When `is_native` is
595    /// true, all fields except digest are zeroed (Move reads actual values via
596    /// native functions). When false, actual field values are used.
597    pub fn to_bcs_legacy_context(&self) -> Vec<u8> {
598        let move_context: MoveLegacyTxContext = if self.is_native {
599            let tx_context = &TxContext {
600                sender: AccountAddress::ZERO,
601                digest: vec![],
602                epoch: 0,
603                epoch_timestamp_ms: 0,
604                ids_created: 0,
605                rgp: 0,
606                gas_price: 0,
607                gas_budget: 0,
608                sponsor: None,
609                is_native: true,
610            };
611            tx_context.into()
612        } else {
613            self.into()
614        };
615        bcs::to_bytes(&move_context).unwrap()
616    }
617
618    /// Updates state of the context instance. It's intended to use
619    /// when mutable context is passed over some boundary via
620    /// serialize/deserialize and this is the reason why this method
621    /// consumes the other context.
622    pub fn update_state(&mut self, other: MoveLegacyTxContext) -> Result<(), ExecutionError> {
623        if !self.is_native {
624            if self.sender != other.sender
625                || self.digest != other.digest
626                || other.ids_created < self.ids_created
627            {
628                return Err(ExecutionError::new_with_source(
629                    ExecutionErrorKind::InvariantViolation,
630                    "Immutable fields for TxContext changed",
631                ));
632            }
633            self.ids_created = other.ids_created;
634        }
635        Ok(())
636    }
637
638    /// Replace all fields. Used by Move test-only native functions.
639    pub fn replace(
640        &mut self,
641        sender: AccountAddress,
642        tx_hash: Vec<u8>,
643        epoch: u64,
644        epoch_timestamp_ms: u64,
645        ids_created: u64,
646        rgp: u64,
647        gas_price: u64,
648        gas_budget: u64,
649        sponsor: Option<AccountAddress>,
650    ) {
651        self.sender = sender;
652        self.digest = tx_hash;
653        self.epoch = epoch;
654        self.epoch_timestamp_ms = epoch_timestamp_ms;
655        self.ids_created = ids_created;
656        self.rgp = rgp;
657        self.gas_price = gas_price;
658        self.gas_budget = gas_budget;
659        self.sponsor = sponsor;
660    }
661
662    // Generate a random TxContext for testing.
663    #[cfg(not(target_arch = "wasm32"))]
664    pub fn random_for_testing_only() -> Self {
665        Self::new(
666            &IotaAddress::random(),
667            &TransactionDigest::random(),
668            &EpochData::new_test(),
669            0,
670            0,
671            0,
672            None,
673            &ProtocolConfig::get_for_max_version_UNSAFE(),
674        )
675    }
676}
677
678/// Generate a fake ObjectId with repeated one byte.
679pub fn dbg_object_id(name: u8) -> ObjectId {
680    ObjectId::new([name; ObjectId::LENGTH])
681}
682
683#[derive(PartialEq, Eq, Clone, Debug, thiserror::Error)]
684pub enum ObjectIdParseError {
685    #[error("ObjectId hex literal must start with 0x")]
686    HexLiteralPrefixMissing,
687
688    #[error("Could not convert from bytes slice")]
689    TryFromSlice,
690}
691
692impl fmt::Display for ObjectType {
693    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
694        match self {
695            ObjectType::Package => write!(f, "{PACKAGE}"),
696            ObjectType::Struct(t) => write!(
697                f,
698                "{}",
699                to_iota_struct_tag_string(t).map_err(fmt::Error::custom)?
700            ),
701        }
702    }
703}
704
705// SizeOneVec is a wrapper around Vec<T> that enforces the size of the vec to be
706// 1. This seems pointless, but it allows us to have fields in protocol messages
707// that are current enforced to be of size 1, but might later allow other sizes,
708// and to have that constraint enforced in the serialization/deserialization
709// layer, instead of requiring manual input validation.
710#[derive(Debug, Deserialize, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
711#[serde(try_from = "Vec<T>")]
712pub struct SizeOneVec<T> {
713    e: T,
714}
715
716impl<T> SizeOneVec<T> {
717    pub fn new(e: T) -> Self {
718        Self { e }
719    }
720
721    pub fn element(&self) -> &T {
722        &self.e
723    }
724
725    pub fn element_mut(&mut self) -> &mut T {
726        &mut self.e
727    }
728
729    pub fn into_inner(self) -> T {
730        self.e
731    }
732
733    pub fn iter(&self) -> std::iter::Once<&T> {
734        std::iter::once(&self.e)
735    }
736}
737
738impl<T> Serialize for SizeOneVec<T>
739where
740    T: Serialize,
741{
742    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
743    where
744        S: Serializer,
745    {
746        let mut seq = serializer.serialize_seq(Some(1))?;
747        seq.serialize_element(&self.e)?;
748        seq.end()
749    }
750}
751
752impl<T> TryFrom<Vec<T>> for SizeOneVec<T> {
753    type Error = anyhow::Error;
754
755    fn try_from(mut v: Vec<T>) -> Result<Self, Self::Error> {
756        if v.len() != 1 {
757            Err(anyhow!("Expected a vec of size 1"))
758        } else {
759            Ok(SizeOneVec {
760                e: v.pop().unwrap(),
761            })
762        }
763    }
764}
765
766#[test]
767fn test_size_one_vec_is_transparent() {
768    let regular = vec![42u8];
769    let size_one = SizeOneVec::new(42u8);
770
771    // Vec -> SizeOneVec serialization is transparent
772    let regular_ser = bcs::to_bytes(&regular).unwrap();
773    let size_one_deser = bcs::from_bytes::<SizeOneVec<u8>>(&regular_ser).unwrap();
774    assert_eq!(size_one, size_one_deser);
775
776    // other direction works too
777    let size_one_ser = bcs::to_bytes(&SizeOneVec::new(43u8)).unwrap();
778    let regular_deser = bcs::from_bytes::<Vec<u8>>(&size_one_ser).unwrap();
779    assert_eq!(regular_deser, vec![43u8]);
780
781    // we get a deserialize error when deserializing a vec with size != 1
782    let empty_ser = bcs::to_bytes(&Vec::<u8>::new()).unwrap();
783    bcs::from_bytes::<SizeOneVec<u8>>(&empty_ser).unwrap_err();
784
785    let size_greater_than_one_ser = bcs::to_bytes(&vec![1u8, 2u8]).unwrap();
786    bcs::from_bytes::<SizeOneVec<u8>>(&size_greater_than_one_ser).unwrap_err();
787}