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