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 .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| match tx {
594 EndOfEpochTransactionKind::ChangeEpoch(e) => {
595 IotaEndOfEpochTransactionKind::ChangeEpoch(e.into())
596 }
597 EndOfEpochTransactionKind::ChangeEpochV2(e) => {
598 IotaEndOfEpochTransactionKind::ChangeEpochV2(e.into())
599 }
600 EndOfEpochTransactionKind::ChangeEpochV3(e) => {
601 IotaEndOfEpochTransactionKind::ChangeEpochV2(e.into())
602 }
603 EndOfEpochTransactionKind::ChangeEpochV4(e) => {
604 IotaEndOfEpochTransactionKind::ChangeEpochV2(e.into())
605 }
606 EndOfEpochTransactionKind::TransactionDenyRulesCreate => {
607 IotaEndOfEpochTransactionKind::TransactionDenyRulesCreate
608 }
609 _ => unimplemented!(
610 "a new EndOfEpochTransactionKind enum variant was added and needs to be handled"
611 ),
612 })
613 .collect(),
614 }))
615 }
616 _ => unimplemented!(
617 "a new TransactionKind enum variant was added and needs to be handled"
618 )
619 }
620 }
621
622 fn try_from_with_module_cache(
623 tx: TransactionKind,
624 module_cache: &impl GetModule,
625 tx_digest: TransactionDigest,
626 ) -> Result<Self, anyhow::Error> {
627 match tx {
628 TransactionKind::Programmable(p) => Ok(Self::ProgrammableTransaction(
629 IotaProgrammableTransactionBlock::try_from_with_module_cache(p, module_cache)?,
630 )),
631 tx => Self::try_from_inner(tx, tx_digest),
632 }
633 }
634
635 async fn try_from_with_package_resolver(
636 tx: TransactionKind,
637 package_resolver: &Resolver<impl PackageStore>,
638 tx_digest: TransactionDigest,
639 ) -> Result<Self, anyhow::Error> {
640 match tx {
641 TransactionKind::Programmable(p) => Ok(Self::ProgrammableTransaction(
642 IotaProgrammableTransactionBlock::try_from_with_package_resolver(
643 p,
644 package_resolver,
645 )
646 .await?,
647 )),
648 tx => Self::try_from_inner(tx, tx_digest),
649 }
650 }
651
652 pub fn transaction_count(&self) -> usize {
653 match self {
654 Self::ProgrammableTransaction(p) => p.commands.len(),
655 _ => 1,
656 }
657 }
658
659 pub fn name(&self) -> &'static str {
660 match self {
661 Self::Genesis(_) => "Genesis",
662 Self::ConsensusCommitPrologueV1(_) => "ConsensusCommitPrologueV1",
663 Self::ProgrammableTransaction(_) => "ProgrammableTransaction",
664 Self::RandomnessStateUpdate(_) => "RandomnessStateUpdate",
665 Self::TransactionDenyRulesUpdate(_) => "TransactionDenyRulesUpdate",
666 Self::EndOfEpochTransaction(_) => "EndOfEpochTransaction",
667 }
668 }
669}
670
671#[serde_as]
672#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
673pub struct IotaChangeEpoch {
674 #[schemars(with = "String")]
675 #[serde_as(as = "DisplayFromStr")]
676 pub epoch: EpochId,
677 #[schemars(with = "String")]
678 #[serde_as(as = "DisplayFromStr")]
679 pub storage_charge: u64,
680 #[schemars(with = "String")]
681 #[serde_as(as = "DisplayFromStr")]
682 pub computation_charge: u64,
683 #[schemars(with = "String")]
684 #[serde_as(as = "DisplayFromStr")]
685 pub storage_rebate: u64,
686 #[schemars(with = "String")]
687 #[serde_as(as = "DisplayFromStr")]
688 pub epoch_start_timestamp_ms: u64,
689}
690
691impl From<ChangeEpoch> for IotaChangeEpoch {
692 fn from(e: ChangeEpoch) -> Self {
693 Self {
694 epoch: e.epoch,
695 storage_charge: e.storage_charge,
696 computation_charge: e.computation_charge,
697 storage_rebate: e.storage_rebate,
698 epoch_start_timestamp_ms: e.epoch_start_timestamp_ms,
699 }
700 }
701}
702
703#[serde_as]
704#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
705pub struct IotaChangeEpochV2 {
706 #[schemars(with = "String")]
707 #[serde_as(as = "DisplayFromStr")]
708 pub epoch: EpochId,
709 #[schemars(with = "String")]
710 #[serde_as(as = "DisplayFromStr")]
711 pub storage_charge: u64,
712 #[schemars(with = "String")]
713 #[serde_as(as = "DisplayFromStr")]
714 pub computation_charge: u64,
715 #[schemars(with = "String")]
716 #[serde_as(as = "DisplayFromStr")]
717 pub computation_charge_burned: u64,
718 #[schemars(with = "String")]
719 #[serde_as(as = "DisplayFromStr")]
720 pub storage_rebate: u64,
721 #[schemars(with = "String")]
722 #[serde_as(as = "DisplayFromStr")]
723 pub epoch_start_timestamp_ms: u64,
724 #[schemars(with = "Option<Vec<String>>")]
725 #[serde_as(as = "Option<Vec<DisplayFromStr>>")]
726 #[serde(skip_serializing_if = "Option::is_none", default)]
727 pub eligible_active_validators: Option<Vec<u64>>,
728 #[schemars(with = "Option<Vec<String>>")]
729 #[serde_as(as = "Option<Vec<DisplayFromStr>>")]
730 #[serde(skip_serializing_if = "Option::is_none", default)]
731 pub scores: Option<Vec<u64>>,
732}
733
734impl From<ChangeEpochV2> for IotaChangeEpochV2 {
735 fn from(e: ChangeEpochV2) -> Self {
736 Self {
737 epoch: e.epoch,
738 storage_charge: e.storage_charge,
739 computation_charge: e.computation_charge,
740 computation_charge_burned: e.computation_charge_burned,
741 storage_rebate: e.storage_rebate,
742 epoch_start_timestamp_ms: e.epoch_start_timestamp_ms,
743 eligible_active_validators: None,
744 scores: None,
745 }
746 }
747}
748
749impl From<ChangeEpochV3> for IotaChangeEpochV2 {
750 fn from(e: ChangeEpochV3) -> Self {
751 Self {
752 epoch: e.epoch,
753 storage_charge: e.storage_charge,
754 computation_charge: e.computation_charge,
755 computation_charge_burned: e.computation_charge_burned,
756 storage_rebate: e.storage_rebate,
757 epoch_start_timestamp_ms: e.epoch_start_timestamp_ms,
758 eligible_active_validators: Some(e.eligible_active_validators),
759 scores: None,
760 }
761 }
762}
763
764impl From<ChangeEpochV4> for IotaChangeEpochV2 {
765 fn from(e: ChangeEpochV4) -> Self {
766 Self {
767 epoch: e.epoch,
768 storage_charge: e.storage_charge,
769 computation_charge: e.computation_charge,
770 computation_charge_burned: e.computation_charge_burned,
771 storage_rebate: e.storage_rebate,
772 epoch_start_timestamp_ms: e.epoch_start_timestamp_ms,
773 eligible_active_validators: Some(e.eligible_active_validators),
774 scores: Some(e.scores),
775 }
776 }
777}
778
779#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
780#[enum_dispatch(IotaTransactionBlockEffectsAPI)]
781#[serde(
782 rename = "TransactionBlockEffects",
783 rename_all = "camelCase",
784 tag = "messageVersion"
785)]
786pub enum IotaTransactionBlockEffects {
787 V1(IotaTransactionBlockEffectsV1),
788}
789
790#[enum_dispatch]
791pub trait IotaTransactionBlockEffectsAPI {
792 fn status(&self) -> &IotaExecutionStatus;
793 fn into_status(self) -> IotaExecutionStatus;
794 fn shared_objects(&self) -> &[ObjectReference];
795 fn created(&self) -> &[OwnedObjectRef];
796 fn mutated(&self) -> &[OwnedObjectRef];
797 fn unwrapped(&self) -> &[OwnedObjectRef];
798 fn deleted(&self) -> &[ObjectReference];
799 fn unwrapped_then_deleted(&self) -> &[ObjectReference];
800 fn wrapped(&self) -> &[ObjectReference];
801 fn gas_object(&self) -> &OwnedObjectRef;
802 fn events_digest(&self) -> Option<&TransactionEventsDigest>;
803 fn dependencies(&self) -> &[TransactionDigest];
804 fn executed_epoch(&self) -> EpochId;
805 fn transaction_digest(&self) -> &TransactionDigest;
806 fn gas_cost_summary(&self) -> &GasCostSummary;
807
808 fn mutated_excluding_gas(&self) -> Vec<OwnedObjectRef>;
810 fn modified_at_versions(&self) -> Vec<(ObjectId, Version)>;
811 fn all_changed_objects(&self) -> Vec<(&OwnedObjectRef, WriteKind)>;
812 fn all_deleted_objects(&self) -> Vec<(&ObjectReference, DeleteKind)>;
813}
814
815#[serde_as]
816#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
817#[serde(
818 rename = "TransactionBlockEffectsModifiedAtVersions",
819 rename_all = "camelCase"
820)]
821pub struct IotaTransactionBlockEffectsModifiedAtVersions {
822 #[serde_as(as = "ObjectIdSchema")]
823 #[schemars(with = "ObjectIdSchema")]
824 object_id: ObjectId,
825 #[schemars(with = "SequenceNumberStringSchema")]
826 #[serde_as(as = "SequenceNumberStringSchema")]
827 sequence_number: Version,
828}
829
830#[serde_as]
832#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
833#[serde(rename = "TransactionBlockEffectsV1", rename_all = "camelCase")]
834pub struct IotaTransactionBlockEffectsV1 {
835 pub status: IotaExecutionStatus,
837 #[schemars(with = "String")]
839 #[serde_as(as = "DisplayFromStr")]
840 pub executed_epoch: EpochId,
841 #[schemars(with = "IotaGasCostSummary")]
842 #[serde_as(as = "IotaGasCostSummary")]
843 pub gas_used: GasCostSummary,
844 #[serde(default, skip_serializing_if = "Vec::is_empty")]
847 pub modified_at_versions: Vec<IotaTransactionBlockEffectsModifiedAtVersions>,
848 #[serde(default, skip_serializing_if = "Vec::is_empty")]
851 #[schemars(with = "Vec<ObjectRefSchema>")]
852 #[serde_as(as = "Vec<ObjectRefSchema>")]
853 pub shared_objects: Vec<ObjectReference>,
854 #[serde_as(as = "Base58Schema")]
856 #[schemars(with = "Base58Schema")]
857 pub transaction_digest: TransactionDigest,
858 #[serde(default, skip_serializing_if = "Vec::is_empty")]
860 pub created: Vec<OwnedObjectRef>,
861 #[serde(default, skip_serializing_if = "Vec::is_empty")]
863 pub mutated: Vec<OwnedObjectRef>,
864 #[serde(default, skip_serializing_if = "Vec::is_empty")]
868 pub unwrapped: Vec<OwnedObjectRef>,
869 #[serde(default, skip_serializing_if = "Vec::is_empty")]
871 #[schemars(with = "Vec<ObjectRefSchema>")]
872 #[serde_as(as = "Vec<ObjectRefSchema>")]
873 pub deleted: Vec<ObjectReference>,
874 #[serde(default, skip_serializing_if = "Vec::is_empty")]
877 #[schemars(with = "Vec<ObjectRefSchema>")]
878 #[serde_as(as = "Vec<ObjectRefSchema>")]
879 pub unwrapped_then_deleted: Vec<ObjectReference>,
880 #[serde(default, skip_serializing_if = "Vec::is_empty")]
882 #[schemars(with = "Vec<ObjectRefSchema>")]
883 #[serde_as(as = "Vec<ObjectRefSchema>")]
884 pub wrapped: Vec<ObjectReference>,
885 pub gas_object: OwnedObjectRef,
888 #[serde(skip_serializing_if = "Option::is_none")]
891 #[serde_as(as = "Option<Base58Schema>")]
892 #[schemars(with = "Option<Base58Schema>")]
893 pub events_digest: Option<TransactionEventsDigest>,
894 #[serde(default, skip_serializing_if = "Vec::is_empty")]
896 #[serde_as(as = "Vec<Base58Schema>")]
897 #[schemars(with = "Vec<Base58Schema>")]
898 pub dependencies: Vec<TransactionDigest>,
899}
900
901impl IotaTransactionBlockEffectsAPI for IotaTransactionBlockEffectsV1 {
902 fn status(&self) -> &IotaExecutionStatus {
903 &self.status
904 }
905 fn into_status(self) -> IotaExecutionStatus {
906 self.status
907 }
908 fn shared_objects(&self) -> &[ObjectReference] {
909 &self.shared_objects
910 }
911 fn created(&self) -> &[OwnedObjectRef] {
912 &self.created
913 }
914 fn mutated(&self) -> &[OwnedObjectRef] {
915 &self.mutated
916 }
917 fn unwrapped(&self) -> &[OwnedObjectRef] {
918 &self.unwrapped
919 }
920 fn deleted(&self) -> &[ObjectReference] {
921 &self.deleted
922 }
923 fn unwrapped_then_deleted(&self) -> &[ObjectReference] {
924 &self.unwrapped_then_deleted
925 }
926 fn wrapped(&self) -> &[ObjectReference] {
927 &self.wrapped
928 }
929 fn gas_object(&self) -> &OwnedObjectRef {
930 &self.gas_object
931 }
932 fn events_digest(&self) -> Option<&TransactionEventsDigest> {
933 self.events_digest.as_ref()
934 }
935 fn dependencies(&self) -> &[TransactionDigest] {
936 &self.dependencies
937 }
938
939 fn executed_epoch(&self) -> EpochId {
940 self.executed_epoch
941 }
942
943 fn transaction_digest(&self) -> &TransactionDigest {
944 &self.transaction_digest
945 }
946
947 fn gas_cost_summary(&self) -> &GasCostSummary {
948 &self.gas_used
949 }
950
951 fn mutated_excluding_gas(&self) -> Vec<OwnedObjectRef> {
952 self.mutated
953 .iter()
954 .filter(|o| *o != &self.gas_object)
955 .cloned()
956 .collect()
957 }
958
959 fn modified_at_versions(&self) -> Vec<(ObjectId, Version)> {
960 self.modified_at_versions
961 .iter()
962 .map(|v| (v.object_id, v.sequence_number))
963 .collect::<Vec<_>>()
964 }
965
966 fn all_changed_objects(&self) -> Vec<(&OwnedObjectRef, WriteKind)> {
967 self.mutated
968 .iter()
969 .map(|owner_ref| (owner_ref, WriteKind::Mutate))
970 .chain(
971 self.created
972 .iter()
973 .map(|owner_ref| (owner_ref, WriteKind::Create)),
974 )
975 .chain(
976 self.unwrapped
977 .iter()
978 .map(|owner_ref| (owner_ref, WriteKind::Unwrap)),
979 )
980 .collect()
981 }
982
983 fn all_deleted_objects(&self) -> Vec<(&ObjectReference, DeleteKind)> {
984 self.deleted
985 .iter()
986 .map(|r| (r, DeleteKind::Normal))
987 .chain(
988 self.unwrapped_then_deleted
989 .iter()
990 .map(|r| (r, DeleteKind::UnwrapThenDelete)),
991 )
992 .chain(self.wrapped.iter().map(|r| (r, DeleteKind::Wrap)))
993 .collect()
994 }
995}
996
997impl IotaTransactionBlockEffects {
998 pub fn new_for_testing(
999 transaction_digest: TransactionDigest,
1000 status: IotaExecutionStatus,
1001 ) -> Self {
1002 Self::V1(IotaTransactionBlockEffectsV1 {
1003 transaction_digest,
1004 status,
1005 gas_object: OwnedObjectRef {
1006 owner: Owner::Address(Address::random()),
1007 reference: iota_types::base_types::random_object_ref(),
1008 },
1009 executed_epoch: 0,
1010 modified_at_versions: vec![],
1011 gas_used: GasCostSummary::default(),
1012 shared_objects: vec![],
1013 created: vec![],
1014 mutated: vec![],
1015 unwrapped: vec![],
1016 deleted: vec![],
1017 unwrapped_then_deleted: vec![],
1018 wrapped: vec![],
1019 events_digest: None,
1020 dependencies: vec![],
1021 })
1022 }
1023
1024 pub async fn from_native_with_clever_error<S: PackageStore>(
1030 native: TransactionEffects,
1031 resolver: &Resolver<S>,
1032 ) -> Self {
1033 let clever_status =
1034 IotaExecutionStatus::from_native_with_clever_error(native.status().clone(), resolver)
1035 .await;
1036 match native {
1037 TransactionEffects::V1(inner) => {
1038 let mut inner = IotaTransactionBlockEffectsV1::from(TransactionEffects::V1(inner));
1039 inner.status = clever_status;
1040 inner.into()
1041 }
1042 _ => unimplemented!(
1043 "a new TransactionEffects enum variant was added and needs to be handled"
1044 ),
1045 }
1046 }
1047}
1048
1049impl TryFrom<TransactionEffects> for IotaTransactionBlockEffects {
1050 type Error = IotaError;
1051
1052 fn try_from(native: TransactionEffects) -> Result<Self, Self::Error> {
1053 Ok(IotaTransactionBlockEffects::V1(native.into()))
1054 }
1055}
1056
1057impl<T: TransactionEffectsAPI> From<T> for IotaTransactionBlockEffectsV1 {
1058 fn from(native: T) -> Self {
1059 Self {
1060 status: native.status().clone().into(),
1061 executed_epoch: native.epoch(),
1062 modified_at_versions: native
1063 .modified_at_versions()
1064 .into_iter()
1065 .map(|modified| IotaTransactionBlockEffectsModifiedAtVersions {
1066 object_id: *modified.object_id(),
1067 sequence_number: modified.version(),
1068 })
1069 .collect(),
1070 gas_used: native.gas_cost_summary().clone(),
1071 shared_objects: native
1072 .input_shared_objects()
1073 .into_iter()
1074 .map(|shared| shared.object_reference())
1075 .collect(),
1076 transaction_digest: *native.transaction_digest(),
1077 created: to_owned_ref(native.created()),
1078 mutated: to_owned_ref(native.mutated().to_vec()),
1079 unwrapped: to_owned_ref(native.unwrapped().to_vec()),
1080 deleted: native.deleted().to_vec(),
1081 unwrapped_then_deleted: native.unwrapped_then_deleted().to_vec(),
1082 wrapped: native.wrapped().to_vec(),
1083 gas_object: OwnedObjectRef {
1084 owner: *native.gas_object().owner(),
1085 reference: *native.gas_object().reference(),
1086 },
1087 events_digest: native.events_digest().copied(),
1088 dependencies: native.dependencies().to_vec(),
1089 }
1090 }
1091}
1092
1093fn owned_objref_string(obj: &OwnedObjectRef) -> String {
1094 format!(
1095 " ┌──\n │ ID: {} \n │ Owner: {} \n │ Version: {} \n │ Digest: {}\n └──",
1096 obj.reference.object_id, obj.owner, obj.reference.version, obj.reference.digest
1097 )
1098}
1099
1100fn objref_string(obj: &ObjectReference) -> String {
1101 format!(
1102 " ┌──\n │ ID: {} \n │ Version: {} \n │ Digest: {}\n └──",
1103 obj.object_id, obj.version, obj.digest
1104 )
1105}
1106
1107impl Display for IotaTransactionBlockEffects {
1108 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1109 let mut builder = TableBuilder::default();
1110
1111 builder.push_record(vec![format!("Digest: {}", self.transaction_digest())]);
1112 builder.push_record(vec![format!("Status: {:?}", self.status())]);
1113 builder.push_record(vec![format!("Executed Epoch: {}", self.executed_epoch())]);
1114
1115 if !self.created().is_empty() {
1116 builder.push_record(vec!["\nCreated Objects: ".to_string()]);
1117
1118 for oref in self.created() {
1119 builder.push_record(vec![owned_objref_string(oref)]);
1120 }
1121 }
1122
1123 if !self.mutated().is_empty() {
1124 builder.push_record(vec!["Mutated Objects: ".to_string()]);
1125 for oref in self.mutated() {
1126 builder.push_record(vec![owned_objref_string(oref)]);
1127 }
1128 }
1129
1130 if !self.shared_objects().is_empty() {
1131 builder.push_record(vec!["Shared Objects: ".to_string()]);
1132 for oref in self.shared_objects() {
1133 builder.push_record(vec![objref_string(oref)]);
1134 }
1135 }
1136
1137 if !self.deleted().is_empty() {
1138 builder.push_record(vec!["Deleted Objects: ".to_string()]);
1139
1140 for oref in self.deleted() {
1141 builder.push_record(vec![objref_string(oref)]);
1142 }
1143 }
1144
1145 if !self.wrapped().is_empty() {
1146 builder.push_record(vec!["Wrapped Objects: ".to_string()]);
1147
1148 for oref in self.wrapped() {
1149 builder.push_record(vec![objref_string(oref)]);
1150 }
1151 }
1152
1153 if !self.unwrapped().is_empty() {
1154 builder.push_record(vec!["Unwrapped Objects: ".to_string()]);
1155 for oref in self.unwrapped() {
1156 builder.push_record(vec![owned_objref_string(oref)]);
1157 }
1158 }
1159
1160 builder.push_record(vec![format!(
1161 "Gas Object: \n{}",
1162 owned_objref_string(self.gas_object())
1163 )]);
1164
1165 let gas_cost_summary = self.gas_cost_summary();
1166 builder.push_record(vec![format!(
1167 "Gas Cost Summary:\n \
1168 Storage Cost: {} NANOS\n \
1169 Computation Cost: {} NANOS\n \
1170 Computation Cost Burned: {} NANOS\n \
1171 Storage Rebate: {} NANOS\n \
1172 Non-refundable Storage Fee: {} NANOS",
1173 gas_cost_summary.storage_cost,
1174 gas_cost_summary.computation_cost,
1175 gas_cost_summary.computation_cost_burned,
1176 gas_cost_summary.storage_rebate,
1177 gas_cost_summary.non_refundable_storage_fee,
1178 )]);
1179
1180 let dependencies = self.dependencies();
1181 if !dependencies.is_empty() {
1182 builder.push_record(vec!["\nTransaction Dependencies:".to_string()]);
1183 for dependency in dependencies {
1184 builder.push_record(vec![format!(" {dependency}")]);
1185 }
1186 }
1187
1188 let mut table = builder.build();
1189 table.with(TablePanel::header("Transaction Effects"));
1190 table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
1191 1,
1192 TableStyle::modern().get_horizontal(),
1193 )]));
1194 write!(f, "{table}")
1195 }
1196}
1197
1198#[serde_as]
1199#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
1200#[serde(rename_all = "camelCase")]
1201pub struct DryRunTransactionBlockResponse {
1202 pub effects: IotaTransactionBlockEffects,
1203 pub events: IotaTransactionBlockEvents,
1204 pub object_changes: Vec<ObjectChange>,
1205 pub balance_changes: Vec<BalanceChange>,
1206 pub input: IotaTransactionBlockData,
1207 #[serde(default, skip_serializing_if = "Option::is_none")]
1209 #[schemars(with = "Option<String>")]
1210 #[serde_as(as = "Option<DisplayFromStr>")]
1211 pub suggested_gas_price: Option<u64>,
1212 pub execution_error_source: Option<String>,
1213}
1214
1215#[derive(Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
1216#[serde(rename = "TransactionBlockEvents", transparent)]
1217pub struct IotaTransactionBlockEvents {
1218 pub data: Vec<IotaEvent>,
1219}
1220
1221impl IotaTransactionBlockEvents {
1222 pub fn try_from(
1223 mut events: TransactionEvents,
1224 tx_digest: TransactionDigest,
1225 timestamp_ms: Option<u64>,
1226 resolver: &mut dyn LayoutResolver,
1227 ) -> IotaResult<Self> {
1228 Ok(Self {
1229 data: events
1230 .drain(..)
1231 .enumerate()
1232 .map(|(seq, event)| {
1233 let layout = resolver.get_annotated_layout(&event.struct_tag)?;
1234 IotaEvent::try_from(event, tx_digest, seq as u64, timestamp_ms, layout)
1235 })
1236 .collect::<Result<_, _>>()?,
1237 })
1238 }
1239
1240 pub fn try_from_using_module_resolver(
1243 mut events: TransactionEvents,
1244 tx_digest: TransactionDigest,
1245 timestamp_ms: Option<u64>,
1246 resolver: &impl GetModule,
1247 ) -> IotaResult<Self> {
1248 Ok(Self {
1249 data: events
1250 .drain(..)
1251 .enumerate()
1252 .map(|(seq, event)| {
1253 let layout = get_layout_from_struct_tag(event.struct_tag.clone(), resolver)?;
1254 IotaEvent::try_from(event, tx_digest, seq as u64, timestamp_ms, layout)
1255 })
1256 .collect::<Result<_, _>>()?,
1257 })
1258 }
1259}
1260
1261impl Display for IotaTransactionBlockEvents {
1262 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1263 if self.data.is_empty() {
1264 writeln!(f, "╭─────────────────────────────╮")?;
1265 writeln!(f, "│ No transaction block events │")?;
1266 writeln!(f, "╰─────────────────────────────╯")
1267 } else {
1268 let mut builder = TableBuilder::default();
1269
1270 for event in &self.data {
1271 builder.push_record(vec![format!("{event}")]);
1272 }
1273
1274 let mut table = builder.build();
1275 table.with(TablePanel::header("Transaction Block Events"));
1276 table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
1277 1,
1278 TableStyle::modern().get_horizontal(),
1279 )]));
1280 write!(f, "{table}")
1281 }
1282 }
1283}
1284
1285#[serde_as]
1289#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
1290#[serde(rename = "DevInspectArgs", rename_all = "camelCase")]
1291pub struct DevInspectArgs {
1292 #[serde_as(as = "Option<AddressSchema>")]
1295 #[schemars(with = "Option<AddressSchema>")]
1296 pub gas_sponsor: Option<Address>,
1297 #[schemars(with = "Option<String>")]
1299 #[serde_as(as = "Option<DisplayFromStr>")]
1300 pub gas_budget: Option<u64>,
1301 #[schemars(with = "Option<Vec<ObjectRefSchema>>")]
1303 #[serde_as(as = "Option<Vec<ObjectRefSchema>>")]
1304 pub gas_objects: Option<Vec<ObjectReference>>,
1305 pub skip_checks: Option<bool>,
1307 pub show_raw_txn_data_and_effects: Option<bool>,
1309}
1310
1311#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1313#[serde(rename = "DevInspectResults", rename_all = "camelCase")]
1314pub struct DevInspectResults {
1315 pub effects: IotaTransactionBlockEffects,
1320 pub events: IotaTransactionBlockEvents,
1323 #[serde(skip_serializing_if = "Option::is_none")]
1326 pub results: Option<Vec<IotaExecutionResult>>,
1327 #[serde(skip_serializing_if = "Option::is_none")]
1329 pub error: Option<String>,
1330 #[serde(skip_serializing_if = "Vec::is_empty", default)]
1332 pub raw_txn_data: Vec<u8>,
1333 #[serde(skip_serializing_if = "Vec::is_empty", default)]
1335 pub raw_effects: Vec<u8>,
1336}
1337
1338#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1339#[serde(rename = "IotaExecutionResult", rename_all = "camelCase")]
1340pub struct IotaExecutionResult {
1341 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1344 #[schemars(with = "Vec<(IotaArgument, Vec<u8>, TypeTagSchema)>")]
1345 pub mutable_reference_outputs: Vec<(IotaArgument, Vec<u8>, IotaTypeTag)>,
1346 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1348 #[schemars(with = "Vec<(Vec<u8>, TypeTagSchema)>")]
1349 pub return_values: Vec<(Vec<u8>, IotaTypeTag)>,
1350}
1351
1352impl IotaExecutionResult {
1353 fn into_stream_return_value_layouts<S: PackageStore>(
1354 self,
1355 package_resolver: &Resolver<S>,
1356 ) -> impl Stream<Item = anyhow::Result<(Vec<u8>, MoveTypeLayout)>> + use<'_, S> {
1357 self.return_values
1358 .into_iter()
1359 .map(|(bytes, iota_type_tag)| async {
1360 let type_tag = TypeTag::try_from(iota_type_tag)?;
1361 let move_type_layout = package_resolver.type_layout(type_tag).await?;
1362 Ok((bytes, move_type_layout))
1363 })
1364 .collect::<FuturesOrdered<_>>()
1365 }
1366}
1367
1368#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1369pub enum IotaMoveViewCallResults {
1370 #[serde(rename = "executionError")]
1372 Error(String),
1373 #[serde(rename = "functionReturnValues")]
1375 Results(Vec<IotaMoveValue>),
1376}
1377
1378impl IotaMoveViewCallResults {
1379 pub async fn from_dev_inspect_results<S: PackageStore>(
1382 package_store: S,
1383 dev_inspect_results: DevInspectResults,
1384 ) -> anyhow::Result<Self> {
1385 if let Some(error) = dev_inspect_results.error {
1386 return Ok(Self::Error(error));
1387 }
1388 let Some(mut tx_execution_results) = dev_inspect_results.results else {
1389 return Ok(Self::Error("function call returned no values".into()));
1390 };
1391 let Some(execution_results) = tx_execution_results.pop() else {
1392 return Ok(Self::Error(
1393 "no results from move view function call".into(),
1394 ));
1395 };
1396 if !tx_execution_results.is_empty() {
1397 return Ok(Self::Error("multiple transactions executed".into()));
1398 }
1399 let mut move_call_results = Vec::with_capacity(execution_results.return_values.len());
1400 let package_resolver = Resolver::new(package_store);
1401 let mut execution_results =
1402 execution_results.into_stream_return_value_layouts(&package_resolver);
1403 while let Some(result) = execution_results.next().await {
1404 let (bytes, move_type_layout) = result?;
1405 let move_value = BoundedVisitor::deserialize_value(&bytes, &move_type_layout)?;
1406 move_call_results.push(IotaMoveValue::from(move_value));
1407 }
1408 Ok(Self::Results(move_call_results))
1409 }
1410
1411 pub fn into_return_values(self) -> Vec<IotaMoveValue> {
1412 match self {
1413 IotaMoveViewCallResults::Error(_) => Default::default(),
1414 IotaMoveViewCallResults::Results(values) => values,
1415 }
1416 }
1417
1418 pub fn error(&self) -> Option<&str> {
1419 match self {
1420 IotaMoveViewCallResults::Error(e) => Some(e.as_str()),
1421 IotaMoveViewCallResults::Results(_) => None,
1422 }
1423 }
1424}
1425
1426type ExecutionResult = (
1427 Vec<(Argument, Vec<u8>, TypeTag)>,
1429 Vec<(Vec<u8>, TypeTag)>,
1431);
1432
1433impl DevInspectResults {
1434 pub fn new(
1435 effects: TransactionEffects,
1436 events: TransactionEvents,
1437 return_values: Result<Vec<ExecutionResult>, ExecutionError>,
1438 raw_txn_data: Vec<u8>,
1439 raw_effects: Vec<u8>,
1440 resolver: &mut dyn LayoutResolver,
1441 ) -> IotaResult<Self> {
1442 let tx_digest = *effects.transaction_digest();
1443 let mut error = None;
1444 let mut results = None;
1445 match return_values {
1446 Err(e) => error = Some(e.to_string()),
1447 Ok(srvs) => {
1448 results = Some(
1449 srvs.into_iter()
1450 .map(|srv| {
1451 let (mutable_reference_outputs, return_values) = srv;
1452 let mutable_reference_outputs = mutable_reference_outputs
1453 .into_iter()
1454 .map(|(a, bytes, tag)| (a.into(), bytes, IotaTypeTag::from(tag)))
1455 .collect();
1456 let return_values = return_values
1457 .into_iter()
1458 .map(|(bytes, tag)| (bytes, IotaTypeTag::from(tag)))
1459 .collect();
1460 IotaExecutionResult {
1461 mutable_reference_outputs,
1462 return_values,
1463 }
1464 })
1465 .collect(),
1466 )
1467 }
1468 };
1469 Ok(Self {
1470 effects: effects.try_into()?,
1471 events: IotaTransactionBlockEvents::try_from(events, tx_digest, None, resolver)?,
1472 results,
1473 error,
1474 raw_txn_data,
1475 raw_effects,
1476 })
1477 }
1478}
1479
1480#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
1481pub enum IotaTransactionBlockBuilderMode {
1482 Commit,
1484 DevInspect,
1487}
1488
1489#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
1490#[serde(rename = "ExecutionStatus", rename_all = "camelCase", tag = "status")]
1491pub enum IotaExecutionStatus {
1492 Success,
1494 Failure { error: String },
1496}
1497
1498impl IotaExecutionStatus {
1499 pub async fn from_native_with_clever_error<S: PackageStore>(
1505 native: ExecutionStatus,
1506 resolver: &Resolver<S>,
1507 ) -> Self {
1508 match native {
1509 ExecutionStatus::Failure {
1510 error,
1511 command: Some(mut command_index),
1512 } => {
1513 let error = 'error: {
1514 let ExecutionFailureStatus::MoveAbort { location, code } = &error else {
1515 break 'error error.to_string();
1516 };
1517 let fname_string = if let Some(fname) = &location.function_name {
1518 format!("::{fname}'")
1519 } else {
1520 "'".to_string()
1521 };
1522
1523 let module_id = ModuleId::new(
1524 AccountAddress::from(location.package.into_bytes()),
1525 identifier_sdk_to_core(&location.module),
1526 );
1527
1528 let Some(CleverError {
1529 module_id,
1530 source_line_number,
1531 error_info,
1532 error_code,
1533 }) = resolver
1534 .resolve_clever_error(module_id.clone(), *code)
1535 .await
1536 else {
1537 break 'error format!(
1538 "from '{}{fname_string} (instruction {}), abort code: {code}",
1539 module_id.to_canonical_display(true),
1540 location.instruction,
1541 );
1542 };
1543
1544 let error_code_str = match error_code {
1545 Some(code) => format!("(code = {code})"),
1546 None => String::new(),
1547 };
1548
1549 match error_info {
1550 ErrorConstants::Rendered {
1551 identifier,
1552 constant,
1553 } => {
1554 format!(
1555 "from '{}{fname_string} (line {source_line_number}), abort{error_code_str} '{identifier}': {constant}",
1556 module_id.to_canonical_display(true)
1557 )
1558 }
1559 ErrorConstants::Raw { identifier, bytes } => {
1560 let const_str = Base64::encode(bytes);
1561 format!(
1562 "from '{}{fname_string} (line {source_line_number}), abort{error_code_str} '{identifier}': {const_str}",
1563 module_id.to_canonical_display(true)
1564 )
1565 }
1566 ErrorConstants::None => {
1567 format!(
1568 "from '{}{fname_string} (line {source_line_number}){}",
1569 module_id.to_canonical_display(true),
1570 match error_code {
1571 Some(code) => format!(" abort(code = {code})"),
1572 None => String::new(),
1573 }
1574 )
1575 }
1576 }
1577 };
1578 command_index += 1;
1580 let suffix = match command_index % 10 {
1581 1 if command_index % 100 != 11 => "st",
1582 2 if command_index % 100 != 12 => "nd",
1583 3 if command_index % 100 != 13 => "rd",
1584 _ => "th",
1585 };
1586 IotaExecutionStatus::Failure {
1587 error: format!("Error in {command_index}{suffix} command, {error}"),
1588 }
1589 }
1590 _ => native.into(),
1591 }
1592 }
1593}
1594
1595impl Display for IotaExecutionStatus {
1596 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1597 match self {
1598 Self::Success => write!(f, "success"),
1599 Self::Failure { error } => write!(f, "failure due to {error}"),
1600 }
1601 }
1602}
1603
1604impl IotaExecutionStatus {
1605 pub fn is_ok(&self) -> bool {
1606 matches!(self, IotaExecutionStatus::Success)
1607 }
1608 pub fn is_err(&self) -> bool {
1609 matches!(self, IotaExecutionStatus::Failure { .. })
1610 }
1611}
1612
1613impl From<ExecutionStatus> for IotaExecutionStatus {
1614 fn from(status: ExecutionStatus) -> Self {
1615 match status {
1616 ExecutionStatus::Success => Self::Success,
1617 ExecutionStatus::Failure {
1618 error,
1619 command: None,
1620 } => Self::Failure {
1621 error: error.to_string(),
1622 },
1623 ExecutionStatus::Failure {
1624 error,
1625 command: Some(idx),
1626 } => Self::Failure {
1627 error: format!("{error} in command {idx}"),
1628 },
1629 _ => unimplemented!(
1630 "a new ExecutionStatus enum variant was added and needs to be handled"
1631 ),
1632 }
1633 }
1634}
1635
1636fn to_owned_ref(owned_refs: Vec<OwnedObjectReference>) -> Vec<OwnedObjectRef> {
1637 owned_refs
1638 .into_iter()
1639 .map(|owned| OwnedObjectRef {
1640 owner: *owned.owner(),
1641 reference: *owned.reference(),
1642 })
1643 .collect()
1644}
1645
1646#[serde_as]
1647#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1648#[serde(rename = "GasData", rename_all = "camelCase")]
1649pub struct IotaGasData {
1650 #[schemars(with = "Vec<ObjectRefSchema>")]
1651 #[serde_as(as = "Vec<ObjectRefSchema>")]
1652 pub payment: Vec<ObjectReference>,
1653 #[serde_as(as = "AddressSchema")]
1654 #[schemars(with = "AddressSchema")]
1655 pub owner: Address,
1656 #[schemars(with = "String")]
1657 #[serde_as(as = "DisplayFromStr")]
1658 pub price: u64,
1659 #[schemars(with = "String")]
1660 #[serde_as(as = "DisplayFromStr")]
1661 pub budget: u64,
1662}
1663
1664impl Display for IotaGasData {
1665 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1666 writeln!(f, "Gas Owner: {}", self.owner)?;
1667 writeln!(f, "Gas Budget: {} NANOS", self.budget)?;
1668 writeln!(f, "Gas Price: {} NANOS", self.price)?;
1669 writeln!(f, "Gas Payment:")?;
1670 for payment in &self.payment {
1671 write!(f, "{} ", objref_string(payment))?;
1672 }
1673 writeln!(f)
1674 }
1675}
1676
1677#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1678#[enum_dispatch(IotaTransactionBlockDataAPI)]
1679#[serde(
1680 rename = "TransactionBlockData",
1681 rename_all = "camelCase",
1682 tag = "messageVersion"
1683)]
1684pub enum IotaTransactionBlockData {
1685 V1(IotaTransactionBlockDataV1),
1686}
1687
1688#[enum_dispatch]
1689pub trait IotaTransactionBlockDataAPI {
1690 fn transaction(&self) -> &IotaTransactionBlockKind;
1691 fn sender(&self) -> &Address;
1692 fn gas_data(&self) -> &IotaGasData;
1693}
1694
1695#[serde_as]
1696#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1697#[serde(rename = "TransactionBlockDataV1", rename_all = "camelCase")]
1698pub struct IotaTransactionBlockDataV1 {
1699 pub transaction: IotaTransactionBlockKind,
1700 #[serde_as(as = "AddressSchema")]
1701 #[schemars(with = "AddressSchema")]
1702 pub sender: Address,
1703 pub gas_data: IotaGasData,
1704}
1705
1706impl IotaTransactionBlockDataAPI for IotaTransactionBlockDataV1 {
1707 fn transaction(&self) -> &IotaTransactionBlockKind {
1708 &self.transaction
1709 }
1710 fn sender(&self) -> &Address {
1711 &self.sender
1712 }
1713 fn gas_data(&self) -> &IotaGasData {
1714 &self.gas_data
1715 }
1716}
1717
1718impl IotaTransactionBlockData {
1719 pub fn move_calls(&self) -> Vec<&IotaProgrammableMoveCall> {
1720 match self {
1721 Self::V1(data) => match &data.transaction {
1722 IotaTransactionBlockKind::ProgrammableTransaction(pt) => pt
1723 .commands
1724 .iter()
1725 .filter_map(|command| match command {
1726 IotaCommand::MoveCall(c) => Some(&**c),
1727 _ => None,
1728 })
1729 .collect(),
1730 _ => vec![],
1731 },
1732 }
1733 }
1734
1735 fn try_from_inner(
1736 tx: Transaction,
1737 transaction: IotaTransactionBlockKind,
1738 ) -> Result<Self, anyhow::Error> {
1739 let message_version = tx.message_version();
1740 let sender = tx.sender();
1741 let gas_data = IotaGasData {
1742 payment: tx.gas().to_vec(),
1743 owner: tx.gas_owner(),
1744 price: tx.gas_price(),
1745 budget: tx.gas_budget(),
1746 };
1747
1748 match message_version {
1749 1 => Ok(IotaTransactionBlockData::V1(IotaTransactionBlockDataV1 {
1750 transaction,
1751 sender,
1752 gas_data,
1753 })),
1754 _ => Err(anyhow::anyhow!(
1755 "Support for Transaction version {message_version} not implemented"
1756 )),
1757 }
1758 }
1759
1760 pub fn try_from_with_module_cache(
1761 tx: Transaction,
1762 module_cache: &impl GetModule,
1763 tx_digest: TransactionDigest,
1764 ) -> Result<Self, anyhow::Error> {
1765 let transaction = IotaTransactionBlockKind::try_from_with_module_cache(
1766 tx.kind().clone(),
1767 module_cache,
1768 tx_digest,
1769 )?;
1770 Self::try_from_inner(tx, transaction)
1771 }
1772
1773 pub async fn try_from_with_package_resolver(
1774 tx: Transaction,
1775 package_resolver: &Resolver<impl PackageStore>,
1776 tx_digest: TransactionDigest,
1777 ) -> Result<Self, anyhow::Error> {
1778 let transaction = IotaTransactionBlockKind::try_from_with_package_resolver(
1779 tx.kind().clone(),
1780 package_resolver,
1781 tx_digest,
1782 )
1783 .await?;
1784 Self::try_from_inner(tx, transaction)
1785 }
1786}
1787
1788impl Display for IotaTransactionBlockData {
1789 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1790 match self {
1791 Self::V1(data) => {
1792 writeln!(f, "Sender: {}", data.sender)?;
1793 writeln!(f, "{}", self.gas_data())?;
1794 writeln!(f, "{}", data.transaction)
1795 }
1796 }
1797 }
1798}
1799
1800#[serde_as]
1801#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1802#[serde(rename = "TransactionBlock", rename_all = "camelCase")]
1803pub struct IotaTransactionBlock {
1804 pub data: IotaTransactionBlockData,
1805 #[serde_as(as = "Vec<UserSignatureSchema>")]
1806 #[schemars(with = "Vec<UserSignatureSchema>")]
1807 pub tx_signatures: Vec<UserSignature>,
1808}
1809
1810impl IotaTransactionBlock {
1811 pub fn try_from(
1812 tx: SenderSignedTransaction,
1813 module_cache: &impl GetModule,
1814 tx_digest: TransactionDigest,
1815 ) -> Result<Self, anyhow::Error> {
1816 Ok(Self {
1817 data: IotaTransactionBlockData::try_from_with_module_cache(
1818 tx.transaction().clone(),
1819 module_cache,
1820 tx_digest,
1821 )?,
1822 tx_signatures: tx.signatures().to_vec(),
1823 })
1824 }
1825
1826 pub async fn try_from_with_package_resolver(
1830 tx: SenderSignedTransaction,
1831 package_resolver: &Resolver<impl PackageStore>,
1832 tx_digest: TransactionDigest,
1833 ) -> Result<Self, anyhow::Error> {
1834 Ok(Self {
1835 data: IotaTransactionBlockData::try_from_with_package_resolver(
1836 tx.transaction().clone(),
1837 package_resolver,
1838 tx_digest,
1839 )
1840 .await?,
1841 tx_signatures: tx.signatures().to_vec(),
1842 })
1843 }
1844}
1845
1846impl Display for IotaTransactionBlock {
1847 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1848 let mut builder = TableBuilder::default();
1849
1850 builder.push_record(vec![format!("{}", self.data)]);
1851 builder.push_record(vec!["Signatures:".to_string()]);
1852 for tx_sig in &self.tx_signatures {
1853 builder.push_record(vec![format!(
1854 " {}\n",
1855 match tx_sig {
1856 UserSignature::Simple(sig) =>
1857 Base64::from_bytes(sig.signature_bytes()).encoded(),
1858 _ => Base64::from_bytes(&tx_sig.to_bytes()).encoded(),
1862 }
1863 )]);
1864 }
1865
1866 let mut table = builder.build();
1867 table.with(TablePanel::header("Transaction Data"));
1868 table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
1869 1,
1870 TableStyle::modern().get_horizontal(),
1871 )]));
1872 write!(f, "{table}")
1873 }
1874}
1875
1876#[serde_as]
1877#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1878pub struct IotaGenesisTransaction {
1879 #[serde_as(as = "Vec<ObjectIdSchema>")]
1880 #[schemars(with = "Vec<ObjectIdSchema>")]
1881 pub objects: Vec<ObjectId>,
1882 #[schemars(with = "Vec<IotaEventID>")]
1883 pub events: Vec<EventID>,
1884}
1885
1886#[serde_as]
1887#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1888pub struct IotaConsensusCommitPrologueV1 {
1889 #[schemars(with = "String")]
1890 #[serde_as(as = "DisplayFromStr")]
1891 pub epoch: u64,
1892 #[schemars(with = "String")]
1893 #[serde_as(as = "DisplayFromStr")]
1894 pub round: u64,
1895 #[schemars(with = "Option<String>")]
1896 #[serde_as(as = "Option<DisplayFromStr>")]
1897 pub sub_dag_index: Option<u64>,
1898 #[schemars(with = "String")]
1899 #[serde_as(as = "DisplayFromStr")]
1900 pub commit_timestamp_ms: u64,
1901 #[serde_as(as = "Base58Schema")]
1902 #[schemars(with = "Base58Schema")]
1903 pub consensus_commit_digest: ConsensusCommitDigest,
1904 pub consensus_determined_version_assignments: IotaConsensusDeterminedVersionAssignments,
1905}
1906
1907#[serde_as]
1910#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, JsonSchema)]
1911#[schemars(rename = "ConsensusDeterminedVersionAssignments")]
1912pub enum IotaConsensusDeterminedVersionAssignments {
1913 CancelledTransactions(
1915 #[serde_as(as = "Vec<(Base58Schema, Vec<(ObjectIdSchema, serde_with::Same)>)>")]
1916 #[schemars(with = "Vec<(Base58Schema, Vec<(ObjectIdSchema, SequenceNumberU64)>)>")]
1917 Vec<(TransactionDigest, Vec<(ObjectId, SequenceNumberU64)>)>,
1918 ),
1919}
1920
1921impl From<ConsensusDeterminedVersionAssignments> for IotaConsensusDeterminedVersionAssignments {
1922 fn from(
1923 consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
1924 ) -> Self {
1925 match consensus_determined_version_assignments {
1926 ConsensusDeterminedVersionAssignments::CanceledTransactions {
1927 canceled_transactions,
1928 } => IotaConsensusDeterminedVersionAssignments::CancelledTransactions(
1929 canceled_transactions
1930 .into_iter()
1931 .map(|cancelled| {
1932 (
1933 cancelled.digest,
1934 cancelled
1935 .version_assignments
1936 .into_iter()
1937 .map(|va| (va.object_id, va.version.into()))
1938 .collect(),
1939 )
1940 })
1941 .collect(),
1942 ),
1943 _ => unimplemented!(
1944 "a new ConsensusDeterminedVersionAssignments enum variant was added and needs to be handled"
1945 ),
1946 }
1947 }
1948}
1949
1950impl From<IotaConsensusDeterminedVersionAssignments> for ConsensusDeterminedVersionAssignments {
1951 fn from(
1952 iota_consensus_determined_version_assignments: IotaConsensusDeterminedVersionAssignments,
1953 ) -> Self {
1954 match iota_consensus_determined_version_assignments {
1955 IotaConsensusDeterminedVersionAssignments::CancelledTransactions(assignments) => {
1956 ConsensusDeterminedVersionAssignments::CanceledTransactions {
1957 canceled_transactions: assignments
1958 .into_iter()
1959 .map(|(digest, version_assignments)| CanceledTransaction {
1960 digest,
1961 version_assignments: version_assignments
1962 .into_iter()
1963 .map(|(object_id, version)| VersionAssignment {
1964 object_id,
1965 version: version.into(),
1966 })
1967 .collect(),
1968 })
1969 .collect(),
1970 }
1971 }
1972 }
1973 }
1974}
1975
1976#[serde_as]
1977#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1978pub struct IotaRandomnessStateUpdate {
1979 #[schemars(with = "String")]
1980 #[serde_as(as = "DisplayFromStr")]
1981 pub epoch: u64,
1982
1983 #[schemars(with = "String")]
1984 #[serde_as(as = "DisplayFromStr")]
1985 pub randomness_round: u64,
1986 pub random_bytes: Vec<u8>,
1987}
1988
1989#[serde_as]
1993#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1994pub struct IotaTransactionDenyRulesUpdate {
1995 #[schemars(with = "String")]
1996 #[serde_as(as = "DisplayFromStr")]
1997 pub epoch: u64,
1998
1999 #[schemars(with = "String")]
2000 #[serde_as(as = "DisplayFromStr")]
2001 pub round: u64,
2002 #[schemars(with = "Vec<AddressSchema>")]
2003 #[serde_as(as = "Vec<AddressSchema>")]
2004 pub added_addresses: Vec<Address>,
2005 #[schemars(with = "Vec<AddressSchema>")]
2006 #[serde_as(as = "Vec<AddressSchema>")]
2007 pub removed_addresses: Vec<Address>,
2008 #[schemars(with = "Vec<ObjectIdSchema>")]
2009 #[serde_as(as = "Vec<ObjectIdSchema>")]
2010 pub added_objects: Vec<ObjectId>,
2011 #[schemars(with = "Vec<ObjectIdSchema>")]
2012 #[serde_as(as = "Vec<ObjectIdSchema>")]
2013 pub removed_objects: Vec<ObjectId>,
2014 #[schemars(with = "Vec<ObjectIdSchema>")]
2015 #[serde_as(as = "Vec<ObjectIdSchema>")]
2016 pub added_packages: Vec<ObjectId>,
2017 #[schemars(with = "Vec<ObjectIdSchema>")]
2018 #[serde_as(as = "Vec<ObjectIdSchema>")]
2019 pub removed_packages: Vec<ObjectId>,
2020 pub package_publish_disabled: bool,
2021 pub package_upgrade_disabled: bool,
2022 pub shared_object_disabled: bool,
2023 pub user_transaction_disabled: bool,
2024 pub receiving_objects_disabled: bool,
2025 pub move_authenticator_disabled: bool,
2026}
2027
2028#[serde_as]
2029#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2030pub struct IotaEndOfEpochTransaction {
2031 pub transactions: Vec<IotaEndOfEpochTransactionKind>,
2032}
2033
2034#[serde_as]
2035#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2036pub enum IotaEndOfEpochTransactionKind {
2037 ChangeEpoch(IotaChangeEpoch),
2038 ChangeEpochV2(IotaChangeEpochV2),
2039 TransactionDenyRulesCreate,
2040}
2041
2042#[serde_as]
2043#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]
2044#[serde(rename = "InputObjectKind")]
2045pub enum IotaInputObjectKind {
2046 MovePackage(
2048 #[serde_as(as = "ObjectIdSchema")]
2049 #[schemars(with = "ObjectIdSchema")]
2050 ObjectId,
2051 ),
2052 ImmOrOwnedMoveObject(
2054 #[schemars(with = "ObjectRefSchema")]
2055 #[serde_as(as = "ObjectRefSchema")]
2056 ObjectReference,
2057 ),
2058 SharedMoveObject {
2060 #[serde_as(as = "ObjectIdSchema")]
2061 #[schemars(with = "ObjectIdSchema")]
2062 id: ObjectId,
2063 #[schemars(with = "SequenceNumberStringSchema")]
2064 #[serde_as(as = "SequenceNumberStringSchema")]
2065 initial_shared_version: Version,
2066 #[serde(default = "default_shared_object_mutability")]
2067 mutable: bool,
2068 },
2069}
2070
2071#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2074pub struct IotaProgrammableTransactionBlock {
2075 pub inputs: Vec<IotaCallArg>,
2077 #[serde(rename = "transactions")]
2078 pub commands: Vec<IotaCommand>,
2082}
2083
2084impl Display for IotaProgrammableTransactionBlock {
2085 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2086 let Self { inputs, commands } = self;
2087 writeln!(f, "Inputs: {inputs:?}")?;
2088 writeln!(f, "Commands: [")?;
2089 for c in commands {
2090 writeln!(f, " {c},")?;
2091 }
2092 writeln!(f, "]")
2093 }
2094}
2095
2096impl IotaProgrammableTransactionBlock {
2097 fn try_from_with_module_cache(
2098 value: ProgrammableTransaction,
2099 module_cache: &impl GetModule,
2100 ) -> Result<Self, anyhow::Error> {
2101 let ProgrammableTransaction { inputs, commands } = value;
2102 let input_types = Self::resolve_input_type(&inputs, &commands, module_cache);
2103 Ok(IotaProgrammableTransactionBlock {
2104 inputs: inputs
2105 .into_iter()
2106 .zip(input_types)
2107 .map(|(arg, layout)| IotaCallArg::try_from(arg, layout.as_ref()))
2108 .collect::<Result<_, _>>()?,
2109 commands: commands.into_iter().map(IotaCommand::from).collect(),
2110 })
2111 }
2112
2113 async fn try_from_with_package_resolver(
2114 value: ProgrammableTransaction,
2115 package_resolver: &Resolver<impl PackageStore>,
2116 ) -> Result<Self, anyhow::Error> {
2117 let input_types = package_resolver
2120 .pure_input_layouts(&value)
2121 .await
2122 .unwrap_or_else(|e| {
2123 tracing::warn!("pure_input_layouts failed: {:?}", e);
2124 vec![None; value.inputs.len()]
2125 });
2126
2127 let ProgrammableTransaction { inputs, commands } = value;
2128 Ok(IotaProgrammableTransactionBlock {
2129 inputs: inputs
2130 .into_iter()
2131 .zip(input_types)
2132 .map(|(arg, layout)| IotaCallArg::try_from(arg, layout.as_ref()))
2133 .collect::<Result<_, _>>()?,
2134 commands: commands.into_iter().map(IotaCommand::from).collect(),
2135 })
2136 }
2137
2138 fn resolve_input_type(
2139 inputs: &[CallArg],
2140 commands: &[Command],
2141 module_cache: &impl GetModule,
2142 ) -> Vec<Option<MoveTypeLayout>> {
2143 let mut result_types = vec![None; inputs.len()];
2144 for command in commands.iter() {
2145 match command {
2146 Command::MoveCall(cmd) => {
2147 let module = identifier_sdk_to_core(&cmd.module);
2148 let id = ModuleId::new(AccountAddress::new(cmd.package.into_bytes()), module);
2149 let Some(types) = get_signature_types(id, &cmd.function, module_cache) else {
2150 return result_types;
2151 };
2152 for (arg, type_) in cmd.arguments.iter().zip(types) {
2153 if let (&Argument::Input(i), Some(type_)) = (arg, type_) {
2154 if let Some(x) = result_types.get_mut(i as usize) {
2155 x.replace(type_);
2156 }
2157 }
2158 }
2159 }
2160 Command::SplitCoins(cmd) => {
2161 for arg in &cmd.amounts {
2162 if let &Argument::Input(i) = arg {
2163 if let Some(x) = result_types.get_mut(i as usize) {
2164 x.replace(MoveTypeLayout::U64);
2165 }
2166 }
2167 }
2168 }
2169 Command::TransferObjects(TransferObjects {
2170 address: Argument::Input(i),
2171 ..
2172 }) => {
2173 if let Some(x) = result_types.get_mut((*i) as usize) {
2174 x.replace(MoveTypeLayout::Address);
2175 }
2176 }
2177 _ => {}
2178 }
2179 }
2180 result_types
2181 }
2182}
2183
2184fn get_signature_types(
2185 id: ModuleId,
2186 function: &Identifier,
2187 module_cache: &impl GetModule,
2188) -> Option<Vec<Option<MoveTypeLayout>>> {
2189 use std::borrow::Borrow;
2190 if let Ok(Some(module)) = module_cache.get_module_by_id(&id) {
2191 let module: &CompiledModule = module.borrow();
2192 let func = module
2193 .function_handles
2194 .iter()
2195 .find(|f| module.identifier_at(f.name).as_str() == function.as_str())?;
2196 Some(
2197 module
2198 .signature_at(func.parameters)
2199 .0
2200 .iter()
2201 .map(|s| primitive_type(module, &[], s))
2202 .collect(),
2203 )
2204 } else {
2205 None
2206 }
2207}
2208
2209#[serde_as]
2211#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2212#[serde(rename = "IotaTransaction")]
2213pub enum IotaCommand {
2214 MoveCall(Box<IotaProgrammableMoveCall>),
2216 TransferObjects(Vec<IotaArgument>, IotaArgument),
2221 SplitCoins(IotaArgument, Vec<IotaArgument>),
2224 MergeCoins(IotaArgument, Vec<IotaArgument>),
2227 Publish(
2230 #[serde_as(as = "Vec<ObjectIdSchema>")]
2231 #[schemars(with = "Vec<ObjectIdSchema>")]
2232 Vec<ObjectId>,
2233 ),
2234 Upgrade(
2236 #[serde_as(as = "Vec<ObjectIdSchema>")]
2237 #[schemars(with = "Vec<ObjectIdSchema>")]
2238 Vec<ObjectId>,
2239 #[serde_as(as = "ObjectIdSchema")]
2240 #[schemars(with = "ObjectIdSchema")]
2241 ObjectId,
2242 IotaArgument,
2243 ),
2244 MakeMoveVec(Option<String>, Vec<IotaArgument>),
2248}
2249
2250impl Display for IotaCommand {
2251 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2252 match self {
2253 Self::MoveCall(p) => {
2254 write!(f, "MoveCall({p})")
2255 }
2256 Self::MakeMoveVec(ty_opt, elems) => {
2257 write!(f, "MakeMoveVec(")?;
2258 if let Some(ty) = ty_opt {
2259 write!(f, "Some{ty}")?;
2260 } else {
2261 write!(f, "None")?;
2262 }
2263 write!(f, ",[")?;
2264 write_sep(f, elems, None, ",")?;
2265 write!(f, "])")
2266 }
2267 Self::TransferObjects(objs, addr) => {
2268 write!(f, "TransferObjects([")?;
2269 write_sep(f, objs, None, ",")?;
2270 write!(f, "],{addr})")
2271 }
2272 Self::SplitCoins(coin, amounts) => {
2273 write!(f, "SplitCoins({coin},")?;
2274 write_sep(f, amounts, None, ",")?;
2275 write!(f, ")")
2276 }
2277 Self::MergeCoins(target, coins) => {
2278 write!(f, "MergeCoins({target},")?;
2279 write_sep(f, coins, None, ",")?;
2280 write!(f, ")")
2281 }
2282 Self::Publish(deps) => {
2283 write!(f, "Publish(<modules>,")?;
2284 write_sep(f, deps, None, ",")?;
2285 write!(f, ")")
2286 }
2287 Self::Upgrade(deps, current_package_id, ticket) => {
2288 write!(f, "Upgrade(<modules>, {ticket},")?;
2289 write_sep(f, deps, None, ",")?;
2290 write!(f, ", {current_package_id}")?;
2291 write!(f, ")")
2292 }
2293 }
2294 }
2295}
2296
2297impl From<Command> for IotaCommand {
2298 fn from(value: Command) -> Self {
2299 match value {
2300 Command::MoveCall(cmd) => IotaCommand::MoveCall(Box::new((cmd).into())),
2301 Command::TransferObjects(cmd) => IotaCommand::TransferObjects(
2302 cmd.objects.into_iter().map(IotaArgument::from).collect(),
2303 cmd.address.into(),
2304 ),
2305 Command::SplitCoins(cmd) => IotaCommand::SplitCoins(
2306 cmd.coin.into(),
2307 cmd.amounts.into_iter().map(IotaArgument::from).collect(),
2308 ),
2309 Command::MergeCoins(cmd) => IotaCommand::MergeCoins(
2310 cmd.coin.into(),
2311 cmd.coins_to_merge
2312 .into_iter()
2313 .map(IotaArgument::from)
2314 .collect(),
2315 ),
2316 Command::Publish(cmd) => IotaCommand::Publish(cmd.dependencies),
2317 Command::MakeMoveVector(cmd) => IotaCommand::MakeMoveVec(
2318 cmd.type_tag.map(|tag| tag.to_string()),
2319 cmd.elements.into_iter().map(IotaArgument::from).collect(),
2320 ),
2321 Command::Upgrade(cmd) => IotaCommand::Upgrade(
2322 cmd.dependencies,
2323 cmd.package,
2324 IotaArgument::from(cmd.ticket),
2325 ),
2326 _ => unimplemented!("a new Command enum variant was added and needs to be handled"),
2327 }
2328 }
2329}
2330
2331#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2333pub enum IotaArgument {
2334 GasCoin,
2337 Input(u16),
2340 Result(u16),
2343 NestedResult(u16, u16),
2347}
2348
2349impl Display for IotaArgument {
2350 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2351 match self {
2352 Self::GasCoin => write!(f, "GasCoin"),
2353 Self::Input(i) => write!(f, "Input({i})"),
2354 Self::Result(i) => write!(f, "Result({i})"),
2355 Self::NestedResult(i, j) => write!(f, "NestedResult({i},{j})"),
2356 }
2357 }
2358}
2359
2360impl From<Argument> for IotaArgument {
2361 fn from(value: Argument) -> Self {
2362 match value {
2363 Argument::Gas => Self::GasCoin,
2364 Argument::Input(i) => Self::Input(i),
2365 Argument::Result(i) => Self::Result(i),
2366 Argument::NestedResult(i, j) => Self::NestedResult(i, j),
2367 _ => unimplemented!("a new Argument enum variant was added and needs to be handled"),
2368 }
2369 }
2370}
2371
2372#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2373#[serde(untagged)]
2374pub enum PtbInput {
2375 PtbRef(IotaArgument),
2376 CallArg(IotaJsonValue),
2377}
2378
2379#[serde_as]
2382#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2383pub struct IotaProgrammableMoveCall {
2384 #[serde_as(as = "ObjectIdSchema")]
2386 #[schemars(with = "ObjectIdSchema")]
2387 pub package: ObjectId,
2388 pub module: String,
2390 pub function: String,
2392 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2393 pub type_arguments: Vec<String>,
2395 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2396 pub arguments: Vec<IotaArgument>,
2398}
2399
2400pub(crate) fn write_sep<T: Display>(
2405 f: &mut Formatter<'_>,
2406 items: impl IntoIterator<Item = T>,
2407 delimiters: Option<(&str, &str)>,
2408 separator: &str,
2409) -> std::fmt::Result {
2410 let mut xs = items.into_iter();
2411 let Some(x) = xs.next() else {
2412 return Ok(());
2413 };
2414 if let Some((left, _)) = delimiters {
2415 write!(f, "{left}")?;
2416 }
2417 write!(f, "{x}")?;
2418 for x in xs {
2419 write!(f, "{separator}{x}")?;
2420 }
2421 if let Some((_, right)) = delimiters {
2422 write!(f, "{right}")?;
2423 }
2424 Ok(())
2425}
2426
2427impl Display for IotaProgrammableMoveCall {
2428 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2429 let Self {
2430 package,
2431 module,
2432 function,
2433 type_arguments,
2434 arguments,
2435 } = self;
2436 write!(f, "{package}::{module}::{function}")?;
2437 if !type_arguments.is_empty() {
2438 write!(f, "<")?;
2439 write_sep(f, type_arguments, None, ",")?;
2440 write!(f, ">")?;
2441 }
2442 write!(f, "(")?;
2443 write_sep(f, arguments, None, ",")?;
2444 write!(f, ")")
2445 }
2446}
2447
2448impl From<MoveCall> for IotaProgrammableMoveCall {
2449 fn from(value: MoveCall) -> Self {
2450 let MoveCall {
2451 package,
2452 module,
2453 function,
2454 type_arguments,
2455 arguments,
2456 } = value;
2457 Self {
2458 package,
2459 module: module.to_string(),
2460 function: function.to_string(),
2461 type_arguments: type_arguments.into_iter().map(|t| t.to_string()).collect(),
2462 arguments: arguments.into_iter().map(IotaArgument::from).collect(),
2463 }
2464 }
2465}
2466
2467const fn default_shared_object_mutability() -> bool {
2468 true
2469}
2470
2471impl From<InputObjectKind> for IotaInputObjectKind {
2472 fn from(input: InputObjectKind) -> Self {
2473 match input {
2474 InputObjectKind::MovePackage(id) => Self::MovePackage(id),
2475 InputObjectKind::ImmOrOwnedMoveObject(oref) => Self::ImmOrOwnedMoveObject(oref),
2476 InputObjectKind::SharedMoveObject {
2477 id,
2478 initial_shared_version,
2479 mutable,
2480 } => Self::SharedMoveObject {
2481 id,
2482 initial_shared_version,
2483 mutable,
2484 },
2485 }
2486 }
2487}
2488
2489#[derive(Debug, Serialize, Deserialize, Clone)]
2490#[serde(rename = "TypeTag", rename_all = "camelCase")]
2491pub struct IotaTypeTag(String);
2492
2493impl IotaTypeTag {
2494 pub fn new(tag: String) -> Self {
2495 Self(tag)
2496 }
2497}
2498
2499impl AsRef<str> for IotaTypeTag {
2500 fn as_ref(&self) -> &str {
2501 &self.0
2502 }
2503}
2504
2505impl TryFrom<IotaTypeTag> for TypeTag {
2506 type Error = anyhow::Error;
2507 fn try_from(tag: IotaTypeTag) -> Result<Self, Self::Error> {
2508 parse_iota_type_tag(&tag.0)
2509 }
2510}
2511
2512impl From<TypeTag> for IotaTypeTag {
2513 fn from(tag: TypeTag) -> Self {
2514 Self(format!("{tag}"))
2515 }
2516}
2517
2518#[derive(Serialize, Deserialize, JsonSchema)]
2519#[serde(rename_all = "camelCase")]
2520pub enum RPCTransactionRequestParams {
2521 TransferObjectRequestParams(TransferObjectParams),
2522 MoveCallRequestParams(MoveCallParams),
2523}
2524
2525#[serde_as]
2526#[derive(Serialize, Deserialize, JsonSchema)]
2527#[serde(rename_all = "camelCase")]
2528pub struct TransferObjectParams {
2529 #[serde_as(as = "AddressSchema")]
2530 #[schemars(with = "AddressSchema")]
2531 pub recipient: Address,
2532 #[serde_as(as = "ObjectIdSchema")]
2533 #[schemars(with = "ObjectIdSchema")]
2534 pub object_id: ObjectId,
2535}
2536
2537#[serde_as]
2538#[derive(Serialize, Deserialize, JsonSchema)]
2539#[serde(rename_all = "camelCase")]
2540pub struct MoveCallParams {
2541 #[serde_as(as = "ObjectIdSchema")]
2542 #[schemars(with = "ObjectIdSchema")]
2543 pub package_object_id: ObjectId,
2544 pub module: String,
2545 pub function: String,
2546 #[serde(default)]
2547 #[schemars(with = "Vec<TypeTagSchema>")]
2548 pub type_arguments: Vec<IotaTypeTag>,
2549 pub arguments: Vec<PtbInput>,
2550}
2551
2552#[serde_as]
2553#[derive(Serialize, Deserialize, Clone, JsonSchema)]
2554#[serde(rename_all = "camelCase")]
2555pub struct TransactionBlockBytes {
2556 #[schemars(with = "Base64Schema")]
2559 pub tx_bytes: Base64,
2560 #[schemars(with = "Vec<ObjectRefSchema>")]
2562 #[serde_as(as = "Vec<ObjectRefSchema>")]
2563 pub gas: Vec<ObjectReference>,
2564 pub input_objects: Vec<IotaInputObjectKind>,
2566}
2567
2568impl TransactionBlockBytes {
2569 pub fn from_data(tx: Transaction) -> Result<Self, anyhow::Error> {
2570 Ok(Self {
2571 tx_bytes: Base64::from_bytes(&tx.to_bcs()),
2572 gas: tx.gas().to_vec(),
2573 input_objects: tx
2574 .input_objects()?
2575 .into_iter()
2576 .map(IotaInputObjectKind::from)
2577 .collect(),
2578 })
2579 }
2580
2581 pub fn to_data(self) -> Result<Transaction, anyhow::Error> {
2582 bcs::from_bytes::<Transaction>(&self.tx_bytes.to_vec().map_err(|e| anyhow::anyhow!(e))?)
2583 .map_err(|e| anyhow::anyhow!(e))
2584 }
2585}
2586
2587#[serde_as]
2588#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
2589#[serde(rename = "OwnedObjectRef")]
2590pub struct OwnedObjectRef {
2591 #[schemars(with = "OwnerSchema")]
2592 #[serde_as(as = "OwnerSchema")]
2593 pub owner: Owner,
2594 #[schemars(with = "ObjectRefSchema")]
2595 #[serde_as(as = "ObjectRefSchema")]
2596 pub reference: ObjectReference,
2597}
2598
2599impl OwnedObjectRef {
2600 pub fn object_id(&self) -> ObjectId {
2601 self.reference.object_id
2602 }
2603 pub fn version(&self) -> Version {
2604 self.reference.version
2605 }
2606}
2607
2608#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2609#[serde(tag = "type", rename_all = "camelCase")]
2610pub enum IotaCallArg {
2611 Object(IotaObjectArg),
2613 Pure(IotaPureValue),
2615}
2616
2617impl IotaCallArg {
2618 pub fn try_from(
2619 value: CallArg,
2620 layout: Option<&MoveTypeLayout>,
2621 ) -> Result<Self, anyhow::Error> {
2622 Ok(match value {
2623 CallArg::Pure(p) => IotaCallArg::Pure(IotaPureValue {
2624 value_type: layout.map(|l| type_tag_core_to_sdk(&l.into())),
2625 value: IotaJsonValue::from_bcs_bytes(layout, &p)?,
2626 }),
2627 CallArg::ImmutableOrOwned(object_ref) => {
2628 IotaCallArg::Object(IotaObjectArg::ImmOrOwnedObject {
2629 object_id: object_ref.object_id,
2630 version: object_ref.version,
2631 digest: object_ref.digest,
2632 })
2633 }
2634 CallArg::Shared(SharedObjectReference {
2635 object_id: id,
2636 initial_shared_version,
2637 mutable,
2638 }) => IotaCallArg::Object(IotaObjectArg::SharedObject {
2639 object_id: id,
2640 initial_shared_version,
2641 mutable,
2642 }),
2643 CallArg::Receiving(object_ref) => IotaCallArg::Object(IotaObjectArg::Receiving {
2644 object_id: object_ref.object_id,
2645 version: object_ref.version,
2646 digest: object_ref.digest,
2647 }),
2648 _ => unimplemented!("a new CallArg enum variant was added and needs to be handled"),
2649 })
2650 }
2651
2652 pub fn pure(&self) -> Option<&IotaJsonValue> {
2653 match self {
2654 IotaCallArg::Pure(v) => Some(&v.value),
2655 _ => None,
2656 }
2657 }
2658
2659 pub fn object(&self) -> Option<&ObjectId> {
2660 match self {
2661 IotaCallArg::Object(IotaObjectArg::SharedObject { object_id, .. })
2662 | IotaCallArg::Object(IotaObjectArg::ImmOrOwnedObject { object_id, .. })
2663 | IotaCallArg::Object(IotaObjectArg::Receiving { object_id, .. }) => Some(object_id),
2664 _ => None,
2665 }
2666 }
2667}
2668
2669#[serde_as]
2670#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2671#[serde(rename_all = "camelCase")]
2672pub struct IotaPureValue {
2673 #[schemars(with = "Option<TypeTagSchema>")]
2674 #[serde_as(as = "Option<TypeTagSchema>")]
2675 value_type: Option<TypeTag>,
2676 value: IotaJsonValue,
2677}
2678
2679impl IotaPureValue {
2680 pub fn value(&self) -> IotaJsonValue {
2681 self.value.clone()
2682 }
2683
2684 pub fn value_type(&self) -> Option<TypeTag> {
2685 self.value_type.clone()
2686 }
2687}
2688
2689#[serde_as]
2690#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2691#[serde(tag = "objectType", rename_all = "camelCase")]
2692pub enum IotaObjectArg {
2693 #[serde(rename_all = "camelCase")]
2695 ImmOrOwnedObject {
2696 #[serde_as(as = "ObjectIdSchema")]
2697 #[schemars(with = "ObjectIdSchema")]
2698 object_id: ObjectId,
2699 #[schemars(with = "SequenceNumberStringSchema")]
2700 #[serde_as(as = "SequenceNumberStringSchema")]
2701 version: Version,
2702 #[serde_as(as = "Base58Schema")]
2703 #[schemars(with = "Base58Schema")]
2704 digest: ObjectDigest,
2705 },
2706 #[serde(rename_all = "camelCase")]
2710 SharedObject {
2711 #[serde_as(as = "ObjectIdSchema")]
2712 #[schemars(with = "ObjectIdSchema")]
2713 object_id: ObjectId,
2714 #[schemars(with = "SequenceNumberStringSchema")]
2715 #[serde_as(as = "SequenceNumberStringSchema")]
2716 initial_shared_version: Version,
2717 mutable: bool,
2718 },
2719 #[serde(rename_all = "camelCase")]
2721 Receiving {
2722 #[serde_as(as = "ObjectIdSchema")]
2723 #[schemars(with = "ObjectIdSchema")]
2724 object_id: ObjectId,
2725 #[schemars(with = "SequenceNumberStringSchema")]
2726 #[serde_as(as = "SequenceNumberStringSchema")]
2727 version: Version,
2728 #[serde_as(as = "Base58Schema")]
2729 #[schemars(with = "Base58Schema")]
2730 digest: ObjectDigest,
2731 },
2732}
2733
2734#[derive(Clone)]
2735pub struct EffectsWithInput {
2736 pub effects: IotaTransactionBlockEffects,
2737 pub input: Transaction,
2738}
2739
2740impl From<EffectsWithInput> for IotaTransactionBlockEffects {
2741 fn from(e: EffectsWithInput) -> Self {
2742 e.effects
2743 }
2744}
2745
2746#[serde_as]
2747#[derive(Clone, Debug, JsonSchema, Serialize, Deserialize)]
2748pub enum TransactionFilter {
2749 Checkpoint(
2751 #[schemars(with = "String")]
2752 #[serde_as(as = "DisplayFromStr")]
2753 CheckpointSequenceNumber,
2754 ),
2755 MoveFunction {
2757 #[serde_as(as = "ObjectIdSchema")]
2758 #[schemars(with = "ObjectIdSchema")]
2759 package: ObjectId,
2760 module: Option<String>,
2761 function: Option<String>,
2762 },
2763 InputObject(
2765 #[serde_as(as = "ObjectIdSchema")]
2766 #[schemars(with = "ObjectIdSchema")]
2767 ObjectId,
2768 ),
2769 ChangedObject(
2772 #[serde_as(as = "ObjectIdSchema")]
2773 #[schemars(with = "ObjectIdSchema")]
2774 ObjectId,
2775 ),
2776 FromAddress(
2778 #[serde_as(as = "AddressSchema")]
2779 #[schemars(with = "AddressSchema")]
2780 Address,
2781 ),
2782 ToAddress(
2784 #[serde_as(as = "AddressSchema")]
2785 #[schemars(with = "AddressSchema")]
2786 Address,
2787 ),
2788 FromAndToAddress {
2790 #[serde_as(as = "AddressSchema")]
2791 #[schemars(with = "AddressSchema")]
2792 from: Address,
2793 #[serde_as(as = "AddressSchema")]
2794 #[schemars(with = "AddressSchema")]
2795 to: Address,
2796 },
2797 FromOrToAddress {
2799 #[serde_as(as = "AddressSchema")]
2800 #[schemars(with = "AddressSchema")]
2801 addr: Address,
2802 },
2803 TransactionKind(IotaTransactionKind),
2805 TransactionKindIn(Vec<IotaTransactionKind>),
2807}
2808
2809impl TransactionFilter {
2810 pub fn as_v2(&self) -> TransactionFilterV2 {
2811 match self {
2812 TransactionFilter::InputObject(o) => TransactionFilterV2::InputObject(*o),
2813 TransactionFilter::ChangedObject(o) => TransactionFilterV2::ChangedObject(*o),
2814 TransactionFilter::FromAddress(a) => TransactionFilterV2::FromAddress(*a),
2815 TransactionFilter::ToAddress(a) => TransactionFilterV2::ToAddress(*a),
2816 TransactionFilter::FromAndToAddress { from, to } => {
2817 TransactionFilterV2::FromAndToAddress {
2818 from: *from,
2819 to: *to,
2820 }
2821 }
2822 TransactionFilter::FromOrToAddress { addr } => {
2823 TransactionFilterV2::FromOrToAddress { addr: *addr }
2824 }
2825 TransactionFilter::MoveFunction {
2826 package,
2827 module,
2828 function,
2829 } => TransactionFilterV2::MoveFunction {
2830 package: *package,
2831 module: module.clone(),
2832 function: function.clone(),
2833 },
2834 TransactionFilter::TransactionKind(kind) => TransactionFilterV2::TransactionKind(*kind),
2835 TransactionFilter::TransactionKindIn(kinds) => {
2836 TransactionFilterV2::TransactionKindIn(kinds.clone())
2837 }
2838 TransactionFilter::Checkpoint(checkpoint) => {
2839 TransactionFilterV2::Checkpoint(*checkpoint)
2840 }
2841 }
2842 }
2843}
2844
2845impl Filter<EffectsWithInput> for TransactionFilter {
2846 fn matches(&self, item: &EffectsWithInput) -> bool {
2847 match self {
2848 TransactionFilter::InputObject(o) => {
2849 let Ok(input_objects) = item.input.input_objects() else {
2850 return false;
2851 };
2852 input_objects.iter().any(|object| object.object_id() == *o)
2853 }
2854 TransactionFilter::ChangedObject(o) => item
2855 .effects
2856 .mutated()
2857 .iter()
2858 .any(|oref: &OwnedObjectRef| &oref.reference.object_id == o),
2859 TransactionFilter::FromAddress(a) => &item.input.sender() == a,
2860 TransactionFilter::ToAddress(a) => {
2861 let mutated: &[OwnedObjectRef] = item.effects.mutated();
2862 mutated.iter().chain(item.effects.unwrapped().iter()).any(|oref: &OwnedObjectRef| {
2863 matches!(oref.owner, Owner::Address(owner) if owner == *a)
2864 })
2865 }
2866 TransactionFilter::FromAndToAddress { from, to } => {
2867 Self::FromAddress(*from).matches(item) && Self::ToAddress(*to).matches(item)
2868 }
2869 TransactionFilter::FromOrToAddress { addr } => {
2870 Self::FromAddress(*addr).matches(item) || Self::ToAddress(*addr).matches(item)
2871 }
2872 TransactionFilter::MoveFunction {
2873 package,
2874 module,
2875 function,
2876 } => item.input.move_calls().into_iter().any(|(p, m, f)| {
2877 p == package
2878 && (module.is_none() || matches!(module, Some(m2) if m2 == &m.to_string()))
2879 && (function.is_none() || matches!(function, Some(f2) if f2 == &f.to_string()))
2880 }),
2881 TransactionFilter::TransactionKind(kind) => {
2882 kind == &IotaTransactionKind::from(item.input.kind())
2883 }
2884 TransactionFilter::TransactionKindIn(kinds) => kinds
2885 .iter()
2886 .any(|kind| kind == &IotaTransactionKind::from(item.input.kind())),
2887 TransactionFilter::Checkpoint(_) => false,
2889 }
2890 }
2891}
2892
2893#[serde_as]
2894#[derive(Clone, Debug, JsonSchema, Serialize, Deserialize)]
2895#[non_exhaustive]
2896pub enum TransactionFilterV2 {
2897 Checkpoint(
2899 #[schemars(with = "String")]
2900 #[serde_as(as = "DisplayFromStr")]
2901 CheckpointSequenceNumber,
2902 ),
2903 MoveFunction {
2905 #[serde_as(as = "ObjectIdSchema")]
2906 #[schemars(with = "ObjectIdSchema")]
2907 package: ObjectId,
2908 module: Option<String>,
2909 function: Option<String>,
2910 },
2911 InputObject(
2913 #[serde_as(as = "ObjectIdSchema")]
2914 #[schemars(with = "ObjectIdSchema")]
2915 ObjectId,
2916 ),
2917 ChangedObject(
2920 #[serde_as(as = "ObjectIdSchema")]
2921 #[schemars(with = "ObjectIdSchema")]
2922 ObjectId,
2923 ),
2924 WrappedOrDeletedObject(
2928 #[serde_as(as = "ObjectIdSchema")]
2929 #[schemars(with = "ObjectIdSchema")]
2930 ObjectId,
2931 ),
2932 FromAddress(
2934 #[serde_as(as = "AddressSchema")]
2935 #[schemars(with = "AddressSchema")]
2936 Address,
2937 ),
2938 ToAddress(
2940 #[serde_as(as = "AddressSchema")]
2941 #[schemars(with = "AddressSchema")]
2942 Address,
2943 ),
2944 FromAndToAddress {
2946 #[serde_as(as = "AddressSchema")]
2947 #[schemars(with = "AddressSchema")]
2948 from: Address,
2949 #[serde_as(as = "AddressSchema")]
2950 #[schemars(with = "AddressSchema")]
2951 to: Address,
2952 },
2953 FromOrToAddress {
2955 #[serde_as(as = "AddressSchema")]
2956 #[schemars(with = "AddressSchema")]
2957 addr: Address,
2958 },
2959 TransactionKind(IotaTransactionKind),
2961 TransactionKindIn(Vec<IotaTransactionKind>),
2963}
2964
2965impl TransactionFilterV2 {
2966 pub fn as_v1(&self) -> Option<TransactionFilter> {
2967 match self {
2968 TransactionFilterV2::InputObject(o) => Some(TransactionFilter::InputObject(*o)),
2969 TransactionFilterV2::ChangedObject(o) => Some(TransactionFilter::ChangedObject(*o)),
2970 TransactionFilterV2::FromAddress(a) => Some(TransactionFilter::FromAddress(*a)),
2971 TransactionFilterV2::ToAddress(a) => Some(TransactionFilter::ToAddress(*a)),
2972 TransactionFilterV2::FromAndToAddress { from, to } => {
2973 Some(TransactionFilter::FromAndToAddress {
2974 from: *from,
2975 to: *to,
2976 })
2977 }
2978 TransactionFilterV2::FromOrToAddress { addr } => {
2979 Some(TransactionFilter::FromOrToAddress { addr: *addr })
2980 }
2981 TransactionFilterV2::MoveFunction {
2982 package,
2983 module,
2984 function,
2985 } => Some(TransactionFilter::MoveFunction {
2986 package: *package,
2987 module: module.clone(),
2988 function: function.clone(),
2989 }),
2990 TransactionFilterV2::TransactionKind(kind) => {
2991 Some(TransactionFilter::TransactionKind(*kind))
2992 }
2993 TransactionFilterV2::TransactionKindIn(kinds) => {
2994 Some(TransactionFilter::TransactionKindIn(kinds.clone()))
2995 }
2996 TransactionFilterV2::Checkpoint(checkpoint) => {
2997 Some(TransactionFilter::Checkpoint(*checkpoint))
2998 }
2999 TransactionFilterV2::WrappedOrDeletedObject(_) => None,
3001 }
3002 }
3003}
3004
3005impl Filter<EffectsWithInput> for TransactionFilterV2 {
3006 fn matches(&self, item: &EffectsWithInput) -> bool {
3007 if let Some(v1) = self.as_v1() {
3008 return v1.matches(item);
3009 }
3010 match self {
3012 TransactionFilterV2::WrappedOrDeletedObject(o) => item
3013 .effects
3014 .wrapped()
3015 .iter()
3016 .chain(item.effects.deleted())
3017 .chain(item.effects.unwrapped_then_deleted())
3018 .any(|oref| &oref.object_id == o),
3019
3020 _ => false,
3021 }
3022 }
3023}
3024
3025#[derive(
3028 Debug, Clone, Copy, PartialEq, Eq, EnumString, Display, Serialize, Deserialize, JsonSchema,
3029)]
3030#[non_exhaustive]
3031pub enum IotaTransactionKind {
3032 SystemTransaction = 0,
3035 ProgrammableTransaction = 1,
3036 Genesis = 2,
3037 ConsensusCommitPrologueV1 = 3,
3038 RandomnessStateUpdate = 5,
3039 EndOfEpochTransaction = 6,
3040 TransactionDenyRulesUpdate = 7,
3041}
3042
3043impl IotaTransactionKind {
3044 pub fn is_system_transaction(&self) -> bool {
3046 !matches!(self, Self::ProgrammableTransaction)
3047 }
3048}
3049
3050impl From<&TransactionKind> for IotaTransactionKind {
3051 fn from(kind: &TransactionKind) -> Self {
3052 match kind {
3053 TransactionKind::Genesis(_) => Self::Genesis,
3054 TransactionKind::ConsensusCommitPrologueV1(_) => Self::ConsensusCommitPrologueV1,
3055 #[allow(deprecated)]
3056 TransactionKind::AuthenticatorStateUpdateV1Deprecated => Self::SystemTransaction,
3057 TransactionKind::RandomnessStateUpdate(_) => Self::RandomnessStateUpdate,
3058 TransactionKind::TransactionDenyRulesUpdate(_) => Self::TransactionDenyRulesUpdate,
3059 TransactionKind::EndOfEpoch(_) => Self::EndOfEpochTransaction,
3060 TransactionKind::Programmable(_) => Self::ProgrammableTransaction,
3061 _ => unimplemented!(
3062 "a new TransactionKind enum variant was added and needs to be handled"
3063 ),
3064 }
3065 }
3066}