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