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