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