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, TransactionEffects, TransactionEffectsDigest,
18    TypeTag, Version,
19};
20use move_binary_format::{CompiledModule, file_format::SignatureToken};
21use move_bytecode_utils::resolve_struct;
22use move_core_types::{
23    account_address::AccountAddress, annotated_value as A, ident_str, identifier::IdentStr,
24};
25use serde::{Deserialize, Serialize, ser::Error};
26
27pub use crate::committee::EpochId;
28use crate::{
29    IOTA_FRAMEWORK_ADDRESS, MOVE_STDLIB_ADDRESS,
30    crypto::{AuthorityPublicKeyBytes, DefaultHash, PublicKey},
31    effects::{TransactionEffectsAPI, TransactionEffectsExt},
32    epoch_data::EpochData,
33    error::{ExecutionError, ExecutionErrorKind},
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::{TransactionEnvelope, 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 object_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            object_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            object_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
221impl From<&PublicKey> for Address {
222    fn from(pk: &PublicKey) -> Self {
223        let mut hasher = DefaultHash::default();
224        update_hasher_with_flag(&mut hasher, pk.scheme());
225        hasher.update(pk);
226        let g_arr = hasher.finalize();
227        Address::new(g_arr.digest)
228    }
229}
230
231/// Generate a fake Address with repeated one byte.
232pub fn dbg_addr(name: u8) -> Address {
233    let addr = [name; IOTA_ADDRESS_LENGTH];
234    Address::new(addr)
235}
236
237#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize, Debug)]
238pub struct ExecutionDigests {
239    pub transaction: TransactionDigest,
240    pub effects: TransactionEffectsDigest,
241}
242
243impl ExecutionDigests {
244    pub fn new(transaction: TransactionDigest, effects: TransactionEffectsDigest) -> Self {
245        Self {
246            transaction,
247            effects,
248        }
249    }
250
251    pub fn random() -> Self {
252        Self {
253            transaction: TransactionDigest::random(),
254            effects: TransactionEffectsDigest::random(),
255        }
256    }
257}
258
259#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
260pub struct ExecutionData {
261    pub transaction: TransactionEnvelope,
262    pub effects: TransactionEffects,
263}
264
265impl ExecutionData {
266    pub fn new(transaction: TransactionEnvelope, effects: TransactionEffects) -> ExecutionData {
267        debug_assert_eq!(transaction.digest(), effects.transaction_digest());
268        Self {
269            transaction,
270            effects,
271        }
272    }
273
274    pub fn digests(&self) -> ExecutionDigests {
275        self.effects.execution_digests()
276    }
277}
278
279#[derive(Clone, Eq, PartialEq, Debug)]
280pub struct VerifiedExecutionData {
281    pub transaction: VerifiedTransaction,
282    pub effects: TransactionEffects,
283}
284
285impl VerifiedExecutionData {
286    pub fn new(transaction: VerifiedTransaction, effects: TransactionEffects) -> Self {
287        debug_assert_eq!(transaction.digest(), effects.transaction_digest());
288        Self {
289            transaction,
290            effects,
291        }
292    }
293
294    pub fn new_unchecked(data: ExecutionData) -> Self {
295        Self {
296            transaction: VerifiedTransaction::new_unchecked(data.transaction),
297            effects: data.effects,
298        }
299    }
300
301    pub fn into_inner(self) -> ExecutionData {
302        ExecutionData {
303            transaction: self.transaction.into_inner(),
304            effects: self.effects,
305        }
306    }
307
308    pub fn digests(&self) -> ExecutionDigests {
309        self.effects.execution_digests()
310    }
311}
312
313pub const RESOLVED_STD_OPTION: (&AccountAddress, &IdentStr, &IdentStr) = (
314    &MOVE_STDLIB_ADDRESS,
315    ident_str!("option"),
316    ident_str!("Option"),
317);
318
319pub const RESOLVED_ASCII_STR: (&AccountAddress, &IdentStr, &IdentStr) = (
320    &MOVE_STDLIB_ADDRESS,
321    ident_str!("ascii"),
322    ident_str!("String"),
323);
324
325pub const RESOLVED_UTF8_STR: (&AccountAddress, &IdentStr, &IdentStr) = (
326    &MOVE_STDLIB_ADDRESS,
327    ident_str!("string"),
328    ident_str!("String"),
329);
330
331pub const RESOLVED_TX_CONTEXT: (&AccountAddress, &IdentStr, &IdentStr) = (
332    &crate::IOTA_FRAMEWORK_ADDRESS,
333    ident_str!("tx_context"),
334    ident_str!("TxContext"),
335);
336
337pub const RESOLVED_IOTA_ID: (&AccountAddress, &IdentStr, &IdentStr) = (
338    &IOTA_FRAMEWORK_ADDRESS,
339    ident_str!("object"),
340    ident_str!("ID"),
341);
342
343pub fn move_ascii_str_layout() -> A::MoveStructLayout {
344    A::MoveStructLayout {
345        type_: struct_tag_sdk_to_core(&StructTag::new_ascii_string()),
346        fields: vec![A::MoveFieldLayout::new(
347            ident_str!("bytes").into(),
348            A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
349        )],
350    }
351}
352
353pub fn move_utf8_str_layout() -> A::MoveStructLayout {
354    A::MoveStructLayout {
355        type_: struct_tag_sdk_to_core(&StructTag::new_string()),
356        fields: vec![A::MoveFieldLayout::new(
357            ident_str!("bytes").into(),
358            A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
359        )],
360    }
361}
362
363pub fn url_layout() -> A::MoveStructLayout {
364    A::MoveStructLayout {
365        type_: struct_tag_sdk_to_core(&StructTag::new_url()),
366        fields: vec![A::MoveFieldLayout::new(
367            ident_str!("url").to_owned(),
368            A::MoveTypeLayout::Struct(Box::new(move_ascii_str_layout())),
369        )],
370    }
371}
372
373// The Rust representation of the Move `TxContext`.
374// This struct must be kept in sync with the Move `TxContext` definition.
375// Moving forward we are going to zero all fields of the Move `TxContext`
376// and use native functions to retrieve info about the transaction.
377// However we cannot remove the Move type and so this struct is going to
378// be the Rust equivalent to the Move `TxContext` for legacy usages.
379//
380// `TxContext` in Rust (see below) is going to be purely used in Rust and can
381// evolve as needed without worrying about any compatibility with Move.
382#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
383pub struct MoveLegacyTxContext {
384    // Signer/sender of the transaction
385    sender: AccountAddress,
386    // Digest of the current transaction
387    digest: Vec<u8>,
388    // The current epoch number
389    epoch: EpochId,
390    // Timestamp that the epoch started at
391    epoch_timestamp_ms: CheckpointTimestamp,
392    // Number of `ObjectId`'s generated during execution of the current transaction
393    ids_created: u64,
394}
395
396impl From<&TxContext> for MoveLegacyTxContext {
397    fn from(tx_context: &TxContext) -> Self {
398        Self {
399            sender: tx_context.sender,
400            digest: tx_context.digest.clone(),
401            epoch: tx_context.epoch,
402            epoch_timestamp_ms: tx_context.epoch_timestamp_ms,
403            ids_created: tx_context.ids_created,
404        }
405    }
406}
407
408// Information about the transaction context.
409// This struct is not related to Move and can evolve as needed/required.
410#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
411pub struct TxContext {
412    /// Signer/sender of the transaction
413    sender: AccountAddress,
414    /// Digest of the current transaction
415    digest: Vec<u8>,
416    /// The current epoch number
417    epoch: EpochId,
418    /// Timestamp that the epoch started at
419    epoch_timestamp_ms: CheckpointTimestamp,
420    /// Number of `ObjectId`'s generated during execution of the current
421    /// transaction
422    ids_created: u64,
423    // Reference gas price
424    rgp: u64,
425    /// Gas price passed to transaction as input
426    gas_price: u64,
427    /// Gas budget passed to transaction as input
428    gas_budget: u64,
429    /// Address of the sponsor if any (gas owner != sender)
430    sponsor: Option<AccountAddress>,
431    /// Whether the `TxContext` is native or not (i.e., Move reads values via
432    /// native functions instead of struct fields).
433    is_native: bool,
434}
435
436#[derive(PartialEq, Eq, Clone, Copy)]
437pub enum TxContextKind {
438    // No TxContext
439    None,
440    // &mut TxContext
441    Mutable,
442    // &TxContext
443    Immutable,
444}
445
446impl TxContext {
447    pub fn new(
448        sender: &Address,
449        digest: &TransactionDigest,
450        epoch_data: &EpochData,
451        rgp: u64,
452        gas_price: u64,
453        gas_budget: u64,
454        sponsor: Option<Address>,
455        protocol_config: &ProtocolConfig,
456    ) -> Self {
457        Self::new_from_components(
458            sender,
459            digest,
460            &epoch_data.epoch_id(),
461            epoch_data.epoch_start_timestamp(),
462            rgp,
463            gas_price,
464            gas_budget,
465            sponsor,
466            protocol_config,
467        )
468    }
469
470    pub fn new_from_components(
471        sender: &Address,
472        digest: &TransactionDigest,
473        epoch_id: &EpochId,
474        epoch_timestamp_ms: u64,
475        rgp: u64,
476        gas_price: u64,
477        gas_budget: u64,
478        sponsor: Option<Address>,
479        protocol_config: &ProtocolConfig,
480    ) -> Self {
481        Self {
482            sender: AccountAddress::new(sender.into_bytes()),
483            digest: digest.into_bytes().to_vec(),
484            epoch: *epoch_id,
485            epoch_timestamp_ms,
486            ids_created: 0,
487            rgp,
488            gas_price,
489            gas_budget,
490            sponsor: sponsor.map(|s| AccountAddress::new(s.into_bytes())),
491            is_native: protocol_config.move_native_tx_context(),
492        }
493    }
494
495    /// Returns whether the type signature is &mut TxContext, &TxContext, or
496    /// none of the above.
497    pub fn kind(view: &CompiledModule, s: &SignatureToken) -> TxContextKind {
498        use SignatureToken as S;
499        let (kind, s) = match s {
500            S::MutableReference(s) => (TxContextKind::Mutable, s),
501            S::Reference(s) => (TxContextKind::Immutable, s),
502            _ => return TxContextKind::None,
503        };
504
505        let S::Datatype(idx) = &**s else {
506            return TxContextKind::None;
507        };
508
509        let (module_addr, module_name, struct_name) = resolve_struct(view, *idx);
510        let is_tx_context_type = module_name.as_str() == Identifier::TX_CONTEXT_MODULE.as_str()
511            && module_addr.as_ref() == Address::FRAMEWORK.as_bytes()
512            && struct_name.as_str() == Identifier::TX_CONTEXT.as_str();
513
514        if is_tx_context_type {
515            kind
516        } else {
517            TxContextKind::None
518        }
519    }
520
521    pub fn epoch(&self) -> EpochId {
522        self.epoch
523    }
524
525    pub fn epoch_timestamp_ms(&self) -> u64 {
526        self.epoch_timestamp_ms
527    }
528
529    /// Return the transaction digest, to include in new objects
530    pub fn digest(&self) -> TransactionDigest {
531        TransactionDigest::new(self.digest.clone().try_into().unwrap())
532    }
533
534    pub fn sponsor(&self) -> Option<Address> {
535        self.sponsor.map(|a| Address::from(a.into_bytes()))
536    }
537
538    pub fn rgp(&self) -> u64 {
539        self.rgp
540    }
541
542    pub fn gas_price(&self) -> u64 {
543        self.gas_price
544    }
545
546    pub fn gas_budget(&self) -> u64 {
547        self.gas_budget
548    }
549
550    pub fn ids_created(&self) -> u64 {
551        self.ids_created
552    }
553
554    /// Derive a globally unique object ID by hashing self.digest |
555    /// self.ids_created
556    pub fn fresh_id(&mut self) -> ObjectId {
557        let id = ObjectId::derive_id(self.digest(), self.ids_created);
558        self.ids_created += 1;
559        id
560    }
561
562    pub fn sender(&self) -> Address {
563        Address::new(self.sender.into_bytes())
564    }
565
566    pub fn to_vec(&self) -> Vec<u8> {
567        bcs::to_bytes(&self).unwrap()
568    }
569
570    /// Serialize this context as a `MoveLegacyTxContext`. When `is_native` is
571    /// true, all fields except digest are zeroed (Move reads actual values via
572    /// native functions). When false, actual field values are used.
573    pub fn to_bcs_legacy_context(&self) -> Vec<u8> {
574        let move_context: MoveLegacyTxContext = if self.is_native {
575            let tx_context = &TxContext {
576                sender: AccountAddress::ZERO,
577                digest: vec![],
578                epoch: 0,
579                epoch_timestamp_ms: 0,
580                ids_created: 0,
581                rgp: 0,
582                gas_price: 0,
583                gas_budget: 0,
584                sponsor: None,
585                is_native: true,
586            };
587            tx_context.into()
588        } else {
589            self.into()
590        };
591        bcs::to_bytes(&move_context).unwrap()
592    }
593
594    /// Updates state of the context instance. It's intended to use
595    /// when mutable context is passed over some boundary via
596    /// serialize/deserialize and this is the reason why this method
597    /// consumes the other context.
598    pub fn update_state(&mut self, other: MoveLegacyTxContext) -> Result<(), ExecutionError> {
599        if !self.is_native {
600            if self.sender != other.sender
601                || self.digest != other.digest
602                || other.ids_created < self.ids_created
603            {
604                return Err(ExecutionError::new_with_source(
605                    ExecutionErrorKind::InvariantViolation,
606                    "Immutable fields for TxContext changed",
607                ));
608            }
609            self.ids_created = other.ids_created;
610        }
611        Ok(())
612    }
613
614    /// Replace all fields. Used by Move test-only native functions.
615    pub fn replace(
616        &mut self,
617        sender: AccountAddress,
618        tx_hash: Vec<u8>,
619        epoch: u64,
620        epoch_timestamp_ms: u64,
621        ids_created: u64,
622        rgp: u64,
623        gas_price: u64,
624        gas_budget: u64,
625        sponsor: Option<AccountAddress>,
626    ) {
627        self.sender = sender;
628        self.digest = tx_hash;
629        self.epoch = epoch;
630        self.epoch_timestamp_ms = epoch_timestamp_ms;
631        self.ids_created = ids_created;
632        self.rgp = rgp;
633        self.gas_price = gas_price;
634        self.gas_budget = gas_budget;
635        self.sponsor = sponsor;
636    }
637
638    // Generate a random TxContext for testing.
639    #[cfg(not(target_arch = "wasm32"))]
640    pub fn random_for_testing_only() -> Self {
641        Self::new(
642            &Address::random(),
643            &TransactionDigest::random(),
644            &EpochData::new_test(),
645            0,
646            0,
647            0,
648            None,
649            &ProtocolConfig::get_for_max_version_UNSAFE(),
650        )
651    }
652}
653
654/// Generate a fake ObjectId with repeated one byte.
655pub fn dbg_object_id(name: u8) -> ObjectId {
656    ObjectId::new([name; ObjectId::LENGTH])
657}
658
659#[derive(PartialEq, Eq, Clone, Debug, thiserror::Error)]
660pub enum ObjectIdParseError {
661    #[error("ObjectId hex literal must start with 0x")]
662    HexLiteralPrefixMissing,
663
664    #[error("Could not convert from bytes slice")]
665    TryFromSlice,
666}
667
668impl fmt::Display for ObjectType {
669    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
670        match self {
671            ObjectType::Package => write!(f, "{PACKAGE}"),
672            ObjectType::Struct(t) => write!(
673                f,
674                "{}",
675                to_iota_struct_tag_string(t).map_err(fmt::Error::custom)?
676            ),
677        }
678    }
679}