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