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