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