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