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