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 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            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            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 fn move_ascii_str_layout() -> A::MoveStructLayout {
333    A::MoveStructLayout {
334        type_: struct_tag_sdk_to_core(&StructTag::new_ascii_string()),
335        fields: vec![A::MoveFieldLayout::new(
336            ident_str!("bytes").into(),
337            A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
338        )],
339    }
340}
341
342pub fn move_utf8_str_layout() -> A::MoveStructLayout {
343    A::MoveStructLayout {
344        type_: struct_tag_sdk_to_core(&StructTag::new_string()),
345        fields: vec![A::MoveFieldLayout::new(
346            ident_str!("bytes").into(),
347            A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
348        )],
349    }
350}
351
352pub fn url_layout() -> A::MoveStructLayout {
353    A::MoveStructLayout {
354        type_: struct_tag_sdk_to_core(&StructTag::new_url()),
355        fields: vec![A::MoveFieldLayout::new(
356            ident_str!("url").to_owned(),
357            A::MoveTypeLayout::Struct(Box::new(move_ascii_str_layout())),
358        )],
359    }
360}
361
362// The Rust representation of the Move `TxContext`.
363// This struct must be kept in sync with the Move `TxContext` definition.
364// Moving forward we are going to zero all fields of the Move `TxContext`
365// and use native functions to retrieve info about the transaction.
366// However we cannot remove the Move type and so this struct is going to
367// be the Rust equivalent to the Move `TxContext` for legacy usages.
368//
369// `TxContext` in Rust (see below) is going to be purely used in Rust and can
370// evolve as needed without worrying about any compatibility with Move.
371#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
372pub struct MoveLegacyTxContext {
373    // Signer/sender of the transaction
374    sender: AccountAddress,
375    // Digest of the current transaction
376    digest: Vec<u8>,
377    // The current epoch number
378    epoch: EpochId,
379    // Timestamp that the epoch started at
380    epoch_timestamp_ms: CheckpointTimestamp,
381    // Number of `ObjectId`'s generated during execution of the current transaction
382    ids_created: u64,
383}
384
385impl From<&TxContext> for MoveLegacyTxContext {
386    fn from(tx_context: &TxContext) -> Self {
387        Self {
388            sender: tx_context.sender,
389            digest: tx_context.digest.clone(),
390            epoch: tx_context.epoch,
391            epoch_timestamp_ms: tx_context.epoch_timestamp_ms,
392            ids_created: tx_context.ids_created,
393        }
394    }
395}
396
397// Information about the transaction context.
398// This struct is not related to Move and can evolve as needed/required.
399#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
400pub struct TxContext {
401    /// Signer/sender of the transaction
402    sender: AccountAddress,
403    /// Digest of the current transaction
404    digest: Vec<u8>,
405    /// The current epoch number
406    epoch: EpochId,
407    /// Timestamp that the epoch started at
408    epoch_timestamp_ms: CheckpointTimestamp,
409    /// Number of `ObjectId`'s generated during execution of the current
410    /// transaction
411    ids_created: u64,
412    // Reference gas price
413    rgp: u64,
414    /// Gas price passed to transaction as input
415    gas_price: u64,
416    /// Gas budget passed to transaction as input
417    gas_budget: u64,
418    /// Address of the sponsor if any (gas owner != sender)
419    sponsor: Option<AccountAddress>,
420    /// Whether the `TxContext` is native or not (i.e., Move reads values via
421    /// native functions instead of struct fields).
422    is_native: bool,
423}
424
425#[derive(PartialEq, Eq, Clone, Copy)]
426pub enum TxContextKind {
427    // No TxContext
428    None,
429    // &mut TxContext
430    Mutable,
431    // &TxContext
432    Immutable,
433}
434
435impl TxContext {
436    pub fn new(
437        sender: &Address,
438        digest: &TransactionDigest,
439        epoch_data: &EpochData,
440        rgp: u64,
441        gas_price: u64,
442        gas_budget: u64,
443        sponsor: Option<Address>,
444        protocol_config: &ProtocolConfig,
445    ) -> Self {
446        Self::new_from_components(
447            sender,
448            digest,
449            &epoch_data.epoch_id(),
450            epoch_data.epoch_start_timestamp(),
451            rgp,
452            gas_price,
453            gas_budget,
454            sponsor,
455            protocol_config,
456        )
457    }
458
459    pub fn new_from_components(
460        sender: &Address,
461        digest: &TransactionDigest,
462        epoch_id: &EpochId,
463        epoch_timestamp_ms: u64,
464        rgp: u64,
465        gas_price: u64,
466        gas_budget: u64,
467        sponsor: Option<Address>,
468        protocol_config: &ProtocolConfig,
469    ) -> Self {
470        Self {
471            sender: AccountAddress::new(sender.into_bytes()),
472            digest: digest.into_inner().to_vec(),
473            epoch: *epoch_id,
474            epoch_timestamp_ms,
475            ids_created: 0,
476            rgp,
477            gas_price,
478            gas_budget,
479            sponsor: sponsor.map(|s| AccountAddress::new(s.into_bytes())),
480            is_native: protocol_config.move_native_tx_context(),
481        }
482    }
483
484    /// Returns whether the type signature is &mut TxContext, &TxContext, or
485    /// none of the above.
486    pub fn kind(view: &CompiledModule, s: &SignatureToken) -> TxContextKind {
487        use SignatureToken as S;
488        let (kind, s) = match s {
489            S::MutableReference(s) => (TxContextKind::Mutable, s),
490            S::Reference(s) => (TxContextKind::Immutable, s),
491            _ => return TxContextKind::None,
492        };
493
494        let S::Datatype(idx) = &**s else {
495            return TxContextKind::None;
496        };
497
498        let (module_addr, module_name, struct_name) = resolve_struct(view, *idx);
499        let is_tx_context_type = module_name.as_str() == Identifier::TX_CONTEXT_MODULE.as_str()
500            && module_addr.as_ref() == Address::FRAMEWORK.as_bytes()
501            && struct_name.as_str() == Identifier::TX_CONTEXT.as_str();
502
503        if is_tx_context_type {
504            kind
505        } else {
506            TxContextKind::None
507        }
508    }
509
510    pub fn epoch(&self) -> EpochId {
511        self.epoch
512    }
513
514    pub fn epoch_timestamp_ms(&self) -> u64 {
515        self.epoch_timestamp_ms
516    }
517
518    /// Return the transaction digest, to include in new objects
519    pub fn digest(&self) -> TransactionDigest {
520        TransactionDigest::new(self.digest.clone().try_into().unwrap())
521    }
522
523    pub fn sponsor(&self) -> Option<Address> {
524        self.sponsor.map(|a| Address::from(a.into_bytes()))
525    }
526
527    pub fn rgp(&self) -> u64 {
528        self.rgp
529    }
530
531    pub fn gas_price(&self) -> u64 {
532        self.gas_price
533    }
534
535    pub fn gas_budget(&self) -> u64 {
536        self.gas_budget
537    }
538
539    pub fn ids_created(&self) -> u64 {
540        self.ids_created
541    }
542
543    /// Derive a globally unique object ID by hashing self.digest |
544    /// self.ids_created
545    pub fn fresh_id(&mut self) -> ObjectId {
546        let id = ObjectId::derive_id(self.digest(), self.ids_created);
547        self.ids_created += 1;
548        id
549    }
550
551    pub fn sender(&self) -> Address {
552        Address::new(self.sender.into_bytes())
553    }
554
555    pub fn to_vec(&self) -> Vec<u8> {
556        bcs::to_bytes(&self).unwrap()
557    }
558
559    /// Serialize this context as a `MoveLegacyTxContext`. When `is_native` is
560    /// true, all fields except digest are zeroed (Move reads actual values via
561    /// native functions). When false, actual field values are used.
562    pub fn to_bcs_legacy_context(&self) -> Vec<u8> {
563        let move_context: MoveLegacyTxContext = if self.is_native {
564            let tx_context = &TxContext {
565                sender: AccountAddress::ZERO,
566                digest: vec![],
567                epoch: 0,
568                epoch_timestamp_ms: 0,
569                ids_created: 0,
570                rgp: 0,
571                gas_price: 0,
572                gas_budget: 0,
573                sponsor: None,
574                is_native: true,
575            };
576            tx_context.into()
577        } else {
578            self.into()
579        };
580        bcs::to_bytes(&move_context).unwrap()
581    }
582
583    /// Updates state of the context instance. It's intended to use
584    /// when mutable context is passed over some boundary via
585    /// serialize/deserialize and this is the reason why this method
586    /// consumes the other context.
587    pub fn update_state(&mut self, other: MoveLegacyTxContext) -> Result<(), ExecutionError> {
588        if !self.is_native {
589            if self.sender != other.sender
590                || self.digest != other.digest
591                || other.ids_created < self.ids_created
592            {
593                return Err(ExecutionError::new_with_source(
594                    ExecutionErrorKind::InvariantViolation,
595                    "Immutable fields for TxContext changed",
596                ));
597            }
598            self.ids_created = other.ids_created;
599        }
600        Ok(())
601    }
602
603    /// Replace all fields. Used by Move test-only native functions.
604    pub fn replace(
605        &mut self,
606        sender: AccountAddress,
607        tx_hash: Vec<u8>,
608        epoch: u64,
609        epoch_timestamp_ms: u64,
610        ids_created: u64,
611        rgp: u64,
612        gas_price: u64,
613        gas_budget: u64,
614        sponsor: Option<AccountAddress>,
615    ) {
616        self.sender = sender;
617        self.digest = tx_hash;
618        self.epoch = epoch;
619        self.epoch_timestamp_ms = epoch_timestamp_ms;
620        self.ids_created = ids_created;
621        self.rgp = rgp;
622        self.gas_price = gas_price;
623        self.gas_budget = gas_budget;
624        self.sponsor = sponsor;
625    }
626
627    // Generate a random TxContext for testing.
628    #[cfg(not(target_arch = "wasm32"))]
629    pub fn random_for_testing_only() -> Self {
630        Self::new(
631            &Address::random(),
632            &TransactionDigest::random(),
633            &EpochData::new_test(),
634            0,
635            0,
636            0,
637            None,
638            &ProtocolConfig::get_for_max_version_UNSAFE(),
639        )
640    }
641}
642
643/// Generate a fake ObjectId with repeated one byte.
644pub fn dbg_object_id(name: u8) -> ObjectId {
645    ObjectId::new([name; ObjectId::LENGTH])
646}
647
648#[derive(PartialEq, Eq, Clone, Debug, thiserror::Error)]
649pub enum ObjectIdParseError {
650    #[error("ObjectId hex literal must start with 0x")]
651    HexLiteralPrefixMissing,
652
653    #[error("Could not convert from bytes slice")]
654    TryFromSlice,
655}
656
657impl fmt::Display for ObjectType {
658    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
659        match self {
660            ObjectType::Package => write!(f, "{PACKAGE}"),
661            ObjectType::Struct(t) => write!(
662                f,
663                "{}",
664                to_iota_struct_tag_string(t).map_err(fmt::Error::custom)?
665            ),
666        }
667    }
668}