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