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