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 EndOfEpochTransactionKind::AddressAliasStateCreate => {
624 SuiEndOfEpochTransactionKind::AddressAliasStateCreate
625 }
626 })
627 .collect(),
628 })
629 }
630 })
631 }
632
633 fn try_from_with_module_cache(
634 tx: TransactionKind,
635 module_cache: &impl GetModule,
636 ) -> Result<Self, anyhow::Error> {
637 match tx {
638 TransactionKind::ProgrammableTransaction(p)
639 | TransactionKind::ProgrammableSystemTransaction(p) => {
640 Ok(Self::ProgrammableTransaction(
641 SuiProgrammableTransactionBlock::try_from_with_module_cache(p, module_cache)?,
642 ))
643 }
644 tx => Self::try_from_inner(tx),
645 }
646 }
647
648 async fn try_from_with_package_resolver(
649 tx: TransactionKind,
650 package_resolver: &Resolver<impl PackageStore>,
651 ) -> Result<Self, anyhow::Error> {
652 match tx {
653 TransactionKind::ProgrammableSystemTransaction(p) => {
654 Ok(Self::ProgrammableSystemTransaction(
655 SuiProgrammableTransactionBlock::try_from_with_package_resolver(
656 p,
657 package_resolver,
658 )
659 .await?,
660 ))
661 }
662 TransactionKind::ProgrammableTransaction(p) => Ok(Self::ProgrammableTransaction(
663 SuiProgrammableTransactionBlock::try_from_with_package_resolver(
664 p,
665 package_resolver,
666 )
667 .await?,
668 )),
669 tx => Self::try_from_inner(tx),
670 }
671 }
672
673 pub fn transaction_count(&self) -> usize {
674 match self {
675 Self::ProgrammableTransaction(p) | Self::ProgrammableSystemTransaction(p) => {
676 p.commands.len()
677 }
678 _ => 1,
679 }
680 }
681
682 pub fn name(&self) -> &'static str {
683 match self {
684 Self::ChangeEpoch(_) => "ChangeEpoch",
685 Self::Genesis(_) => "Genesis",
686 Self::ConsensusCommitPrologue(_) => "ConsensusCommitPrologue",
687 Self::ConsensusCommitPrologueV2(_) => "ConsensusCommitPrologueV2",
688 Self::ConsensusCommitPrologueV3(_) => "ConsensusCommitPrologueV3",
689 Self::ConsensusCommitPrologueV4(_) => "ConsensusCommitPrologueV4",
690 Self::ProgrammableTransaction(_) => "ProgrammableTransaction",
691 Self::ProgrammableSystemTransaction(_) => "ProgrammableSystemTransaction",
692 Self::AuthenticatorStateUpdate(_) => "AuthenticatorStateUpdate",
693 Self::RandomnessStateUpdate(_) => "RandomnessStateUpdate",
694 Self::EndOfEpochTransaction(_) => "EndOfEpochTransaction",
695 }
696 }
697}
698
699#[serde_as]
700#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
701pub struct SuiChangeEpoch {
702 #[schemars(with = "BigInt<u64>")]
703 #[serde_as(as = "BigInt<u64>")]
704 pub epoch: EpochId,
705 #[schemars(with = "BigInt<u64>")]
706 #[serde_as(as = "BigInt<u64>")]
707 pub storage_charge: u64,
708 #[schemars(with = "BigInt<u64>")]
709 #[serde_as(as = "BigInt<u64>")]
710 pub computation_charge: u64,
711 #[schemars(with = "BigInt<u64>")]
712 #[serde_as(as = "BigInt<u64>")]
713 pub storage_rebate: u64,
714 #[schemars(with = "BigInt<u64>")]
715 #[serde_as(as = "BigInt<u64>")]
716 pub epoch_start_timestamp_ms: u64,
717}
718
719impl From<ChangeEpoch> for SuiChangeEpoch {
720 fn from(e: ChangeEpoch) -> Self {
721 Self {
722 epoch: e.epoch,
723 storage_charge: e.storage_charge,
724 computation_charge: e.computation_charge,
725 storage_rebate: e.storage_rebate,
726 epoch_start_timestamp_ms: e.epoch_start_timestamp_ms,
727 }
728 }
729}
730
731#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
732#[enum_dispatch(SuiTransactionBlockEffectsAPI)]
733#[serde(
734 rename = "TransactionBlockEffects",
735 rename_all = "camelCase",
736 tag = "messageVersion"
737)]
738pub enum SuiTransactionBlockEffects {
739 V1(SuiTransactionBlockEffectsV1),
740}
741
742#[enum_dispatch]
743pub trait SuiTransactionBlockEffectsAPI {
744 fn status(&self) -> &SuiExecutionStatus;
745 fn into_status(self) -> SuiExecutionStatus;
746 fn shared_objects(&self) -> &[SuiObjectRef];
747 fn created(&self) -> &[OwnedObjectRef];
748 fn mutated(&self) -> &[OwnedObjectRef];
749 fn unwrapped(&self) -> &[OwnedObjectRef];
750 fn deleted(&self) -> &[SuiObjectRef];
751 fn unwrapped_then_deleted(&self) -> &[SuiObjectRef];
752 fn wrapped(&self) -> &[SuiObjectRef];
753 fn gas_object(&self) -> &OwnedObjectRef;
754 fn events_digest(&self) -> Option<&TransactionEventsDigest>;
755 fn dependencies(&self) -> &[TransactionDigest];
756 fn executed_epoch(&self) -> EpochId;
757 fn transaction_digest(&self) -> &TransactionDigest;
758 fn gas_cost_summary(&self) -> &GasCostSummary;
759
760 fn mutated_excluding_gas(&self) -> Vec<OwnedObjectRef>;
762 fn modified_at_versions(&self) -> Vec<(ObjectID, SequenceNumber)>;
763 fn all_changed_objects(&self) -> Vec<(&OwnedObjectRef, WriteKind)>;
764 fn all_deleted_objects(&self) -> Vec<(&SuiObjectRef, DeleteKind)>;
765
766 fn accumulator_events(&self) -> Vec<SuiAccumulatorEvent>;
767}
768
769#[serde_as]
770#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
771#[serde(
772 rename = "TransactionBlockEffectsModifiedAtVersions",
773 rename_all = "camelCase"
774)]
775pub struct SuiTransactionBlockEffectsModifiedAtVersions {
776 object_id: ObjectID,
777 #[schemars(with = "AsSequenceNumber")]
778 #[serde_as(as = "AsSequenceNumber")]
779 sequence_number: SequenceNumber,
780}
781
782#[serde_as]
783#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
784#[serde(rename = "AccumulatorEvent", rename_all = "camelCase")]
785pub struct SuiAccumulatorEvent {
786 pub accumulator_obj: ObjectID,
787 pub address: SuiAddress,
788 pub ty: SuiTypeTag,
789 pub operation: SuiAccumulatorOperation,
790 pub value: SuiAccumulatorValue,
791}
792
793impl From<AccumulatorEvent> for SuiAccumulatorEvent {
794 fn from(event: AccumulatorEvent) -> Self {
795 let AccumulatorEvent {
796 accumulator_obj,
797 write,
798 } = event;
799 Self {
800 accumulator_obj: accumulator_obj.inner().to_owned(),
801 address: write.address.address,
802 ty: write.address.ty.into(),
803 operation: write.operation.into(),
804 value: write.value.into(),
805 }
806 }
807}
808
809#[serde_as]
810#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
811#[serde(rename = "AccumulatorOperation", rename_all = "camelCase")]
812pub enum SuiAccumulatorOperation {
813 Merge,
814 Split,
815}
816
817impl From<AccumulatorOperation> for SuiAccumulatorOperation {
818 fn from(operation: AccumulatorOperation) -> Self {
819 match operation {
820 AccumulatorOperation::Merge => Self::Merge,
821 AccumulatorOperation::Split => Self::Split,
822 }
823 }
824}
825
826#[serde_as]
827#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
828#[serde(rename = "AccumulatorValue", rename_all = "camelCase")]
829pub enum SuiAccumulatorValue {
830 Integer(u64),
831 IntegerTuple(u64, u64),
832 #[schemars(with = "Vec<(u64, Digest)>")]
833 EventDigest(NonEmpty<(u64 , Digest)>),
834}
835
836impl From<AccumulatorValue> for SuiAccumulatorValue {
837 fn from(value: AccumulatorValue) -> Self {
838 match value {
839 AccumulatorValue::Integer(value) => Self::Integer(value),
840 AccumulatorValue::IntegerTuple(value1, value2) => Self::IntegerTuple(value1, value2),
841 AccumulatorValue::EventDigest(digests) => Self::EventDigest(digests),
842 }
843 }
844}
845
846#[serde_as]
848#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
849#[serde(rename = "TransactionBlockEffectsV1", rename_all = "camelCase")]
850pub struct SuiTransactionBlockEffectsV1 {
851 pub status: SuiExecutionStatus,
853 #[schemars(with = "BigInt<u64>")]
855 #[serde_as(as = "BigInt<u64>")]
856 pub executed_epoch: EpochId,
857 pub gas_used: GasCostSummary,
858 #[serde(default, skip_serializing_if = "Vec::is_empty")]
861 pub modified_at_versions: Vec<SuiTransactionBlockEffectsModifiedAtVersions>,
862 #[serde(default, skip_serializing_if = "Vec::is_empty")]
864 pub shared_objects: Vec<SuiObjectRef>,
865 pub transaction_digest: TransactionDigest,
867 #[serde(default, skip_serializing_if = "Vec::is_empty")]
869 pub created: Vec<OwnedObjectRef>,
870 #[serde(default, skip_serializing_if = "Vec::is_empty")]
872 pub mutated: Vec<OwnedObjectRef>,
873 #[serde(default, skip_serializing_if = "Vec::is_empty")]
877 pub unwrapped: Vec<OwnedObjectRef>,
878 #[serde(default, skip_serializing_if = "Vec::is_empty")]
880 pub deleted: Vec<SuiObjectRef>,
881 #[serde(default, skip_serializing_if = "Vec::is_empty")]
883 pub unwrapped_then_deleted: Vec<SuiObjectRef>,
884 #[serde(default, skip_serializing_if = "Vec::is_empty")]
886 pub wrapped: Vec<SuiObjectRef>,
887 #[serde(default, skip_serializing_if = "Vec::is_empty")]
888 pub accumulator_events: Vec<SuiAccumulatorEvent>,
889 pub gas_object: OwnedObjectRef,
892 #[serde(skip_serializing_if = "Option::is_none")]
895 pub events_digest: Option<TransactionEventsDigest>,
896 #[serde(default, skip_serializing_if = "Vec::is_empty")]
898 pub dependencies: Vec<TransactionDigest>,
899 #[serde(default, skip_serializing_if = "Option::is_none")]
901 pub abort_error: Option<SuiMoveAbort>,
902}
903
904impl SuiTransactionBlockEffectsAPI for SuiTransactionBlockEffectsV1 {
907 fn status(&self) -> &SuiExecutionStatus {
908 &self.status
909 }
910 fn into_status(self) -> SuiExecutionStatus {
911 self.status
912 }
913 fn shared_objects(&self) -> &[SuiObjectRef] {
914 &self.shared_objects
915 }
916 fn created(&self) -> &[OwnedObjectRef] {
917 &self.created
918 }
919 fn mutated(&self) -> &[OwnedObjectRef] {
920 &self.mutated
921 }
922 fn unwrapped(&self) -> &[OwnedObjectRef] {
923 &self.unwrapped
924 }
925 fn deleted(&self) -> &[SuiObjectRef] {
926 &self.deleted
927 }
928 fn unwrapped_then_deleted(&self) -> &[SuiObjectRef] {
929 &self.unwrapped_then_deleted
930 }
931 fn wrapped(&self) -> &[SuiObjectRef] {
932 &self.wrapped
933 }
934 fn gas_object(&self) -> &OwnedObjectRef {
935 &self.gas_object
936 }
937 fn events_digest(&self) -> Option<&TransactionEventsDigest> {
938 self.events_digest.as_ref()
939 }
940 fn dependencies(&self) -> &[TransactionDigest] {
941 &self.dependencies
942 }
943
944 fn executed_epoch(&self) -> EpochId {
945 self.executed_epoch
946 }
947
948 fn transaction_digest(&self) -> &TransactionDigest {
949 &self.transaction_digest
950 }
951
952 fn gas_cost_summary(&self) -> &GasCostSummary {
953 &self.gas_used
954 }
955
956 fn mutated_excluding_gas(&self) -> Vec<OwnedObjectRef> {
957 self.mutated
958 .iter()
959 .filter(|o| *o != &self.gas_object)
960 .cloned()
961 .collect()
962 }
963
964 fn modified_at_versions(&self) -> Vec<(ObjectID, SequenceNumber)> {
965 self.modified_at_versions
966 .iter()
967 .map(|v| (v.object_id, v.sequence_number))
968 .collect::<Vec<_>>()
969 }
970
971 fn all_changed_objects(&self) -> Vec<(&OwnedObjectRef, WriteKind)> {
972 self.mutated
973 .iter()
974 .map(|owner_ref| (owner_ref, WriteKind::Mutate))
975 .chain(
976 self.created
977 .iter()
978 .map(|owner_ref| (owner_ref, WriteKind::Create)),
979 )
980 .chain(
981 self.unwrapped
982 .iter()
983 .map(|owner_ref| (owner_ref, WriteKind::Unwrap)),
984 )
985 .collect()
986 }
987
988 fn all_deleted_objects(&self) -> Vec<(&SuiObjectRef, DeleteKind)> {
989 self.deleted
990 .iter()
991 .map(|r| (r, DeleteKind::Normal))
992 .chain(
993 self.unwrapped_then_deleted
994 .iter()
995 .map(|r| (r, DeleteKind::UnwrapThenDelete)),
996 )
997 .chain(self.wrapped.iter().map(|r| (r, DeleteKind::Wrap)))
998 .collect()
999 }
1000
1001 fn accumulator_events(&self) -> Vec<SuiAccumulatorEvent> {
1002 self.accumulator_events.clone()
1003 }
1004}
1005
1006impl SuiTransactionBlockEffects {
1007 pub fn new_for_testing(
1008 transaction_digest: TransactionDigest,
1009 status: SuiExecutionStatus,
1010 ) -> Self {
1011 Self::V1(SuiTransactionBlockEffectsV1 {
1012 transaction_digest,
1013 status,
1014 gas_object: OwnedObjectRef {
1015 owner: Owner::AddressOwner(SuiAddress::random_for_testing_only()),
1016 reference: sui_types::base_types::random_object_ref().into(),
1017 },
1018 executed_epoch: 0,
1019 modified_at_versions: vec![],
1020 gas_used: GasCostSummary::default(),
1021 shared_objects: vec![],
1022 created: vec![],
1023 mutated: vec![],
1024 unwrapped: vec![],
1025 deleted: vec![],
1026 unwrapped_then_deleted: vec![],
1027 wrapped: vec![],
1028 events_digest: None,
1029 dependencies: vec![],
1030 abort_error: None,
1031 accumulator_events: vec![],
1032 })
1033 }
1034}
1035
1036impl TryFrom<TransactionEffects> for SuiTransactionBlockEffects {
1037 type Error = SuiError;
1038
1039 fn try_from(effect: TransactionEffects) -> Result<Self, Self::Error> {
1040 Ok(SuiTransactionBlockEffects::V1(
1041 SuiTransactionBlockEffectsV1 {
1042 status: effect.status().clone().into(),
1043 executed_epoch: effect.executed_epoch(),
1044 modified_at_versions: effect
1045 .modified_at_versions()
1046 .into_iter()
1047 .map(|(object_id, sequence_number)| {
1048 SuiTransactionBlockEffectsModifiedAtVersions {
1049 object_id,
1050 sequence_number,
1051 }
1052 })
1053 .collect(),
1054 gas_used: effect.gas_cost_summary().clone(),
1055 shared_objects: to_sui_object_ref(
1056 effect
1057 .input_consensus_objects()
1058 .into_iter()
1059 .map(|kind| {
1060 #[allow(deprecated)]
1061 kind.object_ref()
1062 })
1063 .collect(),
1064 ),
1065 transaction_digest: *effect.transaction_digest(),
1066 created: to_owned_ref(effect.created()),
1067 mutated: to_owned_ref(effect.mutated().to_vec()),
1068 unwrapped: to_owned_ref(effect.unwrapped().to_vec()),
1069 deleted: to_sui_object_ref(effect.deleted().to_vec()),
1070 unwrapped_then_deleted: to_sui_object_ref(effect.unwrapped_then_deleted().to_vec()),
1071 wrapped: to_sui_object_ref(effect.wrapped().to_vec()),
1072 gas_object: OwnedObjectRef {
1073 owner: effect.gas_object().1,
1074 reference: effect.gas_object().0.into(),
1075 },
1076 events_digest: effect.events_digest().copied(),
1077 dependencies: effect.dependencies().to_vec(),
1078 abort_error: effect
1079 .move_abort()
1080 .map(|(abort, code)| SuiMoveAbort::new(abort, code)),
1081 accumulator_events: effect
1082 .accumulator_events()
1083 .into_iter()
1084 .map(SuiAccumulatorEvent::from)
1085 .collect(),
1086 },
1087 ))
1088 }
1089}
1090
1091fn owned_objref_string(obj: &OwnedObjectRef) -> String {
1092 format!(
1093 " ┌──\n │ ID: {} \n │ Owner: {} \n │ Version: {} \n │ Digest: {}\n └──",
1094 obj.reference.object_id,
1095 obj.owner,
1096 u64::from(obj.reference.version),
1097 obj.reference.digest
1098 )
1099}
1100
1101fn objref_string(obj: &SuiObjectRef) -> String {
1102 format!(
1103 " ┌──\n │ ID: {} \n │ Version: {} \n │ Digest: {}\n └──",
1104 obj.object_id,
1105 u64::from(obj.version),
1106 obj.digest
1107 )
1108}
1109
1110impl Display for SuiTransactionBlockEffects {
1111 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1112 let mut builder = TableBuilder::default();
1113
1114 builder.push_record(vec![format!("Digest: {}", self.transaction_digest())]);
1115 builder.push_record(vec![format!("Status: {:?}", self.status())]);
1116 builder.push_record(vec![format!("Executed Epoch: {}", self.executed_epoch())]);
1117
1118 if !self.created().is_empty() {
1119 builder.push_record(vec![format!("\nCreated Objects: ")]);
1120
1121 for oref in self.created() {
1122 builder.push_record(vec![owned_objref_string(oref)]);
1123 }
1124 }
1125
1126 if !self.mutated().is_empty() {
1127 builder.push_record(vec![format!("Mutated Objects: ")]);
1128 for oref in self.mutated() {
1129 builder.push_record(vec![owned_objref_string(oref)]);
1130 }
1131 }
1132
1133 if !self.shared_objects().is_empty() {
1134 builder.push_record(vec![format!("Shared Objects: ")]);
1135 for oref in self.shared_objects() {
1136 builder.push_record(vec![objref_string(oref)]);
1137 }
1138 }
1139
1140 if !self.deleted().is_empty() {
1141 builder.push_record(vec![format!("Deleted Objects: ")]);
1142
1143 for oref in self.deleted() {
1144 builder.push_record(vec![objref_string(oref)]);
1145 }
1146 }
1147
1148 if !self.wrapped().is_empty() {
1149 builder.push_record(vec![format!("Wrapped Objects: ")]);
1150
1151 for oref in self.wrapped() {
1152 builder.push_record(vec![objref_string(oref)]);
1153 }
1154 }
1155
1156 if !self.unwrapped().is_empty() {
1157 builder.push_record(vec![format!("Unwrapped Objects: ")]);
1158 for oref in self.unwrapped() {
1159 builder.push_record(vec![owned_objref_string(oref)]);
1160 }
1161 }
1162
1163 builder.push_record(vec![format!(
1164 "Gas Object: \n{}",
1165 owned_objref_string(self.gas_object())
1166 )]);
1167
1168 let gas_cost_summary = self.gas_cost_summary();
1169 builder.push_record(vec![format!(
1170 "Gas Cost Summary:\n \
1171 Storage Cost: {} MIST\n \
1172 Computation Cost: {} MIST\n \
1173 Storage Rebate: {} MIST\n \
1174 Non-refundable Storage Fee: {} MIST",
1175 gas_cost_summary.storage_cost,
1176 gas_cost_summary.computation_cost,
1177 gas_cost_summary.storage_rebate,
1178 gas_cost_summary.non_refundable_storage_fee,
1179 )]);
1180
1181 let dependencies = self.dependencies();
1182 if !dependencies.is_empty() {
1183 builder.push_record(vec![format!("\nTransaction Dependencies:")]);
1184 for dependency in dependencies {
1185 builder.push_record(vec![format!(" {}", dependency)]);
1186 }
1187 }
1188
1189 let mut table = builder.build();
1190 table.with(TablePanel::header("Transaction Effects"));
1191 table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
1192 1,
1193 TableStyle::modern().get_horizontal(),
1194 )]));
1195 write!(f, "{}", table)
1196 }
1197}
1198
1199#[serde_as]
1200#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
1201#[serde(rename_all = "camelCase")]
1202pub struct DryRunTransactionBlockResponse {
1203 pub effects: SuiTransactionBlockEffects,
1204 pub events: SuiTransactionBlockEvents,
1205 pub object_changes: Vec<ObjectChange>,
1206 pub balance_changes: Vec<BalanceChange>,
1207 pub input: SuiTransactionBlockData,
1208 pub execution_error_source: Option<String>,
1209 #[serde(default, skip_serializing_if = "Option::is_none")]
1211 #[schemars(with = "Option<BigInt<u64>>")]
1212 #[serde_as(as = "Option<BigInt<u64>>")]
1213 pub suggested_gas_price: Option<u64>,
1214}
1215
1216#[derive(Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
1217#[serde(rename = "TransactionBlockEvents", transparent)]
1218pub struct SuiTransactionBlockEvents {
1219 pub data: Vec<SuiEvent>,
1220}
1221
1222impl SuiTransactionBlockEvents {
1223 pub fn try_from(
1224 events: TransactionEvents,
1225 tx_digest: TransactionDigest,
1226 timestamp_ms: Option<u64>,
1227 resolver: &mut dyn LayoutResolver,
1228 ) -> SuiResult<Self> {
1229 Ok(Self {
1230 data: events
1231 .data
1232 .into_iter()
1233 .enumerate()
1234 .map(|(seq, event)| {
1235 let layout = resolver.get_annotated_layout(&event.type_)?;
1236 SuiEvent::try_from(event, tx_digest, seq as u64, timestamp_ms, layout)
1237 })
1238 .collect::<Result<_, _>>()?,
1239 })
1240 }
1241
1242 pub fn try_from_using_module_resolver(
1244 events: TransactionEvents,
1245 tx_digest: TransactionDigest,
1246 timestamp_ms: Option<u64>,
1247 resolver: &impl GetModule,
1248 ) -> SuiResult<Self> {
1249 Ok(Self {
1250 data: events
1251 .data
1252 .into_iter()
1253 .enumerate()
1254 .map(|(seq, event)| {
1255 let layout = get_layout_from_struct_tag(event.type_.clone(), resolver)?;
1256 SuiEvent::try_from(event, tx_digest, seq as u64, timestamp_ms, layout)
1257 })
1258 .collect::<Result<_, _>>()?,
1259 })
1260 }
1261}
1262
1263impl Display for SuiTransactionBlockEvents {
1264 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1265 if self.data.is_empty() {
1266 writeln!(f, "╭─────────────────────────────╮")?;
1267 writeln!(f, "│ No transaction block events │")?;
1268 writeln!(f, "╰─────────────────────────────╯")
1269 } else {
1270 let mut builder = TableBuilder::default();
1271
1272 for event in &self.data {
1273 builder.push_record(vec![format!("{}", event)]);
1274 }
1275
1276 let mut table = builder.build();
1277 table.with(TablePanel::header("Transaction Block Events"));
1278 table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
1279 1,
1280 TableStyle::modern().get_horizontal(),
1281 )]));
1282 write!(f, "{}", table)
1283 }
1284 }
1285}
1286
1287#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
1290#[serde(rename = "DevInspectArgs", rename_all = "camelCase")]
1291pub struct DevInspectArgs {
1292 pub gas_sponsor: Option<SuiAddress>,
1294 pub gas_budget: Option<BigInt<u64>>,
1296 pub gas_objects: Option<Vec<ObjectRef>>,
1298 pub skip_checks: Option<bool>,
1300 pub show_raw_txn_data_and_effects: Option<bool>,
1302}
1303
1304#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1306#[serde(rename = "DevInspectResults", rename_all = "camelCase")]
1307pub struct DevInspectResults {
1308 pub effects: SuiTransactionBlockEffects,
1312 pub events: SuiTransactionBlockEvents,
1314 #[serde(skip_serializing_if = "Option::is_none")]
1316 pub results: Option<Vec<SuiExecutionResult>>,
1317 #[serde(skip_serializing_if = "Option::is_none")]
1319 pub error: Option<String>,
1320 #[serde(skip_serializing_if = "Vec::is_empty", default)]
1322 pub raw_txn_data: Vec<u8>,
1323 #[serde(skip_serializing_if = "Vec::is_empty", default)]
1325 pub raw_effects: Vec<u8>,
1326}
1327
1328#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1329#[serde(rename = "SuiExecutionResult", rename_all = "camelCase")]
1330pub struct SuiExecutionResult {
1331 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1334 pub mutable_reference_outputs: Vec<(SuiArgument, Vec<u8>, SuiTypeTag)>,
1335 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1337 pub return_values: Vec<(Vec<u8>, SuiTypeTag)>,
1338}
1339
1340type ExecutionResult = (
1341 Vec<(Argument, Vec<u8>, TypeTag)>,
1342 Vec<(Vec<u8>, TypeTag)>,
1343);
1344
1345impl DevInspectResults {
1346 pub fn new(
1347 effects: TransactionEffects,
1348 events: TransactionEvents,
1349 return_values: Result<Vec<ExecutionResult>, ExecutionError>,
1350 raw_txn_data: Vec<u8>,
1351 raw_effects: Vec<u8>,
1352 resolver: &mut dyn LayoutResolver,
1353 ) -> SuiResult<Self> {
1354 let tx_digest = *effects.transaction_digest();
1355 let mut error = None;
1356 let mut results = None;
1357 match return_values {
1358 Err(e) => error = Some(e.to_string()),
1359 Ok(srvs) => {
1360 results = Some(
1361 srvs.into_iter()
1362 .map(|srv| {
1363 let (mutable_reference_outputs, return_values) = srv;
1364 let mutable_reference_outputs = mutable_reference_outputs
1365 .into_iter()
1366 .map(|(a, bytes, tag)| (a.into(), bytes, SuiTypeTag::from(tag)))
1367 .collect();
1368 let return_values = return_values
1369 .into_iter()
1370 .map(|(bytes, tag)| (bytes, SuiTypeTag::from(tag)))
1371 .collect();
1372 SuiExecutionResult {
1373 mutable_reference_outputs,
1374 return_values,
1375 }
1376 })
1377 .collect(),
1378 )
1379 }
1380 };
1381 Ok(Self {
1382 effects: effects.try_into()?,
1383 events: SuiTransactionBlockEvents::try_from(events, tx_digest, None, resolver)?,
1384 results,
1385 error,
1386 raw_txn_data,
1387 raw_effects,
1388 })
1389 }
1390}
1391
1392#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
1393pub enum SuiTransactionBlockBuilderMode {
1394 Commit,
1396 DevInspect,
1399}
1400
1401#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
1402#[serde(rename = "ExecutionStatus", rename_all = "camelCase", tag = "status")]
1403pub enum SuiExecutionStatus {
1404 Success,
1406 Failure { error: String },
1408}
1409
1410impl Display for SuiExecutionStatus {
1411 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1412 match self {
1413 Self::Success => write!(f, "success"),
1414 Self::Failure { error } => write!(f, "failure due to {error}"),
1415 }
1416 }
1417}
1418
1419impl SuiExecutionStatus {
1420 pub fn is_ok(&self) -> bool {
1421 matches!(self, SuiExecutionStatus::Success)
1422 }
1423 pub fn is_err(&self) -> bool {
1424 matches!(self, SuiExecutionStatus::Failure { .. })
1425 }
1426}
1427
1428impl From<ExecutionStatus> for SuiExecutionStatus {
1429 fn from(status: ExecutionStatus) -> Self {
1430 match status {
1431 ExecutionStatus::Success => Self::Success,
1432 ExecutionStatus::Failure {
1433 error,
1434 command: None,
1435 } => Self::Failure {
1436 error: format!("{error:?}"),
1437 },
1438 ExecutionStatus::Failure {
1439 error,
1440 command: Some(idx),
1441 } => Self::Failure {
1442 error: format!("{error:?} in command {idx}"),
1443 },
1444 }
1445 }
1446}
1447
1448fn to_sui_object_ref(refs: Vec<ObjectRef>) -> Vec<SuiObjectRef> {
1449 refs.into_iter().map(SuiObjectRef::from).collect()
1450}
1451
1452fn to_owned_ref(owned_refs: Vec<(ObjectRef, Owner)>) -> Vec<OwnedObjectRef> {
1453 owned_refs
1454 .into_iter()
1455 .map(|(oref, owner)| OwnedObjectRef {
1456 owner,
1457 reference: oref.into(),
1458 })
1459 .collect()
1460}
1461
1462#[serde_as]
1463#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1464#[serde(rename = "GasData", rename_all = "camelCase")]
1465pub struct SuiGasData {
1466 pub payment: Vec<SuiObjectRef>,
1467 pub owner: SuiAddress,
1468 #[schemars(with = "BigInt<u64>")]
1469 #[serde_as(as = "BigInt<u64>")]
1470 pub price: u64,
1471 #[schemars(with = "BigInt<u64>")]
1472 #[serde_as(as = "BigInt<u64>")]
1473 pub budget: u64,
1474}
1475
1476impl Display for SuiGasData {
1477 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1478 writeln!(f, "Gas Owner: {}", self.owner)?;
1479 writeln!(f, "Gas Budget: {} MIST", self.budget)?;
1480 writeln!(f, "Gas Price: {} MIST", self.price)?;
1481 writeln!(f, "Gas Payment:")?;
1482 for payment in &self.payment {
1483 write!(f, "{} ", objref_string(payment))?;
1484 }
1485 writeln!(f)
1486 }
1487}
1488
1489#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1490#[enum_dispatch(SuiTransactionBlockDataAPI)]
1491#[serde(
1492 rename = "TransactionBlockData",
1493 rename_all = "camelCase",
1494 tag = "messageVersion"
1495)]
1496pub enum SuiTransactionBlockData {
1497 V1(SuiTransactionBlockDataV1),
1498}
1499
1500#[enum_dispatch]
1501pub trait SuiTransactionBlockDataAPI {
1502 fn transaction(&self) -> &SuiTransactionBlockKind;
1503 fn sender(&self) -> &SuiAddress;
1504 fn gas_data(&self) -> &SuiGasData;
1505}
1506
1507#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1508#[serde(rename = "TransactionBlockDataV1", rename_all = "camelCase")]
1509pub struct SuiTransactionBlockDataV1 {
1510 pub transaction: SuiTransactionBlockKind,
1511 pub sender: SuiAddress,
1512 pub gas_data: SuiGasData,
1513}
1514
1515impl SuiTransactionBlockDataAPI for SuiTransactionBlockDataV1 {
1516 fn transaction(&self) -> &SuiTransactionBlockKind {
1517 &self.transaction
1518 }
1519 fn sender(&self) -> &SuiAddress {
1520 &self.sender
1521 }
1522 fn gas_data(&self) -> &SuiGasData {
1523 &self.gas_data
1524 }
1525}
1526
1527impl SuiTransactionBlockData {
1528 pub fn move_calls(&self) -> Vec<&SuiProgrammableMoveCall> {
1529 match self {
1530 Self::V1(data) => match &data.transaction {
1531 SuiTransactionBlockKind::ProgrammableTransaction(pt) => pt
1532 .commands
1533 .iter()
1534 .filter_map(|command| match command {
1535 SuiCommand::MoveCall(c) => Some(&**c),
1536 _ => None,
1537 })
1538 .collect(),
1539 _ => vec![],
1540 },
1541 }
1542 }
1543
1544 fn try_from_inner(
1545 data: TransactionData,
1546 transaction: SuiTransactionBlockKind,
1547 ) -> Result<Self, anyhow::Error> {
1548 let message_version = data.message_version();
1549 let sender = data.sender();
1550 let gas_data = SuiGasData {
1551 payment: data
1552 .gas()
1553 .iter()
1554 .map(|obj_ref| SuiObjectRef::from(*obj_ref))
1555 .collect(),
1556 owner: data.gas_owner(),
1557 price: data.gas_price(),
1558 budget: data.gas_budget(),
1559 };
1560
1561 match message_version {
1562 1 => Ok(SuiTransactionBlockData::V1(SuiTransactionBlockDataV1 {
1563 transaction,
1564 sender,
1565 gas_data,
1566 })),
1567 _ => Err(anyhow::anyhow!(
1568 "Support for TransactionData version {} not implemented",
1569 message_version
1570 )),
1571 }
1572 }
1573
1574 pub fn try_from_with_module_cache(
1575 data: TransactionData,
1576 module_cache: &impl GetModule,
1577 ) -> Result<Self, anyhow::Error> {
1578 let transaction = SuiTransactionBlockKind::try_from_with_module_cache(
1579 data.clone().into_kind(),
1580 module_cache,
1581 )?;
1582 Self::try_from_inner(data, transaction)
1583 }
1584
1585 pub async fn try_from_with_package_resolver(
1586 data: TransactionData,
1587 package_resolver: &Resolver<impl PackageStore>,
1588 ) -> Result<Self, anyhow::Error> {
1589 let transaction = SuiTransactionBlockKind::try_from_with_package_resolver(
1590 data.clone().into_kind(),
1591 package_resolver,
1592 )
1593 .await?;
1594 Self::try_from_inner(data, transaction)
1595 }
1596}
1597
1598impl Display for SuiTransactionBlockData {
1599 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1600 match self {
1601 Self::V1(data) => {
1602 writeln!(f, "Sender: {}", data.sender)?;
1603 writeln!(f, "{}", self.gas_data())?;
1604 writeln!(f, "{}", data.transaction)
1605 }
1606 }
1607 }
1608}
1609
1610#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
1611#[serde(rename = "TransactionBlock", rename_all = "camelCase")]
1612pub struct SuiTransactionBlock {
1613 pub data: SuiTransactionBlockData,
1614 pub tx_signatures: Vec<GenericSignature>,
1615}
1616
1617impl SuiTransactionBlock {
1618 pub fn try_from(
1619 data: SenderSignedData,
1620 module_cache: &impl GetModule,
1621 ) -> Result<Self, anyhow::Error> {
1622 Ok(Self {
1623 data: SuiTransactionBlockData::try_from_with_module_cache(
1624 data.intent_message().value.clone(),
1625 module_cache,
1626 )?,
1627 tx_signatures: data.tx_signatures().to_vec(),
1628 })
1629 }
1630
1631 pub async fn try_from_with_package_resolver(
1634 data: SenderSignedData,
1635 package_resolver: &Resolver<impl PackageStore>,
1636 ) -> Result<Self, anyhow::Error> {
1637 Ok(Self {
1638 data: SuiTransactionBlockData::try_from_with_package_resolver(
1639 data.intent_message().value.clone(),
1640 package_resolver,
1641 )
1642 .await?,
1643 tx_signatures: data.tx_signatures().to_vec(),
1644 })
1645 }
1646}
1647
1648impl Display for SuiTransactionBlock {
1649 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1650 let mut builder = TableBuilder::default();
1651
1652 builder.push_record(vec![format!("{}", self.data)]);
1653 builder.push_record(vec![format!("Signatures:")]);
1654 for tx_sig in &self.tx_signatures {
1655 builder.push_record(vec![format!(
1656 " {}\n",
1657 match tx_sig {
1658 Signature(sig) => Base64::from_bytes(sig.signature_bytes()).encoded(),
1659 _ => Base64::from_bytes(tx_sig.as_ref()).encoded(), }
1661 )]);
1662 }
1663
1664 let mut table = builder.build();
1665 table.with(TablePanel::header("Transaction Data"));
1666 table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
1667 1,
1668 TableStyle::modern().get_horizontal(),
1669 )]));
1670 write!(f, "{}", table)
1671 }
1672}
1673
1674#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1675pub struct SuiGenesisTransaction {
1676 pub objects: Vec<ObjectID>,
1677}
1678
1679#[serde_as]
1680#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1681pub struct SuiConsensusCommitPrologue {
1682 #[schemars(with = "BigInt<u64>")]
1683 #[serde_as(as = "BigInt<u64>")]
1684 pub epoch: u64,
1685 #[schemars(with = "BigInt<u64>")]
1686 #[serde_as(as = "BigInt<u64>")]
1687 pub round: u64,
1688 #[schemars(with = "BigInt<u64>")]
1689 #[serde_as(as = "BigInt<u64>")]
1690 pub commit_timestamp_ms: u64,
1691}
1692
1693#[serde_as]
1694#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1695pub struct SuiConsensusCommitPrologueV2 {
1696 #[schemars(with = "BigInt<u64>")]
1697 #[serde_as(as = "BigInt<u64>")]
1698 pub epoch: u64,
1699 #[schemars(with = "BigInt<u64>")]
1700 #[serde_as(as = "BigInt<u64>")]
1701 pub round: u64,
1702 #[schemars(with = "BigInt<u64>")]
1703 #[serde_as(as = "BigInt<u64>")]
1704 pub commit_timestamp_ms: u64,
1705 pub consensus_commit_digest: ConsensusCommitDigest,
1706}
1707
1708#[serde_as]
1709#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1710pub struct SuiConsensusCommitPrologueV3 {
1711 #[schemars(with = "BigInt<u64>")]
1712 #[serde_as(as = "BigInt<u64>")]
1713 pub epoch: u64,
1714 #[schemars(with = "BigInt<u64>")]
1715 #[serde_as(as = "BigInt<u64>")]
1716 pub round: u64,
1717 #[schemars(with = "Option<BigInt<u64>>")]
1718 #[serde_as(as = "Option<BigInt<u64>>")]
1719 pub sub_dag_index: Option<u64>,
1720 #[schemars(with = "BigInt<u64>")]
1721 #[serde_as(as = "BigInt<u64>")]
1722 pub commit_timestamp_ms: u64,
1723 pub consensus_commit_digest: ConsensusCommitDigest,
1724 pub consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
1725}
1726
1727#[serde_as]
1728#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1729pub struct SuiConsensusCommitPrologueV4 {
1730 #[schemars(with = "BigInt<u64>")]
1731 #[serde_as(as = "BigInt<u64>")]
1732 pub epoch: u64,
1733 #[schemars(with = "BigInt<u64>")]
1734 #[serde_as(as = "BigInt<u64>")]
1735 pub round: u64,
1736 #[schemars(with = "Option<BigInt<u64>>")]
1737 #[serde_as(as = "Option<BigInt<u64>>")]
1738 pub sub_dag_index: Option<u64>,
1739 #[schemars(with = "BigInt<u64>")]
1740 #[serde_as(as = "BigInt<u64>")]
1741 pub commit_timestamp_ms: u64,
1742 pub consensus_commit_digest: ConsensusCommitDigest,
1743 pub consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
1744 pub additional_state_digest: AdditionalConsensusStateDigest,
1745}
1746
1747#[serde_as]
1748#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1749pub struct SuiAuthenticatorStateUpdate {
1750 #[schemars(with = "BigInt<u64>")]
1751 #[serde_as(as = "BigInt<u64>")]
1752 pub epoch: u64,
1753 #[schemars(with = "BigInt<u64>")]
1754 #[serde_as(as = "BigInt<u64>")]
1755 pub round: u64,
1756
1757 pub new_active_jwks: Vec<SuiActiveJwk>,
1758}
1759
1760#[serde_as]
1761#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1762pub struct SuiRandomnessStateUpdate {
1763 #[schemars(with = "BigInt<u64>")]
1764 #[serde_as(as = "BigInt<u64>")]
1765 pub epoch: u64,
1766
1767 #[schemars(with = "BigInt<u64>")]
1768 #[serde_as(as = "BigInt<u64>")]
1769 pub randomness_round: u64,
1770 pub random_bytes: Vec<u8>,
1771}
1772
1773#[serde_as]
1774#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1775pub struct SuiEndOfEpochTransaction {
1776 pub transactions: Vec<SuiEndOfEpochTransactionKind>,
1777}
1778
1779#[serde_as]
1780#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1781pub enum SuiEndOfEpochTransactionKind {
1782 ChangeEpoch(SuiChangeEpoch),
1783 AuthenticatorStateCreate,
1784 AuthenticatorStateExpire(SuiAuthenticatorStateExpire),
1785 RandomnessStateCreate,
1786 CoinDenyListStateCreate,
1787 BridgeStateCreate(CheckpointDigest),
1788 BridgeCommitteeUpdate(SequenceNumber),
1789 StoreExecutionTimeObservations,
1790 AccumulatorRootCreate,
1791 CoinRegistryCreate,
1792 DisplayRegistryCreate,
1793 AddressAliasStateCreate,
1794}
1795
1796#[serde_as]
1797#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1798pub struct SuiAuthenticatorStateExpire {
1799 #[schemars(with = "BigInt<u64>")]
1800 #[serde_as(as = "BigInt<u64>")]
1801 pub min_epoch: u64,
1802}
1803
1804#[serde_as]
1805#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1806pub struct SuiActiveJwk {
1807 pub jwk_id: SuiJwkId,
1808 pub jwk: SuiJWK,
1809
1810 #[schemars(with = "BigInt<u64>")]
1811 #[serde_as(as = "BigInt<u64>")]
1812 pub epoch: u64,
1813}
1814
1815impl From<ActiveJwk> for SuiActiveJwk {
1816 fn from(active_jwk: ActiveJwk) -> Self {
1817 Self {
1818 jwk_id: SuiJwkId {
1819 iss: active_jwk.jwk_id.iss.clone(),
1820 kid: active_jwk.jwk_id.kid.clone(),
1821 },
1822 jwk: SuiJWK {
1823 kty: active_jwk.jwk.kty.clone(),
1824 e: active_jwk.jwk.e.clone(),
1825 n: active_jwk.jwk.n.clone(),
1826 alg: active_jwk.jwk.alg.clone(),
1827 },
1828 epoch: active_jwk.epoch,
1829 }
1830 }
1831}
1832
1833#[serde_as]
1834#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1835pub struct SuiJwkId {
1836 pub iss: String,
1837 pub kid: String,
1838}
1839
1840#[serde_as]
1841#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1842pub struct SuiJWK {
1843 pub kty: String,
1844 pub e: String,
1845 pub n: String,
1846 pub alg: String,
1847}
1848
1849#[serde_as]
1850#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]
1851#[serde(rename = "InputObjectKind")]
1852pub enum SuiInputObjectKind {
1853 MovePackage(ObjectID),
1855 ImmOrOwnedMoveObject(SuiObjectRef),
1857 SharedMoveObject {
1859 id: ObjectID,
1860 #[schemars(with = "AsSequenceNumber")]
1861 #[serde_as(as = "AsSequenceNumber")]
1862 initial_shared_version: SequenceNumber,
1863 #[serde(default = "default_shared_object_mutability")]
1864 mutable: bool,
1865 },
1866}
1867
1868#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1871pub struct SuiProgrammableTransactionBlock {
1872 pub inputs: Vec<SuiCallArg>,
1874 #[serde(rename = "transactions")]
1875 pub commands: Vec<SuiCommand>,
1878}
1879
1880impl Display for SuiProgrammableTransactionBlock {
1881 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1882 let Self { inputs, commands } = self;
1883 writeln!(f, "Inputs: {inputs:?}")?;
1884 writeln!(f, "Commands: [")?;
1885 for c in commands {
1886 writeln!(f, " {c},")?;
1887 }
1888 writeln!(f, "]")
1889 }
1890}
1891
1892impl SuiProgrammableTransactionBlock {
1893 fn try_from_with_module_cache(
1894 value: ProgrammableTransaction,
1895 module_cache: &impl GetModule,
1896 ) -> Result<Self, anyhow::Error> {
1897 let ProgrammableTransaction { inputs, commands } = value;
1898 let input_types = Self::resolve_input_type(&inputs, &commands, module_cache);
1899 Ok(SuiProgrammableTransactionBlock {
1900 inputs: inputs
1901 .into_iter()
1902 .zip(input_types)
1903 .map(|(arg, layout)| SuiCallArg::try_from(arg, layout.as_ref()))
1904 .collect::<Result<_, _>>()?,
1905 commands: commands.into_iter().map(SuiCommand::from).collect(),
1906 })
1907 }
1908
1909 async fn try_from_with_package_resolver(
1910 value: ProgrammableTransaction,
1911 package_resolver: &Resolver<impl PackageStore>,
1912 ) -> Result<Self, anyhow::Error> {
1913 let input_types = package_resolver.pure_input_layouts(&value).await?;
1914 let ProgrammableTransaction { inputs, commands } = value;
1915 Ok(SuiProgrammableTransactionBlock {
1916 inputs: inputs
1917 .into_iter()
1918 .zip(input_types)
1919 .map(|(arg, layout)| SuiCallArg::try_from(arg, layout.as_ref()))
1920 .collect::<Result<_, _>>()?,
1921 commands: commands.into_iter().map(SuiCommand::from).collect(),
1922 })
1923 }
1924
1925 fn resolve_input_type(
1926 inputs: &[CallArg],
1927 commands: &[Command],
1928 module_cache: &impl GetModule,
1929 ) -> Vec<Option<MoveTypeLayout>> {
1930 let mut result_types = vec![None; inputs.len()];
1931 for command in commands.iter() {
1932 match command {
1933 Command::MoveCall(c) => {
1934 let Ok(module) = Identifier::new(c.module.clone()) else {
1935 return result_types;
1936 };
1937
1938 let Ok(function) = Identifier::new(c.function.clone()) else {
1939 return result_types;
1940 };
1941
1942 let id = ModuleId::new(c.package.into(), module);
1943 let Some(types) =
1944 get_signature_types(id, function.as_ident_str(), module_cache)
1945 else {
1946 return result_types;
1947 };
1948 for (arg, type_) in c.arguments.iter().zip(types) {
1949 if let (&Argument::Input(i), Some(type_)) = (arg, type_)
1950 && let Some(x) = result_types.get_mut(i as usize)
1951 {
1952 x.replace(type_);
1953 }
1954 }
1955 }
1956 Command::SplitCoins(_, amounts) => {
1957 for arg in amounts {
1958 if let &Argument::Input(i) = arg
1959 && let Some(x) = result_types.get_mut(i as usize)
1960 {
1961 x.replace(MoveTypeLayout::U64);
1962 }
1963 }
1964 }
1965 Command::TransferObjects(_, Argument::Input(i)) => {
1966 if let Some(x) = result_types.get_mut((*i) as usize) {
1967 x.replace(MoveTypeLayout::Address);
1968 }
1969 }
1970 _ => {}
1971 }
1972 }
1973 result_types
1974 }
1975}
1976
1977fn get_signature_types(
1978 id: ModuleId,
1979 function: &IdentStr,
1980 module_cache: &impl GetModule,
1981) -> Option<Vec<Option<MoveTypeLayout>>> {
1982 use std::borrow::Borrow;
1983 if let Ok(Some(module)) = module_cache.get_module_by_id(&id) {
1984 let module: &CompiledModule = module.borrow();
1985 let func = module
1986 .function_handles
1987 .iter()
1988 .find(|f| module.identifier_at(f.name) == function)?;
1989 Some(
1990 module
1991 .signature_at(func.parameters)
1992 .0
1993 .iter()
1994 .map(|s| primitive_type(module, &[], s))
1995 .collect(),
1996 )
1997 } else {
1998 None
1999 }
2000}
2001
2002#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2004#[serde(rename = "SuiTransaction")]
2005pub enum SuiCommand {
2006 MoveCall(Box<SuiProgrammableMoveCall>),
2008 TransferObjects(Vec<SuiArgument>, SuiArgument),
2013 SplitCoins(SuiArgument, Vec<SuiArgument>),
2016 MergeCoins(SuiArgument, Vec<SuiArgument>),
2019 Publish(Vec<ObjectID>),
2022 Upgrade(Vec<ObjectID>, ObjectID, SuiArgument),
2024 MakeMoveVec(Option<String>, Vec<SuiArgument>),
2028}
2029
2030impl Display for SuiCommand {
2031 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2032 match self {
2033 Self::MoveCall(p) => {
2034 write!(f, "MoveCall({p})")
2035 }
2036 Self::MakeMoveVec(ty_opt, elems) => {
2037 write!(f, "MakeMoveVec(")?;
2038 if let Some(ty) = ty_opt {
2039 write!(f, "Some{ty}")?;
2040 } else {
2041 write!(f, "None")?;
2042 }
2043 write!(f, ",[")?;
2044 write_sep(f, elems, ",")?;
2045 write!(f, "])")
2046 }
2047 Self::TransferObjects(objs, addr) => {
2048 write!(f, "TransferObjects([")?;
2049 write_sep(f, objs, ",")?;
2050 write!(f, "],{addr})")
2051 }
2052 Self::SplitCoins(coin, amounts) => {
2053 write!(f, "SplitCoins({coin},")?;
2054 write_sep(f, amounts, ",")?;
2055 write!(f, ")")
2056 }
2057 Self::MergeCoins(target, coins) => {
2058 write!(f, "MergeCoins({target},")?;
2059 write_sep(f, coins, ",")?;
2060 write!(f, ")")
2061 }
2062 Self::Publish(deps) => {
2063 write!(f, "Publish(<modules>,")?;
2064 write_sep(f, deps, ",")?;
2065 write!(f, ")")
2066 }
2067 Self::Upgrade(deps, current_package_id, ticket) => {
2068 write!(f, "Upgrade(<modules>, {ticket},")?;
2069 write_sep(f, deps, ",")?;
2070 write!(f, ", {current_package_id}")?;
2071 write!(f, ")")
2072 }
2073 }
2074 }
2075}
2076
2077impl From<Command> for SuiCommand {
2078 fn from(value: Command) -> Self {
2079 match value {
2080 Command::MoveCall(m) => SuiCommand::MoveCall(Box::new((*m).into())),
2081 Command::TransferObjects(args, arg) => SuiCommand::TransferObjects(
2082 args.into_iter().map(SuiArgument::from).collect(),
2083 arg.into(),
2084 ),
2085 Command::SplitCoins(arg, args) => SuiCommand::SplitCoins(
2086 arg.into(),
2087 args.into_iter().map(SuiArgument::from).collect(),
2088 ),
2089 Command::MergeCoins(arg, args) => SuiCommand::MergeCoins(
2090 arg.into(),
2091 args.into_iter().map(SuiArgument::from).collect(),
2092 ),
2093 Command::Publish(_modules, dep_ids) => SuiCommand::Publish(dep_ids),
2094 Command::MakeMoveVec(tag_opt, args) => SuiCommand::MakeMoveVec(
2095 tag_opt.map(|tag| tag.to_string()),
2096 args.into_iter().map(SuiArgument::from).collect(),
2097 ),
2098 Command::Upgrade(_modules, dep_ids, current_package_id, ticket) => {
2099 SuiCommand::Upgrade(dep_ids, current_package_id, SuiArgument::from(ticket))
2100 }
2101 }
2102 }
2103}
2104
2105#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2107pub enum SuiArgument {
2108 GasCoin,
2111 Input(u16),
2114 Result(u16),
2116 NestedResult(u16, u16),
2119}
2120
2121impl Display for SuiArgument {
2122 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2123 match self {
2124 Self::GasCoin => write!(f, "GasCoin"),
2125 Self::Input(i) => write!(f, "Input({i})"),
2126 Self::Result(i) => write!(f, "Result({i})"),
2127 Self::NestedResult(i, j) => write!(f, "NestedResult({i},{j})"),
2128 }
2129 }
2130}
2131
2132impl From<Argument> for SuiArgument {
2133 fn from(value: Argument) -> Self {
2134 match value {
2135 Argument::GasCoin => Self::GasCoin,
2136 Argument::Input(i) => Self::Input(i),
2137 Argument::Result(i) => Self::Result(i),
2138 Argument::NestedResult(i, j) => Self::NestedResult(i, j),
2139 }
2140 }
2141}
2142
2143#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2146pub struct SuiProgrammableMoveCall {
2147 pub package: ObjectID,
2149 pub module: String,
2151 pub function: String,
2153 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2154 pub type_arguments: Vec<String>,
2156 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2157 pub arguments: Vec<SuiArgument>,
2159}
2160
2161fn write_sep<T: Display>(
2162 f: &mut Formatter<'_>,
2163 items: impl IntoIterator<Item = T>,
2164 sep: &str,
2165) -> std::fmt::Result {
2166 let mut xs = items.into_iter().peekable();
2167 while let Some(x) = xs.next() {
2168 write!(f, "{x}")?;
2169 if xs.peek().is_some() {
2170 write!(f, "{sep}")?;
2171 }
2172 }
2173 Ok(())
2174}
2175
2176impl Display for SuiProgrammableMoveCall {
2177 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2178 let Self {
2179 package,
2180 module,
2181 function,
2182 type_arguments,
2183 arguments,
2184 } = self;
2185 write!(f, "{package}::{module}::{function}")?;
2186 if !type_arguments.is_empty() {
2187 write!(f, "<")?;
2188 write_sep(f, type_arguments, ",")?;
2189 write!(f, ">")?;
2190 }
2191 write!(f, "(")?;
2192 write_sep(f, arguments, ",")?;
2193 write!(f, ")")
2194 }
2195}
2196
2197impl From<ProgrammableMoveCall> for SuiProgrammableMoveCall {
2198 fn from(value: ProgrammableMoveCall) -> Self {
2199 let ProgrammableMoveCall {
2200 package,
2201 module,
2202 function,
2203 type_arguments,
2204 arguments,
2205 } = value;
2206 Self {
2207 package,
2208 module: module.to_string(),
2209 function: function.to_string(),
2210 type_arguments: type_arguments.into_iter().map(|t| t.to_string()).collect(),
2211 arguments: arguments.into_iter().map(SuiArgument::from).collect(),
2212 }
2213 }
2214}
2215
2216const fn default_shared_object_mutability() -> bool {
2217 true
2218}
2219
2220impl From<InputObjectKind> for SuiInputObjectKind {
2221 fn from(input: InputObjectKind) -> Self {
2222 match input {
2223 InputObjectKind::MovePackage(id) => Self::MovePackage(id),
2224 InputObjectKind::ImmOrOwnedMoveObject(oref) => Self::ImmOrOwnedMoveObject(oref.into()),
2225 InputObjectKind::SharedMoveObject {
2226 id,
2227 initial_shared_version,
2228 mutability,
2229 } => Self::SharedMoveObject {
2230 id,
2231 initial_shared_version,
2232 mutable: match mutability {
2233 SharedObjectMutability::Mutable => true,
2234 SharedObjectMutability::Immutable => false,
2235 SharedObjectMutability::NonExclusiveWrite => false,
2237 },
2238 },
2239 }
2240 }
2241}
2242
2243#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Eq, PartialEq)]
2244#[serde(rename = "TypeTag", rename_all = "camelCase")]
2245pub struct SuiTypeTag(String);
2246
2247impl SuiTypeTag {
2248 pub fn new(tag: String) -> Self {
2249 Self(tag)
2250 }
2251}
2252
2253impl TryInto<TypeTag> for SuiTypeTag {
2254 type Error = anyhow::Error;
2255 fn try_into(self) -> Result<TypeTag, Self::Error> {
2256 parse_sui_type_tag(&self.0)
2257 }
2258}
2259
2260impl From<TypeTag> for SuiTypeTag {
2261 fn from(tag: TypeTag) -> Self {
2262 Self(format!("{}", tag))
2263 }
2264}
2265
2266#[derive(Serialize, Deserialize, JsonSchema, Clone)]
2267#[serde(rename_all = "camelCase")]
2268pub enum RPCTransactionRequestParams {
2269 TransferObjectRequestParams(TransferObjectParams),
2270 MoveCallRequestParams(MoveCallParams),
2271}
2272
2273#[derive(Serialize, Deserialize, JsonSchema, Clone)]
2274#[serde(rename_all = "camelCase")]
2275pub struct TransferObjectParams {
2276 pub recipient: SuiAddress,
2277 pub object_id: ObjectID,
2278}
2279
2280#[derive(Serialize, Deserialize, JsonSchema, Clone)]
2281#[serde(rename_all = "camelCase")]
2282pub struct MoveCallParams {
2283 pub package_object_id: ObjectID,
2284 pub module: String,
2285 pub function: String,
2286 #[serde(default)]
2287 pub type_arguments: Vec<SuiTypeTag>,
2288 pub arguments: Vec<SuiJsonValue>,
2289}
2290
2291#[serde_as]
2292#[derive(Serialize, Deserialize, JsonSchema, Clone)]
2293#[serde(rename_all = "camelCase")]
2294pub struct TransactionBlockBytes {
2295 pub tx_bytes: Base64,
2297 pub gas: Vec<SuiObjectRef>,
2299 pub input_objects: Vec<SuiInputObjectKind>,
2301}
2302
2303impl TransactionBlockBytes {
2304 pub fn from_data(data: TransactionData) -> Result<Self, anyhow::Error> {
2305 Ok(Self {
2306 tx_bytes: Base64::from_bytes(bcs::to_bytes(&data)?.as_slice()),
2307 gas: data
2308 .gas()
2309 .iter()
2310 .map(|obj_ref| SuiObjectRef::from(*obj_ref))
2311 .collect(),
2312 input_objects: data
2313 .input_objects()?
2314 .into_iter()
2315 .map(SuiInputObjectKind::from)
2316 .collect(),
2317 })
2318 }
2319
2320 pub fn to_data(self) -> Result<TransactionData, anyhow::Error> {
2321 bcs::from_bytes::<TransactionData>(&self.tx_bytes.to_vec().map_err(|e| anyhow::anyhow!(e))?)
2322 .map_err(|e| anyhow::anyhow!(e))
2323 }
2324}
2325
2326#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
2327#[serde(rename = "OwnedObjectRef")]
2328pub struct OwnedObjectRef {
2329 pub owner: Owner,
2330 pub reference: SuiObjectRef,
2331}
2332
2333impl OwnedObjectRef {
2334 pub fn object_id(&self) -> ObjectID {
2335 self.reference.object_id
2336 }
2337 pub fn version(&self) -> SequenceNumber {
2338 self.reference.version
2339 }
2340}
2341
2342#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2343#[serde(tag = "type", rename_all = "camelCase")]
2344pub enum SuiCallArg {
2345 Object(SuiObjectArg),
2347 Pure(SuiPureValue),
2349 FundsWithdrawal(SuiFundsWithdrawalArg),
2352}
2353
2354impl SuiCallArg {
2355 pub fn try_from(
2356 value: CallArg,
2357 layout: Option<&MoveTypeLayout>,
2358 ) -> Result<Self, anyhow::Error> {
2359 Ok(match value {
2360 CallArg::Pure(p) => SuiCallArg::Pure(SuiPureValue {
2361 value_type: layout.map(|l| l.into()),
2362 value: SuiJsonValue::from_bcs_bytes(layout, &p)?,
2363 }),
2364 CallArg::Object(ObjectArg::ImmOrOwnedObject((id, version, digest))) => {
2365 SuiCallArg::Object(SuiObjectArg::ImmOrOwnedObject {
2366 object_id: id,
2367 version,
2368 digest,
2369 })
2370 }
2371 CallArg::Object(ObjectArg::SharedObject {
2373 id,
2374 initial_shared_version,
2375 mutability,
2376 }) => SuiCallArg::Object(SuiObjectArg::SharedObject {
2377 object_id: id,
2378 initial_shared_version,
2379 mutable: mutability.is_exclusive(),
2380 }),
2381 CallArg::Object(ObjectArg::Receiving((object_id, version, digest))) => {
2382 SuiCallArg::Object(SuiObjectArg::Receiving {
2383 object_id,
2384 version,
2385 digest,
2386 })
2387 }
2388 CallArg::FundsWithdrawal(arg) => SuiCallArg::FundsWithdrawal(SuiFundsWithdrawalArg {
2389 reservation: match arg.reservation {
2390 Reservation::EntireBalance => SuiReservation::EntireBalance,
2391 Reservation::MaxAmountU64(amount) => SuiReservation::MaxAmountU64(amount),
2392 },
2393 type_arg: match arg.type_arg {
2394 WithdrawalTypeArg::Balance(type_input) => {
2395 SuiWithdrawalTypeArg::Balance(type_input.to_type_tag()?.into())
2396 }
2397 },
2398 withdraw_from: match arg.withdraw_from {
2399 WithdrawFrom::Sender => SuiWithdrawFrom::Sender,
2400 WithdrawFrom::Sponsor => SuiWithdrawFrom::Sponsor,
2401 },
2402 }),
2403 })
2404 }
2405
2406 pub fn pure(&self) -> Option<&SuiJsonValue> {
2407 match self {
2408 SuiCallArg::Pure(v) => Some(&v.value),
2409 _ => None,
2410 }
2411 }
2412
2413 pub fn object(&self) -> Option<&ObjectID> {
2414 match self {
2415 SuiCallArg::Object(SuiObjectArg::SharedObject { object_id, .. })
2416 | SuiCallArg::Object(SuiObjectArg::ImmOrOwnedObject { object_id, .. })
2417 | SuiCallArg::Object(SuiObjectArg::Receiving { object_id, .. }) => Some(object_id),
2418 _ => None,
2419 }
2420 }
2421}
2422
2423#[serde_as]
2424#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2425#[serde(rename_all = "camelCase")]
2426pub struct SuiPureValue {
2427 #[schemars(with = "Option<String>")]
2428 #[serde_as(as = "Option<AsSuiTypeTag>")]
2429 value_type: Option<TypeTag>,
2430 value: SuiJsonValue,
2431}
2432
2433impl SuiPureValue {
2434 pub fn value(&self) -> SuiJsonValue {
2435 self.value.clone()
2436 }
2437
2438 pub fn value_type(&self) -> Option<TypeTag> {
2439 self.value_type.clone()
2440 }
2441}
2442
2443#[serde_as]
2444#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2445#[serde(tag = "objectType", rename_all = "camelCase")]
2446pub enum SuiObjectArg {
2447 #[serde(rename_all = "camelCase")]
2449 ImmOrOwnedObject {
2450 object_id: ObjectID,
2451 #[schemars(with = "AsSequenceNumber")]
2452 #[serde_as(as = "AsSequenceNumber")]
2453 version: SequenceNumber,
2454 digest: ObjectDigest,
2455 },
2456 #[serde(rename_all = "camelCase")]
2459 SharedObject {
2460 object_id: ObjectID,
2461 #[schemars(with = "AsSequenceNumber")]
2462 #[serde_as(as = "AsSequenceNumber")]
2463 initial_shared_version: SequenceNumber,
2464 mutable: bool,
2465 },
2466 #[serde(rename_all = "camelCase")]
2468 Receiving {
2469 object_id: ObjectID,
2470 #[schemars(with = "AsSequenceNumber")]
2471 #[serde_as(as = "AsSequenceNumber")]
2472 version: SequenceNumber,
2473 digest: ObjectDigest,
2474 },
2475}
2476
2477#[serde_as]
2478#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2479#[serde(rename_all = "camelCase")]
2480pub enum SuiReservation {
2481 EntireBalance,
2482 MaxAmountU64(
2483 #[schemars(with = "BigInt<u64>")]
2484 #[serde_as(as = "BigInt<u64>")]
2485 u64,
2486 ),
2487}
2488
2489#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2490#[serde(rename_all = "camelCase")]
2491pub enum SuiWithdrawalTypeArg {
2492 Balance(SuiTypeTag),
2493}
2494
2495#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2496#[serde(rename_all = "camelCase")]
2497pub enum SuiWithdrawFrom {
2498 Sender,
2499 Sponsor,
2500}
2501
2502#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
2503#[serde(rename_all = "camelCase")]
2504pub struct SuiFundsWithdrawalArg {
2505 pub reservation: SuiReservation,
2506 pub type_arg: SuiWithdrawalTypeArg,
2507 pub withdraw_from: SuiWithdrawFrom,
2508}
2509
2510#[derive(Clone)]
2511pub struct EffectsWithInput {
2512 pub effects: SuiTransactionBlockEffects,
2513 pub input: TransactionData,
2514}
2515
2516impl From<EffectsWithInput> for SuiTransactionBlockEffects {
2517 fn from(e: EffectsWithInput) -> Self {
2518 e.effects
2519 }
2520}
2521
2522#[serde_as]
2523#[derive(Clone, Debug, JsonSchema, Serialize, Deserialize)]
2524pub enum TransactionFilter {
2525 Checkpoint(
2527 #[schemars(with = "BigInt<u64>")]
2528 #[serde_as(as = "Readable<BigInt<u64>, _>")]
2529 CheckpointSequenceNumber,
2530 ),
2531 MoveFunction {
2533 package: ObjectID,
2534 module: Option<String>,
2535 function: Option<String>,
2536 },
2537 InputObject(ObjectID),
2539 ChangedObject(ObjectID),
2541 AffectedObject(ObjectID),
2543 FromAddress(SuiAddress),
2545 ToAddress(SuiAddress),
2547 FromAndToAddress { from: SuiAddress, to: SuiAddress },
2549 FromOrToAddress { addr: SuiAddress },
2551 TransactionKind(String),
2553 TransactionKindIn(Vec<String>),
2555}
2556
2557impl Filter<EffectsWithInput> for TransactionFilter {
2558 fn matches(&self, item: &EffectsWithInput) -> bool {
2559 let _scope = monitored_scope("TransactionFilter::matches");
2560 match self {
2561 TransactionFilter::InputObject(o) => {
2562 let Ok(input_objects) = item.input.input_objects() else {
2563 return false;
2564 };
2565 input_objects.iter().any(|object| object.object_id() == *o)
2566 }
2567 TransactionFilter::ChangedObject(o) => item
2568 .effects
2569 .mutated()
2570 .iter()
2571 .any(|oref: &OwnedObjectRef| &oref.reference.object_id == o),
2572 TransactionFilter::AffectedObject(o) => item
2573 .effects
2574 .created()
2575 .iter()
2576 .chain(item.effects.mutated().iter())
2577 .chain(item.effects.unwrapped().iter())
2578 .map(|oref: &OwnedObjectRef| &oref.reference)
2579 .chain(item.effects.shared_objects().iter())
2580 .chain(item.effects.deleted().iter())
2581 .chain(item.effects.unwrapped_then_deleted().iter())
2582 .chain(item.effects.wrapped().iter())
2583 .any(|oref| &oref.object_id == o),
2584 TransactionFilter::FromAddress(a) => &item.input.sender() == a,
2585 TransactionFilter::ToAddress(a) => {
2586 let mutated: &[OwnedObjectRef] = item.effects.mutated();
2587 mutated.iter().chain(item.effects.unwrapped().iter()).any(|oref: &OwnedObjectRef| {
2588 matches!(oref.owner, Owner::AddressOwner(owner) if owner == *a)
2589 })
2590 }
2591 TransactionFilter::FromAndToAddress { from, to } => {
2592 Self::FromAddress(*from).matches(item) && Self::ToAddress(*to).matches(item)
2593 }
2594 TransactionFilter::MoveFunction {
2595 package,
2596 module,
2597 function,
2598 } => item.input.move_calls().into_iter().any(|(p, m, f)| {
2599 p == package
2600 && (module.is_none() || matches!(module, Some(m2) if m2 == &m.to_string()))
2601 && (function.is_none() || matches!(function, Some(f2) if f2 == &f.to_string()))
2602 }),
2603 TransactionFilter::TransactionKind(kind) => item.input.kind().to_string() == *kind,
2604 TransactionFilter::TransactionKindIn(kinds) => {
2605 kinds.contains(&item.input.kind().to_string())
2606 }
2607 TransactionFilter::Checkpoint(_) => false,
2609 TransactionFilter::FromOrToAddress { addr: _ } => false,
2610 }
2611 }
2612}