Skip to main content

iota_json_rpc_types/
iota_transaction.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::fmt::{self, Display, Formatter, Write};
6
7use enum_dispatch::enum_dispatch;
8use fastcrypto::encoding::{Base64, Encoding};
9use futures::{Stream, StreamExt, stream::FuturesOrdered};
10use iota_json::{IotaJsonValue, primitive_type};
11use iota_metrics::monitored_scope;
12use iota_package_resolver::{CleverError, ErrorConstants, PackageStore, Resolver};
13use iota_sdk_types::{
14    Argument, CancelledTransaction, ChangeEpoch, ChangeEpochV2, ChangeEpochV3, ChangeEpochV4,
15    Command, ConsensusDeterminedVersionAssignments, ExecutionError as ExecutionFailureStatus,
16    ExecutionStatus, GenesisObject, Identifier, MoveCall, ObjectId, Owner, TransactionKind,
17    TransferObjects, TypeTag, VersionAssignment, gas::GasCostSummary,
18};
19use iota_types::{
20    base_types::{EpochId, IotaAddress, ObjectRef, SequenceNumber, TransactionDigest},
21    crypto::IotaSignature,
22    digests::{ConsensusCommitDigest, ObjectDigest, TransactionEventsDigest},
23    effects::{TransactionEffects, TransactionEffectsAPI, TransactionEvents},
24    error::{ExecutionError, IotaError, IotaResult},
25    event::EventID,
26    iota_sdk_types_conversions::type_tag_core_to_sdk,
27    iota_serde::BigInt,
28    layout_resolver::{LayoutResolver, get_layout_from_struct_tag},
29    messages_checkpoint::CheckpointSequenceNumber,
30    object::bounded_visitor::BoundedVisitor,
31    parse_iota_type_tag,
32    quorum_driver_types::ExecuteTransactionRequestType as NativeExecuteTransactionRequestType,
33    signature::GenericSignature,
34    storage::{DeleteKind, WriteKind},
35    transaction::{
36        CallArg, EndOfEpochTransactionKind, InputObjectKind, ProgrammableTransaction,
37        SenderSignedData, SharedObjectRef, TransactionData, TransactionDataAPI,
38    },
39};
40use move_binary_format::CompiledModule;
41use move_bytecode_utils::module_cache::GetModule;
42use move_core_types::{
43    account_address::AccountAddress, annotated_value::MoveTypeLayout, language_storage::ModuleId,
44};
45use schemars::JsonSchema;
46use serde::{Deserialize, Serialize};
47use serde_with::{DisplayFromStr, serde_as};
48use strum::{Display, EnumString};
49use tabled::{
50    builder::Builder as TableBuilder,
51    settings::{Panel as TablePanel, Style as TableStyle, style::HorizontalLine},
52};
53
54use crate::{
55    Filter, IotaEvent, IotaEventID, IotaMoveValue, ObjectRefSchema, Page,
56    balance_changes::BalanceChange,
57    iota_gas_cost_summary::IotaGasCostSummary,
58    iota_owner::OwnerSchema,
59    iota_primitives::{
60        Base58 as Base58Schema, Base64 as Base64Schema, GenericSignature as GenericSignatureSchema,
61        IotaAddress as IotaAddressSchema, ObjectId as ObjectIdSchema,
62        SequenceNumberString as SequenceNumberStringSchema,
63        SequenceNumberU64 as SequenceNumberU64Schema, TypeTag as TypeTagSchema,
64    },
65    object_changes::ObjectChange,
66};
67
68// similar to EpochId of iota-types but BigInt
69pub type IotaEpochId = BigInt<u64>;
70
71#[derive(Debug, Serialize, Deserialize, JsonSchema)]
72pub enum ExecuteTransactionRequestType {
73    WaitForEffectsCert,
74    WaitForLocalExecution,
75}
76
77impl From<NativeExecuteTransactionRequestType> for ExecuteTransactionRequestType {
78    fn from(request_type: NativeExecuteTransactionRequestType) -> Self {
79        match request_type {
80            NativeExecuteTransactionRequestType::WaitForEffectsCert => Self::WaitForEffectsCert,
81            NativeExecuteTransactionRequestType::WaitForLocalExecution => {
82                Self::WaitForLocalExecution
83            }
84        }
85    }
86}
87
88impl From<ExecuteTransactionRequestType> for NativeExecuteTransactionRequestType {
89    fn from(request_type: ExecuteTransactionRequestType) -> Self {
90        match request_type {
91            ExecuteTransactionRequestType::WaitForEffectsCert => Self::WaitForEffectsCert,
92            ExecuteTransactionRequestType::WaitForLocalExecution => Self::WaitForLocalExecution,
93        }
94    }
95}
96
97#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Default)]
98#[serde(
99    rename_all = "camelCase",
100    rename = "TransactionBlockResponseQuery",
101    default
102)]
103pub struct IotaTransactionBlockResponseQuery {
104    /// If None, no filter will be applied
105    pub filter: Option<TransactionFilter>,
106    /// config which fields to include in the response, by default only digest
107    /// is included
108    pub options: Option<IotaTransactionBlockResponseOptions>,
109}
110
111impl IotaTransactionBlockResponseQuery {
112    pub fn new(
113        filter: Option<TransactionFilter>,
114        options: Option<IotaTransactionBlockResponseOptions>,
115    ) -> Self {
116        Self { filter, options }
117    }
118
119    pub fn new_with_filter(filter: TransactionFilter) -> Self {
120        Self {
121            filter: Some(filter),
122            options: None,
123        }
124    }
125}
126
127#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Default)]
128#[serde(
129    rename_all = "camelCase",
130    rename = "TransactionBlockResponseQuery",
131    default
132)]
133pub struct IotaTransactionBlockResponseQueryV2 {
134    /// If None, no filter will be applied
135    pub filter: Option<TransactionFilterV2>,
136    /// config which fields to include in the response, by default only digest
137    /// is included
138    pub options: Option<IotaTransactionBlockResponseOptions>,
139}
140
141impl IotaTransactionBlockResponseQueryV2 {
142    pub fn new(
143        filter: Option<TransactionFilterV2>,
144        options: Option<IotaTransactionBlockResponseOptions>,
145    ) -> Self {
146        Self { filter, options }
147    }
148
149    pub fn new_with_filter(filter: TransactionFilterV2) -> Self {
150        Self {
151            filter: Some(filter),
152            options: None,
153        }
154    }
155}
156
157pub type TransactionBlocksPage = Page<IotaTransactionBlockResponse, TransactionDigest>;
158
159#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Eq, PartialEq, Default)]
160#[serde(
161    rename_all = "camelCase",
162    rename = "TransactionBlockResponseOptions",
163    default
164)]
165pub struct IotaTransactionBlockResponseOptions {
166    /// Whether to show transaction input data. Default to be False
167    pub show_input: bool,
168    /// Whether to show bcs-encoded transaction input data
169    pub show_raw_input: bool,
170    /// Whether to show transaction effects. Default to be False
171    pub show_effects: bool,
172    /// Whether to show transaction events. Default to be False
173    pub show_events: bool,
174    /// Whether to show object_changes. Default to be False
175    pub show_object_changes: bool,
176    /// Whether to show balance_changes. Default to be False
177    pub show_balance_changes: bool,
178    /// Whether to show raw transaction effects. Default to be False
179    pub show_raw_effects: bool,
180}
181
182impl IotaTransactionBlockResponseOptions {
183    pub fn new() -> Self {
184        Self::default()
185    }
186
187    pub fn full_content() -> Self {
188        Self {
189            show_effects: true,
190            show_input: true,
191            show_raw_input: true,
192            show_events: true,
193            show_object_changes: true,
194            show_balance_changes: true,
195            // This field is added for graphql execution. We keep it false here
196            // so current users of `full_content` will not get raw effects unexpectedly.
197            show_raw_effects: false,
198        }
199    }
200
201    pub fn with_input(mut self) -> Self {
202        self.show_input = true;
203        self
204    }
205
206    pub fn with_raw_input(mut self) -> Self {
207        self.show_raw_input = true;
208        self
209    }
210
211    pub fn with_effects(mut self) -> Self {
212        self.show_effects = true;
213        self
214    }
215
216    pub fn with_events(mut self) -> Self {
217        self.show_events = true;
218        self
219    }
220
221    pub fn with_balance_changes(mut self) -> Self {
222        self.show_balance_changes = true;
223        self
224    }
225
226    pub fn with_object_changes(mut self) -> Self {
227        self.show_object_changes = true;
228        self
229    }
230
231    pub fn with_raw_effects(mut self) -> Self {
232        self.show_raw_effects = true;
233        self
234    }
235
236    /// default to return `WaitForEffectsCert` unless some options require
237    /// local execution
238    pub fn default_execution_request_type(&self) -> NativeExecuteTransactionRequestType {
239        // if people want effects or events, they typically want to wait for local
240        // execution
241        if self.require_effects() {
242            NativeExecuteTransactionRequestType::WaitForLocalExecution
243        } else {
244            NativeExecuteTransactionRequestType::WaitForEffectsCert
245        }
246    }
247
248    pub fn require_input(&self) -> bool {
249        self.show_input || self.show_raw_input || self.show_object_changes
250    }
251
252    pub fn require_effects(&self) -> bool {
253        self.show_effects
254            || self.show_events
255            || self.show_balance_changes
256            || self.show_object_changes
257            || self.show_raw_effects
258    }
259
260    pub fn only_digest(&self) -> bool {
261        self == &Self::default()
262    }
263}
264
265#[serde_as]
266#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, Default)]
267#[serde(rename_all = "camelCase", rename = "TransactionBlockResponse")]
268pub struct IotaTransactionBlockResponse {
269    #[serde_as(as = "Base58Schema")]
270    #[schemars(with = "Base58Schema")]
271    pub digest: TransactionDigest,
272    /// Transaction input data
273    #[serde(skip_serializing_if = "Option::is_none")]
274    pub transaction: Option<IotaTransactionBlock>,
275    /// BCS encoded [SenderSignedData] that includes input object references
276    /// returns empty array if `show_raw_transaction` is false
277    #[serde_as(as = "Base64")]
278    #[schemars(with = "Base64Schema")]
279    #[serde(skip_serializing_if = "Vec::is_empty", default)]
280    pub raw_transaction: Vec<u8>,
281    #[serde(skip_serializing_if = "Option::is_none")]
282    pub effects: Option<IotaTransactionBlockEffects>,
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub events: Option<IotaTransactionBlockEvents>,
285    #[serde(skip_serializing_if = "Option::is_none")]
286    pub object_changes: Option<Vec<ObjectChange>>,
287    #[serde(skip_serializing_if = "Option::is_none")]
288    pub balance_changes: Option<Vec<BalanceChange>>,
289    #[serde(default, skip_serializing_if = "Option::is_none")]
290    #[schemars(with = "Option<String>")]
291    #[serde_as(as = "Option<DisplayFromStr>")]
292    pub timestamp_ms: Option<u64>,
293    #[serde(default, skip_serializing_if = "Option::is_none")]
294    pub confirmed_local_execution: Option<bool>,
295    /// The checkpoint number when this transaction was included and hence
296    /// finalized. This is only returned in the read api, not in the
297    /// transaction execution api.
298    #[schemars(with = "Option<String>")]
299    #[serde_as(as = "Option<DisplayFromStr>")]
300    #[serde(skip_serializing_if = "Option::is_none")]
301    pub checkpoint: Option<CheckpointSequenceNumber>,
302    #[serde(skip_serializing_if = "Vec::is_empty", default)]
303    pub errors: Vec<String>,
304    #[serde(skip_serializing_if = "Vec::is_empty", default)]
305    pub raw_effects: Vec<u8>,
306}
307
308impl IotaTransactionBlockResponse {
309    pub fn new(digest: TransactionDigest) -> Self {
310        Self {
311            digest,
312            ..Default::default()
313        }
314    }
315
316    pub fn status_ok(&self) -> Option<bool> {
317        self.effects.as_ref().map(|e| e.status().is_ok())
318    }
319
320    /// Get mutated objects if any
321    pub fn mutated_objects(&self) -> impl Iterator<Item = ObjectRef> + '_ {
322        self.object_changes.iter().flat_map(|obj_changes| {
323            obj_changes
324                .iter()
325                .filter(|change| matches!(change, ObjectChange::Mutated { .. }))
326                .map(|change| change.object_ref())
327        })
328    }
329}
330
331/// We are specifically ignoring events for now until events become more stable.
332impl PartialEq for IotaTransactionBlockResponse {
333    fn eq(&self, other: &Self) -> bool {
334        self.transaction == other.transaction
335            && self.effects == other.effects
336            && self.timestamp_ms == other.timestamp_ms
337            && self.confirmed_local_execution == other.confirmed_local_execution
338            && self.checkpoint == other.checkpoint
339    }
340}
341
342impl Display for IotaTransactionBlockResponse {
343    fn fmt(&self, writer: &mut Formatter<'_>) -> fmt::Result {
344        writeln!(writer, "Transaction Digest: {}", &self.digest)?;
345
346        if let Some(t) = &self.transaction {
347            writeln!(writer, "{t}")?;
348        }
349
350        if let Some(e) = &self.effects {
351            writeln!(writer, "{e}")?;
352        }
353
354        if let Some(e) = &self.events {
355            writeln!(writer, "{e}")?;
356        }
357
358        if let Some(object_changes) = &self.object_changes {
359            let mut builder = TableBuilder::default();
360            let (
361                mut created,
362                mut deleted,
363                mut mutated,
364                mut published,
365                mut transferred,
366                mut wrapped,
367                mut unwrapped,
368            ) = (vec![], vec![], vec![], vec![], vec![], vec![], vec![]);
369
370            for obj in object_changes {
371                match obj {
372                    ObjectChange::Created { .. } => created.push(obj),
373                    ObjectChange::Deleted { .. } => deleted.push(obj),
374                    ObjectChange::Mutated { .. } => mutated.push(obj),
375                    ObjectChange::Published { .. } => published.push(obj),
376                    ObjectChange::Transferred { .. } => transferred.push(obj),
377                    ObjectChange::Wrapped { .. } => wrapped.push(obj),
378                    ObjectChange::Unwrapped { .. } => unwrapped.push(obj),
379                };
380            }
381
382            write_obj_changes(created, "Created", &mut builder)?;
383            write_obj_changes(deleted, "Deleted", &mut builder)?;
384            write_obj_changes(mutated, "Mutated", &mut builder)?;
385            write_obj_changes(published, "Published", &mut builder)?;
386            write_obj_changes(transferred, "Transferred", &mut builder)?;
387            write_obj_changes(wrapped, "Wrapped", &mut builder)?;
388            write_obj_changes(unwrapped, "Unwrapped", &mut builder)?;
389
390            let mut table = builder.build();
391            table.with(TablePanel::header("Object Changes"));
392            table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
393                1,
394                TableStyle::modern().get_horizontal(),
395            )]));
396            writeln!(writer, "{table}")?;
397        }
398
399        if let Some(balance_changes) = &self.balance_changes {
400            // Only build a table if the vector of balance changes is non-empty.
401            // Empty balance changes occur, for example, for system transactions
402            // like `ConsensusCommitPrologueV1`
403            if !balance_changes.is_empty() {
404                let mut builder = TableBuilder::default();
405                for balance in balance_changes {
406                    builder.push_record(vec![format!("{balance}")]);
407                }
408                let mut table = builder.build();
409                table.with(TablePanel::header("Balance Changes"));
410                table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
411                    1,
412                    TableStyle::modern().get_horizontal(),
413                )]));
414                writeln!(writer, "{table}")?;
415            } else {
416                writeln!(writer, "╭────────────────────╮")?;
417                writeln!(writer, "│ No balance changes │")?;
418                writeln!(writer, "╰────────────────────╯")?;
419            }
420        }
421        Ok(())
422    }
423}
424
425fn write_obj_changes<T: Display>(
426    values: Vec<T>,
427    output_string: &str,
428    builder: &mut TableBuilder,
429) -> std::fmt::Result {
430    if !values.is_empty() {
431        builder.push_record(vec![format!("{output_string} Objects: ")]);
432        for obj in values {
433            builder.push_record(vec![format!("{obj}")]);
434        }
435    }
436    Ok(())
437}
438
439pub fn get_new_package_obj_from_response(
440    response: &IotaTransactionBlockResponse,
441) -> Option<ObjectRef> {
442    response.object_changes.as_ref().and_then(|changes| {
443        changes
444            .iter()
445            .find(|change| matches!(change, ObjectChange::Published { .. }))
446            .map(|change| change.object_ref())
447    })
448}
449
450pub fn get_new_package_upgrade_cap_from_response(
451    response: &IotaTransactionBlockResponse,
452) -> Option<ObjectRef> {
453    response.object_changes.as_ref().and_then(|changes| {
454        changes
455            .iter()
456            .find(|change| {
457                matches!(change, ObjectChange::Created {
458                    owner: Owner::Address(_),
459                    object_type,
460                    ..
461                } if object_type.is_upgrade_cap())
462            })
463            .map(|change| change.object_ref())
464    })
465}
466
467#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
468#[serde(rename = "TransactionBlockKind", tag = "kind")]
469pub enum IotaTransactionBlockKind {
470    /// A system transaction used for initializing the initial state of the
471    /// chain.
472    Genesis(IotaGenesisTransaction),
473    /// A system transaction marking the start of a series of transactions
474    /// scheduled as part of a checkpoint
475    ConsensusCommitPrologueV1(IotaConsensusCommitPrologueV1),
476    /// A series of transactions where the results of one transaction can be
477    /// used in future transactions
478    ProgrammableTransaction(IotaProgrammableTransactionBlock),
479    /// A transaction which updates global randomness state
480    RandomnessStateUpdate(IotaRandomnessStateUpdate),
481    /// The transaction which occurs only at the end of the epoch
482    EndOfEpochTransaction(IotaEndOfEpochTransaction),
483    // .. more transaction types go here
484}
485
486impl Display for IotaTransactionBlockKind {
487    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
488        let mut writer = String::new();
489        match &self {
490            Self::Genesis(_) => {
491                writeln!(writer, "Transaction Kind: Genesis Transaction")?;
492            }
493            Self::ConsensusCommitPrologueV1(p) => {
494                writeln!(writer, "Transaction Kind: Consensus Commit Prologue V1")?;
495                writeln!(
496                    writer,
497                    "Epoch: {}, Round: {}, SubDagIndex: {:?}, Timestamp: {}, ConsensusCommitDigest: {}",
498                    p.epoch,
499                    p.round,
500                    p.sub_dag_index,
501                    p.commit_timestamp_ms,
502                    p.consensus_commit_digest
503                )?;
504            }
505            Self::ProgrammableTransaction(p) => {
506                write!(writer, "Transaction Kind: Programmable")?;
507                write!(writer, "{}", crate::displays::Pretty(p))?;
508            }
509            Self::RandomnessStateUpdate(_) => {
510                writeln!(writer, "Transaction Kind: Randomness State Update")?;
511            }
512            Self::EndOfEpochTransaction(_) => {
513                writeln!(writer, "Transaction Kind: End of Epoch Transaction")?;
514            }
515        }
516        write!(f, "{writer}")
517    }
518}
519
520impl IotaTransactionBlockKind {
521    fn try_from_inner(
522        tx: TransactionKind,
523        tx_digest: TransactionDigest,
524    ) -> Result<Self, anyhow::Error> {
525        match tx {
526            TransactionKind::Genesis(g) => Ok(Self::Genesis(IotaGenesisTransaction {
527                objects: g.objects.iter().map(GenesisObject::id).collect(),
528                events: g
529                    .events
530                    .into_iter()
531                    .enumerate()
532                    .map(|(seq, _event)| EventID::from((tx_digest, seq as u64)))
533                    .collect(),
534            })),
535            TransactionKind::ConsensusCommitPrologueV1(p) => Ok(Self::ConsensusCommitPrologueV1(
536                IotaConsensusCommitPrologueV1 {
537                    epoch: p.epoch,
538                    round: p.round,
539                    sub_dag_index: p.sub_dag_index,
540                    commit_timestamp_ms: p.commit_timestamp_ms,
541                    consensus_commit_digest: p.consensus_commit_digest,
542                    consensus_determined_version_assignments: p
543                        .consensus_determined_version_assignments
544                        .into(),
545                },
546            )),
547            TransactionKind::Programmable(_) => {
548                // This case is handled separately by the callers
549                Err(anyhow::anyhow!(
550                    "ProgrammableTransaction must be handled by the caller, not try_from_inner"
551                ))
552            }
553            #[allow(deprecated)]
554            TransactionKind::AuthenticatorStateUpdateV1Deprecated => {
555                // Deprecated: Authenticator state (JWK) is deprecated and
556                // was never enabled. These transaction kinds are retained
557                // only for BCS enum variant compatibility.
558                Err(anyhow::anyhow!(
559                    "AuthenticatorStateUpdateV1 transactions are deprecated and were never created on IOTA"
560                ))
561            }
562            TransactionKind::RandomnessStateUpdate(update) => {
563                Ok(Self::RandomnessStateUpdate(IotaRandomnessStateUpdate {
564                    epoch: update.epoch,
565                    randomness_round: update.randomness_round.value(),
566                    random_bytes: update.random_bytes,
567                }))
568            }
569            TransactionKind::EndOfEpoch(end_of_epoch_tx) => {
570                Ok(Self::EndOfEpochTransaction(IotaEndOfEpochTransaction {
571                    transactions: end_of_epoch_tx
572                        .into_iter()
573                        .map(|tx| match tx {
574                            EndOfEpochTransactionKind::ChangeEpoch(e) => {
575                                IotaEndOfEpochTransactionKind::ChangeEpoch(e.into())
576                            }
577                            EndOfEpochTransactionKind::ChangeEpochV2(e) => {
578                                IotaEndOfEpochTransactionKind::ChangeEpochV2(e.into())
579                            }
580                            EndOfEpochTransactionKind::ChangeEpochV3(e) => {
581                                IotaEndOfEpochTransactionKind::ChangeEpochV2(e.into())
582                            }
583                            EndOfEpochTransactionKind::ChangeEpochV4(e) => {
584                                IotaEndOfEpochTransactionKind::ChangeEpochV2(e.into())
585                            }
586                            _ => unimplemented!(
587                                "a new EndOfEpochTransactionKind enum variant was added and needs to be handled"
588                            ),
589                        })
590                        .collect(),
591                }))
592            }
593            _ => unimplemented!(
594                "a new TransactionKind enum variant was added and needs to be handled"
595            )
596        }
597    }
598
599    fn try_from_with_module_cache(
600        tx: TransactionKind,
601        module_cache: &impl GetModule,
602        tx_digest: TransactionDigest,
603    ) -> Result<Self, anyhow::Error> {
604        match tx {
605            TransactionKind::Programmable(p) => Ok(Self::ProgrammableTransaction(
606                IotaProgrammableTransactionBlock::try_from_with_module_cache(p, module_cache)?,
607            )),
608            tx => Self::try_from_inner(tx, tx_digest),
609        }
610    }
611
612    async fn try_from_with_package_resolver(
613        tx: TransactionKind,
614        package_resolver: &Resolver<impl PackageStore>,
615        tx_digest: TransactionDigest,
616    ) -> Result<Self, anyhow::Error> {
617        match tx {
618            TransactionKind::Programmable(p) => Ok(Self::ProgrammableTransaction(
619                IotaProgrammableTransactionBlock::try_from_with_package_resolver(
620                    p,
621                    package_resolver,
622                )
623                .await?,
624            )),
625            tx => Self::try_from_inner(tx, tx_digest),
626        }
627    }
628
629    pub fn transaction_count(&self) -> usize {
630        match self {
631            Self::ProgrammableTransaction(p) => p.commands.len(),
632            _ => 1,
633        }
634    }
635
636    pub fn name(&self) -> &'static str {
637        match self {
638            Self::Genesis(_) => "Genesis",
639            Self::ConsensusCommitPrologueV1(_) => "ConsensusCommitPrologueV1",
640            Self::ProgrammableTransaction(_) => "ProgrammableTransaction",
641            Self::RandomnessStateUpdate(_) => "RandomnessStateUpdate",
642            Self::EndOfEpochTransaction(_) => "EndOfEpochTransaction",
643        }
644    }
645}
646
647#[serde_as]
648#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
649pub struct IotaChangeEpoch {
650    #[schemars(with = "String")]
651    #[serde_as(as = "DisplayFromStr")]
652    pub epoch: EpochId,
653    #[schemars(with = "String")]
654    #[serde_as(as = "DisplayFromStr")]
655    pub storage_charge: u64,
656    #[schemars(with = "String")]
657    #[serde_as(as = "DisplayFromStr")]
658    pub computation_charge: u64,
659    #[schemars(with = "String")]
660    #[serde_as(as = "DisplayFromStr")]
661    pub storage_rebate: u64,
662    #[schemars(with = "String")]
663    #[serde_as(as = "DisplayFromStr")]
664    pub epoch_start_timestamp_ms: u64,
665}
666
667impl From<ChangeEpoch> for IotaChangeEpoch {
668    fn from(e: ChangeEpoch) -> Self {
669        Self {
670            epoch: e.epoch,
671            storage_charge: e.storage_charge,
672            computation_charge: e.computation_charge,
673            storage_rebate: e.storage_rebate,
674            epoch_start_timestamp_ms: e.epoch_start_timestamp_ms,
675        }
676    }
677}
678
679#[serde_as]
680#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
681pub struct IotaChangeEpochV2 {
682    #[schemars(with = "String")]
683    #[serde_as(as = "DisplayFromStr")]
684    pub epoch: EpochId,
685    #[schemars(with = "String")]
686    #[serde_as(as = "DisplayFromStr")]
687    pub storage_charge: u64,
688    #[schemars(with = "String")]
689    #[serde_as(as = "DisplayFromStr")]
690    pub computation_charge: u64,
691    #[schemars(with = "String")]
692    #[serde_as(as = "DisplayFromStr")]
693    pub computation_charge_burned: u64,
694    #[schemars(with = "String")]
695    #[serde_as(as = "DisplayFromStr")]
696    pub storage_rebate: u64,
697    #[schemars(with = "String")]
698    #[serde_as(as = "DisplayFromStr")]
699    pub epoch_start_timestamp_ms: u64,
700    #[schemars(with = "Option<Vec<String>>")]
701    #[serde_as(as = "Option<Vec<DisplayFromStr>>")]
702    #[serde(skip_serializing_if = "Option::is_none", default)]
703    pub eligible_active_validators: Option<Vec<u64>>,
704    #[schemars(with = "Option<Vec<String>>")]
705    #[serde_as(as = "Option<Vec<DisplayFromStr>>")]
706    #[serde(skip_serializing_if = "Option::is_none", default)]
707    pub scores: Option<Vec<u64>>,
708}
709
710impl From<ChangeEpochV2> for IotaChangeEpochV2 {
711    fn from(e: ChangeEpochV2) -> Self {
712        Self {
713            epoch: e.epoch,
714            storage_charge: e.storage_charge,
715            computation_charge: e.computation_charge,
716            computation_charge_burned: e.computation_charge_burned,
717            storage_rebate: e.storage_rebate,
718            epoch_start_timestamp_ms: e.epoch_start_timestamp_ms,
719            eligible_active_validators: None,
720            scores: None,
721        }
722    }
723}
724
725impl From<ChangeEpochV3> for IotaChangeEpochV2 {
726    fn from(e: ChangeEpochV3) -> Self {
727        Self {
728            epoch: e.epoch,
729            storage_charge: e.storage_charge,
730            computation_charge: e.computation_charge,
731            computation_charge_burned: e.computation_charge_burned,
732            storage_rebate: e.storage_rebate,
733            epoch_start_timestamp_ms: e.epoch_start_timestamp_ms,
734            eligible_active_validators: Some(e.eligible_active_validators),
735            scores: None,
736        }
737    }
738}
739
740impl From<ChangeEpochV4> for IotaChangeEpochV2 {
741    fn from(e: ChangeEpochV4) -> Self {
742        Self {
743            epoch: e.epoch,
744            storage_charge: e.storage_charge,
745            computation_charge: e.computation_charge,
746            computation_charge_burned: e.computation_charge_burned,
747            storage_rebate: e.storage_rebate,
748            epoch_start_timestamp_ms: e.epoch_start_timestamp_ms,
749            eligible_active_validators: Some(e.eligible_active_validators),
750            scores: Some(e.scores),
751        }
752    }
753}
754
755#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
756#[enum_dispatch(IotaTransactionBlockEffectsAPI)]
757#[serde(
758    rename = "TransactionBlockEffects",
759    rename_all = "camelCase",
760    tag = "messageVersion"
761)]
762pub enum IotaTransactionBlockEffects {
763    V1(IotaTransactionBlockEffectsV1),
764}
765
766#[enum_dispatch]
767pub trait IotaTransactionBlockEffectsAPI {
768    fn status(&self) -> &IotaExecutionStatus;
769    fn into_status(self) -> IotaExecutionStatus;
770    fn shared_objects(&self) -> &[ObjectRef];
771    fn created(&self) -> &[OwnedObjectRef];
772    fn mutated(&self) -> &[OwnedObjectRef];
773    fn unwrapped(&self) -> &[OwnedObjectRef];
774    fn deleted(&self) -> &[ObjectRef];
775    fn unwrapped_then_deleted(&self) -> &[ObjectRef];
776    fn wrapped(&self) -> &[ObjectRef];
777    fn gas_object(&self) -> &OwnedObjectRef;
778    fn events_digest(&self) -> Option<&TransactionEventsDigest>;
779    fn dependencies(&self) -> &[TransactionDigest];
780    fn executed_epoch(&self) -> EpochId;
781    fn transaction_digest(&self) -> &TransactionDigest;
782    fn gas_cost_summary(&self) -> &GasCostSummary;
783
784    /// Return an iterator of mutated objects, but excluding the gas object.
785    fn mutated_excluding_gas(&self) -> Vec<OwnedObjectRef>;
786    fn modified_at_versions(&self) -> Vec<(ObjectId, SequenceNumber)>;
787    fn all_changed_objects(&self) -> Vec<(&OwnedObjectRef, WriteKind)>;
788    fn all_deleted_objects(&self) -> Vec<(&ObjectRef, DeleteKind)>;
789}
790
791#[serde_as]
792#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
793#[serde(
794    rename = "TransactionBlockEffectsModifiedAtVersions",
795    rename_all = "camelCase"
796)]
797pub struct IotaTransactionBlockEffectsModifiedAtVersions {
798    #[serde_as(as = "ObjectIdSchema")]
799    #[schemars(with = "ObjectIdSchema")]
800    object_id: ObjectId,
801    #[schemars(with = "SequenceNumberStringSchema")]
802    #[serde_as(as = "SequenceNumberStringSchema")]
803    sequence_number: SequenceNumber,
804}
805
806/// The response from processing a transaction or a certified transaction
807#[serde_as]
808#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
809#[serde(rename = "TransactionBlockEffectsV1", rename_all = "camelCase")]
810pub struct IotaTransactionBlockEffectsV1 {
811    /// The status of the execution
812    pub status: IotaExecutionStatus,
813    /// The epoch when this transaction was executed.
814    #[schemars(with = "String")]
815    #[serde_as(as = "DisplayFromStr")]
816    pub executed_epoch: EpochId,
817    #[schemars(with = "IotaGasCostSummary")]
818    #[serde_as(as = "IotaGasCostSummary")]
819    pub gas_used: GasCostSummary,
820    /// The version that every modified (mutated or deleted) object had before
821    /// it was modified by this transaction.
822    #[serde(default, skip_serializing_if = "Vec::is_empty")]
823    pub modified_at_versions: Vec<IotaTransactionBlockEffectsModifiedAtVersions>,
824    /// The object references of the shared objects used in this transaction.
825    /// Empty if no shared objects were used.
826    #[serde(default, skip_serializing_if = "Vec::is_empty")]
827    #[schemars(with = "Vec<ObjectRefSchema>")]
828    #[serde_as(as = "Vec<ObjectRefSchema>")]
829    pub shared_objects: Vec<ObjectRef>,
830    /// The transaction digest
831    #[serde_as(as = "Base58Schema")]
832    #[schemars(with = "Base58Schema")]
833    pub transaction_digest: TransactionDigest,
834    /// ObjectRef and owner of new objects created.
835    #[serde(default, skip_serializing_if = "Vec::is_empty")]
836    pub created: Vec<OwnedObjectRef>,
837    /// ObjectRef and owner of mutated objects, including gas object.
838    #[serde(default, skip_serializing_if = "Vec::is_empty")]
839    pub mutated: Vec<OwnedObjectRef>,
840    /// ObjectRef and owner of objects that are unwrapped in this transaction.
841    /// Unwrapped objects are objects that were wrapped into other objects in
842    /// the past, and just got extracted out.
843    #[serde(default, skip_serializing_if = "Vec::is_empty")]
844    pub unwrapped: Vec<OwnedObjectRef>,
845    /// Object Refs of objects now deleted (the old refs).
846    #[serde(default, skip_serializing_if = "Vec::is_empty")]
847    #[schemars(with = "Vec<ObjectRefSchema>")]
848    #[serde_as(as = "Vec<ObjectRefSchema>")]
849    pub deleted: Vec<ObjectRef>,
850    /// Object refs of objects previously wrapped in other objects but now
851    /// deleted.
852    #[serde(default, skip_serializing_if = "Vec::is_empty")]
853    #[schemars(with = "Vec<ObjectRefSchema>")]
854    #[serde_as(as = "Vec<ObjectRefSchema>")]
855    pub unwrapped_then_deleted: Vec<ObjectRef>,
856    /// Object refs of objects now wrapped in other objects.
857    #[serde(default, skip_serializing_if = "Vec::is_empty")]
858    #[schemars(with = "Vec<ObjectRefSchema>")]
859    #[serde_as(as = "Vec<ObjectRefSchema>")]
860    pub wrapped: Vec<ObjectRef>,
861    /// The updated gas object reference. Have a dedicated field for convenient
862    /// access. It's also included in mutated.
863    pub gas_object: OwnedObjectRef,
864    /// The digest of the events emitted during execution,
865    /// can be None if the transaction does not emit any event.
866    #[serde(skip_serializing_if = "Option::is_none")]
867    #[serde_as(as = "Option<Base58Schema>")]
868    #[schemars(with = "Option<Base58Schema>")]
869    pub events_digest: Option<TransactionEventsDigest>,
870    /// The set of transaction digests this transaction depends on.
871    #[serde(default, skip_serializing_if = "Vec::is_empty")]
872    #[serde_as(as = "Vec<Base58Schema>")]
873    #[schemars(with = "Vec<Base58Schema>")]
874    pub dependencies: Vec<TransactionDigest>,
875}
876
877impl IotaTransactionBlockEffectsAPI for IotaTransactionBlockEffectsV1 {
878    fn status(&self) -> &IotaExecutionStatus {
879        &self.status
880    }
881    fn into_status(self) -> IotaExecutionStatus {
882        self.status
883    }
884    fn shared_objects(&self) -> &[ObjectRef] {
885        &self.shared_objects
886    }
887    fn created(&self) -> &[OwnedObjectRef] {
888        &self.created
889    }
890    fn mutated(&self) -> &[OwnedObjectRef] {
891        &self.mutated
892    }
893    fn unwrapped(&self) -> &[OwnedObjectRef] {
894        &self.unwrapped
895    }
896    fn deleted(&self) -> &[ObjectRef] {
897        &self.deleted
898    }
899    fn unwrapped_then_deleted(&self) -> &[ObjectRef] {
900        &self.unwrapped_then_deleted
901    }
902    fn wrapped(&self) -> &[ObjectRef] {
903        &self.wrapped
904    }
905    fn gas_object(&self) -> &OwnedObjectRef {
906        &self.gas_object
907    }
908    fn events_digest(&self) -> Option<&TransactionEventsDigest> {
909        self.events_digest.as_ref()
910    }
911    fn dependencies(&self) -> &[TransactionDigest] {
912        &self.dependencies
913    }
914
915    fn executed_epoch(&self) -> EpochId {
916        self.executed_epoch
917    }
918
919    fn transaction_digest(&self) -> &TransactionDigest {
920        &self.transaction_digest
921    }
922
923    fn gas_cost_summary(&self) -> &GasCostSummary {
924        &self.gas_used
925    }
926
927    fn mutated_excluding_gas(&self) -> Vec<OwnedObjectRef> {
928        self.mutated
929            .iter()
930            .filter(|o| *o != &self.gas_object)
931            .cloned()
932            .collect()
933    }
934
935    fn modified_at_versions(&self) -> Vec<(ObjectId, SequenceNumber)> {
936        self.modified_at_versions
937            .iter()
938            .map(|v| (v.object_id, v.sequence_number))
939            .collect::<Vec<_>>()
940    }
941
942    fn all_changed_objects(&self) -> Vec<(&OwnedObjectRef, WriteKind)> {
943        self.mutated
944            .iter()
945            .map(|owner_ref| (owner_ref, WriteKind::Mutate))
946            .chain(
947                self.created
948                    .iter()
949                    .map(|owner_ref| (owner_ref, WriteKind::Create)),
950            )
951            .chain(
952                self.unwrapped
953                    .iter()
954                    .map(|owner_ref| (owner_ref, WriteKind::Unwrap)),
955            )
956            .collect()
957    }
958
959    fn all_deleted_objects(&self) -> Vec<(&ObjectRef, DeleteKind)> {
960        self.deleted
961            .iter()
962            .map(|r| (r, DeleteKind::Normal))
963            .chain(
964                self.unwrapped_then_deleted
965                    .iter()
966                    .map(|r| (r, DeleteKind::UnwrapThenDelete)),
967            )
968            .chain(self.wrapped.iter().map(|r| (r, DeleteKind::Wrap)))
969            .collect()
970    }
971}
972
973impl IotaTransactionBlockEffects {
974    pub fn new_for_testing(
975        transaction_digest: TransactionDigest,
976        status: IotaExecutionStatus,
977    ) -> Self {
978        Self::V1(IotaTransactionBlockEffectsV1 {
979            transaction_digest,
980            status,
981            gas_object: OwnedObjectRef {
982                owner: Owner::Address(IotaAddress::random()),
983                reference: iota_types::base_types::random_object_ref(),
984            },
985            executed_epoch: 0,
986            modified_at_versions: vec![],
987            gas_used: GasCostSummary::default(),
988            shared_objects: vec![],
989            created: vec![],
990            mutated: vec![],
991            unwrapped: vec![],
992            deleted: vec![],
993            unwrapped_then_deleted: vec![],
994            wrapped: vec![],
995            events_digest: None,
996            dependencies: vec![],
997        })
998    }
999
1000    /// Construct the RPC view of the transaction effects.
1001    ///
1002    /// This differs from the `TryFrom<TransactionEffects>` implementation
1003    /// in that it tries to convert Move abort errors into human-readable form.
1004    /// This is referred to as clever error.
1005    pub async fn from_native_with_clever_error<S: PackageStore>(
1006        native: TransactionEffects,
1007        resolver: &Resolver<S>,
1008    ) -> Self {
1009        let clever_status =
1010            IotaExecutionStatus::from_native_with_clever_error(native.status().clone(), resolver)
1011                .await;
1012        match native {
1013            TransactionEffects::V1(inner) => {
1014                let mut inner = IotaTransactionBlockEffectsV1::from(*inner);
1015                inner.status = clever_status;
1016                inner.into()
1017            }
1018            _ => unimplemented!(
1019                "a new TransactionEffects enum variant was added and needs to be handled"
1020            ),
1021        }
1022    }
1023}
1024
1025impl TryFrom<TransactionEffects> for IotaTransactionBlockEffects {
1026    type Error = IotaError;
1027
1028    fn try_from(native: TransactionEffects) -> Result<Self, Self::Error> {
1029        Ok(IotaTransactionBlockEffects::V1(native.into()))
1030    }
1031}
1032
1033impl<T: TransactionEffectsAPI> From<T> for IotaTransactionBlockEffectsV1 {
1034    fn from(native: T) -> Self {
1035        Self {
1036            status: native.status().clone().into(),
1037            executed_epoch: native.epoch(),
1038            modified_at_versions: native
1039                .modified_at_versions()
1040                .into_iter()
1041                .map(
1042                    |(object_id, sequence_number)| IotaTransactionBlockEffectsModifiedAtVersions {
1043                        object_id,
1044                        sequence_number,
1045                    },
1046                )
1047                .collect(),
1048            gas_used: native.gas_cost_summary().clone(),
1049            shared_objects: native
1050                .input_shared_objects()
1051                .into_iter()
1052                .map(|kind| kind.object_ref())
1053                .collect(),
1054            transaction_digest: *native.transaction_digest(),
1055            created: to_owned_ref(native.created()),
1056            mutated: to_owned_ref(native.mutated().to_vec()),
1057            unwrapped: to_owned_ref(native.unwrapped().to_vec()),
1058            deleted: native.deleted().to_vec(),
1059            unwrapped_then_deleted: native.unwrapped_then_deleted().to_vec(),
1060            wrapped: native.wrapped().to_vec(),
1061            gas_object: OwnedObjectRef {
1062                owner: native.gas_object().1,
1063                reference: native.gas_object().0,
1064            },
1065            events_digest: native.events_digest().copied(),
1066            dependencies: native.dependencies().to_vec(),
1067        }
1068    }
1069}
1070
1071fn owned_objref_string(obj: &OwnedObjectRef) -> String {
1072    format!(
1073        " ┌──\n │ ID: {} \n │ Owner: {} \n │ Version: {} \n │ Digest: {}\n └──",
1074        obj.reference.object_id, obj.owner, obj.reference.version, obj.reference.digest
1075    )
1076}
1077
1078fn objref_string(obj: &ObjectRef) -> String {
1079    format!(
1080        " ┌──\n │ ID: {} \n │ Version: {} \n │ Digest: {}\n └──",
1081        obj.object_id, obj.version, obj.digest
1082    )
1083}
1084
1085impl Display for IotaTransactionBlockEffects {
1086    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1087        let mut builder = TableBuilder::default();
1088
1089        builder.push_record(vec![format!("Digest: {}", self.transaction_digest())]);
1090        builder.push_record(vec![format!("Status: {:?}", self.status())]);
1091        builder.push_record(vec![format!("Executed Epoch: {}", self.executed_epoch())]);
1092
1093        if !self.created().is_empty() {
1094            builder.push_record(vec![format!("\nCreated Objects: ")]);
1095
1096            for oref in self.created() {
1097                builder.push_record(vec![owned_objref_string(oref)]);
1098            }
1099        }
1100
1101        if !self.mutated().is_empty() {
1102            builder.push_record(vec![format!("Mutated Objects: ")]);
1103            for oref in self.mutated() {
1104                builder.push_record(vec![owned_objref_string(oref)]);
1105            }
1106        }
1107
1108        if !self.shared_objects().is_empty() {
1109            builder.push_record(vec![format!("Shared Objects: ")]);
1110            for oref in self.shared_objects() {
1111                builder.push_record(vec![objref_string(oref)]);
1112            }
1113        }
1114
1115        if !self.deleted().is_empty() {
1116            builder.push_record(vec![format!("Deleted Objects: ")]);
1117
1118            for oref in self.deleted() {
1119                builder.push_record(vec![objref_string(oref)]);
1120            }
1121        }
1122
1123        if !self.wrapped().is_empty() {
1124            builder.push_record(vec![format!("Wrapped Objects: ")]);
1125
1126            for oref in self.wrapped() {
1127                builder.push_record(vec![objref_string(oref)]);
1128            }
1129        }
1130
1131        if !self.unwrapped().is_empty() {
1132            builder.push_record(vec![format!("Unwrapped Objects: ")]);
1133            for oref in self.unwrapped() {
1134                builder.push_record(vec![owned_objref_string(oref)]);
1135            }
1136        }
1137
1138        builder.push_record(vec![format!(
1139            "Gas Object: \n{}",
1140            owned_objref_string(self.gas_object())
1141        )]);
1142
1143        let gas_cost_summary = self.gas_cost_summary();
1144        builder.push_record(vec![format!(
1145            "Gas Cost Summary:\n   \
1146             Storage Cost: {} NANOS\n   \
1147             Computation Cost: {} NANOS\n   \
1148             Computation Cost Burned: {} NANOS\n   \
1149             Storage Rebate: {} NANOS\n   \
1150             Non-refundable Storage Fee: {} NANOS",
1151            gas_cost_summary.storage_cost,
1152            gas_cost_summary.computation_cost,
1153            gas_cost_summary.computation_cost_burned,
1154            gas_cost_summary.storage_rebate,
1155            gas_cost_summary.non_refundable_storage_fee,
1156        )]);
1157
1158        let dependencies = self.dependencies();
1159        if !dependencies.is_empty() {
1160            builder.push_record(vec![format!("\nTransaction Dependencies:")]);
1161            for dependency in dependencies {
1162                builder.push_record(vec![format!("   {dependency}")]);
1163            }
1164        }
1165
1166        let mut table = builder.build();
1167        table.with(TablePanel::header("Transaction Effects"));
1168        table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
1169            1,
1170            TableStyle::modern().get_horizontal(),
1171        )]));
1172        write!(f, "{table}")
1173    }
1174}
1175
1176#[serde_as]
1177#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
1178#[serde(rename_all = "camelCase")]
1179pub struct DryRunTransactionBlockResponse {
1180    pub effects: IotaTransactionBlockEffects,
1181    pub events: IotaTransactionBlockEvents,
1182    pub object_changes: Vec<ObjectChange>,
1183    pub balance_changes: Vec<BalanceChange>,
1184    pub input: IotaTransactionBlockData,
1185    /// If an input object is congested, suggest a gas price to use.
1186    #[serde(default, skip_serializing_if = "Option::is_none")]
1187    #[schemars(with = "Option<String>")]
1188    #[serde_as(as = "Option<DisplayFromStr>")]
1189    pub suggested_gas_price: Option<u64>,
1190    pub execution_error_source: Option<String>,
1191}
1192
1193#[derive(Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
1194#[serde(rename = "TransactionBlockEvents", transparent)]
1195pub struct IotaTransactionBlockEvents {
1196    pub data: Vec<IotaEvent>,
1197}
1198
1199impl IotaTransactionBlockEvents {
1200    pub fn try_from(
1201        mut events: TransactionEvents,
1202        tx_digest: TransactionDigest,
1203        timestamp_ms: Option<u64>,
1204        resolver: &mut dyn LayoutResolver,
1205    ) -> IotaResult<Self> {
1206        Ok(Self {
1207            data: events
1208                .drain(..)
1209                .enumerate()
1210                .map(|(seq, event)| {
1211                    let layout = resolver.get_annotated_layout(&event.type_)?;
1212                    IotaEvent::try_from(event, tx_digest, seq as u64, timestamp_ms, layout)
1213                })
1214                .collect::<Result<_, _>>()?,
1215        })
1216    }
1217
1218    // TODO: this is only called from the indexer. Remove this once indexer moves to
1219    // its own resolver.
1220    pub fn try_from_using_module_resolver(
1221        mut events: TransactionEvents,
1222        tx_digest: TransactionDigest,
1223        timestamp_ms: Option<u64>,
1224        resolver: &impl GetModule,
1225    ) -> IotaResult<Self> {
1226        Ok(Self {
1227            data: events
1228                .drain(..)
1229                .enumerate()
1230                .map(|(seq, event)| {
1231                    let layout = get_layout_from_struct_tag(event.type_.clone(), resolver)?;
1232                    IotaEvent::try_from(event, tx_digest, seq as u64, timestamp_ms, layout)
1233                })
1234                .collect::<Result<_, _>>()?,
1235        })
1236    }
1237}
1238
1239impl Display for IotaTransactionBlockEvents {
1240    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1241        if self.data.is_empty() {
1242            writeln!(f, "╭─────────────────────────────╮")?;
1243            writeln!(f, "│ No transaction block events │")?;
1244            writeln!(f, "╰─────────────────────────────╯")
1245        } else {
1246            let mut builder = TableBuilder::default();
1247
1248            for event in &self.data {
1249                builder.push_record(vec![format!("{event}")]);
1250            }
1251
1252            let mut table = builder.build();
1253            table.with(TablePanel::header("Transaction Block Events"));
1254            table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
1255                1,
1256                TableStyle::modern().get_horizontal(),
1257            )]));
1258            write!(f, "{table}")
1259        }
1260    }
1261}
1262
1263// TODO: this file might not be the best place for this struct.
1264/// Additional arguments supplied to dev inspect beyond what is allowed in
1265/// today's API.
1266#[serde_as]
1267#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
1268#[serde(rename = "DevInspectArgs", rename_all = "camelCase")]
1269pub struct DevInspectArgs {
1270    /// The sponsor of the gas for the transaction, might be different from the
1271    /// sender.
1272    #[serde_as(as = "Option<IotaAddressSchema>")]
1273    #[schemars(with = "Option<IotaAddressSchema>")]
1274    pub gas_sponsor: Option<IotaAddress>,
1275    /// The gas budget for the transaction.
1276    #[schemars(with = "Option<String>")]
1277    #[serde_as(as = "Option<DisplayFromStr>")]
1278    pub gas_budget: Option<u64>,
1279    /// The gas objects used to pay for the transaction.
1280    #[schemars(with = "Option<Vec<ObjectRefSchema>>")]
1281    #[serde_as(as = "Option<Vec<ObjectRefSchema>>")]
1282    pub gas_objects: Option<Vec<ObjectRef>>,
1283    /// Whether to skip transaction checks for the transaction.
1284    pub skip_checks: Option<bool>,
1285    /// Whether to return the raw transaction data and effects.
1286    pub show_raw_txn_data_and_effects: Option<bool>,
1287}
1288
1289/// The response from processing a dev inspect transaction
1290#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1291#[serde(rename = "DevInspectResults", rename_all = "camelCase")]
1292pub struct DevInspectResults {
1293    /// Summary of effects that likely would be generated if the transaction is
1294    /// actually run. Note however, that not all dev-inspect transactions
1295    /// are actually usable as transactions so it might not be possible
1296    /// actually generate these effects from a normal transaction.
1297    pub effects: IotaTransactionBlockEffects,
1298    /// Events that likely would be generated if the transaction is actually
1299    /// run.
1300    pub events: IotaTransactionBlockEvents,
1301    /// Execution results (including return values) from executing the
1302    /// transactions
1303    #[serde(skip_serializing_if = "Option::is_none")]
1304    pub results: Option<Vec<IotaExecutionResult>>,
1305    /// Execution error from executing the transactions
1306    #[serde(skip_serializing_if = "Option::is_none")]
1307    pub error: Option<String>,
1308    /// The raw transaction data that was dev inspected.
1309    #[serde(skip_serializing_if = "Vec::is_empty", default)]
1310    pub raw_txn_data: Vec<u8>,
1311    /// The raw effects of the transaction that was dev inspected.
1312    #[serde(skip_serializing_if = "Vec::is_empty", default)]
1313    pub raw_effects: Vec<u8>,
1314}
1315
1316#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1317#[serde(rename = "IotaExecutionResult", rename_all = "camelCase")]
1318pub struct IotaExecutionResult {
1319    /// The value of any arguments that were mutably borrowed.
1320    /// Non-mut borrowed values are not included
1321    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1322    #[schemars(with = "Vec<(IotaArgument, Vec<u8>, TypeTagSchema)>")]
1323    pub mutable_reference_outputs: Vec<(/* argument */ IotaArgument, Vec<u8>, IotaTypeTag)>,
1324    /// The return values from the transaction
1325    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1326    #[schemars(with = "Vec<(Vec<u8>, TypeTagSchema)>")]
1327    pub return_values: Vec<(Vec<u8>, IotaTypeTag)>,
1328}
1329
1330impl IotaExecutionResult {
1331    fn into_stream_return_value_layouts<S: PackageStore>(
1332        self,
1333        package_resolver: &Resolver<S>,
1334    ) -> impl Stream<Item = anyhow::Result<(Vec<u8>, MoveTypeLayout)>> + use<'_, S> {
1335        self.return_values
1336            .into_iter()
1337            .map(|(bytes, iota_type_tag)| async {
1338                let type_tag = TypeTag::try_from(iota_type_tag)?;
1339                let move_type_layout = package_resolver.type_layout(type_tag).await?;
1340                Ok((bytes, move_type_layout))
1341            })
1342            .collect::<FuturesOrdered<_>>()
1343    }
1344}
1345
1346#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1347pub enum IotaMoveViewCallResults {
1348    /// Execution error from executing the move view call
1349    #[serde(rename = "executionError")]
1350    Error(String),
1351    /// The return values of the move view function
1352    #[serde(rename = "functionReturnValues")]
1353    Results(Vec<IotaMoveValue>),
1354}
1355
1356impl IotaMoveViewCallResults {
1357    /// Processes the dev-inspect results to produce the response
1358    /// of the move-view function call.
1359    pub async fn from_dev_inspect_results<S: PackageStore>(
1360        package_store: S,
1361        dev_inspect_results: DevInspectResults,
1362    ) -> anyhow::Result<Self> {
1363        if let Some(error) = dev_inspect_results.error {
1364            return Ok(Self::Error(error));
1365        }
1366        let Some(mut tx_execution_results) = dev_inspect_results.results else {
1367            return Ok(Self::Error("function call returned no values".into()));
1368        };
1369        let Some(execution_results) = tx_execution_results.pop() else {
1370            return Ok(Self::Error(
1371                "no results from move view function call".into(),
1372            ));
1373        };
1374        if !tx_execution_results.is_empty() {
1375            return Ok(Self::Error("multiple transactions executed".into()));
1376        }
1377        let mut move_call_results = Vec::with_capacity(execution_results.return_values.len());
1378        let package_resolver = Resolver::new(package_store);
1379        let mut execution_results =
1380            execution_results.into_stream_return_value_layouts(&package_resolver);
1381        while let Some(result) = execution_results.next().await {
1382            let (bytes, move_type_layout) = result?;
1383            let move_value = BoundedVisitor::deserialize_value(&bytes, &move_type_layout)?;
1384            move_call_results.push(IotaMoveValue::from(move_value));
1385        }
1386        Ok(Self::Results(move_call_results))
1387    }
1388
1389    pub fn into_return_values(self) -> Vec<IotaMoveValue> {
1390        match self {
1391            IotaMoveViewCallResults::Error(_) => Default::default(),
1392            IotaMoveViewCallResults::Results(values) => values,
1393        }
1394    }
1395
1396    pub fn error(&self) -> Option<&str> {
1397        match self {
1398            IotaMoveViewCallResults::Error(e) => Some(e.as_str()),
1399            IotaMoveViewCallResults::Results(_) => None,
1400        }
1401    }
1402}
1403
1404type ExecutionResult = (
1405    // mutable_reference_outputs
1406    Vec<(Argument, Vec<u8>, TypeTag)>,
1407    // return_values
1408    Vec<(Vec<u8>, TypeTag)>,
1409);
1410
1411impl DevInspectResults {
1412    pub fn new(
1413        effects: TransactionEffects,
1414        events: TransactionEvents,
1415        return_values: Result<Vec<ExecutionResult>, ExecutionError>,
1416        raw_txn_data: Vec<u8>,
1417        raw_effects: Vec<u8>,
1418        resolver: &mut dyn LayoutResolver,
1419    ) -> IotaResult<Self> {
1420        let tx_digest = *effects.transaction_digest();
1421        let mut error = None;
1422        let mut results = None;
1423        match return_values {
1424            Err(e) => error = Some(e.to_string()),
1425            Ok(srvs) => {
1426                results = Some(
1427                    srvs.into_iter()
1428                        .map(|srv| {
1429                            let (mutable_reference_outputs, return_values) = srv;
1430                            let mutable_reference_outputs = mutable_reference_outputs
1431                                .into_iter()
1432                                .map(|(a, bytes, tag)| (a.into(), bytes, IotaTypeTag::from(tag)))
1433                                .collect();
1434                            let return_values = return_values
1435                                .into_iter()
1436                                .map(|(bytes, tag)| (bytes, IotaTypeTag::from(tag)))
1437                                .collect();
1438                            IotaExecutionResult {
1439                                mutable_reference_outputs,
1440                                return_values,
1441                            }
1442                        })
1443                        .collect(),
1444                )
1445            }
1446        };
1447        Ok(Self {
1448            effects: effects.try_into()?,
1449            events: IotaTransactionBlockEvents::try_from(events, tx_digest, None, resolver)?,
1450            results,
1451            error,
1452            raw_txn_data,
1453            raw_effects,
1454        })
1455    }
1456}
1457
1458#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
1459pub enum IotaTransactionBlockBuilderMode {
1460    /// Regular IOTA Transactions that are committed on chain
1461    Commit,
1462    /// Simulated transaction that allows calling any Move function with
1463    /// arbitrary values.
1464    DevInspect,
1465}
1466
1467#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
1468#[serde(rename = "ExecutionStatus", rename_all = "camelCase", tag = "status")]
1469pub enum IotaExecutionStatus {
1470    // Gas used in the success case.
1471    Success,
1472    // Gas used in the failed case, and the error.
1473    Failure { error: String },
1474}
1475
1476impl IotaExecutionStatus {
1477    /// Construct the RPC view of the execution status.
1478    ///
1479    /// This differs from the `From<ExecutionStatus>` implementation
1480    /// in that it tries to convert Move abort errors into human-readable form.
1481    /// This is referred to as clever error.
1482    pub async fn from_native_with_clever_error<S: PackageStore>(
1483        native: ExecutionStatus,
1484        resolver: &Resolver<S>,
1485    ) -> Self {
1486        match native {
1487            ExecutionStatus::Failure {
1488                error,
1489                command: Some(mut command_index),
1490            } => {
1491                let error = 'error: {
1492                    let ExecutionFailureStatus::MoveAbort { location, code } = &error else {
1493                        break 'error error.to_string();
1494                    };
1495                    let fname_string = if let Some(fname) = &location.function_name {
1496                        format!("::{fname}'")
1497                    } else {
1498                        "'".to_string()
1499                    };
1500
1501                    let module_id = ModuleId::new(
1502                        AccountAddress::from(location.package.into_bytes()),
1503                        move_core_types::identifier::Identifier::new(location.module.as_str())
1504                            .unwrap(),
1505                    );
1506
1507                    let Some(CleverError {
1508                        module_id,
1509                        source_line_number,
1510                        error_info,
1511                    }) = resolver
1512                        .resolve_clever_error(module_id.clone(), *code)
1513                        .await
1514                    else {
1515                        break 'error format!(
1516                            "from '{}{fname_string} (instruction {}), abort code: {code}",
1517                            module_id.to_canonical_display(true),
1518                            location.instruction,
1519                        );
1520                    };
1521
1522                    match error_info {
1523                        ErrorConstants::Rendered {
1524                            identifier,
1525                            constant,
1526                        } => {
1527                            format!(
1528                                "from '{}{fname_string} (line {source_line_number}), abort '{identifier}': {constant}",
1529                                module_id.to_canonical_display(true)
1530                            )
1531                        }
1532                        ErrorConstants::Raw { identifier, bytes } => {
1533                            let const_str = Base64::encode(bytes);
1534                            format!(
1535                                "from '{}{fname_string} (line {source_line_number}), abort '{identifier}': {const_str}",
1536                                module_id.to_canonical_display(true)
1537                            )
1538                        }
1539                        ErrorConstants::None => {
1540                            format!(
1541                                "from '{}{fname_string} (line {source_line_number})",
1542                                module_id.to_canonical_display(true)
1543                            )
1544                        }
1545                    }
1546                };
1547                // Convert the command index into an ordinal.
1548                command_index += 1;
1549                let suffix = match command_index % 10 {
1550                    1 => "st",
1551                    2 => "nd",
1552                    3 => "rd",
1553                    _ => "th",
1554                };
1555                IotaExecutionStatus::Failure {
1556                    error: format!("Error in {command_index}{suffix} command, {error}"),
1557                }
1558            }
1559            _ => native.into(),
1560        }
1561    }
1562}
1563
1564impl Display for IotaExecutionStatus {
1565    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1566        match self {
1567            Self::Success => write!(f, "success"),
1568            Self::Failure { error } => write!(f, "failure due to {error}"),
1569        }
1570    }
1571}
1572
1573impl IotaExecutionStatus {
1574    pub fn is_ok(&self) -> bool {
1575        matches!(self, IotaExecutionStatus::Success)
1576    }
1577    pub fn is_err(&self) -> bool {
1578        matches!(self, IotaExecutionStatus::Failure { .. })
1579    }
1580}
1581
1582impl From<ExecutionStatus> for IotaExecutionStatus {
1583    fn from(status: ExecutionStatus) -> Self {
1584        match status {
1585            ExecutionStatus::Success => Self::Success,
1586            ExecutionStatus::Failure {
1587                error,
1588                command: None,
1589            } => Self::Failure {
1590                error: error.to_string(),
1591            },
1592            ExecutionStatus::Failure {
1593                error,
1594                command: Some(idx),
1595            } => Self::Failure {
1596                error: format!("{error} in command {idx}"),
1597            },
1598            _ => unimplemented!(
1599                "a new ExecutionStatus enum variant was added and needs to be handled"
1600            ),
1601        }
1602    }
1603}
1604
1605fn to_owned_ref(owned_refs: Vec<(ObjectRef, Owner)>) -> Vec<OwnedObjectRef> {
1606    owned_refs
1607        .into_iter()
1608        .map(|(oref, owner)| OwnedObjectRef {
1609            owner,
1610            reference: oref,
1611        })
1612        .collect()
1613}
1614
1615#[serde_as]
1616#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1617#[serde(rename = "GasData", rename_all = "camelCase")]
1618pub struct IotaGasData {
1619    #[schemars(with = "Vec<ObjectRefSchema>")]
1620    #[serde_as(as = "Vec<ObjectRefSchema>")]
1621    pub payment: Vec<ObjectRef>,
1622    #[serde_as(as = "IotaAddressSchema")]
1623    #[schemars(with = "IotaAddressSchema")]
1624    pub owner: IotaAddress,
1625    #[schemars(with = "String")]
1626    #[serde_as(as = "DisplayFromStr")]
1627    pub price: u64,
1628    #[schemars(with = "String")]
1629    #[serde_as(as = "DisplayFromStr")]
1630    pub budget: u64,
1631}
1632
1633impl Display for IotaGasData {
1634    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1635        writeln!(f, "Gas Owner: {}", self.owner)?;
1636        writeln!(f, "Gas Budget: {} NANOS", self.budget)?;
1637        writeln!(f, "Gas Price: {} NANOS", self.price)?;
1638        writeln!(f, "Gas Payment:")?;
1639        for payment in &self.payment {
1640            write!(f, "{} ", objref_string(payment))?;
1641        }
1642        writeln!(f)
1643    }
1644}
1645
1646#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1647#[enum_dispatch(IotaTransactionBlockDataAPI)]
1648#[serde(
1649    rename = "TransactionBlockData",
1650    rename_all = "camelCase",
1651    tag = "messageVersion"
1652)]
1653pub enum IotaTransactionBlockData {
1654    V1(IotaTransactionBlockDataV1),
1655}
1656
1657#[enum_dispatch]
1658pub trait IotaTransactionBlockDataAPI {
1659    fn transaction(&self) -> &IotaTransactionBlockKind;
1660    fn sender(&self) -> &IotaAddress;
1661    fn gas_data(&self) -> &IotaGasData;
1662}
1663
1664#[serde_as]
1665#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1666#[serde(rename = "TransactionBlockDataV1", rename_all = "camelCase")]
1667pub struct IotaTransactionBlockDataV1 {
1668    pub transaction: IotaTransactionBlockKind,
1669    #[serde_as(as = "IotaAddressSchema")]
1670    #[schemars(with = "IotaAddressSchema")]
1671    pub sender: IotaAddress,
1672    pub gas_data: IotaGasData,
1673}
1674
1675impl IotaTransactionBlockDataAPI for IotaTransactionBlockDataV1 {
1676    fn transaction(&self) -> &IotaTransactionBlockKind {
1677        &self.transaction
1678    }
1679    fn sender(&self) -> &IotaAddress {
1680        &self.sender
1681    }
1682    fn gas_data(&self) -> &IotaGasData {
1683        &self.gas_data
1684    }
1685}
1686
1687impl IotaTransactionBlockData {
1688    pub fn move_calls(&self) -> Vec<&IotaProgrammableMoveCall> {
1689        match self {
1690            Self::V1(data) => match &data.transaction {
1691                IotaTransactionBlockKind::ProgrammableTransaction(pt) => pt
1692                    .commands
1693                    .iter()
1694                    .filter_map(|command| match command {
1695                        IotaCommand::MoveCall(c) => Some(&**c),
1696                        _ => None,
1697                    })
1698                    .collect(),
1699                _ => vec![],
1700            },
1701        }
1702    }
1703
1704    fn try_from_inner(
1705        data: TransactionData,
1706        transaction: IotaTransactionBlockKind,
1707    ) -> Result<Self, anyhow::Error> {
1708        let message_version = data.message_version();
1709        let sender = data.sender();
1710        let gas_data = IotaGasData {
1711            payment: data.gas().to_vec(),
1712            owner: data.gas_owner(),
1713            price: data.gas_price(),
1714            budget: data.gas_budget(),
1715        };
1716
1717        match message_version {
1718            1 => Ok(IotaTransactionBlockData::V1(IotaTransactionBlockDataV1 {
1719                transaction,
1720                sender,
1721                gas_data,
1722            })),
1723            _ => Err(anyhow::anyhow!(
1724                "Support for TransactionData version {} not implemented",
1725                message_version
1726            )),
1727        }
1728    }
1729
1730    pub fn try_from_with_module_cache(
1731        data: TransactionData,
1732        module_cache: &impl GetModule,
1733        tx_digest: TransactionDigest,
1734    ) -> Result<Self, anyhow::Error> {
1735        let transaction = IotaTransactionBlockKind::try_from_with_module_cache(
1736            data.kind().clone(),
1737            module_cache,
1738            tx_digest,
1739        )?;
1740        Self::try_from_inner(data, transaction)
1741    }
1742
1743    pub async fn try_from_with_package_resolver(
1744        data: TransactionData,
1745        package_resolver: &Resolver<impl PackageStore>,
1746        tx_digest: TransactionDigest,
1747    ) -> Result<Self, anyhow::Error> {
1748        let transaction = IotaTransactionBlockKind::try_from_with_package_resolver(
1749            data.kind().clone(),
1750            package_resolver,
1751            tx_digest,
1752        )
1753        .await?;
1754        Self::try_from_inner(data, transaction)
1755    }
1756}
1757
1758impl Display for IotaTransactionBlockData {
1759    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1760        match self {
1761            Self::V1(data) => {
1762                writeln!(f, "Sender: {}", data.sender)?;
1763                writeln!(f, "{}", self.gas_data())?;
1764                writeln!(f, "{}", data.transaction)
1765            }
1766        }
1767    }
1768}
1769
1770#[serde_as]
1771#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1772#[serde(rename = "TransactionBlock", rename_all = "camelCase")]
1773pub struct IotaTransactionBlock {
1774    pub data: IotaTransactionBlockData,
1775    #[serde_as(as = "Vec<GenericSignatureSchema>")]
1776    #[schemars(with = "Vec<GenericSignatureSchema>")]
1777    pub tx_signatures: Vec<GenericSignature>,
1778}
1779
1780impl IotaTransactionBlock {
1781    pub fn try_from(
1782        data: SenderSignedData,
1783        module_cache: &impl GetModule,
1784        tx_digest: TransactionDigest,
1785    ) -> Result<Self, anyhow::Error> {
1786        Ok(Self {
1787            data: IotaTransactionBlockData::try_from_with_module_cache(
1788                data.intent_message().value.clone(),
1789                module_cache,
1790                tx_digest,
1791            )?,
1792            tx_signatures: data.tx_signatures().to_vec(),
1793        })
1794    }
1795
1796    // TODO: the IotaTransactionBlock `try_from` can be removed after cleaning up
1797    // indexer v1, so are the related `try_from` methods for nested structs like
1798    // IotaTransactionBlockData etc.
1799    pub async fn try_from_with_package_resolver(
1800        data: SenderSignedData,
1801        package_resolver: &Resolver<impl PackageStore>,
1802        tx_digest: TransactionDigest,
1803    ) -> Result<Self, anyhow::Error> {
1804        Ok(Self {
1805            data: IotaTransactionBlockData::try_from_with_package_resolver(
1806                data.intent_message().value.clone(),
1807                package_resolver,
1808                tx_digest,
1809            )
1810            .await?,
1811            tx_signatures: data.tx_signatures().to_vec(),
1812        })
1813    }
1814}
1815
1816impl Display for IotaTransactionBlock {
1817    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1818        let mut builder = TableBuilder::default();
1819
1820        builder.push_record(vec![format!("{}", self.data)]);
1821        builder.push_record(vec![format!("Signatures:")]);
1822        for tx_sig in &self.tx_signatures {
1823            builder.push_record(vec![format!(
1824                "   {}\n",
1825                match tx_sig {
1826                    GenericSignature::Signature(sig) =>
1827                        Base64::from_bytes(sig.signature_bytes()).encoded(),
1828                    // the signatures for multisig and zklogin
1829                    // are not suited to be parsed out. they
1830                    // should be interpreted as a whole
1831                    _ => Base64::from_bytes(tx_sig.as_ref()).encoded(),
1832                }
1833            )]);
1834        }
1835
1836        let mut table = builder.build();
1837        table.with(TablePanel::header("Transaction Data"));
1838        table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
1839            1,
1840            TableStyle::modern().get_horizontal(),
1841        )]));
1842        write!(f, "{table}")
1843    }
1844}
1845
1846#[serde_as]
1847#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1848pub struct IotaGenesisTransaction {
1849    #[serde_as(as = "Vec<ObjectIdSchema>")]
1850    #[schemars(with = "Vec<ObjectIdSchema>")]
1851    pub objects: Vec<ObjectId>,
1852    #[schemars(with = "Vec<IotaEventID>")]
1853    pub events: Vec<EventID>,
1854}
1855
1856#[serde_as]
1857#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1858pub struct IotaConsensusCommitPrologueV1 {
1859    #[schemars(with = "String")]
1860    #[serde_as(as = "DisplayFromStr")]
1861    pub epoch: u64,
1862    #[schemars(with = "String")]
1863    #[serde_as(as = "DisplayFromStr")]
1864    pub round: u64,
1865    #[schemars(with = "Option<String>")]
1866    #[serde_as(as = "Option<DisplayFromStr>")]
1867    pub sub_dag_index: Option<u64>,
1868    #[schemars(with = "String")]
1869    #[serde_as(as = "DisplayFromStr")]
1870    pub commit_timestamp_ms: u64,
1871    #[serde_as(as = "Base58Schema")]
1872    #[schemars(with = "Base58Schema")]
1873    pub consensus_commit_digest: ConsensusCommitDigest,
1874    pub consensus_determined_version_assignments: IotaConsensusDeterminedVersionAssignments,
1875}
1876
1877/// Uses an enum to allow for future expansion of the
1878/// ConsensusDeterminedVersionAssignments.
1879#[serde_as]
1880#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, JsonSchema)]
1881#[schemars(rename = "ConsensusDeterminedVersionAssignments")]
1882pub enum IotaConsensusDeterminedVersionAssignments {
1883    // Cancelled transaction version assignment.
1884    CancelledTransactions(
1885        #[serde_as(as = "Vec<(Base58Schema, Vec<(ObjectIdSchema, SequenceNumberU64Schema)>)>")]
1886        #[schemars(with = "Vec<(Base58Schema, Vec<(ObjectIdSchema, SequenceNumberU64Schema)>)>")]
1887        Vec<(TransactionDigest, Vec<(ObjectId, SequenceNumber)>)>,
1888    ),
1889}
1890
1891impl From<ConsensusDeterminedVersionAssignments> for IotaConsensusDeterminedVersionAssignments {
1892    fn from(
1893        consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
1894    ) -> Self {
1895        match consensus_determined_version_assignments {
1896            ConsensusDeterminedVersionAssignments::CancelledTransactions {
1897                cancelled_transactions,
1898            } => IotaConsensusDeterminedVersionAssignments::CancelledTransactions(
1899                cancelled_transactions
1900                    .into_iter()
1901                    .map(|cancelled| {
1902                        (
1903                            cancelled.digest,
1904                            cancelled
1905                                .version_assignments
1906                                .into_iter()
1907                                .map(|va| (va.object_id, va.version))
1908                                .collect(),
1909                        )
1910                    })
1911                    .collect(),
1912            ),
1913            _ => unimplemented!(
1914                "a new ConsensusDeterminedVersionAssignments enum variant was added and needs to be handled"
1915            ),
1916        }
1917    }
1918}
1919
1920impl From<IotaConsensusDeterminedVersionAssignments> for ConsensusDeterminedVersionAssignments {
1921    fn from(
1922        iota_consensus_determined_version_assignments: IotaConsensusDeterminedVersionAssignments,
1923    ) -> Self {
1924        match iota_consensus_determined_version_assignments {
1925            IotaConsensusDeterminedVersionAssignments::CancelledTransactions(assignments) => {
1926                ConsensusDeterminedVersionAssignments::CancelledTransactions {
1927                    cancelled_transactions: assignments
1928                        .into_iter()
1929                        .map(|(digest, version_assignments)| CancelledTransaction {
1930                            digest,
1931                            version_assignments: version_assignments
1932                                .into_iter()
1933                                .map(|(object_id, version)| VersionAssignment {
1934                                    object_id,
1935                                    version,
1936                                })
1937                                .collect(),
1938                        })
1939                        .collect(),
1940                }
1941            }
1942        }
1943    }
1944}
1945
1946#[serde_as]
1947#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1948pub struct IotaRandomnessStateUpdate {
1949    #[schemars(with = "String")]
1950    #[serde_as(as = "DisplayFromStr")]
1951    pub epoch: u64,
1952
1953    #[schemars(with = "String")]
1954    #[serde_as(as = "DisplayFromStr")]
1955    pub randomness_round: u64,
1956    pub random_bytes: Vec<u8>,
1957}
1958
1959#[serde_as]
1960#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1961pub struct IotaEndOfEpochTransaction {
1962    pub transactions: Vec<IotaEndOfEpochTransactionKind>,
1963}
1964
1965#[serde_as]
1966#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1967pub enum IotaEndOfEpochTransactionKind {
1968    ChangeEpoch(IotaChangeEpoch),
1969    ChangeEpochV2(IotaChangeEpochV2),
1970}
1971
1972#[serde_as]
1973#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]
1974#[serde(rename = "InputObjectKind")]
1975pub enum IotaInputObjectKind {
1976    // A Move package, must be immutable.
1977    MovePackage(
1978        #[serde_as(as = "ObjectIdSchema")]
1979        #[schemars(with = "ObjectIdSchema")]
1980        ObjectId,
1981    ),
1982    // A Move object, either immutable, or owned mutable.
1983    ImmOrOwnedMoveObject(
1984        #[schemars(with = "ObjectRefSchema")]
1985        #[serde_as(as = "ObjectRefSchema")]
1986        ObjectRef,
1987    ),
1988    // A Move object that's shared and mutable.
1989    SharedMoveObject {
1990        #[serde_as(as = "ObjectIdSchema")]
1991        #[schemars(with = "ObjectIdSchema")]
1992        id: ObjectId,
1993        #[schemars(with = "SequenceNumberStringSchema")]
1994        #[serde_as(as = "SequenceNumberStringSchema")]
1995        initial_shared_version: SequenceNumber,
1996        #[serde(default = "default_shared_object_mutability")]
1997        mutable: bool,
1998    },
1999}
2000
2001/// A series of commands where the results of one command can be used in future
2002/// commands
2003#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2004pub struct IotaProgrammableTransactionBlock {
2005    /// Input objects or primitive values
2006    pub inputs: Vec<IotaCallArg>,
2007    #[serde(rename = "transactions")]
2008    /// The transactions to be executed sequentially. A failure in any
2009    /// transaction will result in the failure of the entire programmable
2010    /// transaction block.
2011    pub commands: Vec<IotaCommand>,
2012}
2013
2014impl Display for IotaProgrammableTransactionBlock {
2015    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2016        let Self { inputs, commands } = self;
2017        writeln!(f, "Inputs: {inputs:?}")?;
2018        writeln!(f, "Commands: [")?;
2019        for c in commands {
2020            writeln!(f, "  {c},")?;
2021        }
2022        writeln!(f, "]")
2023    }
2024}
2025
2026impl IotaProgrammableTransactionBlock {
2027    fn try_from_with_module_cache(
2028        value: ProgrammableTransaction,
2029        module_cache: &impl GetModule,
2030    ) -> Result<Self, anyhow::Error> {
2031        let ProgrammableTransaction { inputs, commands } = value;
2032        let input_types = Self::resolve_input_type(&inputs, &commands, module_cache);
2033        Ok(IotaProgrammableTransactionBlock {
2034            inputs: inputs
2035                .into_iter()
2036                .zip(input_types)
2037                .map(|(arg, layout)| IotaCallArg::try_from(arg, layout.as_ref()))
2038                .collect::<Result<_, _>>()?,
2039            commands: commands.into_iter().map(IotaCommand::from).collect(),
2040        })
2041    }
2042
2043    async fn try_from_with_package_resolver(
2044        value: ProgrammableTransaction,
2045        package_resolver: &Resolver<impl PackageStore>,
2046    ) -> Result<Self, anyhow::Error> {
2047        // If the pure input layouts cannot be built, we will use `None` for the input
2048        // types.
2049        let input_types = package_resolver
2050            .pure_input_layouts(&value)
2051            .await
2052            .unwrap_or_else(|e| {
2053                tracing::warn!("pure_input_layouts failed: {:?}", e);
2054                vec![None; value.inputs.len()]
2055            });
2056
2057        let ProgrammableTransaction { inputs, commands } = value;
2058        Ok(IotaProgrammableTransactionBlock {
2059            inputs: inputs
2060                .into_iter()
2061                .zip(input_types)
2062                .map(|(arg, layout)| IotaCallArg::try_from(arg, layout.as_ref()))
2063                .collect::<Result<_, _>>()?,
2064            commands: commands.into_iter().map(IotaCommand::from).collect(),
2065        })
2066    }
2067
2068    fn resolve_input_type(
2069        inputs: &[CallArg],
2070        commands: &[Command],
2071        module_cache: &impl GetModule,
2072    ) -> Vec<Option<MoveTypeLayout>> {
2073        let mut result_types = vec![None; inputs.len()];
2074        for command in commands.iter() {
2075            match command {
2076                Command::MoveCall(cmd) => {
2077                    // Unsafe: `cmd.module` is an already validated `Identifier`
2078                    let module = unsafe {
2079                        move_core_types::identifier::Identifier::new_unchecked(cmd.module.as_str())
2080                    };
2081                    let id = ModuleId::new(AccountAddress::new(cmd.package.into_bytes()), module);
2082                    let Some(types) = get_signature_types(id, &cmd.function, module_cache) else {
2083                        return result_types;
2084                    };
2085                    for (arg, type_) in cmd.arguments.iter().zip(types) {
2086                        if let (&Argument::Input(i), Some(type_)) = (arg, type_) {
2087                            if let Some(x) = result_types.get_mut(i as usize) {
2088                                x.replace(type_);
2089                            }
2090                        }
2091                    }
2092                }
2093                Command::SplitCoins(cmd) => {
2094                    for arg in &cmd.amounts {
2095                        if let &Argument::Input(i) = arg {
2096                            if let Some(x) = result_types.get_mut(i as usize) {
2097                                x.replace(MoveTypeLayout::U64);
2098                            }
2099                        }
2100                    }
2101                }
2102                Command::TransferObjects(TransferObjects {
2103                    address: Argument::Input(i),
2104                    ..
2105                }) => {
2106                    if let Some(x) = result_types.get_mut((*i) as usize) {
2107                        x.replace(MoveTypeLayout::Address);
2108                    }
2109                }
2110                _ => {}
2111            }
2112        }
2113        result_types
2114    }
2115}
2116
2117fn get_signature_types(
2118    id: ModuleId,
2119    function: &Identifier,
2120    module_cache: &impl GetModule,
2121) -> Option<Vec<Option<MoveTypeLayout>>> {
2122    use std::borrow::Borrow;
2123    if let Ok(Some(module)) = module_cache.get_module_by_id(&id) {
2124        let module: &CompiledModule = module.borrow();
2125        let func = module
2126            .function_handles
2127            .iter()
2128            .find(|f| module.identifier_at(f.name).as_str() == function.as_str())?;
2129        Some(
2130            module
2131                .signature_at(func.parameters)
2132                .0
2133                .iter()
2134                .map(|s| primitive_type(module, &[], s))
2135                .collect(),
2136        )
2137    } else {
2138        None
2139    }
2140}
2141
2142/// A single transaction in a programmable transaction block.
2143#[serde_as]
2144#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2145#[serde(rename = "IotaTransaction")]
2146pub enum IotaCommand {
2147    /// A call to either an entry or a public Move function
2148    MoveCall(Box<IotaProgrammableMoveCall>),
2149    /// `(Vec<forall T:key+store. T>, address)`
2150    /// It sends n-objects to the specified address. These objects must have
2151    /// store (public transfer) and either the previous owner must be an
2152    /// address or the object must be newly created.
2153    TransferObjects(Vec<IotaArgument>, IotaArgument),
2154    /// `(&mut Coin<T>, Vec<u64>)` -> `Vec<Coin<T>>`
2155    /// It splits off some amounts into a new coins with those amounts
2156    SplitCoins(IotaArgument, Vec<IotaArgument>),
2157    /// `(&mut Coin<T>, Vec<Coin<T>>)`
2158    /// It merges n-coins into the first coin
2159    MergeCoins(IotaArgument, Vec<IotaArgument>),
2160    /// Publishes a Move package. It takes the package bytes and a list of the
2161    /// package's transitive dependencies to link against on-chain.
2162    Publish(
2163        #[serde_as(as = "Vec<ObjectIdSchema>")]
2164        #[schemars(with = "Vec<ObjectIdSchema>")]
2165        Vec<ObjectId>,
2166    ),
2167    /// Upgrades a Move package
2168    Upgrade(
2169        #[serde_as(as = "Vec<ObjectIdSchema>")]
2170        #[schemars(with = "Vec<ObjectIdSchema>")]
2171        Vec<ObjectId>,
2172        #[serde_as(as = "ObjectIdSchema")]
2173        #[schemars(with = "ObjectIdSchema")]
2174        ObjectId,
2175        IotaArgument,
2176    ),
2177    /// `forall T: Vec<T> -> vector<T>`
2178    /// Given n-values of the same type, it constructs a vector. For non objects
2179    /// or an empty vector, the type tag must be specified.
2180    MakeMoveVec(Option<String>, Vec<IotaArgument>),
2181}
2182
2183impl Display for IotaCommand {
2184    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2185        match self {
2186            Self::MoveCall(p) => {
2187                write!(f, "MoveCall({p})")
2188            }
2189            Self::MakeMoveVec(ty_opt, elems) => {
2190                write!(f, "MakeMoveVec(")?;
2191                if let Some(ty) = ty_opt {
2192                    write!(f, "Some{ty}")?;
2193                } else {
2194                    write!(f, "None")?;
2195                }
2196                write!(f, ",[")?;
2197                write_sep(f, elems, ",")?;
2198                write!(f, "])")
2199            }
2200            Self::TransferObjects(objs, addr) => {
2201                write!(f, "TransferObjects([")?;
2202                write_sep(f, objs, ",")?;
2203                write!(f, "],{addr})")
2204            }
2205            Self::SplitCoins(coin, amounts) => {
2206                write!(f, "SplitCoins({coin},")?;
2207                write_sep(f, amounts, ",")?;
2208                write!(f, ")")
2209            }
2210            Self::MergeCoins(target, coins) => {
2211                write!(f, "MergeCoins({target},")?;
2212                write_sep(f, coins, ",")?;
2213                write!(f, ")")
2214            }
2215            Self::Publish(deps) => {
2216                write!(f, "Publish(<modules>,")?;
2217                write_sep(f, deps, ",")?;
2218                write!(f, ")")
2219            }
2220            Self::Upgrade(deps, current_package_id, ticket) => {
2221                write!(f, "Upgrade(<modules>, {ticket},")?;
2222                write_sep(f, deps, ",")?;
2223                write!(f, ", {current_package_id}")?;
2224                write!(f, ")")
2225            }
2226        }
2227    }
2228}
2229
2230impl From<Command> for IotaCommand {
2231    fn from(value: Command) -> Self {
2232        match value {
2233            Command::MoveCall(cmd) => IotaCommand::MoveCall(Box::new((cmd).into())),
2234            Command::TransferObjects(cmd) => IotaCommand::TransferObjects(
2235                cmd.objects.into_iter().map(IotaArgument::from).collect(),
2236                cmd.address.into(),
2237            ),
2238            Command::SplitCoins(cmd) => IotaCommand::SplitCoins(
2239                cmd.coin.into(),
2240                cmd.amounts.into_iter().map(IotaArgument::from).collect(),
2241            ),
2242            Command::MergeCoins(cmd) => IotaCommand::MergeCoins(
2243                cmd.coin.into(),
2244                cmd.coins_to_merge
2245                    .into_iter()
2246                    .map(IotaArgument::from)
2247                    .collect(),
2248            ),
2249            Command::Publish(cmd) => IotaCommand::Publish(cmd.dependencies),
2250            Command::MakeMoveVector(cmd) => IotaCommand::MakeMoveVec(
2251                cmd.type_.map(|tag| tag.to_string()),
2252                cmd.elements.into_iter().map(IotaArgument::from).collect(),
2253            ),
2254            Command::Upgrade(cmd) => IotaCommand::Upgrade(
2255                cmd.dependencies,
2256                cmd.package,
2257                IotaArgument::from(cmd.ticket),
2258            ),
2259            _ => unimplemented!("a new Command enum variant was added and needs to be handled"),
2260        }
2261    }
2262}
2263
2264/// An argument to a transaction in a programmable transaction block
2265#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2266pub enum IotaArgument {
2267    /// The gas coin. The gas coin can only be used by-ref, except for with
2268    /// `TransferObjects`, which can use it by-value.
2269    GasCoin,
2270    /// One of the input objects or primitive values (from
2271    /// `ProgrammableTransactionBlock` inputs)
2272    Input(u16),
2273    /// The result of another transaction (from `ProgrammableTransactionBlock`
2274    /// transactions)
2275    Result(u16),
2276    /// Like a `Result` but it accesses a nested result. Currently, the only
2277    /// usage of this is to access a value from a Move call with multiple
2278    /// return values.
2279    NestedResult(u16, u16),
2280}
2281
2282impl Display for IotaArgument {
2283    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2284        match self {
2285            Self::GasCoin => write!(f, "GasCoin"),
2286            Self::Input(i) => write!(f, "Input({i})"),
2287            Self::Result(i) => write!(f, "Result({i})"),
2288            Self::NestedResult(i, j) => write!(f, "NestedResult({i},{j})"),
2289        }
2290    }
2291}
2292
2293impl From<Argument> for IotaArgument {
2294    fn from(value: Argument) -> Self {
2295        match value {
2296            Argument::Gas => Self::GasCoin,
2297            Argument::Input(i) => Self::Input(i),
2298            Argument::Result(i) => Self::Result(i),
2299            Argument::NestedResult(i, j) => Self::NestedResult(i, j),
2300            _ => unimplemented!("a new Argument enum variant was added and needs to be handled"),
2301        }
2302    }
2303}
2304
2305#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2306#[serde(untagged)]
2307pub enum PtbInput {
2308    PtbRef(IotaArgument),
2309    CallArg(IotaJsonValue),
2310}
2311
2312/// The transaction for calling a Move function, either an entry function or a
2313/// public function (which cannot return references).
2314#[serde_as]
2315#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2316pub struct IotaProgrammableMoveCall {
2317    /// The package containing the module and function.
2318    #[serde_as(as = "ObjectIdSchema")]
2319    #[schemars(with = "ObjectIdSchema")]
2320    pub package: ObjectId,
2321    /// The specific module in the package containing the function.
2322    pub module: String,
2323    /// The function to be called.
2324    pub function: String,
2325    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2326    /// The type arguments to the function.
2327    pub type_arguments: Vec<String>,
2328    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2329    /// The arguments to the function.
2330    pub arguments: Vec<IotaArgument>,
2331}
2332
2333fn write_sep<T: Display>(
2334    f: &mut Formatter<'_>,
2335    items: impl IntoIterator<Item = T>,
2336    sep: &str,
2337) -> std::fmt::Result {
2338    let mut xs = items.into_iter().peekable();
2339    while let Some(x) = xs.next() {
2340        write!(f, "{x}")?;
2341        if xs.peek().is_some() {
2342            write!(f, "{sep}")?;
2343        }
2344    }
2345    Ok(())
2346}
2347
2348impl Display for IotaProgrammableMoveCall {
2349    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2350        let Self {
2351            package,
2352            module,
2353            function,
2354            type_arguments,
2355            arguments,
2356        } = self;
2357        write!(f, "{package}::{module}::{function}")?;
2358        if !type_arguments.is_empty() {
2359            write!(f, "<")?;
2360            write_sep(f, type_arguments, ",")?;
2361            write!(f, ">")?;
2362        }
2363        write!(f, "(")?;
2364        write_sep(f, arguments, ",")?;
2365        write!(f, ")")
2366    }
2367}
2368
2369impl From<MoveCall> for IotaProgrammableMoveCall {
2370    fn from(value: MoveCall) -> Self {
2371        let MoveCall {
2372            package,
2373            module,
2374            function,
2375            type_arguments,
2376            arguments,
2377        } = value;
2378        Self {
2379            package,
2380            module: module.to_string(),
2381            function: function.to_string(),
2382            type_arguments: type_arguments.into_iter().map(|t| t.to_string()).collect(),
2383            arguments: arguments.into_iter().map(IotaArgument::from).collect(),
2384        }
2385    }
2386}
2387
2388const fn default_shared_object_mutability() -> bool {
2389    true
2390}
2391
2392impl From<InputObjectKind> for IotaInputObjectKind {
2393    fn from(input: InputObjectKind) -> Self {
2394        match input {
2395            InputObjectKind::MovePackage(id) => Self::MovePackage(id),
2396            InputObjectKind::ImmOrOwnedMoveObject(oref) => Self::ImmOrOwnedMoveObject(oref),
2397            InputObjectKind::SharedMoveObject {
2398                id,
2399                initial_shared_version,
2400                mutable,
2401            } => Self::SharedMoveObject {
2402                id,
2403                initial_shared_version,
2404                mutable,
2405            },
2406        }
2407    }
2408}
2409
2410#[derive(Debug, Serialize, Deserialize, Clone)]
2411#[serde(rename = "TypeTag", rename_all = "camelCase")]
2412pub struct IotaTypeTag(String);
2413
2414impl IotaTypeTag {
2415    pub fn new(tag: String) -> Self {
2416        Self(tag)
2417    }
2418}
2419
2420impl AsRef<str> for IotaTypeTag {
2421    fn as_ref(&self) -> &str {
2422        &self.0
2423    }
2424}
2425
2426impl TryFrom<IotaTypeTag> for TypeTag {
2427    type Error = anyhow::Error;
2428    fn try_from(tag: IotaTypeTag) -> Result<Self, Self::Error> {
2429        parse_iota_type_tag(&tag.0)
2430    }
2431}
2432
2433impl From<TypeTag> for IotaTypeTag {
2434    fn from(tag: TypeTag) -> Self {
2435        Self(format!("{tag}"))
2436    }
2437}
2438
2439#[derive(Serialize, Deserialize, JsonSchema)]
2440#[serde(rename_all = "camelCase")]
2441pub enum RPCTransactionRequestParams {
2442    TransferObjectRequestParams(TransferObjectParams),
2443    MoveCallRequestParams(MoveCallParams),
2444}
2445
2446#[serde_as]
2447#[derive(Serialize, Deserialize, JsonSchema)]
2448#[serde(rename_all = "camelCase")]
2449pub struct TransferObjectParams {
2450    #[serde_as(as = "IotaAddressSchema")]
2451    #[schemars(with = "IotaAddressSchema")]
2452    pub recipient: IotaAddress,
2453    #[serde_as(as = "ObjectIdSchema")]
2454    #[schemars(with = "ObjectIdSchema")]
2455    pub object_id: ObjectId,
2456}
2457
2458#[serde_as]
2459#[derive(Serialize, Deserialize, JsonSchema)]
2460#[serde(rename_all = "camelCase")]
2461pub struct MoveCallParams {
2462    #[serde_as(as = "ObjectIdSchema")]
2463    #[schemars(with = "ObjectIdSchema")]
2464    pub package_object_id: ObjectId,
2465    pub module: String,
2466    pub function: String,
2467    #[serde(default)]
2468    #[schemars(with = "Vec<TypeTagSchema>")]
2469    pub type_arguments: Vec<IotaTypeTag>,
2470    pub arguments: Vec<PtbInput>,
2471}
2472
2473#[serde_as]
2474#[derive(Serialize, Deserialize, Clone, JsonSchema)]
2475#[serde(rename_all = "camelCase")]
2476pub struct TransactionBlockBytes {
2477    /// BCS serialized transaction data bytes without its type tag, as base-64
2478    /// encoded string.
2479    #[schemars(with = "Base64Schema")]
2480    pub tx_bytes: Base64,
2481    /// the gas objects to be used
2482    #[schemars(with = "Vec<ObjectRefSchema>")]
2483    #[serde_as(as = "Vec<ObjectRefSchema>")]
2484    pub gas: Vec<ObjectRef>,
2485    /// objects to be used in this transaction
2486    pub input_objects: Vec<IotaInputObjectKind>,
2487}
2488
2489impl TransactionBlockBytes {
2490    pub fn from_data(data: TransactionData) -> Result<Self, anyhow::Error> {
2491        Ok(Self {
2492            tx_bytes: Base64::from_bytes(bcs::to_bytes(&data)?.as_slice()),
2493            gas: data.gas().to_vec(),
2494            input_objects: data
2495                .input_objects()?
2496                .into_iter()
2497                .map(IotaInputObjectKind::from)
2498                .collect(),
2499        })
2500    }
2501
2502    pub fn to_data(self) -> Result<TransactionData, anyhow::Error> {
2503        bcs::from_bytes::<TransactionData>(&self.tx_bytes.to_vec().map_err(|e| anyhow::anyhow!(e))?)
2504            .map_err(|e| anyhow::anyhow!(e))
2505    }
2506}
2507
2508#[serde_as]
2509#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
2510#[serde(rename = "OwnedObjectRef")]
2511pub struct OwnedObjectRef {
2512    #[schemars(with = "OwnerSchema")]
2513    #[serde_as(as = "OwnerSchema")]
2514    pub owner: Owner,
2515    #[schemars(with = "ObjectRefSchema")]
2516    #[serde_as(as = "ObjectRefSchema")]
2517    pub reference: ObjectRef,
2518}
2519
2520impl OwnedObjectRef {
2521    pub fn object_id(&self) -> ObjectId {
2522        self.reference.object_id
2523    }
2524    pub fn version(&self) -> SequenceNumber {
2525        self.reference.version
2526    }
2527}
2528
2529#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2530#[serde(tag = "type", rename_all = "camelCase")]
2531pub enum IotaCallArg {
2532    // Needs to become an Object Ref or Object ID, depending on object type
2533    Object(IotaObjectArg),
2534    // pure value, bcs encoded
2535    Pure(IotaPureValue),
2536}
2537
2538impl IotaCallArg {
2539    pub fn try_from(
2540        value: CallArg,
2541        layout: Option<&MoveTypeLayout>,
2542    ) -> Result<Self, anyhow::Error> {
2543        Ok(match value {
2544            CallArg::Pure(p) => IotaCallArg::Pure(IotaPureValue {
2545                value_type: layout.map(|l| type_tag_core_to_sdk(&l.into())),
2546                value: IotaJsonValue::from_bcs_bytes(layout, &p)?,
2547            }),
2548            CallArg::ImmutableOrOwned(object_ref) => {
2549                IotaCallArg::Object(IotaObjectArg::ImmOrOwnedObject {
2550                    object_id: object_ref.object_id,
2551                    version: object_ref.version,
2552                    digest: object_ref.digest,
2553                })
2554            }
2555            CallArg::Shared(SharedObjectRef {
2556                object_id: id,
2557                initial_shared_version,
2558                mutable,
2559            }) => IotaCallArg::Object(IotaObjectArg::SharedObject {
2560                object_id: id,
2561                initial_shared_version,
2562                mutable,
2563            }),
2564            CallArg::Receiving(object_ref) => IotaCallArg::Object(IotaObjectArg::Receiving {
2565                object_id: object_ref.object_id,
2566                version: object_ref.version,
2567                digest: object_ref.digest,
2568            }),
2569            _ => unimplemented!("a new CallArg enum variant was added and needs to be handled"),
2570        })
2571    }
2572
2573    pub fn pure(&self) -> Option<&IotaJsonValue> {
2574        match self {
2575            IotaCallArg::Pure(v) => Some(&v.value),
2576            _ => None,
2577        }
2578    }
2579
2580    pub fn object(&self) -> Option<&ObjectId> {
2581        match self {
2582            IotaCallArg::Object(IotaObjectArg::SharedObject { object_id, .. })
2583            | IotaCallArg::Object(IotaObjectArg::ImmOrOwnedObject { object_id, .. })
2584            | IotaCallArg::Object(IotaObjectArg::Receiving { object_id, .. }) => Some(object_id),
2585            _ => None,
2586        }
2587    }
2588}
2589
2590#[serde_as]
2591#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2592#[serde(rename_all = "camelCase")]
2593pub struct IotaPureValue {
2594    #[schemars(with = "Option<TypeTagSchema>")]
2595    #[serde_as(as = "Option<TypeTagSchema>")]
2596    value_type: Option<TypeTag>,
2597    value: IotaJsonValue,
2598}
2599
2600impl IotaPureValue {
2601    pub fn value(&self) -> IotaJsonValue {
2602        self.value.clone()
2603    }
2604
2605    pub fn value_type(&self) -> Option<TypeTag> {
2606        self.value_type.clone()
2607    }
2608}
2609
2610#[serde_as]
2611#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2612#[serde(tag = "objectType", rename_all = "camelCase")]
2613pub enum IotaObjectArg {
2614    // A Move object, either immutable, or owned mutable.
2615    #[serde(rename_all = "camelCase")]
2616    ImmOrOwnedObject {
2617        #[serde_as(as = "ObjectIdSchema")]
2618        #[schemars(with = "ObjectIdSchema")]
2619        object_id: ObjectId,
2620        #[schemars(with = "SequenceNumberStringSchema")]
2621        #[serde_as(as = "SequenceNumberStringSchema")]
2622        version: SequenceNumber,
2623        #[serde_as(as = "Base58Schema")]
2624        #[schemars(with = "Base58Schema")]
2625        digest: ObjectDigest,
2626    },
2627    // A Move object that's shared.
2628    // SharedObject::mutable controls whether caller asks for a mutable reference to shared
2629    // object.
2630    #[serde(rename_all = "camelCase")]
2631    SharedObject {
2632        #[serde_as(as = "ObjectIdSchema")]
2633        #[schemars(with = "ObjectIdSchema")]
2634        object_id: ObjectId,
2635        #[schemars(with = "SequenceNumberStringSchema")]
2636        #[serde_as(as = "SequenceNumberStringSchema")]
2637        initial_shared_version: SequenceNumber,
2638        mutable: bool,
2639    },
2640    // A reference to a Move object that's going to be received in the transaction.
2641    #[serde(rename_all = "camelCase")]
2642    Receiving {
2643        #[serde_as(as = "ObjectIdSchema")]
2644        #[schemars(with = "ObjectIdSchema")]
2645        object_id: ObjectId,
2646        #[schemars(with = "SequenceNumberStringSchema")]
2647        #[serde_as(as = "SequenceNumberStringSchema")]
2648        version: SequenceNumber,
2649        #[serde_as(as = "Base58Schema")]
2650        #[schemars(with = "Base58Schema")]
2651        digest: ObjectDigest,
2652    },
2653}
2654
2655#[derive(Clone)]
2656pub struct EffectsWithInput {
2657    pub effects: IotaTransactionBlockEffects,
2658    pub input: TransactionData,
2659}
2660
2661impl From<EffectsWithInput> for IotaTransactionBlockEffects {
2662    fn from(e: EffectsWithInput) -> Self {
2663        e.effects
2664    }
2665}
2666
2667#[serde_as]
2668#[derive(Clone, Debug, JsonSchema, Serialize, Deserialize)]
2669pub enum TransactionFilter {
2670    /// Query by checkpoint.
2671    Checkpoint(
2672        #[schemars(with = "String")]
2673        #[serde_as(as = "DisplayFromStr")]
2674        CheckpointSequenceNumber,
2675    ),
2676    /// Query by move function.
2677    MoveFunction {
2678        #[serde_as(as = "ObjectIdSchema")]
2679        #[schemars(with = "ObjectIdSchema")]
2680        package: ObjectId,
2681        module: Option<String>,
2682        function: Option<String>,
2683    },
2684    /// Query by input object.
2685    InputObject(
2686        #[serde_as(as = "ObjectIdSchema")]
2687        #[schemars(with = "ObjectIdSchema")]
2688        ObjectId,
2689    ),
2690    /// Query by changed object, including created, mutated and unwrapped
2691    /// objects.
2692    ChangedObject(
2693        #[serde_as(as = "ObjectIdSchema")]
2694        #[schemars(with = "ObjectIdSchema")]
2695        ObjectId,
2696    ),
2697    /// Query by sender address.
2698    FromAddress(
2699        #[serde_as(as = "IotaAddressSchema")]
2700        #[schemars(with = "IotaAddressSchema")]
2701        IotaAddress,
2702    ),
2703    /// Query by recipient address.
2704    ToAddress(
2705        #[serde_as(as = "IotaAddressSchema")]
2706        #[schemars(with = "IotaAddressSchema")]
2707        IotaAddress,
2708    ),
2709    /// Query by sender and recipient address.
2710    FromAndToAddress {
2711        #[serde_as(as = "IotaAddressSchema")]
2712        #[schemars(with = "IotaAddressSchema")]
2713        from: IotaAddress,
2714        #[serde_as(as = "IotaAddressSchema")]
2715        #[schemars(with = "IotaAddressSchema")]
2716        to: IotaAddress,
2717    },
2718    /// Query txs that have a given address as sender or recipient.
2719    FromOrToAddress {
2720        #[serde_as(as = "IotaAddressSchema")]
2721        #[schemars(with = "IotaAddressSchema")]
2722        addr: IotaAddress,
2723    },
2724    /// Query by transaction kind
2725    TransactionKind(IotaTransactionKind),
2726    /// Query transactions of any given kind in the input.
2727    TransactionKindIn(Vec<IotaTransactionKind>),
2728}
2729
2730impl TransactionFilter {
2731    pub fn as_v2(&self) -> TransactionFilterV2 {
2732        match self {
2733            TransactionFilter::InputObject(o) => TransactionFilterV2::InputObject(*o),
2734            TransactionFilter::ChangedObject(o) => TransactionFilterV2::ChangedObject(*o),
2735            TransactionFilter::FromAddress(a) => TransactionFilterV2::FromAddress(*a),
2736            TransactionFilter::ToAddress(a) => TransactionFilterV2::ToAddress(*a),
2737            TransactionFilter::FromAndToAddress { from, to } => {
2738                TransactionFilterV2::FromAndToAddress {
2739                    from: *from,
2740                    to: *to,
2741                }
2742            }
2743            TransactionFilter::FromOrToAddress { addr } => {
2744                TransactionFilterV2::FromOrToAddress { addr: *addr }
2745            }
2746            TransactionFilter::MoveFunction {
2747                package,
2748                module,
2749                function,
2750            } => TransactionFilterV2::MoveFunction {
2751                package: *package,
2752                module: module.clone(),
2753                function: function.clone(),
2754            },
2755            TransactionFilter::TransactionKind(kind) => TransactionFilterV2::TransactionKind(*kind),
2756            TransactionFilter::TransactionKindIn(kinds) => {
2757                TransactionFilterV2::TransactionKindIn(kinds.clone())
2758            }
2759            TransactionFilter::Checkpoint(checkpoint) => {
2760                TransactionFilterV2::Checkpoint(*checkpoint)
2761            }
2762        }
2763    }
2764}
2765
2766impl Filter<EffectsWithInput> for TransactionFilter {
2767    fn matches(&self, item: &EffectsWithInput) -> bool {
2768        let _scope = monitored_scope("TransactionFilter::matches");
2769        match self {
2770            TransactionFilter::InputObject(o) => {
2771                let Ok(input_objects) = item.input.input_objects() else {
2772                    return false;
2773                };
2774                input_objects.iter().any(|object| object.object_id() == *o)
2775            }
2776            TransactionFilter::ChangedObject(o) => item
2777                .effects
2778                .mutated()
2779                .iter()
2780                .any(|oref: &OwnedObjectRef| &oref.reference.object_id == o),
2781            TransactionFilter::FromAddress(a) => &item.input.sender() == a,
2782            TransactionFilter::ToAddress(a) => {
2783                let mutated: &[OwnedObjectRef] = item.effects.mutated();
2784                mutated.iter().chain(item.effects.unwrapped().iter()).any(|oref: &OwnedObjectRef| {
2785                    matches!(oref.owner, Owner::Address(owner) if owner == *a)
2786                })
2787            }
2788            TransactionFilter::FromAndToAddress { from, to } => {
2789                Self::FromAddress(*from).matches(item) && Self::ToAddress(*to).matches(item)
2790            }
2791            TransactionFilter::FromOrToAddress { addr } => {
2792                Self::FromAddress(*addr).matches(item) || Self::ToAddress(*addr).matches(item)
2793            }
2794            TransactionFilter::MoveFunction {
2795                package,
2796                module,
2797                function,
2798            } => item.input.move_calls().into_iter().any(|(p, m, f)| {
2799                p == package
2800                    && (module.is_none() || matches!(module,  Some(m2) if m2 == &m.to_string()))
2801                    && (function.is_none() || matches!(function, Some(f2) if f2 == &f.to_string()))
2802            }),
2803            TransactionFilter::TransactionKind(kind) => {
2804                kind == &IotaTransactionKind::from(item.input.kind())
2805            }
2806            TransactionFilter::TransactionKindIn(kinds) => kinds
2807                .iter()
2808                .any(|kind| kind == &IotaTransactionKind::from(item.input.kind())),
2809            // this filter is not supported, RPC will reject it on subscription
2810            TransactionFilter::Checkpoint(_) => false,
2811        }
2812    }
2813}
2814
2815#[serde_as]
2816#[derive(Clone, Debug, JsonSchema, Serialize, Deserialize)]
2817#[non_exhaustive]
2818pub enum TransactionFilterV2 {
2819    /// Query by checkpoint.
2820    Checkpoint(
2821        #[schemars(with = "String")]
2822        #[serde_as(as = "DisplayFromStr")]
2823        CheckpointSequenceNumber,
2824    ),
2825    /// Query by move function.
2826    MoveFunction {
2827        #[serde_as(as = "ObjectIdSchema")]
2828        #[schemars(with = "ObjectIdSchema")]
2829        package: ObjectId,
2830        module: Option<String>,
2831        function: Option<String>,
2832    },
2833    /// Query by input object.
2834    InputObject(
2835        #[serde_as(as = "ObjectIdSchema")]
2836        #[schemars(with = "ObjectIdSchema")]
2837        ObjectId,
2838    ),
2839    /// Query by changed object, including created, mutated and unwrapped
2840    /// objects.
2841    ChangedObject(
2842        #[serde_as(as = "ObjectIdSchema")]
2843        #[schemars(with = "ObjectIdSchema")]
2844        ObjectId,
2845    ),
2846    /// Query transactions that wrapped or deleted the specified object.
2847    /// Includes transactions that either created and immediately wrapped
2848    /// the object or unwrapped and immediately deleted it.
2849    WrappedOrDeletedObject(
2850        #[serde_as(as = "ObjectIdSchema")]
2851        #[schemars(with = "ObjectIdSchema")]
2852        ObjectId,
2853    ),
2854    /// Query by sender address.
2855    FromAddress(
2856        #[serde_as(as = "IotaAddressSchema")]
2857        #[schemars(with = "IotaAddressSchema")]
2858        IotaAddress,
2859    ),
2860    /// Query by recipient address.
2861    ToAddress(
2862        #[serde_as(as = "IotaAddressSchema")]
2863        #[schemars(with = "IotaAddressSchema")]
2864        IotaAddress,
2865    ),
2866    /// Query by sender and recipient address.
2867    FromAndToAddress {
2868        #[serde_as(as = "IotaAddressSchema")]
2869        #[schemars(with = "IotaAddressSchema")]
2870        from: IotaAddress,
2871        #[serde_as(as = "IotaAddressSchema")]
2872        #[schemars(with = "IotaAddressSchema")]
2873        to: IotaAddress,
2874    },
2875    /// Query txs that have a given address as sender or recipient.
2876    FromOrToAddress {
2877        #[serde_as(as = "IotaAddressSchema")]
2878        #[schemars(with = "IotaAddressSchema")]
2879        addr: IotaAddress,
2880    },
2881    /// Query by transaction kind
2882    TransactionKind(IotaTransactionKind),
2883    /// Query transactions of any given kind in the input.
2884    TransactionKindIn(Vec<IotaTransactionKind>),
2885}
2886
2887impl TransactionFilterV2 {
2888    pub fn as_v1(&self) -> Option<TransactionFilter> {
2889        match self {
2890            TransactionFilterV2::InputObject(o) => Some(TransactionFilter::InputObject(*o)),
2891            TransactionFilterV2::ChangedObject(o) => Some(TransactionFilter::ChangedObject(*o)),
2892            TransactionFilterV2::FromAddress(a) => Some(TransactionFilter::FromAddress(*a)),
2893            TransactionFilterV2::ToAddress(a) => Some(TransactionFilter::ToAddress(*a)),
2894            TransactionFilterV2::FromAndToAddress { from, to } => {
2895                Some(TransactionFilter::FromAndToAddress {
2896                    from: *from,
2897                    to: *to,
2898                })
2899            }
2900            TransactionFilterV2::FromOrToAddress { addr } => {
2901                Some(TransactionFilter::FromOrToAddress { addr: *addr })
2902            }
2903            TransactionFilterV2::MoveFunction {
2904                package,
2905                module,
2906                function,
2907            } => Some(TransactionFilter::MoveFunction {
2908                package: *package,
2909                module: module.clone(),
2910                function: function.clone(),
2911            }),
2912            TransactionFilterV2::TransactionKind(kind) => {
2913                Some(TransactionFilter::TransactionKind(*kind))
2914            }
2915            TransactionFilterV2::TransactionKindIn(kinds) => {
2916                Some(TransactionFilter::TransactionKindIn(kinds.clone()))
2917            }
2918            TransactionFilterV2::Checkpoint(checkpoint) => {
2919                Some(TransactionFilter::Checkpoint(*checkpoint))
2920            }
2921            // V2-only variants which do not have a V1 equivalent
2922            TransactionFilterV2::WrappedOrDeletedObject(_) => None,
2923        }
2924    }
2925}
2926
2927impl Filter<EffectsWithInput> for TransactionFilterV2 {
2928    fn matches(&self, item: &EffectsWithInput) -> bool {
2929        let _scope = monitored_scope("TransactionFilterV2::matches");
2930        if let Some(v1) = self.as_v1() {
2931            return v1.matches(item);
2932        }
2933        // Fallback for new V2-only variants:
2934        match self {
2935            TransactionFilterV2::WrappedOrDeletedObject(o) => item
2936                .effects
2937                .wrapped()
2938                .iter()
2939                .chain(item.effects.deleted())
2940                .chain(item.effects.unwrapped_then_deleted())
2941                .any(|oref| &oref.object_id == o),
2942
2943            _ => false,
2944        }
2945    }
2946}
2947
2948/// Represents the type of a transaction. All transactions except
2949/// `ProgrammableTransaction` are considered system transactions.
2950#[derive(
2951    Debug, Clone, Copy, PartialEq, Eq, EnumString, Display, Serialize, Deserialize, JsonSchema,
2952)]
2953#[non_exhaustive]
2954pub enum IotaTransactionKind {
2955    /// The `SystemTransaction` variant can be used to filter for all types of
2956    /// system transactions.
2957    SystemTransaction = 0,
2958    ProgrammableTransaction = 1,
2959    Genesis = 2,
2960    ConsensusCommitPrologueV1 = 3,
2961    RandomnessStateUpdate = 5,
2962    EndOfEpochTransaction = 6,
2963}
2964
2965impl IotaTransactionKind {
2966    /// Returns true if the transaction is a system transaction.
2967    pub fn is_system_transaction(&self) -> bool {
2968        !matches!(self, Self::ProgrammableTransaction)
2969    }
2970}
2971
2972impl From<&TransactionKind> for IotaTransactionKind {
2973    fn from(kind: &TransactionKind) -> Self {
2974        match kind {
2975            TransactionKind::Genesis(_) => Self::Genesis,
2976            TransactionKind::ConsensusCommitPrologueV1(_) => Self::ConsensusCommitPrologueV1,
2977            #[allow(deprecated)]
2978            TransactionKind::AuthenticatorStateUpdateV1Deprecated => Self::SystemTransaction,
2979            TransactionKind::RandomnessStateUpdate(_) => Self::RandomnessStateUpdate,
2980            TransactionKind::EndOfEpoch(_) => Self::EndOfEpochTransaction,
2981            TransactionKind::Programmable(_) => Self::ProgrammableTransaction,
2982            _ => unimplemented!(
2983                "a new TransactionKind enum variant was added and needs to be handled"
2984            ),
2985        }
2986    }
2987}