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