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